No public description

PiperOrigin-RevId: 637906749
Change-Id: Id54040917d0f295f701d24276ae2a8606f9b476c
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 0000000..e3690a1
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,23 @@
+# Copyright 2024 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Build configurations for the project
+build --action_env=BAZEL_CXXOPTS="-std=c++17"
+build --action_env=CC="clang"
+
+# Disable warnings we don't care about or that generally have a low signal/noise
+# ratio.
+
+build --copt=-Wno-unused-function
+build --host_copt=-Wno-unused-function
\ No newline at end of file
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..f3ab219
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2024 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This package contains the MPACT-Sim based CherIoT simulator.
+
+load("@rules_license//rules:license.bzl", "license")
+
+package(
+    default_applicable_licenses = [":license"],
+    default_visibility = ["//visibility:public"],
+)
+
+license(
+    name = "license",
+    package_name = "mpact-cheriot",
+)
+
+licenses(["notice"])
+
+exports_files(["LICENSE"])
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..8410ba3
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,96 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+*   Using welcoming and inclusive language
+*   Being respectful of differing viewpoints and experiences
+*   Gracefully accepting constructive criticism
+*   Focusing on what is best for the community
+*   Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+*   The use of sexualized language or imagery and unwelcome sexual attention or
+    advances
+*   Trolling, insulting/derogatory comments, and personal or political attacks
+*   Public or private harassment
+*   Publishing others' private information, such as a physical or electronic
+    address, without explicit permission
+*   Other conduct which could reasonably be considered inappropriate in a
+    professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to *[Tor Jeremiassen](torerik@google.com)*, the
+Project Steward(s) for *[MPACT-Sim](https://mpact.googlesource.com)*. It is the
+Project Steward’s duty to receive and address reported violations of the code of
+conduct. They will then work with a committee consisting of representatives from
+the Open Source Programs Office and the Google Open Source Strategy team. If for
+any reason you are uncomfortable reaching out to the Project Steward, please
+email opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+Note: A version of this file is also available in the
+[New Project repo](https://github.com/google/new-project/blob/master/docs/code-of-conduct.md).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..37ae0a4
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project.
+
+## Before you begin
+
+### Sign our Contributor License Agreement
+
+Contributions to this project must be accompanied by a
+[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
+You (or your employer) retain the copyright to your contribution; this simply
+gives us permission to use and redistribute your contributions as part of the
+project.
+
+If you or your current employer have already signed the Google CLA (even if it
+was for a different project), you probably don't need to do it again.
+
+Visit <https://cla.developers.google.com/> to see your current agreements or to
+sign a new one.
+
+### Review our Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+## Contribution process
+
+### Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..70babd8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# MPACT-Cheriot
+
+MPACT-Cheriot is an implementation of an instruction set simulator for the
+CherIoT instruction set architecture created using the MPACT-Sim simulator tools
+and framework and reusing code from MPACT-RiscV.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..4648e5e
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,4 @@
+To report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz).
+We use g.co/vulnz for our intake, and do coordination and disclosure here on
+GitHub (including using GitHub Security Advisory). The Google Security Team will
+respond within 5 working days of your report on g.co/vulnz.
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..bfc07eb
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,49 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+workspace(name = "com_google_mpact-cheriot")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+
+# MPACT-Sim repo
+http_archive(
+    name = "com_google_mpact-sim",
+    sha256 = "384daa4f4bcacf0552cebdb5b3001b51af19dfed3dfa3367de5e3babcd65511d",
+    strip_prefix = "mpact-sim-a3dbb21f3f3d3193a8d0b6667f1d0807d1dc4dd4",
+    url = "https://github.com/google/mpact-sim/archive/a3dbb21f3f3d3193a8d0b6667f1d0807d1dc4dd4.tar.gz"
+)
+
+# MPACT-RiscV repo
+http_archive(
+    name = "com_google_mpact-riscv",
+    sha256 = "c30dc2ec57fd476d833dc8e5c02045acdfda4136cb5c2376044432d453aecd4d",
+    strip_prefix = "mpact-riscv-a43a92ba38d8260f4207416e11721ceee7135624",
+    url = "https://github.com/google/mpact-riscv/archive/a43a92ba38d8260f4207416e11721ceee7135624.tar.gz"
+)
+
+# MPACT-ReNode repo will go here.
+git_repository(
+  name = "com_google_mpact-renode",
+  branch = "main",
+  remote = "https://mpact.googlesource.com/mpact-renode",
+)
+
+load("@com_google_mpact-sim//:repos.bzl", "mpact_sim_repos")
+
+mpact_sim_repos()
+
+load("@com_google_mpact-sim//:deps.bzl", "mpact_sim_deps")
+
+mpact_sim_deps()
diff --git a/cheriot/BUILD b/cheriot/BUILD
new file mode 100644
index 0000000..990e5e2
--- /dev/null
+++ b/cheriot/BUILD
@@ -0,0 +1,420 @@
+# Copyright 2024 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# CHERI RiscV simulator code.
+
+load("@com_google_mpact-sim//mpact/sim/decoder:mpact_sim_isa.bzl", "mpact_bin_fmt_decoder", "mpact_isa_decoder")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
+
+exports_files([
+    "riscv_cheriot.bin_fmt",
+    "riscv_cheriot.isa",
+])
+
+mpact_isa_decoder(
+    name = "riscv_cheriot_isa",
+    src = "riscv_cheriot.isa",
+    includes = [],
+    isa_name = "RiscVCheriot",
+    deps = [
+        ":riscv_cheriot",
+        "@com_google_absl//absl/functional:bind_front",
+    ],
+)
+
+mpact_bin_fmt_decoder(
+    name = "riscv_cheriot_bin_fmt",
+    src = "riscv_cheriot.bin_fmt",
+    decoder_name = "RiscVCheriot",
+    includes = [
+    ],
+    deps = [
+        ":riscv_cheriot_isa",
+    ],
+)
+
+cc_library(
+    name = "riscv_cheriot",
+    srcs = [
+        "cheriot_register.cc",
+        "cheriot_state.cc",
+        "riscv_cheriot_a_instructions.cc",
+        "riscv_cheriot_f_instructions.cc",
+        "riscv_cheriot_fp_state.cc",
+        "riscv_cheriot_i_instructions.cc",
+        "riscv_cheriot_instructions.cc",
+        "riscv_cheriot_m_instructions.cc",
+        "riscv_cheriot_minstret.cc",
+        "riscv_cheriot_priv_instructions.cc",
+        "riscv_cheriot_zicsr_instructions.cc",
+    ],
+    hdrs = [
+        "cheriot_register.h",
+        "cheriot_state.h",
+        "riscv_cheriot_a_instructions.h",
+        "riscv_cheriot_csr_enum.h",
+        "riscv_cheriot_f_instructions.h",
+        "riscv_cheriot_fp_state.h",
+        "riscv_cheriot_i_instructions.h",
+        "riscv_cheriot_instruction_helpers.h",
+        "riscv_cheriot_instructions.h",
+        "riscv_cheriot_m_instructions.h",
+        "riscv_cheriot_minstret.h",
+        "riscv_cheriot_priv_instructions.h",
+        "riscv_cheriot_zicsr_instructions.h",
+    ],
+    copts = [
+        "-ffp-model=strict",
+        "-fprotect-parens",
+    ],
+    tags = ["not_run:arm"],
+    deps = [
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/numeric:bits",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-riscv//riscv:stoull_wrapper",
+        "@com_google_mpact-sim//mpact/sim/generic:arch_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_library(
+    name = "riscv_cheriot_decoder",
+    srcs = [
+        "cheriot_decoder.cc",
+        "riscv_cheriot_encoding.cc",
+    ],
+    hdrs = [
+        "cheriot_decoder.h",
+        "riscv_cheriot_encoding.h",
+        "riscv_cheriot_register_aliases.h",
+    ],
+    deps = [
+        ":riscv_cheriot",
+        ":riscv_cheriot_bin_fmt",
+        ":riscv_cheriot_isa",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/strings",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:arch_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:program_error",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_library(
+    name = "cheriot_top",
+    srcs = [
+        "cheriot_top.cc",
+    ],
+    hdrs = [
+        "cheriot_top.h",
+    ],
+    copts = ["-O3"],
+    deps = [
+        ":cheriot_debug_interface",
+        ":riscv_cheriot",
+        ":riscv_cheriot_decoder",
+        ":riscv_cheriot_isa",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/functional:bind_front",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/numeric:bits",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/synchronization",
+        "@com_google_mpact-riscv//riscv:riscv_breakpoint",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:component",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/generic:decode_cache",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_library(
+    name = "cheriot_debug_interface",
+    hdrs = [
+        "cheriot_debug_interface.h",
+    ],
+    deps = [
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+    ],
+)
+
+cc_library(
+    name = "debug_command_shell",
+    srcs = [
+        "debug_command_shell.cc",
+    ],
+    hdrs = [
+        "debug_command_shell.h",
+    ],
+    copts = ["-O3"],
+    deps = [
+        ":cheriot_debug_interface",
+        ":cheriot_top",
+        ":riscv_cheriot",
+        ":riscv_cheriot_decoder",
+        ":riscv_cheriot_isa",
+        "@com_google_absl//absl/container:btree",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_mpact-riscv//riscv:stoull_wrapper",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:debug_command_shell_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_binary(
+    name = "mpact_cheriot",
+    srcs = [
+        "mpact_cheriot.cc",
+    ],
+    copts = ["-O3"],
+    deps = [
+        ":cheriot_top",
+        ":debug_command_shell",
+        ":instrumentation",
+        ":riscv_cheriot",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/flags:usage",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/functional:bind_front",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/time",
+        "@com_google_mpact-riscv//riscv:riscv_arm_semihost",
+        "@com_google_mpact-riscv//riscv:riscv_clint",
+        "@com_google_mpact-riscv//riscv:stoull_wrapper",
+        "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/proto:component_data_cc_proto",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+        "@com_google_mpact-sim//mpact/sim/util/other:simple_uart",
+        "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
+        "@com_google_protobuf//:protobuf",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_library(
+    name = "cheriot_load_filter",
+    srcs = [
+        "cheriot_load_filter.cc",
+    ],
+    hdrs = [
+        "cheriot_load_filter.h",
+    ],
+    deps = [
+        ":riscv_cheriot",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_library(
+    name = "cheriot_debug_info",
+    srcs = [
+        "cheriot_debug_info.cc",
+    ],
+    hdrs = [
+        "cheriot_debug_info.h",
+    ],
+    deps = [
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
+cc_library(
+    name = "instrumentation",
+    srcs = [
+        "cheriot_instrumentation_control.cc",
+        "memory_use_profiler.cc",
+        "profiler.cc",
+    ],
+    hdrs = [
+        "cheriot_instrumentation_control.h",
+        "memory_use_profiler.h",
+        "profiler.h",
+    ],
+    deps = [
+        ":cheriot_top",
+        ":debug_command_shell",
+        "@com_github_serge1_elfio//:elfio",
+        "@com_google_absl//absl/container:btree",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/numeric:bits",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_mpact-riscv//riscv:stoull_wrapper",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+        "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_library(
+    name = "cheriot_renode",
+    srcs = [
+        "cheriot_cli_forwarder.cc",
+        "cheriot_renode.cc",
+        "cheriot_renode_cli_top.cc",
+        "cheriot_renode_register_info.cc",
+    ],
+    hdrs = [
+        "cheriot_cli_forwarder.h",
+        "cheriot_renode.h",
+        "cheriot_renode_cli_top.h",
+        "cheriot_renode_register_info.h",
+    ],
+    deps = [
+        ":cheriot_debug_info",
+        ":cheriot_debug_interface",
+        ":cheriot_top",
+        ":debug_command_shell",
+        ":instrumentation",
+        ":riscv_cheriot",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/functional:bind_front",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_mpact-renode//renode",
+        "@com_google_mpact-renode//renode:renode_debug_interface",
+        "@com_google_mpact-renode//renode:socket_cli",
+        "@com_google_mpact-riscv//riscv:riscv_arm_semihost",
+        "@com_google_mpact-riscv//riscv:riscv_clint",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-riscv//riscv:stoull_wrapper",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/proto:component_data_cc_proto",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+        "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_binary(
+    name = "renode_mpact_cheriot",
+    linkshared = True,
+    linkstatic = True,
+    deps = [
+        ":cheriot_renode",
+        "@com_google_mpact-renode//renode",
+    ],
+)
+
+cc_library(
+    name = "cheriot_test_rig_lib",
+    srcs = [
+        "cheriot_test_rig.cc",
+        "cheriot_test_rig_decoder.cc",
+    ],
+    hdrs = [
+        "cheriot_test_rig.h",
+        "cheriot_test_rig_decoder.h",
+        "test_rig_packets.h",
+    ],
+    deps = [
+        ":riscv_cheriot",
+        ":riscv_cheriot_bin_fmt",
+        ":riscv_cheriot_decoder",
+        ":riscv_cheriot_isa",
+        "@com_google_absl//absl/functional:bind_front",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:arch_state",
+        "@com_google_mpact-sim//mpact/sim/generic:component",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:program_error",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_binary(
+    name = "cheriot_test_rig",
+    srcs = ["cheriot_test_rig_main.cc"],
+    deps = [
+        ":cheriot_test_rig_lib",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/flags:usage",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+    ],
+)
diff --git a/cheriot/cheriot_cli_forwarder.cc b/cheriot/cheriot_cli_forwarder.cc
new file mode 100644
index 0000000..8ba7364
--- /dev/null
+++ b/cheriot/cheriot_cli_forwarder.cc
@@ -0,0 +1,83 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_cli_forwarder.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "cheriot/cheriot_renode_cli_top.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "third_party/mpact_renode/cli_forwarder.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::AccessType;
+using ::mpact::sim::renode::CLIForwarder;
+using RunStatus = ::mpact::sim::generic::CoreDebugInterface::RunStatus;
+using HaltReasonValueType =
+    ::mpact::sim::generic::CoreDebugInterface::HaltReasonValueType;
+
+CheriotCLIForwarder::CheriotCLIForwarder(CheriotRenodeCLITop *cheriot_cli_top)
+    : CLIForwarder(cheriot_cli_top), cheriot_cli_top_(cheriot_cli_top) {}
+
+// Forward the calls to the CheriotRenodeCLITop class - CLI methods.
+
+absl::StatusOr<size_t> CheriotCLIForwarder::ReadTagMemory(uint64_t address,
+                                                          void *buf,
+                                                          size_t length) {
+  return cheriot_cli_top_->CLIReadTagMemory(address, buf, length);
+}
+
+absl::Status CheriotCLIForwarder::SetDataWatchpoint(uint64_t address,
+                                                    size_t length,
+                                                    AccessType access_type) {
+  return cheriot_cli_top_->CLISetDataWatchpoint(address, length, access_type);
+}
+
+absl::Status CheriotCLIForwarder::ClearDataWatchpoint(uint64_t address,
+                                                      AccessType access_type) {
+  return cheriot_cli_top_->CLIClearDataWatchpoint(address, access_type);
+}
+
+absl::StatusOr<int> CheriotCLIForwarder::SetActionPoint(
+    uint64_t address, absl::AnyInvocable<void(uint64_t, int)> action) {
+  return cheriot_cli_top_->CLISetActionPoint(address, std::move(action));
+}
+
+absl::Status CheriotCLIForwarder::ClearActionPoint(uint64_t address, int id) {
+  return cheriot_cli_top_->CLIClearActionPoint(address, id);
+}
+
+absl::Status CheriotCLIForwarder::EnableAction(uint64_t address, int id) {
+  return cheriot_cli_top_->CLIEnableAction(address, id);
+}
+
+absl::Status CheriotCLIForwarder::DisableAction(uint64_t address, int id) {
+  return cheriot_cli_top_->CLIDisableAction(address, id);
+}
+
+void CheriotCLIForwarder::SetBreakOnControlFlowChange(bool value) {
+  cheriot_cli_top_->CLISetBreakOnControlFlowChange(value);
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_cli_forwarder.h b/cheriot/cheriot_cli_forwarder.h
new file mode 100644
index 0000000..ac72a45
--- /dev/null
+++ b/cheriot/cheriot_cli_forwarder.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_CLI_FORWARDER_H_
+#define MPACT_CHERIOT__CHERIOT_CLI_FORWARDER_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "cheriot/cheriot_renode_cli_top.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "third_party/mpact_renode/cli_forwarder.h"
+
+// This file defines a class that forwards calls from the CLI to the class that
+// merges requests from the CLI and ReNode.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::AccessType;
+using ::mpact::sim::renode::CLIForwarder;
+
+class CheriotCLIForwarder : public CLIForwarder {
+ public:
+  explicit CheriotCLIForwarder(CheriotRenodeCLITop *top);
+  CheriotCLIForwarder() = delete;
+  CheriotCLIForwarder(const CLIForwarder &) = delete;
+  CheriotCLIForwarder &operator=(const CLIForwarder &) = delete;
+
+  absl::StatusOr<size_t> ReadTagMemory(uint64_t address, void *buf,
+                                       size_t length);
+  // Set a data watchpoint for the given memory range. Any access matching the
+  // given access type (load/store) will halt execution following the completion
+  // of that access.
+  absl::Status SetDataWatchpoint(uint64_t address, size_t length,
+                                 AccessType access_type);
+  // Clear data watchpoint for the given memory address and access type.
+  absl::Status ClearDataWatchpoint(uint64_t address, AccessType access_type);
+
+  // Set an action point at the given address to execute the specified action.
+  absl::StatusOr<int> SetActionPoint(
+      uint64_t address, absl::AnyInvocable<void(uint64_t, int)> action);
+  // Clear action point id at the given address.
+  absl::Status ClearActionPoint(uint64_t address, int id);
+  // Enable/disable action id at the given address.
+  absl::Status EnableAction(uint64_t address, int id);
+  absl::Status DisableAction(uint64_t address, int id);
+  // Enable breaking on control flow change.
+  void SetBreakOnControlFlowChange(bool value);
+
+ private:
+  CheriotRenodeCLITop *cheriot_cli_top_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_CLI_FORWARDER_H_
diff --git a/cheriot/cheriot_debug_info.cc b/cheriot/cheriot_debug_info.cc
new file mode 100644
index 0000000..b6e6c40
--- /dev/null
+++ b/cheriot/cheriot_debug_info.cc
@@ -0,0 +1,170 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_debug_info.h"
+
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+constexpr char kPcName[] = "pcc";
+
+constexpr char kC0Name[] = "c0";
+constexpr char kC1Name[] = "c1";
+constexpr char kC2Name[] = "c2";
+constexpr char kC3Name[] = "c3";
+constexpr char kC4Name[] = "c4";
+constexpr char kC5Name[] = "c5";
+constexpr char kC6Name[] = "c6";
+constexpr char kC7Name[] = "c7";
+constexpr char kC8Name[] = "c8";
+constexpr char kC9Name[] = "c9";
+constexpr char kC10Name[] = "c10";
+constexpr char kC11Name[] = "c11";
+constexpr char kC12Name[] = "c12";
+constexpr char kC13Name[] = "c13";
+constexpr char kC14Name[] = "c14";
+constexpr char kC15Name[] = "c15";
+constexpr char kC16Name[] = "c16";
+constexpr char kC17Name[] = "c17";
+constexpr char kC18Name[] = "c18";
+constexpr char kC19Name[] = "c19";
+constexpr char kC20Name[] = "c20";
+constexpr char kC21Name[] = "c21";
+constexpr char kC22Name[] = "c22";
+constexpr char kC23Name[] = "c23";
+constexpr char kC24Name[] = "c24";
+constexpr char kC25Name[] = "c25";
+constexpr char kC26Name[] = "c26";
+constexpr char kC27Name[] = "c27";
+constexpr char kC28Name[] = "c28";
+constexpr char kC29Name[] = "c29";
+constexpr char kC30Name[] = "c30";
+constexpr char kC31Name[] = "c31";
+
+constexpr char kF0Name[] = "f0";
+constexpr char kF1Name[] = "f1";
+constexpr char kF2Name[] = "f2";
+constexpr char kF3Name[] = "f3";
+constexpr char kF4Name[] = "f4";
+constexpr char kF5Name[] = "f5";
+constexpr char kF6Name[] = "f6";
+constexpr char kF7Name[] = "f7";
+constexpr char kF8Name[] = "f8";
+constexpr char kF9Name[] = "f9";
+constexpr char kF10Name[] = "f10";
+constexpr char kF11Name[] = "f11";
+constexpr char kF12Name[] = "f12";
+constexpr char kF13Name[] = "f13";
+constexpr char kF14Name[] = "f14";
+constexpr char kF15Name[] = "f15";
+constexpr char kF16Name[] = "f16";
+constexpr char kF17Name[] = "f17";
+constexpr char kF18Name[] = "f18";
+constexpr char kF19Name[] = "f19";
+constexpr char kF20Name[] = "f20";
+constexpr char kF21Name[] = "f21";
+constexpr char kF22Name[] = "f22";
+constexpr char kF23Name[] = "f23";
+constexpr char kF24Name[] = "f24";
+constexpr char kF25Name[] = "f25";
+constexpr char kF26Name[] = "f26";
+constexpr char kF27Name[] = "f27";
+constexpr char kF28Name[] = "f28";
+constexpr char kF29Name[] = "f29";
+constexpr char kF30Name[] = "f30";
+constexpr char kF31Name[] = "f31";
+
+CheriotDebugInfo::CheriotDebugInfo()
+    : debug_register_map_({{*DebugRegisterEnum::kPc, kPcName},
+                           {*DebugRegisterEnum::kC0, kC0Name},
+                           {*DebugRegisterEnum::kC1, kC1Name},
+                           {*DebugRegisterEnum::kC2, kC2Name},
+                           {*DebugRegisterEnum::kC3, kC3Name},
+                           {*DebugRegisterEnum::kC4, kC4Name},
+                           {*DebugRegisterEnum::kC5, kC5Name},
+                           {*DebugRegisterEnum::kC6, kC6Name},
+                           {*DebugRegisterEnum::kC7, kC7Name},
+                           {*DebugRegisterEnum::kC8, kC8Name},
+                           {*DebugRegisterEnum::kC9, kC9Name},
+                           {*DebugRegisterEnum::kC10, kC10Name},
+                           {*DebugRegisterEnum::kC11, kC11Name},
+                           {*DebugRegisterEnum::kC12, kC12Name},
+                           {*DebugRegisterEnum::kC13, kC13Name},
+                           {*DebugRegisterEnum::kC14, kC14Name},
+                           {*DebugRegisterEnum::kC15, kC15Name},
+                           {*DebugRegisterEnum::kC16, kC16Name},
+                           {*DebugRegisterEnum::kC17, kC17Name},
+                           {*DebugRegisterEnum::kC18, kC18Name},
+                           {*DebugRegisterEnum::kC19, kC19Name},
+                           {*DebugRegisterEnum::kC20, kC20Name},
+                           {*DebugRegisterEnum::kC21, kC21Name},
+                           {*DebugRegisterEnum::kC22, kC22Name},
+                           {*DebugRegisterEnum::kC23, kC23Name},
+                           {*DebugRegisterEnum::kC24, kC24Name},
+                           {*DebugRegisterEnum::kC25, kC25Name},
+                           {*DebugRegisterEnum::kC26, kC26Name},
+                           {*DebugRegisterEnum::kC27, kC27Name},
+                           {*DebugRegisterEnum::kC28, kC28Name},
+                           {*DebugRegisterEnum::kC29, kC29Name},
+                           {*DebugRegisterEnum::kC30, kC30Name},
+                           {*DebugRegisterEnum::kC31, kC31Name},
+                           {*DebugRegisterEnum::kF0, kF0Name},
+                           {*DebugRegisterEnum::kF1, kF1Name},
+                           {*DebugRegisterEnum::kF2, kF2Name},
+                           {*DebugRegisterEnum::kF3, kF3Name},
+                           {*DebugRegisterEnum::kF4, kF4Name},
+                           {*DebugRegisterEnum::kF5, kF5Name},
+                           {*DebugRegisterEnum::kF6, kF6Name},
+                           {*DebugRegisterEnum::kF7, kF7Name},
+                           {*DebugRegisterEnum::kF8, kF8Name},
+                           {*DebugRegisterEnum::kF9, kF9Name},
+                           {*DebugRegisterEnum::kF10, kF10Name},
+                           {*DebugRegisterEnum::kF11, kF11Name},
+                           {*DebugRegisterEnum::kF12, kF12Name},
+                           {*DebugRegisterEnum::kF13, kF13Name},
+                           {*DebugRegisterEnum::kF14, kF14Name},
+                           {*DebugRegisterEnum::kF15, kF15Name},
+                           {*DebugRegisterEnum::kF16, kF16Name},
+                           {*DebugRegisterEnum::kF17, kF17Name},
+                           {*DebugRegisterEnum::kF18, kF18Name},
+                           {*DebugRegisterEnum::kF19, kF19Name},
+                           {*DebugRegisterEnum::kF20, kF20Name},
+                           {*DebugRegisterEnum::kF21, kF21Name},
+                           {*DebugRegisterEnum::kF22, kF22Name},
+                           {*DebugRegisterEnum::kF23, kF23Name},
+                           {*DebugRegisterEnum::kF24, kF24Name},
+                           {*DebugRegisterEnum::kF25, kF25Name},
+                           {*DebugRegisterEnum::kF26, kF26Name},
+                           {*DebugRegisterEnum::kF27, kF27Name},
+                           {*DebugRegisterEnum::kF28, kF28Name},
+                           {*DebugRegisterEnum::kF29, kF29Name},
+                           {*DebugRegisterEnum::kF30, kF30Name},
+                           {*DebugRegisterEnum::kF31, kF31Name}}) {}
+
+CheriotDebugInfo *CheriotDebugInfo::Instance() {
+  static CheriotDebugInfo *instance_ = nullptr;
+  if (instance_ == nullptr) {
+    instance_ = new CheriotDebugInfo();
+  }
+  return instance_;
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_debug_info.h b/cheriot/cheriot_debug_info.h
new file mode 100644
index 0000000..c8a0d61
--- /dev/null
+++ b/cheriot/cheriot_debug_info.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_DEBUG_INFO_H_
+#define MPACT_CHERIOT__CHERIOT_DEBUG_INFO_H_
+
+#include <cstdint>
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+// Enumeration of the RiscV debug ids for accessible registers.
+enum class DebugRegisterEnum : uint32_t {
+  // CSRs.
+  // Program counter.
+  kPc = 0x07b1,
+
+  // Capability registers.
+  kC0 = 0x1000,
+  kC1,
+  kC2,
+  kC3,
+  kC4,
+  kC5,
+  kC6,
+  kC7,
+  kC8,
+  kC9,
+  kC10,
+  kC11,
+  kC12,
+  kC13,
+  kC14,
+  kC15,
+  kC16,
+  kC17,
+  kC18,
+  kC19,
+  kC20,
+  kC21,
+  kC22,
+  kC23,
+  kC24,
+  kC25,
+  kC26,
+  kC27,
+  kC28,
+  kC29,
+  kC30,
+  kC31,
+
+  // Floating point registers.
+  kF0 = 0x1020,
+  kF1,
+  kF2,
+  kF3,
+  kF4,
+  kF5,
+  kF6,
+  kF7,
+  kF8,
+  kF9,
+  kF10,
+  kF11,
+  kF12,
+  kF13,
+  kF14,
+  kF15,
+  kF16,
+  kF17,
+  kF18,
+  kF19,
+  kF20,
+  kF21,
+  kF22,
+  kF23,
+  kF24,
+  kF25,
+  kF26,
+  kF27,
+  kF28,
+  kF29,
+  kF30,
+  kF31,
+};
+
+// Singleton class used to store RiscV debug register ids.
+class CheriotDebugInfo {
+ public:
+  using DebugRegisterMap = absl::flat_hash_map<uint32_t, std::string>;
+
+  static CheriotDebugInfo* Instance();
+
+  const DebugRegisterMap& debug_register_map() const {
+    return debug_register_map_;
+  }
+
+ private:
+  CheriotDebugInfo();
+  DebugRegisterMap debug_register_map_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_DEBUG_INFO_H_
diff --git a/cheriot/cheriot_debug_interface.h b/cheriot/cheriot_debug_interface.h
new file mode 100644
index 0000000..ae46629
--- /dev/null
+++ b/cheriot/cheriot_debug_interface.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_DEBUG_INTERFACE_H_
+#define MPACT_CHERIOT__CHERIOT_DEBUG_INTERFACE_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+
+namespace mpact::sim::cheriot {
+
+using ::mpact::sim::generic::AccessType;
+
+class CheriotDebugInterface : public generic::CoreDebugInterface {
+ public:
+  ~CheriotDebugInterface() override = default;
+
+  // Read tags from memory from the byte address (not tag address) given. The
+  // tag address is computed by shifting the byte address right by three. The
+  // length specifies the number of bytes (tags) to read.
+  virtual absl::StatusOr<size_t> ReadTagMemory(uint64_t address, void *buf,
+                                               size_t length) = 0;
+  // Set a data watchpoint for the given memory range. Any access matching the
+  // given access type (load/store) will halt execution following the completion
+  // of that access.
+  virtual absl::Status SetDataWatchpoint(uint64_t address, size_t length,
+                                         AccessType access_type) = 0;
+  // Clear data watchpoint for the given memory address and access type.
+  virtual absl::Status ClearDataWatchpoint(uint64_t address,
+                                           AccessType access_type) = 0;
+  // Set an action point at the given address to execute the specified action.
+  virtual absl::StatusOr<int> SetActionPoint(
+      uint64_t address, absl::AnyInvocable<void(uint64_t, int)> action) = 0;
+  // Clear action point id at the given address.
+  virtual absl::Status ClearActionPoint(uint64_t address, int id) = 0;
+  // Enable/disable action id at the given address.
+  virtual absl::Status EnableAction(uint64_t address, int id) = 0;
+  virtual absl::Status DisableAction(uint64_t address, int id) = 0;
+
+  // Enable/disable breaking on control flow change.
+  virtual void SetBreakOnControlFlowChange(bool enabled) = 0;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__CHERIOT_DEBUG_INTERFACE_H_
diff --git a/cheriot/cheriot_decoder.cc b/cheriot/cheriot_decoder.cc
new file mode 100644
index 0000000..ba5e4d7
--- /dev/null
+++ b/cheriot/cheriot_decoder.cc
@@ -0,0 +1,108 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_decoder.h"
+
+#include <cstdint>
+#include <string>
+
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_decoder.h"
+#include "cheriot/riscv_cheriot_encoding.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/program_error.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using RV_EC = ::mpact::sim::riscv::ExceptionCode;
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+CheriotDecoder::CheriotDecoder(CheriotState *state,
+                               util::MemoryInterface *memory)
+    : state_(state), memory_(memory) {
+  // Get a handle to the internal error in the program error controller.
+  decode_error_ = state->program_error_controller()->GetProgramError(
+      generic::ProgramErrorController::kInternalErrorName);
+
+  // Need a data buffer to load instructions from memory. Allocate a single
+  // buffer that can be reused for each instruction word.
+  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
+  // Allocate the isa factory class, the top level isa decoder instance, and
+  // the encoding parser.
+  cheriot_isa_factory_ = new CheriotIsaFactory();
+  cheriot_isa_ =
+      new isa32::RiscVCheriotInstructionSet(state, cheriot_isa_factory_);
+  cheriot_encoding_ = new isa32::RiscVCheriotEncoding(state);
+}
+
+CheriotDecoder::~CheriotDecoder() {
+  delete cheriot_isa_;
+  delete cheriot_isa_factory_;
+  delete cheriot_encoding_;
+  inst_db_->DecRef();
+}
+
+generic::Instruction *CheriotDecoder::DecodeInstruction(uint64_t address) {
+  // First check that the address is aligned properly. If not, create and return
+  // an instruction object that will raise an exception.
+  if (address & 0x1) {
+    auto *inst = new generic::Instruction(0, state_);
+    inst->set_size(1);
+    inst->SetDisassemblyString("Misaligned instruction address");
+    inst->set_opcode(*isa32::OpcodeEnum::kNone);
+    inst->set_address(address);
+    inst->set_semantic_function([this](generic::Instruction *inst) {
+      state_->Trap(/*is_interrupt*/ false, inst->address(),
+                   *RV_EC::kInstructionAddressMisaligned, inst->address() ^ 0x1,
+                   inst);
+    });
+    return inst;
+  }
+
+  // If the address is greater than the max address, return an instruction
+  // that will raise an exception.
+  if (address > state_->max_physical_address()) {
+    auto *inst = new generic::Instruction(0, state_);
+    inst->set_size(0);
+    inst->SetDisassemblyString("Instruction access fault");
+    inst->set_opcode(*isa32::OpcodeEnum::kNone);
+    inst->set_address(address);
+    inst->set_semantic_function([this](generic::Instruction *inst) {
+      state_->Trap(/*is_interrupt*/ false, inst->address(),
+                   *RV_EC::kInstructionAccessFault, inst->address(), nullptr);
+    });
+    return inst;
+  }
+
+  // Read the instruction word from memory and parse it in the encoding parser.
+  memory_->Load(address, inst_db_, nullptr, nullptr);
+  uint32_t iword = inst_db_->Get<uint32_t>(0);
+  cheriot_encoding_->ParseInstruction(iword);
+
+  // Call the isa decoder to obtain a new instruction object for the instruction
+  // word that was parsed above.
+  auto *instruction = cheriot_isa_->Decode(address, cheriot_encoding_);
+  return instruction;
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_decoder.h b/cheriot/cheriot_decoder.h
new file mode 100644
index 0000000..93442f3
--- /dev/null
+++ b/cheriot/cheriot_decoder.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_DECODER_H_
+#define MPACT_CHERIOT__CHERIOT_DECODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_decoder.h"
+#include "cheriot/riscv_cheriot_encoding.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/program_error.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::ArchState;
+
+// This is the factory class needed by the generated decoder. It is responsible
+// for creating the decoder for each slot instance. Since the riscv architecture
+// only has a single slot, it's a pretty simple class.
+class CheriotIsaFactory : public isa32::RiscVCheriotInstructionSetFactory {
+ public:
+  std::unique_ptr<isa32::Riscv32CheriotSlot> CreateRiscv32CheriotSlot(
+      ArchState *state) override {
+    return std::make_unique<isa32::Riscv32CheriotSlot>(state);
+  }
+};
+
+// This class implements the generic DecoderInterface and provides a bridge
+// to the (isa specific) generated decoder classes.
+class CheriotDecoder : public generic::DecoderInterface {
+ public:
+  using SlotEnum = isa32::SlotEnum;
+  using OpcodeEnum = isa32::OpcodeEnum;
+
+  CheriotDecoder(CheriotState *state, util::MemoryInterface *memory);
+  CheriotDecoder() = delete;
+  ~CheriotDecoder() override;
+
+  // This will always return a valid instruction that can be executed. In the
+  // case of a decode error, the semantic function in the instruction object
+  // instance will raise an internal simulator error when executed.
+  generic::Instruction *DecodeInstruction(uint64_t address) override;
+
+  // Getter.
+  isa32::RiscVCheriotEncoding *cheriot_encoding() const {
+    return cheriot_encoding_;
+  }
+
+ private:
+  CheriotState *state_;
+  util::MemoryInterface *memory_;
+  std::unique_ptr<generic::ProgramError> decode_error_;
+  generic::DataBuffer *inst_db_;
+  isa32::RiscVCheriotEncoding *cheriot_encoding_;
+  isa32::RiscVCheriotInstructionSetFactory *cheriot_isa_factory_;
+  isa32::RiscVCheriotInstructionSet *cheriot_isa_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_DECODER_H_
diff --git a/cheriot/cheriot_instrumentation_control.cc b/cheriot/cheriot_instrumentation_control.cc
new file mode 100644
index 0000000..51cb100
--- /dev/null
+++ b/cheriot/cheriot_instrumentation_control.cc
@@ -0,0 +1,154 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_instrumentation_control.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "cheriot/cheriot_top.h"
+#include "cheriot/debug_command_shell.h"
+#include "cheriot/memory_use_profiler.h"
+#include "re2/re2.h"
+#include "riscv//stoull_wrapper.h"
+
+namespace mpact::sim::cheriot {
+
+CheriotInstrumentationControl::CheriotInstrumentationControl(
+    DebugCommandShell *shell, CheriotTop *cheriot_top,
+    TaggedMemoryUseProfiler *mem_profiler)
+    : shell_(shell),
+      top_(cheriot_top),
+      mem_profiler_(mem_profiler),
+      pattern_re_{
+          R"(\s*stats\s+(enable|disable)\s+(counters|iprofile|mprofile|all)(?:\s+(\w+))?\s*)"} {
+}
+
+bool CheriotInstrumentationControl::PerformShellCommand(
+    absl::string_view input, const DebugCommandShell::CoreAccess &core_access,
+    std::string &output) {
+  std::string cmd;
+  std::string what;
+  std::string where;
+  absl::AnyInvocable<void(uint64_t, int)> function;
+  if (!RE2::FullMatch(input, *pattern_re_, &cmd, &what, &where)) return false;
+  // See what the pattern matched and set up an appropriate lambda.
+  if (cmd == "enable") {
+    if (what == "counters") {
+      function = [this](uint64_t, int) { top_->EnableStatistics(); };
+    } else if (what == "iprofile") {
+      function = [this](uint64_t, int) {
+        top_->counter_pc()->SetIsEnabled(true);
+      };
+    } else if (what == "mprofile") {
+      function = [this](uint64_t, int) { mem_profiler_->set_is_enabled(true); };
+    } else if (what == "all") {
+      function = [this](uint64_t, int) {
+        top_->EnableStatistics();
+        top_->counter_pc()->SetIsEnabled(true);
+        mem_profiler_->set_is_enabled(true);
+      };
+    } else {
+      output = absl::StrCat("Error: Unknown instrumentation: '", what, "'");
+      return true;
+    }
+  } else if (cmd == "disable") {
+    if (what == "counters") {
+      function = [this](uint64_t, int) { top_->DisableStatistics(); };
+    } else if (what == "iprofile") {
+      function = [this](uint64_t, int) {
+        top_->counter_pc()->SetIsEnabled(false);
+      };
+    } else if (what == "mprofile") {
+      function = [this](uint64_t, int) {
+        mem_profiler_->set_is_enabled(false);
+      };
+    } else if (what == "all") {
+      function = [this](uint64_t, int) {
+        top_->DisableStatistics();
+        top_->counter_pc()->SetIsEnabled(false);
+        mem_profiler_->set_is_enabled(false);
+      };
+    } else {
+      output = absl::StrCat("Error: Unknown instrumentation: '", what, "'");
+      return true;
+    }
+  }
+  // If 'where' is empty, we just execute the callable. It is not attached
+  // to any instruction.
+  if (where.empty()) {
+    function(0, 0);
+    return true;
+  }
+  // At this point we need to set the function to be executed upon execution
+  // of an instruction a specified in 'where'.
+  size_t index;
+  uint64_t address = 0;
+  // Attempt to convert to a number.
+  auto convert_result = riscv::internal::stoull(where, &index, 0);
+  // If successful and the entire string was consumed, the number is good and
+  // we use that as the address.
+  if (convert_result.ok() && (index >= where.size())) {
+    address = convert_result.value();
+  } else {
+    // If it's out of range, signal an error.
+    if (convert_result.status().code() == absl::StatusCode::kOutOfRange) {
+      output = absl::StrCat("Error: ", where, " is out of range");
+      return true;
+    }
+    // Let's see if it is a symbol.
+    if (core_access.loader == nullptr) {
+      output = "Error: cannot perform symbol lookup";
+      return true;
+    }
+    auto result = core_access.loader->GetSymbol(where);
+    if (!result.ok()) {
+      output = absl::StrCat("Error: symbol ", where, " not found");
+      return true;
+    }
+    address = result.value().first;
+  }
+  // Now we set an action for the function.
+  std::string action_name = absl::StrCat(cmd, "-", what, "-", where);
+  auto status =
+      shell_->SetActionPoint(address, action_name, std::move(function));
+  if (!status.ok()) {
+    output = absl::StrCat("Error: ", status.message());
+  }
+  return true;
+}
+
+std::string CheriotInstrumentationControl::Usage() const {
+  return
+      R"raw(
+  stats enable (counters|iprofile|mprofile|all) [VALUE|SYMBOL]
+                                   - enable counters, iprofile, mprofile, or all
+                                     when executing instruction at address VALUE
+                                     or value of SYMBOL, or immediately if
+                                     neither is specified.
+  stats disable (counters|iprofile|mprofile|all) [VALUE|SYMBOL]
+                                   - disable counters, iprofile, mprofile, or all
+                                     when executing instruction at address VALUE
+                                     or value of SYMBOL, or immediately if
+                                     neither is specified.
+    )raw";
+}
+
+}  // namespace mpact::sim::cheriot
diff --git a/cheriot/cheriot_instrumentation_control.h b/cheriot/cheriot_instrumentation_control.h
new file mode 100644
index 0000000..66dc61a
--- /dev/null
+++ b/cheriot/cheriot_instrumentation_control.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_INSTRUMENTATION_CONTROL_H_
+#define MPACT_CHERIOT__CHERIOT_INSTRUMENTATION_CONTROL_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "cheriot/cheriot_top.h"
+#include "cheriot/debug_command_shell.h"
+#include "cheriot/memory_use_profiler.h"
+#include "re2/re2.h"
+
+namespace mpact::sim::cheriot {
+
+class CheriotInstrumentationControl {
+ public:
+  CheriotInstrumentationControl(DebugCommandShell *shell,
+                                CheriotTop *cheriot_top,
+                                TaggedMemoryUseProfiler *mem_profiler);
+
+  bool PerformShellCommand(absl::string_view input,
+                           const DebugCommandShell::CoreAccess &core_access,
+                           std::string &output);
+
+  std::string Usage() const;
+
+ private:
+  DebugCommandShell *shell_;
+  CheriotTop *top_ = nullptr;
+  TaggedMemoryUseProfiler *mem_profiler_;
+  LazyRE2 pattern_re_;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__CHERIOT_INSTRUMENTATION_CONTROL_H_
diff --git a/cheriot/cheriot_load_filter.cc b/cheriot/cheriot_load_filter.cc
new file mode 100644
index 0000000..5d56bef
--- /dev/null
+++ b/cheriot/cheriot_load_filter.cc
@@ -0,0 +1,109 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_load_filter.h"
+
+#include <cstdint>
+
+#include "cheriot/cheriot_register.h"
+
+namespace mpact::sim::cheriot {
+
+CheriotLoadFilter::CheriotLoadFilter(TaggedMemoryInterface *tagged_memory,
+                                     int period, int count, uint64_t base,
+                                     uint64_t top, uint64_t cap_base,
+                                     uint64_t revocation_base)
+    : tagged_memory_(tagged_memory),
+      period_(period),
+      count_(count),
+      base_(base),
+      top_(top),
+      cap_base_(cap_base),
+      revocation_base_(revocation_base) {
+  cap_reg_ = new CheriotRegister(nullptr, "filter_cap");
+  db_ = db_factory_.Allocate<uint32_t>(1);
+  cap_reg_->SetDataBuffer(db_);
+  db_->DecRef();
+  // Allocate data buffers used in loads/stores.
+  db_ = db_factory_.Allocate<uint8_t>(CheriotRegister::kCapabilitySizeInBytes);
+  tag_db_ = db_factory_.Allocate<uint8_t>(1);
+  cap_address_ = base_;
+}
+
+CheriotLoadFilter::~CheriotLoadFilter() {
+  delete cap_reg_;
+  db_->DecRef();
+  tag_db_->DecRef();
+}
+
+// This is called when the linked counter increments. We are not interested
+// in the value of the counter, just the number of increments.
+void CheriotLoadFilter::SetValue(const uint64_t &) {
+  if (++update_counter_ >= period_) {
+    update_counter_ = 0;
+    // Once triggered, perform count_ filter loads/stores.
+    for (int i = 0; i < count_; ++i) {
+      FilterCapability(cap_address_);
+      cap_address_ += CheriotRegister::kCapabilitySizeInBytes;
+      // Reset the capability pointer if it wrapped or went past the memory
+      // space.
+      if ((cap_address_ < base_) || (cap_address_ >= top_)) {
+        cap_address_ = base_;
+      }
+    }
+  }
+}
+
+// Filter the capability at the given address.
+void CheriotLoadFilter::FilterCapability(uint64_t address) {
+  // Load the capability.
+  tagged_memory_->Load(address, db_, tag_db_, nullptr, nullptr);
+  // If the tag is 0, no need to go on.
+  auto tag = tag_db_->Get<uint8_t>(0);
+  if (tag == 0) return;
+
+  // Expand the capability. Return false if the tag is not valid.
+  cap_reg_->Expand(db_->Get<uint32_t>(0), db_->Get<uint32_t>(1), tag);
+  if (!cap_reg_->tag()) return;
+
+  // Check for revocation.
+  if (!MustRevoke(cap_reg_->base())) return;
+
+  // Invalidate and store the capability back to memory.
+  cap_reg_->Invalidate();
+  db_->Set<uint32_t>(0, cap_reg_->address());
+  db_->Set<uint32_t>(1, cap_reg_->Compress());
+  tag_db_->Set<uint8_t>(0, cap_reg_->tag());
+  tagged_memory_->Store(cap_address_, db_, tag_db_);
+}
+
+// Check if the capability must be revoked.
+bool CheriotLoadFilter::MustRevoke(uint64_t address) {
+  // If the address is less than the memory base, return false.
+  if (address < cap_base_) return false;
+  // Compute the address of the byte containing the revocation information.
+  uint64_t offset = address - cap_base_;
+  // Shift by 3 for the size of the capability (8), and by 3 for 8 bits in a
+  // byte.
+  uint64_t revocation_offset = offset >> 6;
+  tagged_memory_->Load(revocation_base_ + revocation_offset, tag_db_, nullptr,
+                       nullptr);
+  // Get the revocation bit.
+  uint8_t revocation_bits = tag_db_->Get<uint8_t>(0);
+  int bit_offset = (offset >> 3) & 0b111;
+  bool result = revocation_bits & (1 << bit_offset);
+  return result;
+}
+
+}  // namespace mpact::sim::cheriot
diff --git a/cheriot/cheriot_load_filter.h b/cheriot/cheriot_load_filter.h
new file mode 100644
index 0000000..716c139
--- /dev/null
+++ b/cheriot/cheriot_load_filter.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_LOAD_FILTER_H_
+#define MPACT_CHERIOT__CHERIOT_LOAD_FILTER_H_
+
+#include <cstdint>
+
+#include "cheriot/cheriot_register.h"
+#include "mpact/sim/generic/counters_base.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+
+// This file defines a class that performs periodic revocation filtering of
+// capabilities in memory. It does so by in effect doing a "load capability"
+// instruction, which checks for revocation, followed by a "store capability"
+// instruction, if the load capability invalidated the capability due to
+// revocation. No exceptions are thrown. This class is linked to a counter and
+// the SetValue method is invoked every time that counter changes values.
+
+namespace mpact::sim::cheriot {
+
+using ::mpact::sim::generic::CounterValueSetInterface;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::DataBufferFactory;
+using ::mpact::sim::util::TaggedMemoryInterface;
+
+class CheriotLoadFilter : public CounterValueSetInterface<uint64_t> {
+ public:
+  // The constructor takes the following arguments:
+  //   tagged_memory: The memory interface to use for capability loads/stores
+  //                  and revocation queries.
+  //   period:        The number of times SetValue is called before triggering
+  //                  a filtering operation.
+  //   count:         The number of capabilities to filter in an operation.
+  //   base:          The base address of the capability filter range.
+  //   top:           The top address of the filter range (inclusive).
+  //   cap_base:      The base address of the capabilities address space. E.g.,
+  //                  the base of the region of memory to which revokable
+  //                  capabilities may use as their base addresses.
+  //   revocation_base: The base address of the revocation data area.
+  CheriotLoadFilter(TaggedMemoryInterface *tagged_memory, int period, int count,
+                    uint64_t base, uint64_t top, uint64_t cap_base,
+                    uint64_t revocation_base);
+  CheriotLoadFilter() = delete;
+  CheriotLoadFilter(const CheriotLoadFilter &) = delete;
+  CheriotLoadFilter &operator=(const CheriotLoadFilter &) = delete;
+  ~CheriotLoadFilter() override;
+
+  // Overridden method called by updates of a linked counter.
+  void SetValue(const uint64_t &value) override;
+
+ private:
+  // Loads the capability at the given address, checks for valid tag and
+  // capability validity, and if valid, checks for revocation. If revoked,
+  // it invalidates the capability and stores it back to memory.
+  void FilterCapability(uint64_t address);
+  // Checks for revocation status for the capability at the given address.
+  // Returns true if it has been revoked.
+  bool MustRevoke(uint64_t address);
+
+  // Memory interface to use for loads/stores.
+  TaggedMemoryInterface *tagged_memory_;
+  // Counter to keep track of the number of times SetValue is called.
+  int update_counter_ = 0;
+  int period_;
+  int count_;
+  // Base and top addresses of the region to filter.
+  uint64_t base_;
+  uint64_t top_;
+  uint64_t cap_base_;
+  // Base address of the revocation data area.
+  uint64_t revocation_base_;
+  // Current capability address.
+  uint64_t cap_address_;
+  // Capability register used to expand the loaded capability into.
+  CheriotRegister *cap_reg_;
+  // Data buffer factory and data buffers used in loads/stores.
+  DataBufferFactory db_factory_;
+  DataBuffer *db_;
+  DataBuffer *tag_db_;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__CHERIOT_LOAD_FILTER_H_
diff --git a/cheriot/cheriot_register.cc b/cheriot/cheriot_register.cc
new file mode 100644
index 0000000..af76381
--- /dev/null
+++ b/cheriot/cheriot_register.cc
@@ -0,0 +1,617 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_register.h"
+
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "absl/numeric/bits.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/register.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using PermissionFormats = CheriotRegister::PermissionFormats;
+
+// Helper function to extract a bit range from a uint64_t given msb and lsb.
+template <typename T>
+static inline T Extract(T value, int msb, int lsb) {
+  T shift = lsb;
+  T mask = (1ULL << (msb - lsb + 1)) - 1;
+  return (value >> shift) & mask;
+}
+
+CheriotRegister::CheriotRegister(generic::ArchState *state,
+                                 absl::string_view name)
+    : generic::Register<uint32_t>(state, name) {
+  ResetNull();
+}
+
+void CheriotRegister::ResetNull() {
+  // When called by the constructor, the data buffer may be nullptr.
+  if (data_buffer() != nullptr) {
+    data_buffer()->Set<uint32_t>(0, 0);
+  }
+  Clear();
+}
+
+void CheriotRegister::ResetMemoryRoot() {
+  set_base(0);
+  set_top(0x1'0000'0000ULL);
+  set_permissions_format(kMemoryCapReadWrite);
+  set_permissions(kPermitGlobal | kPermitLoad | kPermitStore |
+                  kPermitLoadStoreCapability | kPermitStoreLocalCapability |
+                  kPermitLoadGlobal | kPermitLoadMutable);
+  set_object_type(kUnsealed);
+  set_reserved(0);
+  set_tag(true);
+  is_dirty_ = true;
+  is_null_ = false;
+  exponent_ = 24;
+  raw_ = Compress();
+}
+
+void CheriotRegister::ResetExecuteRoot() {
+  set_base(0);
+  set_top(0x1'0000'0000ULL);
+  set_permissions_format(kExecutable);
+  set_permissions(kPermitGlobal | kPermitExecute | kPermitLoad |
+                  kPermitLoadStoreCapability | kPermitLoadGlobal |
+                  kPermitLoadMutable | kPermitAccessSystemRegisters);
+  set_object_type(kUnsealed);
+  set_reserved(0);
+  set_tag(true);
+  is_dirty_ = true;
+  is_null_ = false;
+  exponent_ = 24;
+  raw_ = Compress();
+}
+
+void CheriotRegister::ResetSealingRoot() {
+  set_base(0);
+  set_top(0x1'0000'0000ULL);
+  set_permissions_format(kSealing);
+  set_permissions(kPermitGlobal | kPermitSeal | kPermitUnseal | kUserPerm0);
+  set_object_type(kUnsealed);
+  set_reserved(0);
+  set_tag(true);
+  is_dirty_ = true;
+  is_null_ = false;
+  exponent_ = 24;
+  raw_ = Compress();
+}
+
+void CheriotRegister::Clear() {
+  set_base(0);
+  set_top(0);
+  set_permissions_format(kSealing);
+  set_permissions(0);
+  set_object_type(kUnsealed);
+  set_reserved(0);
+  set_tag(false);
+  is_dirty_ = true;
+  is_null_ = false;
+  raw_ = 0;
+  exponent_ = 0;
+}
+
+void CheriotRegister::ClearPermissions(uint32_t permission_bits) {
+  set_permissions(permissions() & ~permission_bits);
+  set_permissions(ExpandPermissions(CompressPermissions()));
+}
+
+bool CheriotRegister::SetBounds(uint32_t req_base, uint64_t req_length) {
+  if (is_null_) {
+    Expand(address(), 0, /*tag=*/false);
+    is_null_ = false;
+  }
+  // Compute the requested top based on base and length.
+  uint64_t new_top = static_cast<uint64_t>(req_base) + req_length;
+  uint32_t exp;
+  // Determine the appropriate exponent in order to perform proper rounding
+  // of base and top.
+  if (req_length >= 0x1'0000'0000ULL) {
+    exp = 24;
+  } else {
+    exp = 23 - absl::countl_zero(static_cast<uint32_t>(req_length) | 0x1ff);
+    if (exp > 14) exp = 24;
+  }
+  if (exp == 0) {
+    set_base(req_base);
+    set_top(req_base + req_length);
+    exponent_ = 0;
+    raw_ = Compress();
+    return true; /* exact */
+  }
+
+  // Adjust base and top as needed.
+  uint64_t exp_mask = (1ULL << exp) - 1;
+  uint64_t new_base = req_base & ~exp_mask;
+  uint64_t new_top_correction =
+      static_cast<uint64_t>((new_top & exp_mask) != 0);
+  new_top &= ~exp_mask;
+  // Correct for any truncation in top if top was rounded down.
+  new_top += new_top_correction << exp;
+  // Recompute the length based on the rounded base and top.
+  uint64_t new_length = new_top - new_base;
+  uint32_t new_exp;
+  if (new_length >= 0x1'0000'0000ULL) {
+    new_exp = 24;
+  } else {
+    new_exp = 23 - absl::countl_zero(static_cast<uint32_t>(new_length) | 0x1ff);
+    if (new_exp > 14) new_exp = 24;
+  }
+  exponent_ = new_exp;
+  // Check if the rounding of base and top increased the length so much that it
+  // now requires a larger exponent. If so, recompute base and top. This can
+  // only happen once, so no need to recheck.
+  if (new_exp > exp) {
+    new_top = static_cast<uint64_t>(new_base) + new_length;
+    if (tag() && (new_top > 0x1'0000'0000ULL)) new_top = 0x1'0000'0000ULL;
+    exp_mask = (1ULL << new_exp) - 1;
+    // Adjust base and top as needed.
+    new_base &= ~exp_mask;
+    new_top_correction = static_cast<uint64_t>((new_top & exp_mask) != 0);
+    new_top &= ~exp_mask;
+    // Correct for any truncation.
+    new_top += new_top_correction << exp;
+  }
+  // Set the top and base.
+  set_top(new_top);
+  set_base(new_base);
+  raw_ = Compress();
+  // If the address is not in bounds, clear the tag.
+  if ((address() > top()) || (address() < base())) Invalidate();
+  // If the length and the base are the same as requested, the bounds were
+  // set exactly.
+  return (req_base == new_base) && (new_length == req_length);
+}
+
+std::pair<uint32_t, uint64_t> CheriotRegister::ComputeBounds() {
+  if (is_null_) {
+    Expand(address(), 0, /*tag=*/false);
+  }
+  uint64_t base_bits = raw_ & 0x1ff;
+  uint64_t top_bits = (raw_ >> 9) & 0x1ff;
+  uint64_t a_mid = (address() >> exponent_) & 0x1ff;
+  uint64_t a_hi = (a_mid < base_bits ? 1 : 0);
+  uint64_t t_hi = top_bits < base_bits ? 1 : 0;
+  uint64_t c_b = 0 - a_hi;
+  uint64_t c_t = t_hi - a_hi;
+  uint64_t a_top = address() >> (exponent_ + 9);
+  uint64_t base = ((a_top + c_b) << (exponent_ + 9)) | (base_bits << exponent_);
+  base &= 0xffff'ffff;
+  uint64_t top = ((a_top + c_t) << (exponent_ + 9)) | (top_bits << exponent_);
+  top &= 0x1'ffff'ffffULL;
+  return std::make_pair(static_cast<uint32_t>(base), top);
+}
+
+uint32_t CheriotRegister::Compress() const {
+  if (is_null_) return 0;
+
+  uint32_t compressed = 0;
+  // Compute the compressed permissions representation.
+  uint32_t permissions_field = CompressPermissions();
+  // Only store the low 3 bits of the object type. The fourth is implied based
+  // on the capability type.
+  compressed |= (object_type() & 0b111) << 22;
+  compressed |= permissions_field << 25;
+  compressed |= reserved_ << 31;
+  // If the expanded capability is not dirty, we don't have to re-do the
+  // exponent and top/base computations.
+  if (!is_dirty_) {
+    compressed |= raw_ & 0x3f'ffff;
+    return compressed;
+  }
+  // Compute exponent.
+  uint32_t exp;
+  if (length() >= 0x1'0000'0000ULL) {
+    exp = 24;
+  } else {
+    exp = 23 - absl::countl_zero(static_cast<uint32_t>(length()) | 0x1ff);
+  }
+  uint32_t exp_field = exp;
+  // Any exponent over 14 gets set to 24 (the max).
+  if (exp > 14) {
+    exp = 24;
+    exp_field = 0xf;
+  }
+  // Get the bounds. Note, there is no need for any specific rounding, since
+  // bounds are automatically rounded by SetBounds.
+  uint32_t top_field = (top() >> exp) & 0x1ff;
+  uint32_t base_field = (base() >> exp) & 0x1ff;
+  // Assemble the fields.
+  compressed |= base_field;
+  compressed |= top_field << 9;
+  compressed |= exp_field << 18;
+  return compressed;
+}
+
+void CheriotRegister::Expand(uint32_t address, uint32_t compressed, bool tag) {
+  // Extract bit fields.
+  uint64_t top_9 = Extract(compressed, kTop[0], kTop[1]);
+  uint32_t base_9 = Extract(compressed, kBase[0], kBase[1]);
+  uint32_t exp = Extract(compressed, kExponent[0], kExponent[1]);
+  if (exp == 15) exp = 24;
+  uint64_t a_top = static_cast<uint64_t>(address) >> (exp + 9);
+  uint32_t a_mid = Extract(address, exp + 8, exp);
+
+  // Compute correction factors.
+  uint64_t a_hi = a_mid < base_9 ? 1 : 0;
+  uint64_t t_hi = top_9 < base_9 ? 1 : 0;
+  uint64_t c_b = 0 - a_hi;
+  uint64_t c_t = t_hi - a_hi;
+  uint64_t new_base64 = ((a_top + c_b) << (exp + 9)) | (base_9 << exp);
+  uint64_t new_top = ((a_top + c_t) << (exp + 9)) | (top_9 << exp);
+
+  // Only use the lower 32 bits.
+  uint64_t new_base = static_cast<uint32_t>(new_base64);
+  // Expand permissions.
+  uint32_t compressed_permissions =
+      Extract(compressed, kPermissions[0], kPermissions[1]);
+  uint32_t new_permissions = ExpandPermissions(compressed_permissions);
+
+  // Set the fields.
+  if (tag) {
+    set_base(0);
+    set_top(0x1'0000'0000ULL);
+    (void)SetBounds(new_base, new_top - new_base64);
+  } else {
+    set_base(new_base);
+    set_top(new_top & 0x1'ffff'ffffULL);
+  }
+  exponent_ = exp;
+  uint32_t obj_type = Extract(compressed, kObjectType[0], kObjectType[1]);
+  if (obj_type && (new_permissions & kPermitExecute) == 0) {
+    // Bit 3 of the object type is implied by the capability type. For non
+    // execute capabilities, set it to one.
+    obj_type |= 0b1'000;
+  }
+  set_object_type(obj_type);
+  set_permissions(ExpandPermissions(compressed_permissions));
+  set_reserved(Extract(compressed, kReserved[0], kReserved[1]));
+  set_tag(tag);
+  data_buffer()->Set<uint32_t>(0, address);
+  raw_ = compressed;
+  is_dirty_ = false;
+  is_null_ = false;
+}
+
+void CheriotRegister::Validate() {
+  if (!tag() | is_null_) return;
+
+  // The capability is still valid if the address is representable.
+  set_tag(IsRepresentable());
+}
+
+bool CheriotRegister::IsValid() const {
+  if (!tag() || is_null_) return false;
+  uint32_t address = data_buffer()->Get<uint32_t>(0);
+  return (address < top()) && (address >= base());
+}
+
+bool CheriotRegister::IsSealed() const {
+  if (is_null_ || !tag()) return false;
+
+  if (HasPermission(kPermitExecute)) {
+    return (object_type() == kSentry) ||
+           (object_type() == kInterruptEnablingSentry) ||
+           (object_type() == kInterruptDisablingSentry) ||
+           (object_type() == kSealedExecutable6) ||
+           (object_type() == kSealedExecutable7);
+  } else {
+    return ((object_type() >= 9) && (object_type() <= 15));
+  }
+}
+
+absl::Status CheriotRegister::Seal(const CheriotRegister &source,
+                                   uint32_t obj_type) {
+  if (is_null_) {
+    Expand(address(), 0, /*tag=*/false);
+    is_null_ = false;
+  }
+  absl::Status status = absl::OkStatus();
+  // Check that the conditions are correct for sealing the target capability.
+  if (!tag()) {
+    status = absl::InvalidArgumentError("Target is not a valid capability");
+  } else if (IsSealed()) {
+    status =
+        absl::InvalidArgumentError("Cannot seal already sealed capability");
+  } else if (!source.tag()) {
+    status = absl::InvalidArgumentError(
+        "Sealing capability is not a valid capability");
+  } else if (source.IsSealed()) {
+    status =
+        absl::InvalidArgumentError("Cannot seal using a sealed capability");
+  } else if ((source.permissions() & PermissionBits::kPermitSeal) == 0) {
+    status = absl::PermissionDeniedError("Missing sealing permission");
+  } else if (!source.IsValid()) {
+    status =
+        absl::InvalidArgumentError("Sealing capability address out of range");
+  }
+
+  // Different sealing values for memory and execute capabilities.
+  if (status.ok()) {
+    if (HasPermission(PermissionBits::kPermitExecute)) {
+      switch (obj_type) {
+        case kSentry:
+        case kInterruptEnablingSentry:
+        case kInterruptDisablingSentry:
+        case kSealedExecutable6:
+        case kSealedExecutable7:
+          // The sealing type is ok.
+          break;
+        default:
+          // Invalidate if the capability does not have execute permission.
+          status = absl::InvalidArgumentError(
+              "Invalid object type for executable capability");
+          break;
+      }
+    } else {
+      // Invalidate if the object type is out of range.
+      if ((obj_type <= 0b1000) || (obj_type > 0b1111)) {
+        status = absl::InvalidArgumentError(
+            "Invalid object type for non-execute capability");
+      }
+    }
+  }
+  // Keep object type 1 wider than the compressed object type.
+  obj_type &= (1 << (kObjectType[0] - kObjectType[1] + 1 + 1)) - 1;
+  set_object_type(obj_type);
+  if (!status.ok()) Invalidate();
+  return status;
+}
+
+absl::Status CheriotRegister::Unseal(const CheriotRegister &source,
+                                     uint32_t obj_type) {
+  if (is_null_) {
+    Expand(address(), 0, /*tag=*/false);
+    is_null_ = false;
+  }
+  // Check that the conditions are correct for unsealing the target capability.
+  auto status = absl::OkStatus();
+  if (!tag()) {
+    status = absl::InvalidArgumentError("Target is not a valid capability");
+  } else if (IsUnsealed()) {
+    status =
+        absl::InvalidArgumentError("Cannot unseal already unsealed capability");
+  } else if (!source.tag()) {
+    status = absl::InvalidArgumentError(
+        "Unsealing capability is not a valid capability");
+  } else if (source.IsSealed()) {
+    status =
+        absl::InvalidArgumentError("Cannot unseal using a sealed capability");
+  } else if ((source.permissions() & PermissionBits::kPermitUnseal) == 0) {
+    status = absl::PermissionDeniedError("Missing unsealing permission");
+  } else if (!source.IsValid()) {
+    status =
+        absl::InvalidArgumentError("Unsealing capability address out of range");
+  } else if (obj_type != object_type()) {
+    status =
+        absl::InvalidArgumentError("Unsealing capability object type mismatch");
+  }
+  // Unseal the capability.
+  set_object_type(kUnsealed);
+  return status;
+}
+
+bool CheriotRegister::IsUnsealed() const {
+  return tag() && (object_type() == kUnsealed);
+}
+
+bool CheriotRegister::IsRepresentable() const {
+  if (exponent_ == 24) return true;
+  uint64_t address = data_buffer()->Get<uint32_t>(0);
+  uint64_t cap_base = base();
+  return (cap_base <= address) &&
+         (address < (cap_base + (1ULL << (exponent_ + 9))));
+}
+
+bool CheriotRegister::IsSentry() const {
+  return !is_null_ && (object_type() >= kSentry) &&
+         (object_type() <= kInterruptEnablingSentry);
+}
+
+void CheriotRegister::CopyFrom(const CheriotRegister &other) {
+  data_buffer()->CopyFrom(other.data_buffer());
+  if (other.is_null_) {
+    Expand(address(), 0, /*tag=*/false);
+    return;
+  }
+  is_null_ = false;
+  set_tag(other.tag());
+  set_top(other.top());
+  set_base(other.base());
+  set_permissions(other.permissions());
+  set_object_type(other.object_type());
+  set_reserved(other.reserved());
+  exponent_ = other.exponent();
+  is_dirty_ = other.is_dirty_;
+  raw_ = other.raw_;
+}
+
+bool CheriotRegister::operator==(const CheriotRegister &other) const {
+  return (tag() == other.tag()) && (top() == other.top()) &&
+         (base() == other.base()) && (permissions() == other.permissions()) &&
+         (object_type() == other.object_type()) &&
+         (reserved() == other.reserved());
+}
+
+bool CheriotRegister::IsMemoryEqual(const CheriotRegister &other) const {
+  return (address() == other.address()) && (Compress() == other.Compress());
+}
+
+void CheriotRegister::SetAddress(uint32_t address) {
+  if (!tag()) {
+    data_buffer()->Set<uint32_t>(0, address);
+    uint64_t mask = ~((1ULL << (exponent_ + 9)) - 1);
+    auto len = length();
+    set_base(address & mask);
+    set_top(static_cast<uint64_t>(base()) + len);
+    return;
+  }
+  set_address(address);
+}
+
+// Create a text representation of the capability.
+std::string CheriotRegister::AsString() const {
+  std::string output;
+  absl::StrAppend(&output, absl::StrFormat("0x%08X", address()));
+  absl::StrAppend(
+      &output, " (v:", tag() ? "1 " : "0 ", absl::StrFormat("0x%08X", base()),
+      "-", absl::StrFormat("0x%09X", top()),
+      " l:", absl::StrFormat("0x%09X", length()), " o:",
+      absl::StrFormat(
+          "0x%X",
+          object_type() |
+              ((object_type() && !(permissions() & kPermitExecute)) ? 0x8
+                                                                    : 0x0)),
+      " p:", permissions() & kPermitGlobal ? "G " : "- ",
+      permissions() & kPermitLoad ? "R" : "-",
+      permissions() & kPermitStore ? "W" : "-",
+      permissions() & kPermitLoadStoreCapability ? "c" : "-",
+      permissions() & kPermitLoadMutable ? "m" : "-",
+      permissions() & kPermitLoadGlobal ? "g" : "-",
+      permissions() & kPermitStoreLocalCapability ? "l " : "- ",
+      permissions() & kPermitExecute ? "X" : "-",
+      permissions() & kPermitAccessSystemRegisters ? "a " : "- ",
+      permissions() & kPermitSeal ? "S" : "-",
+      permissions() & kPermitUnseal ? "U" : "-",
+      permissions() & kUserPerm0 ? "0)" : "-)");
+  return output;
+}
+
+// Determine the current permission format.
+PermissionFormats CheriotRegister::GetPermissionFormat() const {
+  if (is_null_) return kSealing;
+  // Check to see if it is an executable permission format.
+  if ((permissions() & kImpliedCapabilities[kExecutable]) ==
+      kImpliedCapabilities[kExecutable]) {
+    return kExecutable;
+  }
+
+  // Check to see if it is a mem_cap_rw format.
+  if ((permissions() & kImpliedCapabilities[kMemoryCapReadWrite]) ==
+      kImpliedCapabilities[kMemoryCapReadWrite]) {
+    return kMemoryCapReadWrite;
+  }
+
+  // Check to see if it is a mem_cap_ro format.
+  if ((permissions() & kImpliedCapabilities[kMemoryCapReadOnly]) ==
+      kImpliedCapabilities[kMemoryCapReadOnly]) {
+    return kMemoryCapReadOnly;
+  }
+
+  // Check to see if it is a mem_cap_wo format.
+  if ((permissions() & kImpliedCapabilities[kMemoryCapWriteOnly]) ==
+      kImpliedCapabilities[kMemoryCapWriteOnly]) {
+    return kMemoryCapWriteOnly;
+  }
+
+  // Check to see if is a memory data only format.
+  if ((permissions() & kPermitLoad) || (permissions() & kPermitStore)) {
+    return kMemoryDataOnly;
+  }
+
+  return kSealing;
+}
+
+uint32_t CheriotRegister::CompressPermissions() const {
+  // Determine the target format based on the currently set permissions.
+  if (is_null_) return 0;
+
+  auto format = GetPermissionFormat();
+  uint32_t compressed;
+  switch (format) {
+    case kMemoryCapReadWrite:
+      compressed = 0b0'11'000;
+      compressed |= ((permissions() & kPermitGlobal) != 0) << 5;
+      compressed |= ((permissions() & kPermitStoreLocalCapability) != 0) << 2;
+      compressed |= ((permissions() & kPermitLoadMutable) != 0) << 1;
+      compressed |= ((permissions() & kPermitLoadGlobal) != 0);
+      return compressed;
+    case kMemoryCapReadOnly:
+      compressed = 0b0'101'00;
+      compressed |= ((permissions() & kPermitGlobal) != 0) << 5;
+      compressed |= ((permissions() & kPermitLoadMutable) != 0) << 1;
+      compressed |= ((permissions() & kPermitLoadGlobal) != 0);
+      return compressed;
+    case kMemoryCapWriteOnly:
+      compressed = 0b0'10000;
+      compressed |= ((permissions() & kPermitGlobal) != 0) << 5;
+      return compressed;
+    case kExecutable:
+      compressed = 0b0'01'000;
+      compressed |= ((permissions() & kPermitGlobal) != 0) << 5;
+      compressed |= ((permissions() & kPermitAccessSystemRegisters) != 0) << 2;
+      compressed |= ((permissions() & kPermitLoadMutable) != 0) << 1;
+      compressed |= ((permissions() & kPermitLoadGlobal) != 0);
+      return compressed;
+    case kMemoryDataOnly:
+      compressed = 0b0'100'00;
+      compressed |= ((permissions() & kPermitGlobal) != 0) << 5;
+      compressed |= ((permissions() & kPermitLoad) != 0) << 1;
+      compressed |= ((permissions() & kPermitStore) != 0);
+      return compressed;
+    default:
+      compressed = 0b0'00'000;
+      compressed |= ((permissions() & kPermitGlobal) != 0) << 5;
+      compressed |= ((permissions() & kUserPerm0) != 0) << 2;
+      compressed |= ((permissions() & kPermitSeal) != 0) << 1;
+      compressed |= ((permissions() & kPermitUnseal) != 0);
+      return compressed;
+  }
+}
+
+uint32_t CheriotRegister::ExpandPermissions(uint32_t compressed) const {
+  // Determine the source compressed format based on table lookup.
+  PermissionFormats format = kPermissionFormat[compressed & 0x1f];
+  // First add the implied capabilities using table lookup.
+  uint32_t expanded = kImpliedCapabilities[format];
+  // Add the global format if present.
+  expanded |= compressed & 0b1'00000 ? kPermitGlobal : 0;
+  // Perform permission expansion based on format using table lookup.
+  switch (format) {
+    case kSealing:
+      expanded |= kExpandSealed[compressed & 0b111];
+      return expanded;
+    case kExecutable:
+      expanded |= kExpandExecutable[compressed & 0b111];
+      return expanded;
+    case kMemoryCapWriteOnly:
+      // Only implied permissions, so just return the expanded value.
+      return expanded;
+    case kMemoryDataOnly:
+      expanded |= kExpandMemoryDataOnly[compressed & 0b11];
+      return expanded;
+    case kMemoryCapReadOnly:
+      expanded |= kExpandMemoryCapReadOnly[compressed & 0b11];
+      return expanded;
+    case kMemoryCapReadWrite:
+      expanded |= kExpandMemoryCapReadWrite[compressed & 0b111];
+      return expanded;
+  }
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_register.h b/cheriot/cheriot_register.h
new file mode 100644
index 0000000..57c19f2
--- /dev/null
+++ b/cheriot/cheriot_register.h
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_REGISTER_H_
+#define MPACT_CHERIOT__CHERIOT_REGISTER_H_
+
+// This file contains the definition of the Capability class used to implement
+// the CHERI RiscV IoT capability.
+
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/register.h"
+
+// This file defines the CHERI RiscV 64 bit capability register for use in
+// a unified register file as described in CHERIoT.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+class CheriotState;
+
+class CheriotRegister : public generic::Register<uint32_t> {
+ public:
+  // Set of permission bits in uncompressed view.
+  enum PermissionBits : uint32_t {
+    kPermitNone = 0,
+    kPermitGlobal = 1 << 0,
+    kPermitLoadGlobal = 1 << 1,
+    kPermitStore = 1 << 2,
+    kPermitLoadMutable = 1 << 3,
+    kPermitStoreLocalCapability = 1 << 4,
+    kPermitLoad = 1 << 5,
+    kPermitLoadStoreCapability = 1 << 6,
+    kPermitAccessSystemRegisters = 1 << 7,
+    kPermitExecute = 1 << 8,
+    kPermitUnseal = 1 << 9,
+    kPermitSeal = 1 << 10,
+    kUserPerm0 = 1 << 11,
+    kPermitMask = (1 << 12) - 1,
+  };
+  // In the compressed representation of the capability, the permission bits
+  // are stored in one of the below compressed formats.
+  enum PermissionFormats : uint32_t {
+    kMemoryCapReadWrite,
+    kMemoryCapReadOnly,
+    kMemoryCapWriteOnly,
+    kMemoryDataOnly,
+    kExecutable,
+    kSealing,
+  };
+  // Special object types.
+  enum ObjectType : uint32_t {
+    kUnsealed = 0,
+    kSentry = 1,
+    kInterruptDisablingSentry = 2,
+    kInterruptEnablingSentry = 3,
+    kReserved4 = 4,
+    kReserved5 = 5,
+    kSealedExecutable6 = 6,
+    kSealedExecutable7 = 7,
+    kReserved8 = 8,
+    // 9-15 are sealed non-executable capabilities.
+  };
+
+  static constexpr uint32_t kNullCapability = 0;
+  static constexpr unsigned kCapabilitySizeInBytes = 8;
+  static constexpr unsigned kGranuleShift = 3;
+
+  // Value type of this register class.
+  using ValueType = uint32_t;
+
+  // {msb, lsb} pairs of fields in the compressed capability.
+  // Bit layout:
+  // |3|3    2|2   2|2   1|1       |         |
+  // |1|0    5|4   2|1   8|7      9|8       0|
+  //  R  perm  otype  exp   base       top
+  //
+  static constexpr int kBase[2] = {8, 0};
+  static constexpr int kTop[2] = {17, 9};
+  static constexpr int kExponent[2] = {21, 18};
+  static constexpr int kObjectType[2] = {24, 22};
+  static constexpr int kPermissions[2] = {30, 25};
+  static constexpr int kReserved[2] = {31, 31};
+
+  // Constructor. Default constructors and assignment are disabled.
+  CheriotRegister(generic::ArchState *state, absl::string_view name);
+  CheriotRegister() = delete;
+  CheriotRegister(const CheriotRegister &) = delete;
+  CheriotRegister &operator=(const CheriotRegister &) = delete;
+
+  // These functions set the capability register to a known state, either the
+  // null capability, or one of the three root capabilities. All capabilities
+  // have to be derived from a root capability.
+  void ResetNull();
+  void ResetMemoryRoot();
+  void ResetExecuteRoot();
+  void ResetSealingRoot();
+  // Return true if the capability register has the given permission.
+  bool HasPermission(uint32_t permission_bits) const {
+    return (permissions() & permission_bits) != 0;
+  }
+  // Clears the capability as a null capability, but does not change the
+  // address.
+  void Clear();
+  // Removes one or more permission from the current capability.
+  void ClearPermissions(uint32_t permission_bits);
+  // Clear the tag - invalidates the capability.
+  void ClearTag();
+  // Compute the bounds.
+  std::pair<uint32_t, uint64_t> ComputeBounds();
+  // Get the compressed representation of the capability.
+  uint32_t Compress() const;
+  // Expand the compressed capability representation.
+  void Expand(uint32_t address, uint32_t compressed, bool tag);
+  // If the address is out of range, invalidate the tag.
+  void Validate();
+  // Return true if the current capability is valid, i.e., tag is true and
+  // the address is in range.
+  bool IsValid() const;
+  // Is representable.
+  bool IsRepresentable() const;
+  // Seal (unseal) the current capability based on permissions and address field
+  // in source. Returns ok status if the operation is successful.
+  absl::Status Seal(const CheriotRegister &source, uint32_t obj_type);
+  absl::Status Unseal(const CheriotRegister &source, uint32_t obj_type);
+  // Returns true if the capability is sealed, i.e., that the object type is set
+  // to a valid, non-reserved object type.
+  bool IsSealed() const;
+  // Returns true if the capability is unsealed, i.e., that the object type is
+  // zero.
+  bool IsUnsealed() const;
+  // Returns true if the capability is a sentry.
+  bool IsSentry() const;
+  // Clears the tag.
+  void Invalidate() { set_tag(false); }
+  // Set bounds, return true if they're precise, i.e., that the base and length
+  // do not have to be rounded, false otherwise.
+  bool SetBounds(uint32_t req_base, uint64_t req_length);
+  // Return true if the address is in bounds of the capability.
+  bool IsInBounds(uint32_t cap_address, uint32_t size) const {
+    return (cap_address >= base()) &&
+           (top() >= (uint64_t)cap_address + (uint64_t)size);
+  }
+  // Copy fields from other capability register.
+  void CopyFrom(const CheriotRegister &other);
+  // Equal operator.
+  bool operator==(const CheriotRegister &other) const;
+  bool IsMemoryEqual(const CheriotRegister &other) const;
+  // Text representation.
+  std::string AsString() const;
+  // Update address with change to base and top as needed.
+  void SetAddress(uint32_t address);
+  // Accessors.
+  bool tag() const { return is_null_ ? false : tag_; }
+  void set_tag(bool tag) { tag_ = tag; }
+
+  uint32_t address() const { return data_buffer()->Get<uint32_t>(0); }
+  void set_address(uint32_t address) {
+    data_buffer()->Set<uint32_t>(0, address);
+    Validate();
+  }
+
+  uint64_t top() const { return is_null_ ? address() & ~0x1ffULL : top_; }
+  uint32_t base() const { return is_null_ ? address() & ~0x1ffULL : base_; }
+  uint64_t length() const {
+    // Length is only 33 bits, so mask off the value.
+    return is_null_ ? 0 : (top_ - base_) & 0x1'ffff'ffffULL;
+  }
+  uint32_t exponent() const { return is_null_ ? 0 : exponent_; }
+  uint32_t permissions() const { return is_null_ ? 0 : permissions_; }
+  void set_permissions(uint32_t permissions) { permissions_ = permissions; }
+
+  uint32_t object_type() const { return is_null_ ? 0 : object_type_; }
+  void set_object_type(uint32_t object_type) {
+    object_type_ = object_type & 0xf;
+  }
+
+  uint32_t reserved() const { return is_null_ ? 0 : reserved_; }
+  void set_reserved(uint32_t reserved) { reserved_ = reserved & 0x1; }
+
+  bool is_null() const { return is_null_; }
+  void set_is_null() { is_null_ = true; }
+
+ private:
+  // These are the capabilities in each compressed capability permission format
+  // that are writable.
+  static constexpr uint32_t kWritableCapabilites[] = {
+      /* kMemoryCapReadWrite */ kPermitGlobal | kPermitStoreLocalCapability |
+          kPermitLoadMutable | kPermitLoadGlobal,
+      /* kMemoryCapReadOnly */ kPermitGlobal | kPermitLoadMutable |
+          kPermitLoadGlobal,
+      /* kMemoryCapWriteOnly */ kPermitGlobal,
+      /* kMemoryDataOnly */ kPermitGlobal | kPermitLoad | kPermitStore,
+      /* kExecutable */ kPermitGlobal | kPermitAccessSystemRegisters |
+          kPermitLoadMutable | kPermitLoadGlobal,
+      /* kSealing */ kPermitGlobal | kUserPerm0 | kPermitSeal | kPermitUnseal,
+  };
+
+  // The different compressed capability permission formats have different sets
+  // of implied capabilities.
+  static constexpr uint32_t kImpliedCapabilities[] = {
+      /* kMemoryCapReadWrite */ kPermitLoad | kPermitLoadStoreCapability |
+          kPermitStore,
+      /* kMemoryCapReadOnly */ kPermitLoad | kPermitLoadStoreCapability,
+      /* kMemoryCapWriteOnly */ kPermitStore | kPermitLoadStoreCapability,
+      /* kMemoryDataOnly */ 0,
+      /* kExecutable */ kPermitExecute | kPermitLoad |
+          kPermitLoadStoreCapability,
+      /* kSealing */ 0,
+  };
+
+  // Decoding table for compressed permission formats.
+  static constexpr PermissionFormats kPermissionFormat[] = {
+      /* 00000 */ kSealing,
+      /* 00001 */ kSealing,
+      /* 00010 */ kSealing,
+      /* 00011 */ kSealing,
+      /* 00100 */ kSealing,
+      /* 00101 */ kSealing,
+      /* 00110 */ kSealing,
+      /* 00111 */ kSealing,
+      /* 01000 */ kExecutable,
+      /* 01001 */ kExecutable,
+      /* 01010 */ kExecutable,
+      /* 01011 */ kExecutable,
+      /* 01100 */ kExecutable,
+      /* 01101 */ kExecutable,
+      /* 01110 */ kExecutable,
+      /* 01111 */ kExecutable,
+      /* 10000 */ kMemoryCapWriteOnly,
+      /* 10001 */ kMemoryDataOnly,
+      /* 10010 */ kMemoryDataOnly,
+      /* 10011 */ kMemoryDataOnly,
+      /* 10100 */ kMemoryCapReadOnly,
+      /* 10101 */ kMemoryCapReadOnly,
+      /* 10110 */ kMemoryCapReadOnly,
+      /* 10111 */ kMemoryCapReadOnly,
+      /* 11000 */ kMemoryCapReadWrite,
+      /* 11001 */ kMemoryCapReadWrite,
+      /* 11010 */ kMemoryCapReadWrite,
+      /* 11011 */ kMemoryCapReadWrite,
+      /* 11100 */ kMemoryCapReadWrite,
+      /* 11101 */ kMemoryCapReadWrite,
+      /* 11110 */ kMemoryCapReadWrite,
+      /* 11111 */ kMemoryCapReadWrite,
+  };
+
+  // Expansion table for permissions in the sealing format.
+  static constexpr uint32_t kExpandSealed[8] = {
+      kPermitNone,
+      kPermitUnseal,
+      kPermitSeal,
+      kPermitUnseal | kPermitSeal,
+      kUserPerm0,
+      kUserPerm0 | kPermitUnseal,
+      kUserPerm0 | kPermitSeal,
+      kUserPerm0 | kPermitUnseal | kPermitSeal,
+  };
+  // Expansion table for permissions in the executable format.
+  static constexpr uint32_t kExpandExecutable[8] = {
+      kPermitNone,
+      kPermitLoadGlobal,
+      kPermitLoadMutable,
+      kPermitLoadMutable | kPermitLoadGlobal,
+      kPermitAccessSystemRegisters,
+      kPermitAccessSystemRegisters | kPermitLoadGlobal,
+      kPermitAccessSystemRegisters | kPermitLoadMutable,
+      kPermitAccessSystemRegisters | kPermitLoadMutable | kPermitLoadGlobal,
+  };
+  // Expansion table for permissions in the memory data only format.
+  static constexpr uint32_t kExpandMemoryDataOnly[4] = {
+      kPermitNone,
+      kPermitStore,
+      kPermitLoad,
+      kPermitStore | kPermitLoad,
+  };
+  // Expansion table for permissions in the memory cap read only format.
+  static constexpr uint32_t kExpandMemoryCapReadOnly[4] = {
+      kPermitNone,
+      kPermitLoadGlobal,
+      kPermitLoadMutable,
+      kPermitLoadMutable | kPermitLoadGlobal,
+  };
+  // Expansion table for permissions in the memory cap read/write format.
+  static constexpr uint32_t kExpandMemoryCapReadWrite[8] = {
+      kPermitNone,
+      kPermitLoadGlobal,
+      kPermitLoadMutable,
+      kPermitLoadMutable | kPermitLoadGlobal,
+      kPermitStoreLocalCapability,
+      kPermitStoreLocalCapability | kPermitLoadGlobal,
+      kPermitStoreLocalCapability | kPermitLoadMutable,
+      kPermitStoreLocalCapability | kPermitLoadMutable | kPermitLoadGlobal,
+  };
+  // Get the permission format based on the current permissions.
+  PermissionFormats GetPermissionFormat() const;
+  // Return the current permissions in compressed form.
+  uint32_t CompressPermissions() const;
+  // Return the expanded view of the given compressed form of permissions.
+  uint32_t ExpandPermissions(uint32_t compressed) const;
+
+  // If top or base is changed, set is_dirty_ so that the values get properly
+  // compressed if written to memory.
+  void set_top(uint64_t top) {
+    top_ = top;
+    is_dirty_ = true;
+  }
+  void set_base(uint32_t base) {
+    base_ = base;
+    is_dirty_ = true;
+  }
+
+  PermissionFormats permissions_format() const { return permissions_format_; }
+  void set_permissions_format(PermissionFormats format) {
+    permissions_format_ = format;
+  }
+  bool tag_ = false;
+  uint64_t top_;
+  uint32_t base_;
+  // Stores the 12 permissions.
+  uint32_t permissions_;
+  PermissionFormats permissions_format_;
+  // Stores the 3 bit object type.
+  uint32_t object_type_;
+  uint32_t reserved_;
+  bool is_dirty_ = false;
+  bool is_null_ = false;
+  uint32_t raw_ = 0xdeadbeef;
+  uint32_t exponent_ = 0;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_REGISTER_H_
diff --git a/cheriot/cheriot_renode.cc b/cheriot/cheriot_renode.cc
new file mode 100644
index 0000000..d32bc39
--- /dev/null
+++ b/cheriot/cheriot_renode.cc
@@ -0,0 +1,496 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_renode.h"
+
+#include <cerrno>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <functional>
+#include <ios>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "absl/functional/bind_front.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "cheriot/cheriot_cli_forwarder.h"
+#include "cheriot/cheriot_debug_info.h"
+#include "cheriot/cheriot_instrumentation_control.h"
+#include "cheriot/cheriot_renode_cli_top.h"
+#include "cheriot/cheriot_renode_register_info.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/cheriot_top.h"
+#include "cheriot/debug_command_shell.h"
+#include "cheriot/memory_use_profiler.h"
+#include "cheriot/profiler.h"
+#include "cheriot/riscv_cheriot_minstret.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/proto/component_data.pb.h"
+#include "mpact/sim/util/memory/atomic_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/single_initiator_router.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+#include "riscv//riscv_arm_semihost.h"
+#include "riscv//riscv_clint.h"
+#include "riscv//riscv_state.h"
+#include "riscv//stoull_wrapper.h"
+#include "src/google/protobuf/text_format.h"
+#include "third_party/mpact_renode/renode_debug_interface.h"
+#include "third_party/mpact_renode/tagged_to_untagged_memory_transactor.h"
+
+::mpact::sim::renode::RenodeDebugInterface *CreateMpactSim(
+    std::string name, ::mpact::sim::util::MemoryInterface *renode_sysbus) {
+  auto *top = new ::mpact::sim::cheriot::CheriotRenode(name, renode_sysbus);
+  return top;
+}
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::cheriot::RiscVCheriotMInstret;
+using ::mpact::sim::cheriot::RiscVCheriotMInstreth;
+using ::mpact::sim::proto::ComponentData;
+using ::mpact::sim::renode::TaggedToUntaggedMemoryTransactor;
+using ::mpact::sim::riscv::RiscVClint;
+using ::mpact::sim::util::AtomicMemoryOpInterface;
+using ::mpact::sim::util::TaggedMemoryWatcher;
+
+using HaltReasonValueType =
+    ::mpact::sim::generic::CoreDebugInterface::HaltReasonValueType;
+using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
+using RunStatus = ::mpact::sim::generic::CoreDebugInterface::RunStatus;
+
+constexpr int kCapabilityGranule = 8;
+
+// Configuration names.
+constexpr std::string_view kTaggedMemoryBase = "memoryBase";
+constexpr std::string_view kTaggedMemorySize = "memorySize";
+constexpr std::string_view kRevocationMemoryBase = "revocationMemoryBase";
+constexpr std::string_view kClintMMRBase = "clintMMRBase";
+constexpr std::string_view kCLIPort = "cliPort";
+constexpr std::string_view kWaitForCLI = "waitForCLI";
+constexpr std::string_view kInstProfile = "instProfile";
+constexpr std::string_view kMemProfile = "memProfile";
+
+CheriotRenode::CheriotRenode(std::string name, MemoryInterface *renode_sysbus)
+    : name_(name), renode_sysbus_(renode_sysbus) {
+  router_ = new mpact::sim::util::SingleInitiatorRouter(name + "_router");
+  renode_router_ =
+      new mpact::sim::util::SingleInitiatorRouter(name + "_renode_router");
+  auto *data_memory = static_cast<TaggedMemoryInterface *>(router_);
+  // Instantiate memory profiler, but disable it until the config information
+  // has been received.
+  mem_profiler_ = new TaggedMemoryUseProfiler(data_memory);
+  data_memory = mem_profiler_;
+  mem_profiler_->set_is_enabled(false);
+  // Instantiate cheriot_top.
+  cheriot_top_ =
+      new CheriotTop(name, static_cast<MemoryInterface *>(router_), data_memory,
+                     static_cast<MemoryInterface *>(router_));
+  // Initialize minstret/minstreth. Bind the instruction counter to those
+  // registers.
+  auto minstret_res = cheriot_top_->state()->csr_set()->GetCsr("minstret");
+  auto minstreth_res = cheriot_top_->state()->csr_set()->GetCsr("minstreth");
+  if (!minstret_res.ok() || !minstreth_res.ok()) {
+    LOG(ERROR) << name << ":Error while initializing minstret/minstreth\n";
+  }
+  auto *minstret = static_cast<RiscVCheriotMInstret *>(minstret_res.value());
+  auto *minstreth = static_cast<RiscVCheriotMInstreth *>(minstreth_res.value());
+  minstret->set_counter(cheriot_top_->counter_num_instructions());
+  minstreth->set_counter(cheriot_top_->counter_num_instructions());
+
+  // Set up the memory router with the system bus. Other devices are added once
+  // config info has been received. Add a tagged default memory transactor, so
+  // that any tagged loads/stores are forward to the sysbus without tags.
+  tagged_sysbus_ = new TaggedToUntaggedMemoryTransactor(renode_sysbus_);
+  CHECK_OK(router_->AddDefaultTarget<MemoryInterface>(renode_sysbus));
+  CHECK_OK(router_->AddDefaultTarget<TaggedMemoryInterface>(tagged_sysbus_));
+
+  // Create memory. These memories will be added to the core router when there
+  // is configuration data for the address space that belongs to the core. The
+  // memories will be added to the renode router immediately as the default
+  // target, since memory references from ReNode are only in the memory range
+  // exposed on the sysbus.
+  tagged_memory_ =
+      new mpact::sim::util::TaggedFlatDemandMemory(kCapabilityGranule);
+  atomic_memory_ = new mpact::sim::util::AtomicMemory(tagged_memory_);
+
+  // Need to set up the renode router with the tagged_memory.
+  CHECK_OK(
+      renode_router_->AddDefaultTarget<TaggedMemoryInterface>(tagged_memory_));
+  CHECK_OK(renode_router_->AddDefaultTarget<MemoryInterface>(tagged_memory_));
+
+  // Set up semihosting.
+  semihost_ = new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord32,
+                                   cheriot_top_->inst_memory(),
+                                   cheriot_top_->data_memory());
+  // Set up special handlers (ebreak, wfi, ecall).
+  cheriot_top_->state()->AddEbreakHandler([this](const Instruction *inst) {
+    if (this->semihost_->IsSemihostingCall(inst)) {
+      this->semihost_->OnEBreak(inst);
+      return true;
+    }
+    if (this->cheriot_top_->HasBreakpoint(inst->address())) {
+      this->cheriot_top_->RequestHalt(HaltReason::kSoftwareBreakpoint, nullptr);
+      return true;
+    }
+    return false;
+  });
+  cheriot_top_->state()->set_on_wfi([](const Instruction *) { return true; });
+  cheriot_top_->state()->set_on_ecall(
+      [](const Instruction *) { return false; });
+  semihost_->set_exit_callback([this]() {
+    this->cheriot_top_->RequestHalt(HaltReason::kProgramDone, nullptr);
+  });
+}
+
+CheriotRenode::~CheriotRenode() {
+  // Halt the core just to be safe.
+  (void)cheriot_top_->Halt();
+  // Write out instruction profile.
+  if (inst_profiler_ != nullptr) {
+    std::string inst_profile_file_name =
+        absl::StrCat("./mpact_cheriot_", name_, "_inst_profile.csv");
+    std::fstream inst_profile_file(inst_profile_file_name.c_str(),
+                                   std::ios_base::out);
+    if (!inst_profile_file.good()) {
+      LOG(ERROR) << "Failed to write profile to file";
+    } else {
+      inst_profiler_->WriteProfile(inst_profile_file);
+    }
+    inst_profile_file.close();
+  }
+  // Export counters.
+  auto component_proto = std::make_unique<ComponentData>();
+  CHECK_OK(cheriot_top_->Export(component_proto.get()))
+      << "Failed to export proto";
+  std::string proto_file_name;
+  proto_file_name = absl::StrCat("./mpact_cheriot_", name_, ".proto");
+  std::fstream proto_file(proto_file_name.c_str(), std::ios_base::out);
+  std::string serialized;
+  if (!proto_file.good() || !google::protobuf::TextFormat::PrintToString(
+                                *component_proto.get(), &serialized)) {
+    LOG(ERROR) << "Failed to write proto to file";
+  } else {
+    proto_file << serialized;
+    proto_file.close();
+  }
+  // Clean up.
+  delete mem_profiler_;
+  delete inst_profiler_;
+  delete instrumentation_control_;
+  delete program_loader_;
+  delete cmd_shell_;
+  delete socket_cli_;
+  delete cheriot_renode_cli_top_;
+  delete cheriot_cli_forwarder_;
+  delete cheriot_top_;
+  delete semihost_;
+  delete router_;
+  delete atomic_memory_;
+  delete tagged_memory_;
+  delete clint_;
+  delete tagged_sysbus_;
+}
+
+absl::StatusOr<uint64_t> CheriotRenode::LoadExecutable(
+    const char *elf_file_name, bool for_symbols_only) {
+  program_loader_ = new ElfProgramLoader(this);
+  uint64_t entry_pt = 0;
+  if (for_symbols_only) {
+    auto res = program_loader_->LoadSymbols(elf_file_name);
+    if (!res.ok()) {
+      return res.status();
+    }
+    entry_pt = res.value();
+  } else {
+    auto res = program_loader_->LoadProgram(elf_file_name);
+    if (!res.ok()) {
+      return res.status();
+    }
+    entry_pt = res.value();
+  }
+  auto res = program_loader_->GetSymbol("tohost");
+  // if 'tohost' is not found, just return entry_pt.
+  if (!res.ok()) return entry_pt;
+  // If there is a 'tohost' symbol, set up a write watchpoint on that address
+  // to catch writes that mark program exit.
+  uint64_t tohost_addr = res.value().first;
+  // Add to_host watchpoint that halts the execution when program exit is
+  // signaled.
+  auto *db = cheriot_top_->state()->db_factory()->Allocate<uint32_t>(2);
+  auto status = cheriot_top_->tagged_watcher()->SetStoreWatchCallback(
+      TaggedMemoryWatcher::AddressRange{tohost_addr,
+                                        tohost_addr + 2 * sizeof(uint32_t) - 1},
+      [this, tohost_addr, db](uint64_t addr, int sz) {
+        static DataBuffer *load_db = db;
+        if (load_db == nullptr) return;
+        tagged_memory_->Load(tohost_addr, load_db, nullptr, nullptr);
+        uint32_t code = load_db->Get<uint32_t>(0);
+        if (code & 0x1) {
+          // The return code is in the upper 31 bits.
+          code >>= 1;
+          LOG(INFO) << absl::StrCat(
+              "Simulation halting due to tohost write: exit ", absl::Hex(code));
+          (void)cheriot_top_->RequestHalt(HaltReason::kProgramDone, nullptr);
+          load_db->DecRef();
+        }
+      });
+  // Add instruction profiler it hasn't already been added.
+  if (inst_profiler_ == nullptr) {
+    inst_profiler_ = new Profiler(*program_loader_, 2);
+    cheriot_top_->counter_pc()->AddListener(inst_profiler_);
+    cheriot_top_->counter_pc()->SetIsEnabled(false);
+  } else {
+    // If it has been added already, set the elf loader, and make sure the pc
+    // counter is enabled.
+    inst_profiler_->SetElfLoader(program_loader_);
+    cheriot_top_->counter_pc()->SetIsEnabled(true);
+  }
+  return entry_pt;
+}
+
+// Each of the following methods checks to see if the command line enabled
+// "top" interface is null. If it is not, it uses that to control the simulator,
+// as it provides proper prioritization and handling of both ReNode and command
+// line commands. Otherwise it uses the cheriot_top interface directly.
+
+absl::StatusOr<int> CheriotRenode::Step(int num) {
+  if (cheriot_renode_cli_top_ != nullptr)
+    return cheriot_renode_cli_top_->RenodeStep(num);
+  return cheriot_top_->Step(num);
+}
+
+absl::StatusOr<HaltReasonValueType> CheriotRenode::GetLastHaltReason() {
+  if (cheriot_renode_cli_top_ != nullptr)
+    return cheriot_renode_cli_top_->RenodeGetLastHaltReason();
+  return cheriot_top_->GetLastHaltReason();
+}
+
+// Perform direct read of the memory through the renode router. The renode
+// router avoids routing the request back out to the sysbus.
+absl::StatusOr<size_t> CheriotRenode::ReadMemory(uint64_t address, void *buf,
+                                                 size_t length) {
+  auto *db = db_factory_.Allocate<uint8_t>(length);
+  renode_router_->Load(address, db, nullptr, nullptr);
+  std::memcpy(buf, db->raw_ptr(), length);
+  db->DecRef();
+  return length;
+}
+
+// Perform direct write of the memory through the renode router. The renode
+// router avoids routing the request back out to the sysbus.
+absl::StatusOr<size_t> CheriotRenode::WriteMemory(uint64_t address,
+                                                  const void *buf,
+                                                  size_t length) {
+  auto *db = db_factory_.Allocate<uint8_t>(length);
+  std::memcpy(db->raw_ptr(), buf, length);
+  renode_router_->Store(address, db);
+  db->DecRef();
+  return length;
+}
+
+absl::StatusOr<uint64_t> CheriotRenode::ReadRegister(uint32_t reg_id) {
+  auto ptr = CheriotDebugInfo::Instance()->debug_register_map().find(reg_id);
+  if (ptr == CheriotDebugInfo::Instance()->debug_register_map().end()) {
+    return absl::NotFoundError(
+        absl::StrCat("Not found reg id: ", absl::Hex(reg_id)));
+  }
+  if (cheriot_renode_cli_top_ != nullptr)
+    return cheriot_renode_cli_top_->RenodeReadRegister(ptr->second);
+  return cheriot_top_->ReadRegister(ptr->second);
+}
+
+absl::Status CheriotRenode::WriteRegister(uint32_t reg_id, uint64_t value) {
+  auto ptr = CheriotDebugInfo::Instance()->debug_register_map().find(reg_id);
+  if (ptr == CheriotDebugInfo::Instance()->debug_register_map().end()) {
+    return absl::NotFoundError(
+        absl::StrCat("Not found reg id: ", absl::Hex(reg_id)));
+  }
+  if (cheriot_renode_cli_top_ != nullptr)
+    return cheriot_renode_cli_top_->RenodeWriteRegister(ptr->second, value);
+  return cheriot_top_->WriteRegister(ptr->second, value);
+}
+
+int32_t CheriotRenode::GetRenodeRegisterInfoSize() const {
+  return CheriotRenodeRegisterInfo::GetRenodeRegisterInfo().size();
+}
+
+absl::Status CheriotRenode::GetRenodeRegisterInfo(int32_t index,
+                                                  int32_t max_len, char *name,
+                                                  RenodeCpuRegister &info) {
+  auto const &register_info =
+      CheriotRenodeRegisterInfo::GetRenodeRegisterInfo();
+  if ((index < 0) || (index >= register_info.size())) {
+    return absl::OutOfRangeError(
+        absl::StrCat("Register info index (", index, ") out of range"));
+  }
+  info = register_info[index];
+  auto const &reg_map = CheriotDebugInfo::Instance()->debug_register_map();
+  auto ptr = reg_map.find(info.index);
+  if (ptr == reg_map.end()) {
+    name[0] = '\0';
+  } else {
+    strncpy(name, ptr->second.c_str(), max_len);
+  }
+  return absl::OkStatus();
+}
+
+static absl::StatusOr<uint64_t> ParseNumber(const std::string &number) {
+  if (number.empty()) {
+    return absl::InvalidArgumentError("Empty number");
+  }
+  absl::StatusOr<uint64_t> res;
+  if ((number.size() > 2) && (number.substr(0, 2) == "0x")) {
+    res = riscv::internal::stoull(number.substr(2), nullptr, 16);
+  } else if (number[0] == '0') {
+    res = riscv::internal::stoull(number.substr(1), nullptr, 8);
+  } else {
+    res = riscv::internal::stoull(number.substr(2), nullptr, 16);
+  }
+  if (!res.ok()) {
+    LOG(ERROR) << "Invalid number: " << number;
+    return absl::InvalidArgumentError(absl::StrCat("Invalid number: ", number));
+  }
+  return res.value();
+}
+
+absl::Status CheriotRenode::SetConfig(const char *config_names[],
+                                      const char *config_values[], int size) {
+  uint64_t tagged_memory_base = 0;
+  uint64_t tagged_memory_size = 0;
+  uint64_t revocation_memory_base = 0;
+  uint64_t clint_mmr_base = 0;
+  bool do_inst_profile = false;
+  int cli_port = 0;
+  int wait_for_cli = 0;
+  for (int i = 0; i < size; ++i) {
+    std::string name(config_names[i]);
+    auto res = ParseNumber(config_values[i]);
+    if (!res.ok()) {
+      return res.status();
+    }
+    auto value = res.value();
+    if (name == kTaggedMemoryBase) {
+      tagged_memory_base = value;
+    } else if (name == kTaggedMemorySize) {
+      tagged_memory_size = value;
+    } else if (name == kRevocationMemoryBase) {
+      revocation_memory_base = value;
+    } else if (name == kClintMMRBase) {
+      clint_mmr_base = value;
+    } else if (name == kCLIPort) {
+      cli_port = value;
+    } else if (name == kWaitForCLI) {
+      wait_for_cli = value;
+    } else if (name == kInstProfile) {
+      do_inst_profile = value != 0;
+    } else if (name == kMemProfile) {
+      mem_profiler_->set_is_enabled(value != 0);
+    } else {
+      LOG(ERROR) << "Unknown config name: " << name << " " << config_values[i];
+    }
+  }
+  if (tagged_memory_size == 0) {
+    return absl::InvalidArgumentError("tagged_memory_size is 0");
+  }
+  // Add the memory targets.
+  CHECK_OK(router_->AddTarget<AtomicMemoryOpInterface>(
+      atomic_memory_, tagged_memory_base,
+      tagged_memory_base + tagged_memory_size - 1));
+  CHECK_OK(router_->AddTarget<TaggedMemoryInterface>(
+      tagged_memory_, tagged_memory_base,
+      tagged_memory_base + tagged_memory_size - 1));
+  CHECK_OK(router_->AddTarget<MemoryInterface>(
+      tagged_memory_, tagged_memory_base,
+      tagged_memory_base + tagged_memory_size - 1));
+  // Memory mapped devices.
+  if (clint_mmr_base != 0) {
+    clint_ = new RiscVClint(/*period=*/100, cheriot_top_->state()->mip());
+    cheriot_top_->counter_num_cycles()->AddListener(clint_);
+    // Core local interrupt controller - clint.
+    CHECK_OK(router_->AddTarget<MemoryInterface>(clint_, clint_mmr_base,
+                                                 clint_mmr_base + 0xffffULL));
+  }
+  // Instruction profiler.
+  if (do_inst_profile) {
+    if (inst_profiler_ == nullptr) {
+      if (program_loader_ == nullptr) {
+        // If the program loader is null, assume that it will be added later,
+        // but don't enable the trace until it is.
+        inst_profiler_ = new Profiler(2);
+        cheriot_top_->counter_pc()->SetIsEnabled(false);
+      } else {
+        inst_profiler_ = new Profiler(*program_loader_, 2);
+        cheriot_top_->counter_pc()->SetIsEnabled(true);
+      }
+      cheriot_top_->counter_pc()->AddListener(inst_profiler_);
+    }
+  }
+  // If the cli port has been specified, then instantiate the requisite classes.
+  if (cli_port != 0 && (cheriot_renode_cli_top_ == nullptr)) {
+    cheriot_renode_cli_top_ =
+        new CheriotRenodeCLITop(cheriot_top_, wait_for_cli != 0);
+    cheriot_cli_forwarder_ = new CheriotCLIForwarder(cheriot_renode_cli_top_);
+    cmd_shell_ = new DebugCommandShell();
+    instrumentation_control_ = new CheriotInstrumentationControl(
+        cmd_shell_, cheriot_top_, mem_profiler_);
+    cmd_shell_->AddCore({cheriot_cli_forwarder_, program_loader_});
+    cmd_shell_->AddCommand(
+        instrumentation_control_->Usage(),
+        absl::bind_front(&CheriotInstrumentationControl::PerformShellCommand,
+                         instrumentation_control_));
+    socket_cli_ =
+        new SocketCLI(cli_port, *cmd_shell_,
+                      absl::bind_front(&CheriotRenodeCLITop::SetConnected,
+                                       cheriot_renode_cli_top_));
+    if (!socket_cli_->good()) {
+      return absl::InternalError(
+          absl::StrCat("Failed to create socket CLI (", errno, ")"));
+    }
+  }
+  return absl::OkStatus();
+}
+
+absl::Status CheriotRenode::SetIrqValue(int32_t irq_num, bool irq_value) {
+  switch (irq_num) {
+    case *riscv::InterruptCode::kMachineExternalInterrupt:
+      cheriot_top_->state()->mip()->set_meip(irq_value);
+      return absl::OkStatus();
+    case *riscv::InterruptCode::kMachineTimerInterrupt:
+      cheriot_top_->state()->mip()->set_mtip(irq_value);
+      return absl::OkStatus();
+    case *riscv::InterruptCode::kMachineSoftwareInterrupt:
+      cheriot_top_->state()->mip()->set_msip(irq_value);
+      return absl::OkStatus();
+    default:
+      return absl::NotFoundError(
+          absl::StrCat("Unsupported irq number: ", irq_num));
+  }
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_renode.h b/cheriot/cheriot_renode.h
new file mode 100644
index 0000000..0edfa14
--- /dev/null
+++ b/cheriot/cheriot_renode.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_RENODE_H_
+#define MPACT_CHERIOT__CHERIOT_RENODE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "cheriot/cheriot_cli_forwarder.h"
+#include "cheriot/cheriot_instrumentation_control.h"
+#include "cheriot/cheriot_renode_cli_top.h"
+#include "cheriot/cheriot_top.h"
+#include "cheriot/debug_command_shell.h"
+#include "cheriot/memory_use_profiler.h"
+#include "cheriot/profiler.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/util/memory/atomic_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/single_initiator_router.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "mpact/sim/util/program_loader/elf_program_loader.h"
+#include "riscv//riscv_arm_semihost.h"
+#include "riscv//riscv_clint.h"
+#include "third_party/mpact_renode/renode_debug_interface.h"
+#include "third_party/mpact_renode/socket_cli.h"
+
+// This file defines a wrapper class for the CheriotTop that adds Arm
+// semihosting. In addition, the .cc class defines a global namespace
+// function that is used by the renode wrapper to create a top simulator
+// instance.
+//
+// In addition, when the configuration specifies a command line interface port,
+// an object of the SocketCLI class is instantiated to provide a command line
+// interface accessible over a socket. In this case the wrapper no longer
+// directly calls the top simulator control class, but routes the calls through
+// a combined ReNode/CLI interface that manages the priorities and access of
+// ReNode and command line commands to the simulator control class.
+
+extern ::mpact::sim::renode::RenodeDebugInterface *CreateMpactSim(
+    std::string name, ::mpact::sim::util::MemoryInterface *renode_sysbus);
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::DataBufferFactory;
+using ::mpact::sim::renode::SocketCLI;
+using ::mpact::sim::riscv::RiscVArmSemihost;
+using ::mpact::sim::riscv::RiscVClint;
+using ::mpact::sim::util::AtomicMemory;
+using ::mpact::sim::util::ElfProgramLoader;
+using ::mpact::sim::util::MemoryInterface;
+using ::mpact::sim::util::SingleInitiatorRouter;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+using ::mpact::sim::util::TaggedMemoryInterface;
+
+class CheriotRenode : public renode::RenodeDebugInterface {
+ public:
+  // Supported IRQ request types.
+  enum class IrqType {
+    kMachineSoftwareInterrupt = 0x3,
+    kMachineExternalInterrupt = 0xb,
+  };
+
+  enum RenodeState {
+    kIdle = 0,
+    kStepping = 1,
+    kRunning = 2,
+  };
+
+  enum CLIState {
+    kDisconnected = 0,
+    kConnected = 1,
+  };
+
+  using ::mpact::sim::generic::CoreDebugInterface::HaltReason;
+  using ::mpact::sim::generic::CoreDebugInterface::RunStatus;
+  using RenodeCpuRegister = ::mpact::sim::renode::RenodeCpuRegister;
+
+  // Constructor takes a name and a memory interface that is used for memory
+  // transactions routed to the system bus.
+  CheriotRenode(std::string name, MemoryInterface *renode_sysbus);
+  ~CheriotRenode() override;
+
+  absl::StatusOr<uint64_t> LoadExecutable(const char *elf_file_name,
+                                          bool for_symbols_only) override;
+  // Step the core by num instructions.
+  absl::StatusOr<int> Step(int num) override;
+  // Returns the reason for the most recent halt.
+  absl::StatusOr<HaltReasonValueType> GetLastHaltReason() override;
+  // Read/write the numeric id registers.
+  absl::StatusOr<uint64_t> ReadRegister(uint32_t reg_id) override;
+  absl::Status WriteRegister(uint32_t reg_id, uint64_t value) override;
+  // Get register data buffer call. Not implemented, stubbed out to return null.
+  // Read/write the buffers to memory.
+  absl::StatusOr<size_t> ReadMemory(uint64_t address, void *buf,
+                                    size_t length) override;
+  absl::StatusOr<size_t> WriteMemory(uint64_t address, const void *buf,
+                                     size_t length) override;
+  // Return register information.
+  int32_t GetRenodeRegisterInfoSize() const override;
+  absl::Status GetRenodeRegisterInfo(int32_t index, int32_t max_len, char *name,
+                                     RenodeCpuRegister &info) override;
+
+  // Set configuration value.
+  absl::Status SetConfig(const char *config_names[],
+                         const char *config_values[], int size) override;
+
+  // Set IRQ value for supported IRQs. Supported irq_nums are:
+  //          MachineSoftwareInterrupt = 0x3
+  //          handled by the clint for now: MachineTimerInterrupt = 0x7
+  //          MachineExternalInterrupt = 0xb
+  // These correspond to the msip and meip bits of the mip register.
+  absl::Status SetIrqValue(int32_t irq_num, bool irq_value) override;
+
+ private:
+  std::string name_;
+  MemoryInterface *renode_sysbus_ = nullptr;
+  TaggedMemoryInterface *tagged_sysbus_ = nullptr;
+  CheriotTop *cheriot_top_ = nullptr;
+  RiscVArmSemihost *semihost_ = nullptr;
+  SingleInitiatorRouter *router_ = nullptr;
+  SingleInitiatorRouter *renode_router_ = nullptr;
+  DataBufferFactory db_factory_;
+  AtomicMemory *atomic_memory_ = nullptr;
+  TaggedFlatDemandMemory *tagged_memory_ = nullptr;
+  RiscVClint *clint_ = nullptr;
+  SocketCLI *socket_cli_ = nullptr;
+  CheriotRenodeCLITop *cheriot_renode_cli_top_ = nullptr;
+  CheriotCLIForwarder *cheriot_cli_forwarder_ = nullptr;
+  ElfProgramLoader *program_loader_ = nullptr;
+  DebugCommandShell *cmd_shell_ = nullptr;
+  Profiler *inst_profiler_ = nullptr;
+  TaggedMemoryUseProfiler *mem_profiler_ = nullptr;
+  CheriotInstrumentationControl *instrumentation_control_ = nullptr;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_RENODE_H_
diff --git a/cheriot/cheriot_renode_cli_top.cc b/cheriot/cheriot_renode_cli_top.cc
new file mode 100644
index 0000000..302a7fc
--- /dev/null
+++ b/cheriot/cheriot_renode_cli_top.cc
@@ -0,0 +1,96 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_renode_cli_top.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "cheriot/cheriot_debug_interface.h"
+#include "cheriot/cheriot_top.h"
+#include "third_party/mpact_renode/renode_cli_top.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+CheriotRenodeCLITop::CheriotRenodeCLITop(CheriotTop *cheriot_top,
+                                         bool wait_for_cli)
+    : renode::RenodeCLITop(cheriot_top, wait_for_cli),
+      cheriot_top_(cheriot_top) {}
+
+absl::StatusOr<size_t> CheriotRenodeCLITop::CLIReadTagMemory(uint64_t address,
+                                                             void *buf,
+                                                             size_t length) {
+  return DoWhenInControl<absl::StatusOr<size_t>>(
+      [this, address, buf, length]() {
+        return cheriot_top_->ReadTagMemory(address, buf, length);
+      });
+}
+
+absl::Status CheriotRenodeCLITop::CLISetDataWatchpoint(uint64_t address,
+                                                       size_t length,
+                                                       AccessType access_type) {
+  return DoWhenInControl<absl::Status>([this, address, length, access_type]() {
+    return cheriot_top_->SetDataWatchpoint(address, length, access_type);
+  });
+}
+
+absl::Status CheriotRenodeCLITop::CLIClearDataWatchpoint(
+    uint64_t address, AccessType access_type) {
+  return DoWhenInControl<absl::Status>([this, address, access_type]() {
+    return cheriot_top_->ClearDataWatchpoint(address, access_type);
+  });
+}
+
+void CheriotRenodeCLITop::CLISetBreakOnControlFlowChange(bool value) {
+  DoWhenInControl<void>(
+      [this, value]() { cheriot_top_->SetBreakOnControlFlowChange(value); });
+}
+
+absl::StatusOr<int> CheriotRenodeCLITop::CLISetActionPoint(
+    uint64_t address, absl::AnyInvocable<void(uint64_t, int)> action) {
+  return DoWhenInControl<absl::StatusOr<int>>([this, address, &action]() {
+    return cheriot_top_->SetActionPoint(address, std::move(action));
+  });
+}
+
+absl::Status CheriotRenodeCLITop::CLIClearActionPoint(uint64_t address,
+                                                      int id) {
+  return DoWhenInControl<absl::Status>([this, address, id]() {
+    return cheriot_top_->ClearActionPoint(address, id);
+  });
+}
+
+absl::Status CheriotRenodeCLITop::CLIEnableAction(uint64_t address, int id) {
+  return DoWhenInControl<absl::Status>([this, address, id]() {
+    return cheriot_top_->EnableAction(address, id);
+  });
+}
+
+absl::Status CheriotRenodeCLITop::CLIDisableAction(uint64_t address, int id) {
+  return DoWhenInControl<absl::Status>([this, address, id]() {
+    return cheriot_top_->DisableAction(address, id);
+  });
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_renode_cli_top.h b/cheriot/cheriot_renode_cli_top.h
new file mode 100644
index 0000000..0819616
--- /dev/null
+++ b/cheriot/cheriot_renode_cli_top.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_RENODE_CLI_TOP_H_
+#define MPACT_CHERIOT__CHERIOT_RENODE_CLI_TOP_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "cheriot/cheriot_debug_interface.h"
+#include "cheriot/cheriot_top.h"
+#include "third_party/mpact_renode/renode_cli_top.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+// This class extends the RenodeCLITop with a few features specific to the
+// CherIoT CLI.
+class CheriotRenodeCLITop : public renode::RenodeCLITop {
+ public:
+  CheriotRenodeCLITop(CheriotTop *cheriot_top, bool wait_for_cli);
+
+  absl::StatusOr<size_t> CLIReadTagMemory(uint64_t address, void *buf,
+                                          size_t length);
+
+  absl::Status CLISetDataWatchpoint(uint64_t address, size_t length,
+                                    AccessType access_type);
+  absl::Status CLIClearDataWatchpoint(uint64_t address, AccessType access_type);
+  void CLISetBreakOnControlFlowChange(bool value);
+
+  absl::StatusOr<int> CLISetActionPoint(
+      uint64_t address, absl::AnyInvocable<void(uint64_t, int)> action);
+  absl::Status CLIClearActionPoint(uint64_t address, int id);
+  absl::Status CLIEnableAction(uint64_t address, int id);
+  absl::Status CLIDisableAction(uint64_t address, int id);
+
+ private:
+  CheriotTop *cheriot_top_ = nullptr;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_RENODE_CLI_TOP_H_
diff --git a/cheriot/cheriot_renode_register_info.cc b/cheriot/cheriot_renode_register_info.cc
new file mode 100644
index 0000000..96f0dba
--- /dev/null
+++ b/cheriot/cheriot_renode_register_info.cc
@@ -0,0 +1,91 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_renode_register_info.h"
+
+#include "cheriot/cheriot_debug_info.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::operator*;  // NOLINT - used below.
+
+CheriotRenodeRegisterInfo *CheriotRenodeRegisterInfo::instance_ = nullptr;
+
+void CheriotRenodeRegisterInfo::InitializeRenodeRegisterInfo() {
+  using DbgReg = DebugRegisterEnum;
+
+  renode_register_info_ = {
+      {*DbgReg::kPc, 32, true, false},   {*DbgReg::kC0, 32, true, true},
+      {*DbgReg::kC1, 32, true, false},   {*DbgReg::kC2, 32, true, false},
+      {*DbgReg::kC3, 32, true, false},   {*DbgReg::kC4, 32, true, false},
+      {*DbgReg::kC5, 32, true, false},   {*DbgReg::kC6, 32, true, false},
+      {*DbgReg::kC7, 32, true, false},   {*DbgReg::kC8, 32, true, false},
+      {*DbgReg::kC9, 32, true, false},   {*DbgReg::kC10, 32, true, false},
+      {*DbgReg::kC11, 32, true, false},  {*DbgReg::kC12, 32, true, false},
+      {*DbgReg::kC13, 32, true, false},  {*DbgReg::kC14, 32, true, false},
+      {*DbgReg::kC15, 32, true, false},  {*DbgReg::kC16, 32, true, false},
+      {*DbgReg::kC17, 32, true, false},  {*DbgReg::kC18, 32, true, false},
+      {*DbgReg::kC19, 32, true, false},  {*DbgReg::kC20, 32, true, false},
+      {*DbgReg::kC21, 32, true, false},  {*DbgReg::kC22, 32, true, false},
+      {*DbgReg::kC23, 32, true, false},  {*DbgReg::kC24, 32, true, false},
+      {*DbgReg::kC25, 32, true, false},  {*DbgReg::kC26, 32, true, false},
+      {*DbgReg::kC27, 32, true, false},  {*DbgReg::kC28, 32, true, false},
+      {*DbgReg::kC29, 32, true, false},  {*DbgReg::kC30, 32, true, false},
+      {*DbgReg::kC31, 32, true, false},  {*DbgReg::kF0, 32, false, false},
+      {*DbgReg::kF1, 32, false, false},  {*DbgReg::kF2, 32, false, false},
+      {*DbgReg::kF3, 32, false, false},  {*DbgReg::kF4, 32, false, false},
+      {*DbgReg::kF5, 32, false, false},  {*DbgReg::kF6, 32, false, false},
+      {*DbgReg::kF7, 32, false, false},  {*DbgReg::kF8, 32, false, false},
+      {*DbgReg::kF9, 32, false, false},  {*DbgReg::kF10, 32, false, false},
+      {*DbgReg::kF11, 32, false, false}, {*DbgReg::kF12, 32, false, false},
+      {*DbgReg::kF13, 32, false, false}, {*DbgReg::kF14, 32, false, false},
+      {*DbgReg::kF15, 32, false, false}, {*DbgReg::kF16, 32, false, false},
+      {*DbgReg::kF17, 32, false, false}, {*DbgReg::kF18, 32, false, false},
+      {*DbgReg::kF19, 32, false, false}, {*DbgReg::kF20, 32, false, false},
+      {*DbgReg::kF21, 32, false, false}, {*DbgReg::kF22, 32, false, false},
+      {*DbgReg::kF23, 32, false, false}, {*DbgReg::kF24, 32, false, false},
+      {*DbgReg::kF25, 32, false, false}, {*DbgReg::kF26, 32, false, false},
+      {*DbgReg::kF27, 32, false, false}, {*DbgReg::kF28, 32, false, false},
+      {*DbgReg::kF29, 32, false, false}, {*DbgReg::kF30, 32, false, false},
+      {*DbgReg::kF31, 32, false, false},
+  };
+}
+
+CheriotRenodeRegisterInfo::CheriotRenodeRegisterInfo() {
+  InitializeRenodeRegisterInfo();
+}
+
+const CheriotRenodeRegisterInfo::RenodeRegisterInfo &
+CheriotRenodeRegisterInfo::GetRenodeRegisterInfo() {
+  return Instance()->GetRenodeRegisterInfoPrivate();
+}
+
+CheriotRenodeRegisterInfo *CheriotRenodeRegisterInfo::Instance() {
+  if (instance_ == nullptr) {
+    instance_ = new CheriotRenodeRegisterInfo();
+  }
+  return instance_;
+}
+
+const CheriotRenodeRegisterInfo::RenodeRegisterInfo &
+CheriotRenodeRegisterInfo::GetRenodeRegisterInfoPrivate() {
+  return renode_register_info_;
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_renode_register_info.h b/cheriot/cheriot_renode_register_info.h
new file mode 100644
index 0000000..a3888d4
--- /dev/null
+++ b/cheriot/cheriot_renode_register_info.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_RENODE_REGISTER_INFO_H_
+#define MPACT_CHERIOT__CHERIOT_RENODE_REGISTER_INFO_H_
+
+#include <vector>
+
+#include "third_party/mpact_renode/renode_debug_interface.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+// This file defines a class that is used to store the register information
+// that needs to be provided to renode for the RiscV registers.
+
+class CheriotRenodeRegisterInfo {
+ public:
+  using RenodeRegisterInfo = std::vector<renode::RenodeCpuRegister>;
+
+  static const RenodeRegisterInfo &GetRenodeRegisterInfo();
+
+ private:
+  CheriotRenodeRegisterInfo();
+  static CheriotRenodeRegisterInfo *Instance();
+  void InitializeRenodeRegisterInfo();
+  const RenodeRegisterInfo &GetRenodeRegisterInfoPrivate();
+
+  static CheriotRenodeRegisterInfo *instance_;
+  RenodeRegisterInfo renode_register_info_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_RENODE_REGISTER_INFO_H_
diff --git a/cheriot/cheriot_state.cc b/cheriot/cheriot_state.cc
new file mode 100644
index 0000000..95c795e
--- /dev/null
+++ b/cheriot/cheriot_state.cc
@@ -0,0 +1,741 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_state.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/riscv_cheriot_csr_enum.h"
+#include "cheriot/riscv_cheriot_minstret.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "riscv//riscv_csr.h"
+#include "riscv//riscv_misa.h"
+#include "riscv//riscv_state.h"
+
+ABSL_FLAG(uint64_t, revocation_ram_base, 0x8000'0000,
+          "Ram base for revocation.");
+ABSL_FLAG(uint64_t, revocation_mem_base, 0x8300'0000,
+          "Revocation memory base.");
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
+using ::mpact::sim::riscv::IsaExtension;
+using ::mpact::sim::riscv::RiscVXlen;
+using EC = ::mpact::sim::riscv::ExceptionCode;
+using ::mpact::sim::riscv::RiscVCsrEnum;
+using ::mpact::sim::riscv::RiscVCsrInterface;
+using ::mpact::sim::riscv::RiscVSimpleCsr;
+
+// These helper templates are used to store information about the CSR registers
+// used in CHERIoT RiscV (32 bits).
+template <typename T>
+struct CsrInfo {};
+
+template <>
+struct CsrInfo<uint32_t> {
+  using T = uint32_t;
+  static constexpr T kMhartidRMask = std::numeric_limits<T>::max();
+  static constexpr T kMhartidWMask = 0;
+  static constexpr T kMstatusInitialValue = 0x1800;
+  static constexpr T kMisaInitialValue =
+      (*RiscVXlen::RV32 << 30) | *IsaExtension::kIntegerMulDiv |
+      *IsaExtension::kRVIBaseIsa | *IsaExtension::kGExtension |
+      *IsaExtension::kSinglePrecisionFp | *IsaExtension::kDoublePrecisionFp |
+      *IsaExtension::kCompressed | *IsaExtension::kAtomic |
+      *IsaExtension::kSupervisorMode;
+  static constexpr T kMisaRMask = 0xc3ff'ffff;
+  static constexpr T kMisaWMask = 0x0;
+};
+
+// Three templated helper functions used to create individual CSRs.
+
+// This creates the CSR and assigns it to a pointer in the state object. Type
+// can be inferred from the state object pointer.
+template <typename T, typename... Ps>
+T *CreateCsr(CheriotState *state, T *&ptr,
+             std::vector<RiscVCsrInterface *> &csr_vec, Ps... pargs) {
+  auto *csr = new T(pargs...);
+  auto result = state->csr_set()->AddCsr(csr);
+  if (!result.ok()) {
+    LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
+                               "': ", result.message());
+    delete csr;
+    return nullptr;
+  }
+  csr_vec.push_back(csr);
+  ptr = csr;
+  return csr;
+}
+
+// This creates the CSR and assigns it to a pointer in the state object, however
+// that pointer is of abstract type, so the CSR type cannot be inferred, but
+// has to be specified in the call.
+template <typename T, typename... Ps>
+T *CreateCsr(CheriotState *state, RiscVCsrInterface *&ptr,
+             std::vector<RiscVCsrInterface *> &csr_vec, Ps... pargs) {
+  auto *csr = new T(pargs...);
+  auto result = state->csr_set()->AddCsr(csr);
+  if (!result.ok()) {
+    LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
+                               "': ", result.message());
+    delete csr;
+    return nullptr;
+  }
+  csr_vec.push_back(csr);
+  ptr = csr;
+  return csr;
+}
+
+// This creates the CSR, but does not assign it to a pointer in the state
+// object. That means the type cannot be inferred, but has to be specified
+// in the call.
+template <typename T, typename... Ps>
+T *CreateCsr(CheriotState *state, std::vector<RiscVCsrInterface *> &csr_vec,
+             Ps... pargs) {
+  auto *csr = new T(pargs...);
+  auto result = state->csr_set()->AddCsr(csr);
+  if (!result.ok()) {
+    LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
+                               "': ", result.message());
+    delete csr;
+    return nullptr;
+  }
+  csr_vec.push_back(csr);
+  return csr;
+}
+
+// Templated helper function that is used to create the set of CSRs needed
+// for simulation.
+template <typename T>
+void CreateCsrs(CheriotState *state,
+                std::vector<RiscVCsrInterface *> &csr_vec) {
+  absl::Status result;
+  // Create CSRs.
+  // misa
+  auto *misa = CreateCsr(state, state->misa_, csr_vec,
+                         CsrInfo<T>::kMisaInitialValue, state);
+  CHECK_NE(misa, nullptr);
+  // mtvec is replaced by mtcc
+
+  // mcause
+  CHECK_NE(
+      CreateCsr<RiscVSimpleCsr<T>>(state, state->mcause_, csr_vec, "mcause",
+                                   RiscVCsrEnum::kMCause, 0, state),
+      nullptr);
+
+  // Mip and Mie are always 32 bit.
+  // mip
+  auto *mip = CreateCsr(state, state->mip_, csr_vec, 0, state);
+  CHECK_NE(mip, nullptr);
+
+  // mie
+  auto *mie = CreateCsr(state, state->mie_, csr_vec, 0, state);
+  CHECK_NE(mie, nullptr);
+
+  // mhartid
+  CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(
+               state, csr_vec, "mhartid", *RiscVCheriotCsrEnum::kMHartId, 0,
+               CsrInfo<T>::kMhartidRMask, CsrInfo<T>::kMhartidWMask, state),
+           nullptr);
+
+  // mepc is replaced by mepcc
+
+  // mscratch
+  CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "mscratch",
+                                        *RiscVCsrEnum::kMScratch, 0, state),
+           nullptr);
+
+  // medeleg - machine mode exception delegation register. Not used.
+
+  // mideleg - machine mode interrupt delegation register. Not used.
+
+  // mstatus
+  auto *mstatus =
+      CreateCsr(state, state->mstatus_, csr_vec,
+                CsrInfo<uint32_t>::kMstatusInitialValue, state, misa);
+  CHECK_NE(mstatus, nullptr);
+  // mtval
+  auto *mtval = CreateCsr<RiscVSimpleCsr<T>>(
+      state, state->mtval_, csr_vec, "mtval", *RiscVCsrEnum::kMTval, 0, state);
+  CHECK_NE(mtval, nullptr);
+
+  // minstret/minstreth
+  CHECK_NE(CreateCsr<RiscVCheriotMInstret>(state, csr_vec, "minstret", state),
+           nullptr);
+  CHECK_NE(CreateCsr<RiscVCheriotMInstreth>(state, csr_vec, "minstreth", state),
+           nullptr);
+
+  // Stack high water mark CSRs. Mshwm gets updated automatically during
+  // execution. mshwm
+  auto *mshwm = CreateCsr<RiscVSimpleCsr<T>>(
+      state, state->mshwm_, csr_vec, "mshwm", *RiscVCheriotCsrEnum::kMshwm,
+      /*initial_value=*/0,
+      /*read_mask=*/0xffff'fff0, /*write_mask=*/0xffff'fff0, state);
+  CHECK_NE(mshwm, nullptr);
+  // mshwmb
+  auto *mshwmb = CreateCsr<RiscVSimpleCsr<T>>(
+      state, state->mshwmb_, csr_vec, "mshwmb", *RiscVCheriotCsrEnum::kMshwmb,
+      /*initial_value=*/0,
+      /*read_mask=*/0xffff'fff0, /*write_mask=*/0xffff'fff0, state);
+  CHECK_NE(mshwmb, nullptr);
+
+  // mccsr.
+  auto *mccsr = CreateCsr<RiscVSimpleCsr<T>>(
+      state, csr_vec, "mccsr", *RiscVCheriotCsrEnum::kMCcsr,
+      /*initial_value=*/0x3,
+      /*read_mask=*/0x3, /*write_mask=*/0x2, state);
+  CHECK_NE(mccsr, nullptr);
+
+  // Supervisor level CSRs
+  // None in CHERIoT.
+
+  // User level CSRs
+  // None in CHERIoT.
+
+  // Simulator CSRs
+
+  // Access current privilege mode. Omitted.
+}
+
+// This value is in the RV32ISA manual to support MMU, although in "BARE" mode
+// only the bottom 32-bit is valid.
+constexpr uint64_t kRiscv32MaxMemorySize = 0x3f'ffff'ffffULL;
+
+CheriotState::CheriotState(std::string_view id,
+                           util::TaggedMemoryInterface *memory,
+                           util::AtomicMemoryOpInterface *atomic_memory)
+    : generic::ArchState(id),
+      tagged_memory_(memory),
+      atomic_tagged_memory_(atomic_memory) {
+  for (auto &[name, index] : std::vector<std::pair<std::string, unsigned>>{
+           {"c0", 0b0'00000},   {"c1", 0b0'00001},   {"c2", 0b0'00010},
+           {"c3", 0b0'00011},   {"c4", 0b0'00100},   {"c5", 0b0'00101},
+           {"c6", 0b0'00110},   {"c7", 0b0'00111},   {"c8", 0b0'01000},
+           {"c9", 0b0'01001},   {"c10", 0b0'01010},  {"c11", 0b0'01011},
+           {"c12", 0b0'01100},  {"c13", 0b0'01101},  {"c14", 0b0'01110},
+           {"c15", 0b0'01111},  {"c16", 0b0'10000},  {"c17", 0b0'10001},
+           {"c18", 0b0'10010},  {"c19", 0b0'10011},  {"c20", 0b0'10100},
+           {"c21", 0b0'10101},  {"c22", 0b0'10110},  {"c23", 0b0'10111},
+           {"c24", 0b0'11000},  {"c25", 0b0'11001},  {"c26", 0b0'11010},
+           {"c27", 0b0'11011},  {"c28", 0b0'11100},  {"c29", 0b0'11101},
+           {"c30", 0b0'11110},  {"c31", 0b0'11111},  {"pcc", 0b1'00000},
+           {"mtcc", 0b1'11100}, {"mtdc", 0b1'11101}, {"mscratchc", 0b1'11110},
+           {"mepcc", 0b1'11111}}) {
+    cap_index_map_.emplace(name, index);
+  }
+  // Create root capabilities and the special capability CSRs.
+  executable_root_ = new CheriotRegister(this, "executable_root");
+  executable_root_->ResetExecuteRoot();
+  sealing_root_ = new CheriotRegister(this, "sealing_root");
+  sealing_root_->ResetSealingRoot();
+  memory_root_ = new CheriotRegister(this, "memory_root");
+  memory_root_->ResetMemoryRoot();
+  mtcc_ = AddRegister<CheriotRegister>("mtcc");
+  mtcc_->ResetExecuteRoot();
+  mepcc_ = AddRegister<CheriotRegister>("mepcc");
+  mepcc_->ResetExecuteRoot();
+  mtdc_ = AddRegister<CheriotRegister>("mtdc");
+  mtdc_->ResetMemoryRoot();
+  mscratchc_ = AddRegister<CheriotRegister>("mscratchc");
+  mscratchc_->ResetSealingRoot();
+  pcc_ = AddRegister<CheriotRegister>("pcc");
+  auto status = AddRegisterAlias<CheriotRegister>("pcc", "pc");
+  if (!status.ok()) {
+    LOG(ERROR) << "Error creating pc alias of 'pcc': " << status.message();
+    return;
+  }
+  pcc_->ResetExecuteRoot();
+  temp_reg_ = new CheriotRegister(this, "temp_reg");
+  // Add the general capability registers.
+  for (int i = 0; i < 32; i++) {
+    AddRegister<CheriotRegister>(absl::StrCat("c", i));
+  }
+  status = AddRegisterAlias<CheriotRegister>("c3", "cgp");
+  if (!status.ok()) {
+    LOG(ERROR) << "Error creating cgp alias of 'c3': " << status.message();
+    return;
+  }
+  auto [cgp_reg, unused] = GetRegister<CheriotRegister>("cgp");
+  cgp_ = cgp_reg;
+  // Create the other CSRs.
+  csr_set_ = new RiscVCsrSet();
+  CreateCsrs<uint32_t>(this, csr_vec_);
+  if (tagged_memory_ == nullptr) {
+    owned_tagged_memory_ = new util::TaggedFlatDemandMemory(
+        CheriotRegister::kCapabilitySizeInBytes);
+    tagged_memory_ = owned_tagged_memory_;
+  }
+  pc_src_operand_ = new RiscVCheri32PcSourceOperand(this);
+  set_pc_operand(pc_src_operand_);
+  // Create the revocation data buffer.
+  revocation_db_ = db_factory()->Allocate<uint8_t>(1);
+  revocation_ram_base_ = absl::GetFlag(FLAGS_revocation_ram_base);
+  revocation_mem_base_ = absl::GetFlag(FLAGS_revocation_mem_base);
+
+  set_max_physical_address(kRiscv32MaxMemorySize);
+}
+
+CheriotState::~CheriotState() {
+  delete executable_root_;
+  delete sealing_root_;
+  delete memory_root_;
+  delete owned_tagged_memory_;
+  delete pc_src_operand_;
+  for (auto *csr : csr_vec_) delete csr;
+  delete csr_set_;
+  delete temp_reg_;
+  revocation_db_->DecRef();
+}
+
+void CheriotState::Reset() {
+  for (auto &[unused, reg_ptr] : *registers()) {
+    reg_ptr->data_buffer()->Set<uint32_t>(0, 0);
+  }
+  // Reset CSRs.
+  pcc_->ResetExecuteRoot();
+  mtcc_->ResetExecuteRoot();
+  mepcc_->ResetExecuteRoot();
+  mtdc_->ResetMemoryRoot();
+  mscratchc_->ResetSealingRoot();
+  mstatus_->Set(CsrInfo<uint32_t>::kMstatusInitialValue);
+  mtval_->Set(0UL);
+  mshwm_->Set(0UL);
+  mshwmb_->Set(0UL);
+  mip_->Set(0UL);
+  mie_->Set(0UL);
+  csr_set_->GetCsr("minstret").value()->Set(0UL);
+  csr_set_->GetCsr("minstreth").value()->Set(0UL);
+  csr_set_->GetCsr("mcause").value()->Set(0UL);
+  csr_set_->GetCsr("misa").value()->Set(CsrInfo<uint32_t>::kMisaInitialValue);
+}
+
+void CheriotState::HandleCheriRegException(const Instruction *instruction,
+                                           uint64_t epc, ExceptionCode code,
+                                           const CheriotRegister *reg) {
+  // Map the CHERIoT exception to a RiscV trap.
+  unsigned mcause = kCheriExceptionCode;
+  uint32_t mtval = *code & 0b1'1111;
+  auto const &name = reg->name();
+  auto iter = cap_index_map_.find(name);
+  uint32_t cap_index = 0x1f;  // Set to a default value.
+  if (iter != cap_index_map_.end()) {
+    cap_index = iter->second;
+  }
+  mtval |= cap_index << 5;
+  Trap(/*is_interrupt*/ false, mtval, mcause, epc, instruction);
+}
+
+void CheriotState::set_max_physical_address(uint64_t max_physical_address) {
+  max_physical_address_ = std::min(max_physical_address, kRiscv32MaxMemorySize);
+}
+
+void CheriotState::set_min_physical_address(uint64_t min_physical_address) {
+  min_physical_address_ = std::min(min_physical_address, max_physical_address_);
+}
+
+void CheriotState::LoadCapability(const Instruction *instruction,
+                                  uint32_t address, DataBuffer *db,
+                                  DataBuffer *tags, Instruction *child,
+                                  CapabilityLoadContext32 *context) {
+  // Check for alignment.
+  uint64_t mask = db->size<uint8_t>() - 1;
+  if ((address & mask) != 0) {
+    Trap(/*is_interrupt*/ false, address, *EC::kLoadAddressMisaligned,
+         instruction == nullptr ? 0 : instruction->address(), instruction);
+    return;
+  }
+  // Check for physical address violation.
+  if (address < min_physical_address_ || address > max_physical_address_) {
+    Trap(/*is_interrupt*/ false, address, *EC::kLoadAccessFault,
+         instruction == nullptr ? 0 : instruction->address(), instruction);
+    return;
+  }
+  // Forward the load.
+  tagged_memory_->Load(address, db, tags, child, context);
+  if (!tracing_active_) return;
+  load_address_ = address;
+  load_db_ = db;
+  load_db_->IncRef();
+  load_tags_ = tags;
+  load_tags_->IncRef();
+}
+
+void CheriotState::StoreCapability(const Instruction *instruction,
+                                   uint32_t address, DataBuffer *db,
+                                   DataBuffer *tags) {
+  // Check for alignment.
+  uint64_t mask = db->size<uint8_t>() - 1;
+  if ((address & mask) != 0) {
+    Trap(/*is_interrupt*/ false, address, *EC::kStoreAddressMisaligned,
+         instruction == nullptr ? 0 : instruction->address(), instruction);
+    return;
+  }
+  // Check for physical address violation.
+  if (address < min_physical_address_ || address > max_physical_address_) {
+    Trap(/*is_interrupt*/ false, address, *EC::kStoreAccessFault,
+         instruction == nullptr ? 0 : instruction->address(), instruction);
+    return;
+  }
+  // Check for stack accesses relative to mshwm/mshwmb.
+  if ((address >= mshwmb_->GetUint32()) && (address < mshwm_->GetUint32())) {
+    mshwm_->Set(address);
+  }
+  // Forward the store.
+  tagged_memory_->Store(address, db, tags);
+  if (!tracing_active_) return;
+  store_address_ = address;
+  store_db_ = db;
+  store_db_->IncRef();
+  store_tags_ = tags;
+  store_tags_->IncRef();
+}
+
+void CheriotState::LoadMemory(const Instruction *inst, uint64_t address,
+                              DataBuffer *db, Instruction *child_inst,
+                              ReferenceCount *context) {
+  // Check for alignment.
+  uint64_t mask = db->size<uint8_t>() - 1;
+  if ((address & mask) != 0) {
+    Trap(/*is_interrupt*/ false, address, *EC::kLoadAddressMisaligned,
+         inst == nullptr ? 0 : inst->address(), inst);
+    return;
+  }
+  // Check for physical address violation.
+  if (address < min_physical_address_ || address > max_physical_address_) {
+    Trap(/*is_interrupt*/ false, address, *EC::kLoadAccessFault,
+         inst == nullptr ? 0 : inst->address(), inst);
+    return;
+  }
+  // Forward the load.
+  tagged_memory_->Load(address, db, child_inst, context);
+  if (!tracing_active_) return;
+  load_address_ = address;
+  load_db_ = db;
+  load_db_->IncRef();
+  load_tags_ = nullptr;
+}
+
+void CheriotState::LoadMemory(const Instruction *inst, DataBuffer *address_db,
+                              DataBuffer *mask_db, int el_size, DataBuffer *db,
+                              Instruction *child_inst,
+                              ReferenceCount *context) {
+  // Check for alignment.
+  uint64_t mask = el_size - 1;
+  for (auto address : address_db->Get<uint64_t>()) {
+    if ((address & mask) != 0) {
+      Trap(/*is_interrupt*/ false, address, *EC::kLoadAddressMisaligned,
+           inst == nullptr ? 0 : inst->address(), inst);
+      return;
+    }
+  }
+  // Check for physical address violation.
+  for (auto address : address_db->Get<uint64_t>()) {
+    if (address < min_physical_address_ || address > max_physical_address_) {
+      Trap(/*is_interrupt*/ false, address, *EC::kLoadAccessFault,
+           inst == nullptr ? 0 : inst->address(), inst);
+      return;
+    }
+  }
+  // Forward the load.
+  tagged_memory_->Load(address_db, mask_db, el_size, db, child_inst, context);
+}
+
+void CheriotState::StoreMemory(const Instruction *inst, uint64_t address,
+                               DataBuffer *db) {
+  // Check for alignment.
+  uint64_t mask = db->size<uint8_t>() - 1;
+  if ((address & mask) != 0) {
+    Trap(/*is_interrupt*/ false, address, *EC::kStoreAddressMisaligned,
+         inst == nullptr ? 0 : inst->address(), inst);
+    return;
+  }
+  // Check for physical address violation.
+  if (address < min_physical_address_ || address > max_physical_address_) {
+    Trap(/*is_interrupt*/ false, address, *EC::kStoreAccessFault,
+         inst == nullptr ? 0 : inst->address(), inst);
+    return;
+  }
+  // Check for stack accesses relative to mshwm/mshwmb.
+  uint32_t address32 = static_cast<uint32_t>(address);
+  if ((address32 >= mshwmb_->GetUint32()) &&
+      (address32 < mshwm_->GetUint32())) {
+    mshwm_->Set(address32);
+  }
+  // Forward the store.
+  tagged_memory_->Store(address, db);
+  if (!tracing_active_) return;
+  store_address_ = address;
+  store_db_ = db;
+  store_db_->IncRef();
+  store_tags_ = nullptr;
+}
+
+void CheriotState::StoreMemory(const Instruction *inst, DataBuffer *address_db,
+                               DataBuffer *mask_db, int el_size,
+                               DataBuffer *db) {
+  // Check for alignment.
+  uint64_t mask = el_size - 1;
+  for (auto address : address_db->Get<uint64_t>()) {
+    if ((address & mask) != 0) {
+      Trap(/*is_interrupt*/ false, address, *EC::kStoreAddressMisaligned,
+           inst == nullptr ? 0 : inst->address(), inst);
+      return;
+    }
+  }
+  // Check for physical address violation.
+  for (auto address : address_db->Get<uint64_t>()) {
+    if (address < min_physical_address_ || address > max_physical_address_) {
+      Trap(/*is_interrupt*/ false, address, *EC::kStoreAccessFault,
+           inst == nullptr ? 0 : inst->address(), inst);
+      return;
+    }
+  }
+  // Check for stack accesses relative to mshwm/mshwmb.
+  for (auto address : address_db->Get<uint64_t>()) {
+    uint32_t address32 = static_cast<uint32_t>(address);
+    if ((address32 >= mshwmb_->GetUint32()) &&
+        (address32 < mshwm_->GetUint32())) {
+      mshwm_->Set(address32);
+    }
+  }
+  // Forward the store.
+  tagged_memory_->Store(address_db, mask_db, el_size, db);
+}
+
+void CheriotState::DbgLoadMemory(uint64_t address, DataBuffer *db) {
+  tagged_memory_->Load(address, db, nullptr, nullptr);
+}
+
+void CheriotState::Fence(const Instruction *inst, int fm, int predecessor,
+                         int successor) {
+  // TODO: Add fence operation once operations have non-zero latency.
+}
+
+void CheriotState::FenceI(const Instruction *inst) {
+  // TODO: Add instruction fence operation when needed.
+}
+
+void CheriotState::ECall(const Instruction *inst) {
+  // If there is a handler, call it.
+  if (on_ecall_ != nullptr) {
+    auto res = on_ecall_(inst);
+    // If the handler returns true, the ecall has been handled, just return.
+    if (res) return;
+  }
+
+  // Otherwise trap.
+  std::string where = (inst != nullptr)
+                          ? absl::StrCat(absl::Hex(inst->address()))
+                          : "unknown location";
+
+  EC code;
+  code = EC::kEnvCallFromMMode;
+
+  uint64_t epc = inst->address();
+  Trap(/*is_interrupt*/ false, 0, *code, epc, inst);
+}
+
+void CheriotState::EBreak(const Instruction *inst) {
+  // Call the handlers.
+  for (auto &handler : on_ebreak_) {
+    bool res = handler(inst);
+    // If a handler returns true, the ebreak has been handled. Just return.
+    if (res) return;
+  }
+  // Otherwise trap.
+  // Set the return address to the current instruction.
+  auto epc = (inst != nullptr) ? inst->address() : 0;
+  Trap(/*is_interrupt=*/false, /*trap_value=*/epc, 3, epc, inst);
+}
+
+void CheriotState::WFI(const Instruction *inst) {
+  // Call the handler.
+  if (on_wfi_ != nullptr) {
+    bool res = on_wfi_(inst);
+    // If the handler returns true, the wfi has been handled. Just return.
+    if (res) return;
+  }
+  // If no handler is specified, or if no handlers returns true, treat it
+  // as a nop.
+  std::string where = (inst != nullptr)
+                          ? absl::StrCat(absl::Hex(inst->address()))
+                          : "unknown location";
+
+  LOG(INFO) << "No handler for wfi: treating as nop: " << where;
+}
+
+void CheriotState::Cease(const Instruction *inst) {
+  // Call the handler.
+  if (on_cease_ != nullptr) {
+    const bool res = on_cease_(inst);
+    if (res) return;
+  }
+
+  // If no handler is specified, then CEASE is treated as an infinite loop.
+  // TODO(torerik): set next pc to the right value.
+
+  const std::string where = (inst != nullptr)
+                                ? absl::StrCat(absl::Hex(inst->address()))
+                                : "unknown location";
+
+  LOG(INFO) << "No handler for cease: treating as an infinite loop: " << where;
+}
+
+void CheriotState::Trap(bool is_interrupt, uint64_t trap_value,
+                        uint64_t exception_code, uint64_t epc,
+                        const Instruction *inst) {
+  // Call the handler.
+  if (on_trap_ != nullptr) {
+    bool res = on_trap_(is_interrupt, trap_value, exception_code, epc, inst);
+    // If the handler returns true, the trap has been handled. Just return.
+    if (res) return;
+  }
+  // Get trap destination.
+  int trap_vector_mode = mtcc_->address() & 0x3ULL;
+  uint64_t trap_target = mtcc_->address() & ~0x3ULL;
+  if (trap_vector_mode == 1) {
+    trap_target += 4 * exception_code;
+  }
+
+  // Set mepc by copying pcc to mepc and setting the address to epc.
+  mepcc_->CopyFrom(*pcc());
+  mepcc_->set_address(epc);
+  // Set xcause.
+  mcause_->Set(exception_code);
+  if (is_interrupt) {
+    mcause_->SetBits(static_cast<uint32_t>(0x8000'0000));
+  }
+  // Set mstatus bits accordingly.
+
+  // Set the privilege mode to return to after the interrupt.
+  mstatus_->set_mpp(*PrivilegeMode::kMachine);
+  // Save the current interrupt enable to mpie.
+  mstatus_->set_mpie(mstatus_->mie());
+  // Disable further interrupts.
+  mstatus_->set_mie(0);
+
+  // Advance data buffer delay line until empty. Flush pending writes to
+  // register and possibly pc.
+  while (!data_buffer_delay_line()->IsEmpty()) {
+    data_buffer_delay_line()->Advance();
+  }
+
+  // Set mtval.
+  mtval_->Write(trap_value);
+
+  // Update the PC from the mtvec_ capability. Update the address in case of
+  // vectored mode.
+  pcc()->CopyFrom(*mtcc_);
+  set_branch(true);
+  // TODO(torerik): set next pc
+  mstatus_->Submit();
+}
+
+// CheckForInterrupt is called whenever any relevant bits in the interrupt
+// enable and set bits are changed. It should always be scheduled to execute
+// from the function_delay_line, that way it is executed after an instruction
+// has completed execution.
+void CheriotState::CheckForInterrupt() {
+  // Get global interrupts enable bit.
+  bool mie = mstatus_->mie();
+  // No interrupts can be taken.
+  if (!mie) return;
+
+  // Get pending and enabled interrupts.
+  uint32_t interrupts = mip_->AsUint32() & mie_->AsUint32();
+  // If there are no enabled pending interrupts, just return.
+  if (interrupts == 0) return;
+
+  InterruptCode code = PickInterrupt(interrupts);
+
+  available_interrupt_code_ = code;
+  is_interrupt_available_ = true;
+}
+
+// Take the interrupt that is pending.
+void CheriotState::TakeAvailableInterrupt(uint64_t epc) {
+  // Make sure an interrupt is set as pending by CheckForInterrupt.
+  if (!is_interrupt_available_) return;
+  // Initiate the interrupt.
+  Trap(/*is_interrupt*/ true, 0, *available_interrupt_code_, epc, nullptr);
+  // Clear pending interrupt.
+  is_interrupt_available_ = false;
+  ++interrupt_handler_depth_;
+  available_interrupt_code_ = InterruptCode::kNone;
+}
+
+// The priority order of the interrupts are as follows:
+// mei, msi, mti, sei, ssi, sti, uei, usi, uti.
+InterruptCode CheriotState::PickInterrupt(uint32_t interrupts) {
+  if (interrupts & (1 << *InterruptCode::kMachineExternalInterrupt))
+    return InterruptCode::kMachineExternalInterrupt;
+  if (interrupts & (1 << *InterruptCode::kMachineSoftwareInterrupt))
+    return InterruptCode::kMachineSoftwareInterrupt;
+  if (interrupts & (1 << *InterruptCode::kMachineTimerInterrupt))
+    return InterruptCode::kMachineTimerInterrupt;
+
+  // No supervisor or user mode.
+
+  return InterruptCode::kNone;
+}
+
+// Check if the address is for a capability that has been revoked. If so,
+// return true;
+bool CheriotState::MustRevoke(uint32_t address) const {
+  uint64_t revocation_address =
+      address & ~(static_cast<uint64_t>(kCapabilitySizeInBytes) - 1ULL);
+  // If the address is less than the revocation memory base, return false.
+  if (revocation_address < revocation_ram_base()) return false;
+  uint64_t offset = (revocation_address - revocation_ram_base());
+  uint64_t revocation_offset = offset >> 6;
+  tagged_memory_->Load(revocation_mem_base() + revocation_offset,
+                       revocation_db_, nullptr, nullptr);
+  uint8_t revocation_bits = revocation_db_->Get<uint8_t>(0);
+  int bit_offset = (offset >> 3) & 0b111;
+  return revocation_bits & (1 << bit_offset);
+}
+
+uint64_t RiscVCheri32PcSourceOperand::GetPC() {
+  auto *pcc = state_->pcc();
+  // PCC should always be a valid capability, otherwise an exception would
+  // have been taken. It should also have execute permissions. The only thing
+  // to check for is that the address is within bounds.
+  if (!pcc->IsInBounds(pcc->address(), state_->has_compact() ? 2 : 4)) {
+    state_->HandleCheriRegException(nullptr, pcc->address(),
+                                    ExceptionCode::kCapExBoundsViolation, pcc);
+  }
+  return pcc->address();
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_state.h b/cheriot/cheriot_state.h
new file mode 100644
index 0000000..42d22c5
--- /dev/null
+++ b/cheriot/cheriot_state.h
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_STATE_H_
+#define MPACT_CHERIOT__CHERIOT_STATE_H_
+
+#include <any>
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/ref_count.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "riscv//riscv_csr.h"
+#include "riscv//riscv_misa.h"
+#include "riscv//riscv_state.h"
+#include "riscv//riscv_xip_xie.h"
+#include "riscv//riscv_xstatus.h"
+
+// This file defines the mpact_sim architectural state class for RiscV CHERIoT.
+// It is very similar to the RiscVState class defined in mpact_riscv, but due
+// to the changes to the register architecture driven by CHERIoT, a new class
+// was defined instead of attempting to inherit from RiscVState. That being
+// said, several types from mpact_riscv are re-used here.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::ReferenceCount;
+using ::mpact::sim::riscv::InterruptCode;
+using ::mpact::sim::riscv::IsaExtension;
+using ::mpact::sim::riscv::PrivilegeMode;
+using ::mpact::sim::riscv::RiscVCsrInterface;
+using ::mpact::sim::riscv::RiscVCsrSet;
+using ::mpact::sim::riscv::RiscVMIe;
+using ::mpact::sim::riscv::RiscVMIp;
+using ::mpact::sim::riscv::RiscVMIsa;
+using ::mpact::sim::riscv::RiscVMStatus;
+using ::mpact::sim::riscv::RiscVSimpleCsr;
+
+// Forward declare the CHERIoT register type.
+class CheriotRegister;
+class RiscVCheriotFPState;
+
+// CHERIoT exception codes. These are used in addition to the ones defined for
+// vanilla RiscV.
+
+enum class ExceptionCode : uint32_t {
+  kCapExBoundsViolation = 0x01,
+  kCapExTagViolation = 0x02,
+  kCapExSealViolation = 0x03,
+  kCapExPermitExecuteViolation = 0x11,
+  kCapExPermitLoadViolation = 0x12,
+  kCapExPermitStoreViolation = 0x13,
+  kCapExPermitStoreCapabilityViolation = 0x15,
+  kCapExPermitStoreLocalCapabilityViolation = 0x16,
+  kCapExPermitAccessSystemRegistersViolation = 0x18,
+};
+
+// Load context used for capability tag loads. See below for the context type
+// for capability loads.
+struct CapabilityTagsLoadContext32 : public generic::ReferenceCount {
+  CapabilityTagsLoadContext32(DataBuffer *tags, CheriotRegister *dest)
+      : tags(tags), dest(dest) {}
+  ~CapabilityTagsLoadContext32() override {
+    if (tags != nullptr) tags->DecRef();
+  }
+
+  void OnRefCountIsZero() override {
+    if (tags != nullptr) tags->DecRef();
+    tags = nullptr;
+    generic::ReferenceCount::OnRefCountIsZero();
+  }
+  // Data buffer for the tags. One tag bit is stored in each byte.
+  DataBuffer *tags;
+  // The destination register.
+  CheriotRegister *dest;
+};
+
+// Load context used for capability loads.
+struct CapabilityLoadContext32 : public generic::ReferenceCount {
+  CapabilityLoadContext32(DataBuffer *db, DataBuffer *tag_db,
+                          uint32_t permissions, bool clear_tag)
+      : db(db),
+        tag_db(tag_db),
+        permissions(permissions),
+        clear_tag(clear_tag) {}
+  ~CapabilityLoadContext32() override {
+    if (db != nullptr) db->DecRef();
+    if (tag_db != nullptr) tag_db->DecRef();
+  }
+
+  void OnRefCountIsZero() override {
+    if (db != nullptr) db->DecRef();
+    db = nullptr;
+    if (tag_db != nullptr) tag_db->DecRef();
+    tag_db = nullptr;
+    generic::ReferenceCount::OnRefCountIsZero();
+  }
+
+  // Data buffer for the memory content.
+  DataBuffer *db;
+  // Data buffer for the tags. One tag bit is stored in each byte.
+  DataBuffer *tag_db;
+  // The permissions of the capability used for the load.
+  uint32_t permissions;
+  // If true, clear the tag upon writing the result to the capability register.
+  bool clear_tag;
+};
+
+class CheriotState;
+
+// Forward declare a template function defined in the .cc file that is
+// a friend of the state class.
+template <typename T>
+void CreateCsrs(CheriotState *, std::vector<RiscVCsrInterface *> &);
+
+class RiscVCheri32PcSourceOperand;
+
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
+
+class CheriotState : public generic::ArchState {
+ public:
+  static int constexpr kCapRegQueueSizeMask = 0x11;
+  static constexpr uint32_t kCheriExceptionCode = 0x1c;
+  static constexpr char kCregPrefix[] = "c";
+  static constexpr char kFregPrefix[] = "f";
+  static constexpr char kXregPrefix[] = "x";
+  static constexpr char kCsrName[] = "csr";
+  friend void CreateCsrs<uint32_t>(CheriotState *,
+                                   std::vector<RiscVCsrInterface *> &);
+  friend void CreateCsrs<uint64_t>(CheriotState *,
+                                   std::vector<RiscVCsrInterface *> &);
+  // Memory footprint of a capability register.
+  static constexpr int kCapabilitySizeInBytes = 8;
+  // Pc name.
+  static constexpr char kPcName[] = "pcc";
+  // Constructors and destructor.
+  CheriotState(std::string_view id, util::TaggedMemoryInterface *memory,
+               util::AtomicMemoryOpInterface *atomic_memory);
+  CheriotState(std::string_view id, util::TaggedMemoryInterface *memory)
+      : CheriotState(id, memory, nullptr) {}
+  explicit CheriotState(std::string_view id)
+      : CheriotState(id, nullptr, nullptr) {}
+  ~CheriotState() override;
+
+  // Deleted constructors and operators.
+  CheriotState(const CheriotState &) = delete;
+  CheriotState &operator=(const CheriotState &) = delete;
+  CheriotState(CheriotState &&) = delete;
+  CheriotState &operator=(CheriotState &&) = delete;
+
+  // Reset all registers and CSRs to initial values.
+  void Reset();
+  // Return a pair consisting of pointer to the named register and a bool that
+  // is true if the register had to be created, and false if it was found
+  // in the register map (or if nullptr is returned).
+  template <typename RegisterType>
+  std::pair<RegisterType *, bool> GetRegister(absl::string_view name) {
+    // If the register already exists, return a pointer to the register.
+    auto ptr = registers()->find(std::string(name));
+    if (ptr != registers()->end())
+      return std::make_pair(static_cast<RegisterType *>(ptr->second), false);
+    // Create a new register and return a pointer to the object.
+    return std::make_pair(AddRegister<RegisterType>(name), true);
+  }
+
+  // Add register alias.
+  template <typename RegisterType>
+  absl::Status AddRegisterAlias(absl::string_view current_name,
+                                absl::string_view new_name) {
+    auto ptr = registers()->find(std::string(current_name));
+    if (ptr == registers()->end()) {
+      return absl::NotFoundError(
+          absl::StrCat("Register '", current_name, "' does not exist."));
+    }
+    AddRegister(new_name, ptr->second);
+    return absl::OkStatus();
+  }
+  // This is called by instruction semantic functions to register a CHERIoT
+  // specific exception.
+  void HandleCheriRegException(const Instruction *inst, uint64_t epc,
+                               ExceptionCode code, const CheriotRegister *reg);
+
+  // Methods called by instruction semantic functions to load from memory.
+  void LoadMemory(const Instruction *inst, uint64_t address, DataBuffer *db,
+                  Instruction *child_inst, ReferenceCount *context);
+  void LoadMemory(const Instruction *inst, DataBuffer *address_db,
+                  DataBuffer *mask_db, int el_size, DataBuffer *db,
+                  Instruction *child_inst, ReferenceCount *context);
+  // Methods called by instruction semantic functions to store to memory.
+  void StoreMemory(const Instruction *inst, uint64_t address, DataBuffer *db);
+  void StoreMemory(const Instruction *inst, DataBuffer *address_db,
+                   DataBuffer *mask_db, int el_size, DataBuffer *db);
+  // Methods called by instruction semantic functions to load and store
+  // capabilities.
+  void LoadCapability(const Instruction *instruction, uint32_t address,
+                      DataBuffer *db, DataBuffer *tags, Instruction *child,
+                      CapabilityLoadContext32 *context);
+  void StoreCapability(const Instruction *instruction, uint32_t address,
+                       DataBuffer *db, DataBuffer *tags);
+
+  // Debug memory methods.
+  void DbgLoadMemory(uint64_t address, DataBuffer *db);
+  // Called by the fence instruction semantic function to signal a fence
+  // operation.
+  void Fence(const Instruction *inst, int fm, int predecessor, int successor);
+  // Synchronize instruction and data streams.
+  void FenceI(const Instruction *inst);
+  // System call.
+  void ECall(const Instruction *inst);
+  // Breakpoint.
+  void EBreak(const Instruction *inst);
+  // WFI
+  void WFI(const Instruction *inst);
+  // Ceases execution on the core. This is a non-standard instruction that
+  // quiesces traffic for embedded cores before halting. The core must be reset
+  // to come out of this state.
+  void Cease(const Instruction *inst);
+  // This method is called to trigger a RiscV trap.
+  void Trap(bool is_interrupt, uint64_t trap_value, uint64_t exception_code,
+            uint64_t epc, const Instruction *inst);
+  // Add ebreak handler.
+  void AddEbreakHandler(absl::AnyInvocable<bool(const Instruction *)> handler) {
+    on_ebreak_.emplace_back(std::move(handler));
+  }
+  // This function is called after any event that may have caused an interrupt
+  // to be registered as pending or enabled. If the interrupt can be taken
+  // it registers it as available.
+  void CheckForInterrupt() override;
+  // This function is called when the return pc for the available interrupt
+  // is known. If there is no available interrupt, it just returns.
+  void TakeAvailableInterrupt(uint64_t epc);
+
+  // Indicates that the program has returned from handling an interrupt. This
+  // decrements the interrupt handler depth and should be called by the
+  // implementations of mret, sret, and uret.
+  void SignalReturnFromInterrupt() { --interrupt_handler_depth_; }
+
+  // Returns the depth of the interrupt handler currently being executed, or
+  // zero if no interrupt handler is being executed.
+  int InterruptHandlerDepth() const { return interrupt_handler_depth_; }
+
+  // Returns true if a capability register with the given base should be
+  // revoked.
+  bool MustRevoke(uint32_t address) const;
+
+  // Accessors.
+  // Returns true if an interrupt is available for the core to take or false
+  // otherwise.
+  inline bool is_interrupt_available() const { return is_interrupt_available_; }
+  // Resets the is_interrupt_available flag to false. This should only be called
+  // when resetting the RISCV core, as 'is_interrupt_available' is Normally
+  // reset during the interrupt handling flow.
+  inline void reset_is_interrupt_available() {
+    is_interrupt_available_ = false;
+  }
+  void set_memory(util::TaggedMemoryInterface *tagged_memory) {
+    tagged_memory_ = tagged_memory;
+  }
+  util::TaggedMemoryInterface *tagged_memory() const { return tagged_memory_; }
+  util::AtomicMemoryOpInterface *atomic_tagged_memory() const {
+    return atomic_tagged_memory_;
+  }
+  void set_atomic_tagged_memory(
+      util::AtomicMemoryOpInterface *atomic_tagged_memory) {
+    atomic_tagged_memory_ = atomic_tagged_memory;
+  }
+
+  void set_branch(bool value) { branch_ = value; }
+  bool branch() const { return branch_; }
+
+  void set_max_physical_address(uint64_t max_physical_address);
+  uint64_t max_physical_address() const { return max_physical_address_; }
+  void set_min_physical_address(uint64_t min_physical_address);
+  uint64_t min_physical_address() const { return min_physical_address_; }
+  // These root capabilities are clean versions of each type of capability with
+  // maximum permissions.
+  const CheriotRegister *executable_root() const { return executable_root_; }
+  const CheriotRegister *sealing_root() const { return sealing_root_; }
+  const CheriotRegister *memory_root() const { return memory_root_; }
+  // Floating point state.
+  RiscVCheriotFPState *rv_fp() const { return rv_fp_; }
+  void set_rv_fp(RiscVCheriotFPState *value) { rv_fp_ = value; }
+  // Special capability registers. Pcc replaces the pc. Cgp is a global pointer
+  // capability that is aliased with c3.
+  CheriotRegister *pcc() const { return pcc_; }
+  CheriotRegister *cgp() const { return cgp_; }
+  // True if the misa register encodes support for compact instructions.
+  bool has_compact() const {
+    return (misa_->AsUint64() & *IsaExtension::kCompressed) != 0;
+  }
+  // Returns the number of tags that can be loaded in a single load tags
+  // instruction.
+  int num_tags_per_load() const { return num_tags_per_load_; }
+  // Provides access to the set of CSRs of this architectural state instance.
+  RiscVCsrSet *csr_set() { return csr_set_; }
+  // Setters for handlers for ecall, and trap. The handler returns true
+  // if the instruction/event was handled, and false otherwise.
+
+  void set_on_ecall(absl::AnyInvocable<bool(const Instruction *)> callback) {
+    on_ecall_ = std::move(callback);
+  }
+
+  void set_on_wfi(absl::AnyInvocable<bool(const Instruction *)> callback) {
+    on_wfi_ = std::move(callback);
+  }
+
+  void set_on_cease(absl::AnyInvocable<bool(const Instruction *)> callback) {
+    on_cease_ = std::move(callback);
+  }
+
+  void set_on_trap(
+      absl::AnyInvocable<bool(bool /*is_interrupt*/, uint64_t /*trap_value*/,
+                              uint64_t /*exception_code*/, uint64_t /*epc*/,
+                              const Instruction *)>
+          callback) {
+    on_trap_ = std::move(callback);
+  }
+
+  RiscVMStatus *mstatus() { return mstatus_; }
+  RiscVMIsa *misa() { return misa_; }
+  RiscVMIp *mip() { return mip_; }
+  RiscVMIe *mie() { return mie_; }
+  CheriotRegister *mtcc() { return mtcc_; }
+  CheriotRegister *mepcc() { return mepcc_; }
+  CheriotRegister *mscratchc() { return mscratchc_; }
+  CheriotRegister *mtdc() { return mtdc_; }
+  CheriotRegister *temp_reg() { return temp_reg_; }
+  RiscVCsrInterface *mcause() { return mcause_; }
+  RiscVCheri32PcSourceOperand *pc_src_operand() { return pc_src_operand_; }
+
+  uint64_t revocation_mem_base() const { return revocation_mem_base_; }
+  uint64_t revocation_ram_base() const { return revocation_ram_base_; }
+
+  // Tracing accessors.
+  bool tracing_active() const { return tracing_active_; }
+  void set_tracing_active(bool active) { tracing_active_ = active; }
+  uint64_t load_address() const { return load_address_; }
+  DataBuffer *load_db() const { return load_db_; }
+  void set_load_db(DataBuffer *db) { load_db_ = db; }
+  DataBuffer *load_tags() const { return load_tags_; }
+  void set_load_tags(DataBuffer *tags) { load_tags_ = tags; }
+  uint64_t store_address() const { return store_address_; }
+  DataBuffer *store_db() const { return store_db_; }
+  void set_store_db(DataBuffer *db) { store_db_ = db; }
+  DataBuffer *store_tags() const { return store_tags_; }
+  void set_store_tags(DataBuffer *tags) { store_tags_ = tags; }
+
+ private:
+  InterruptCode PickInterrupt(uint32_t interrupts);
+  // A map from register name to entry in the mtval register.
+  absl::flat_hash_map<std::string, uint32_t> cap_index_map_;
+  // These are root capabilities
+  CheriotRegister *executable_root_ = nullptr;
+  CheriotRegister *sealing_root_ = nullptr;
+  CheriotRegister *memory_root_ = nullptr;
+  // Special capability registers.
+  CheriotRegister *pcc_ = nullptr;
+  CheriotRegister *cgp_ = nullptr;
+  bool branch_ = false;
+  uint64_t max_physical_address_;
+  uint64_t min_physical_address_ = 0;
+  RiscVCheriotFPState *rv_fp_ = nullptr;
+  int num_tags_per_load_;
+  util::TaggedMemoryInterface *owned_tagged_memory_ = nullptr;
+  util::TaggedMemoryInterface *tagged_memory_;
+  util::AtomicMemoryOpInterface *atomic_tagged_memory_;
+  RiscVCsrSet *csr_set_;
+  std::vector<absl::AnyInvocable<bool(const Instruction *)>> on_ebreak_;
+  absl::AnyInvocable<bool(const Instruction *)> on_ecall_;
+  absl::AnyInvocable<bool(bool, uint64_t, uint64_t, uint64_t,
+                          const Instruction *)>
+      on_trap_;
+  absl::AnyInvocable<bool(const Instruction *)> on_wfi_;
+  absl::AnyInvocable<bool(const Instruction *)> on_cease_;
+  std::vector<RiscVCsrInterface *> csr_vec_;
+  // For interrupt handling.
+  bool is_interrupt_available_ = false;
+  int interrupt_handler_depth_ = 0;
+  InterruptCode available_interrupt_code_ = InterruptCode::kNone;
+  // By default, execute in machine mode.
+  PrivilegeMode privilege_mode_ = PrivilegeMode::kMachine;
+  // Handles to frequently used CSRs.
+  RiscVMStatus *mstatus_ = nullptr;
+  RiscVMIsa *misa_ = nullptr;
+  RiscVMIp *mip_ = nullptr;
+  RiscVMIe *mie_ = nullptr;
+  RiscVSimpleCsr<uint32_t> *mshwm_ = nullptr;
+  RiscVSimpleCsr<uint32_t> *mshwmb_ = nullptr;
+  CheriotRegister *mtcc_ = nullptr;
+  CheriotRegister *mepcc_ = nullptr;
+  CheriotRegister *mscratchc_ = nullptr;
+  CheriotRegister *mtdc_ = nullptr;
+  CheriotRegister *temp_reg_ = nullptr;
+  RiscVCsrInterface *mtval_ = nullptr;
+  RiscVCsrInterface *mcause_ = nullptr;
+  RiscVCheri32PcSourceOperand *pc_src_operand_ = nullptr;
+  // DataBuffer and info used to check for revocation.
+  DataBuffer *revocation_db_ = nullptr;
+  uint64_t revocation_mem_base_;
+  uint64_t revocation_ram_base_;
+  // Active tracing flag.
+  bool tracing_active_ = false;
+  // Members for collecting trace data.
+  uint64_t load_address_;
+  DataBuffer *load_db_ = nullptr;
+  DataBuffer *load_tags_ = nullptr;
+  uint64_t store_address_;
+  DataBuffer *store_db_ = nullptr;
+  DataBuffer *store_tags_ = nullptr;
+};
+
+// This class implements the source operand interface on top of a capability
+// register so that its value (contained address) can be read as an operand.
+class RiscVCheri32PcSourceOperand : public generic::SourceOperandInterface {
+ public:
+  explicit RiscVCheri32PcSourceOperand(CheriotState *state) : state_(state) {}
+  RiscVCheri32PcSourceOperand() = delete;
+  RiscVCheri32PcSourceOperand(const RiscVCheri32PcSourceOperand &) = delete;
+  RiscVCheri32PcSourceOperand &operator=(const RiscVCheri32PcSourceOperand &) =
+      delete;
+  ~RiscVCheri32PcSourceOperand() override = default;
+  // Methods for accessing the nth value element.
+  bool AsBool(int index) override { return static_cast<bool>(GetPC()); }
+  int8_t AsInt8(int index) override { return static_cast<int8_t>(GetPC()); }
+  uint8_t AsUint8(int index) override { return static_cast<uint8_t>(GetPC()); }
+  int16_t AsInt16(int index) override { return static_cast<int16_t>(GetPC()); }
+  uint16_t AsUint16(int) override { return static_cast<uint16_t>(GetPC()); }
+  int32_t AsInt32(int index) override { return static_cast<int32_t>(GetPC()); }
+  uint32_t AsUint32(int index) override {
+    return static_cast<uint32_t>(GetPC());
+  }
+  int64_t AsInt64(int index) override { return static_cast<int64_t>(GetPC()); }
+  uint64_t AsUint64(int index) override { return GetPC(); }
+
+  // Return a pointer to the object instance that implements the state in
+  // question (or nullptr) if no such object "makes sense". This is used if
+  // the object requires additional manipulation - such as a fifo that needs
+  // to be popped. If no such manipulation is required, nullptr should be
+  // returned.
+  std::any GetObject() const override { return std::any(state_->pcc()); }
+  // Return the shape of the operand (the number of elements in each dimension).
+  // For instance {1} indicates a scalar quantity, whereas {128} indicates an
+  // 128 element vector quantity.
+  std::vector<int> shape() const override { return {1}; };
+  // Return a string representation of the operand suitable for display in
+  // disassembly.
+  std::string AsString() const override { return "PC"; };
+
+ private:
+  uint64_t GetPC();
+  CheriotState *state_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_STATE_H_
diff --git a/cheriot/cheriot_test_rig.cc b/cheriot/cheriot_test_rig.cc
new file mode 100644
index 0000000..0daca50
--- /dev/null
+++ b/cheriot/cheriot_test_rig.cc
@@ -0,0 +1,538 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_test_rig.h"
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+
+#include "absl/functional/bind_front.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/cheriot_test_rig_decoder.h"
+#include "cheriot/riscv_cheriot_minstret.h"
+#include "cheriot/riscv_cheriot_register_aliases.h"
+#include "cheriot/test_rig_packets.h"
+#include "mpact/sim/generic/component.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+#include "riscv//riscv_register.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact::sim::cheriot {
+
+using EC = ::mpact::sim::riscv::ExceptionCode;
+using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
+using CheriotEC = ::mpact::sim::cheriot::ExceptionCode;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+using ::mpact::sim::util::TaggedMemoryWatcher;
+
+constexpr char kCheriotTestRigName[] = "CheriotTestRig";
+
+CheriotTestRig::CheriotTestRig()
+    : generic::Component(kCheriotTestRigName),
+      counter_num_instructions_("num_instructions", 0) {
+  // Set up memory.
+  tagged_memory_ = new TaggedFlatDemandMemory(8);
+  // Set up watcher and add callbacks.
+  tagged_memory_watcher_ = new TaggedMemoryWatcher(tagged_memory_);
+  CHECK_OK(tagged_memory_watcher_->SetLoadWatchCallback(
+      TaggedMemoryWatcher::AddressRange{0, 0x1'0000'0000ULL},
+      absl::bind_front(&CheriotTestRig::OnLoad, this)));
+  CHECK_OK(tagged_memory_watcher_->SetStoreWatchCallback(
+      TaggedMemoryWatcher::AddressRange{0, 0x1'0000'0000ULL},
+      absl::bind_front(&CheriotTestRig::OnStore, this)));
+  // Set up sim state.
+  state_ = new CheriotState(kCheriotTestRigName, tagged_memory_watcher_);
+  fp_state_ = new RiscVCheriotFPState(state_);
+  state_->set_rv_fp(fp_state_);
+  // Initialize pcc to 0x8000'0000.
+  pcc_ = static_cast<CheriotRegister *>(
+      state_->registers()->at(CheriotState::kPcName));
+  pcc_->set_address(0x8000'0000);
+  cheriot_decoder_ = new CheriotTestRigDecoder(state_);
+  // Register instruction counter.
+  CHECK_OK(AddCounter(&counter_num_instructions_))
+      << "Failed to register counter";
+  // Make sure the architectural and abi register aliases are added.
+  std::string reg_name;
+  std::string xreg_name;
+  // Add aliases for capability registers.
+  for (int i = 0; i < 32; i++) {
+    reg_name = absl::StrCat(CheriotState::kCregPrefix, i);
+    // Alias the register with x register names.
+    // E.g., 'c10' === 'x10'
+    xreg_name = absl::StrCat(CheriotState::kXregPrefix, i);
+    CHECK_OK(state_->AddRegisterAlias<CheriotRegister>(reg_name, xreg_name));
+    // Alias the register with capability abi register names.
+    // E.g., 'c10' === 'ca0'
+    CHECK_OK(state_->AddRegisterAlias<CheriotRegister>(reg_name,
+                                                       kCRegisterAliases[i]));
+    // Alias the register with abi register names.
+    // E.g., 'c10' === 'a0'
+    CHECK_OK(state_->AddRegisterAlias<CheriotRegister>(reg_name,
+                                                       kXRegisterAliases[i]));
+  }
+
+  for (int i = 0; i < 32; i++) {
+    reg_name = absl::StrCat(CheriotState::kFregPrefix, i);
+    (void)state_->AddRegister<riscv::RVFpRegister>(reg_name);
+    CHECK_OK(state_->AddRegisterAlias<riscv::RVFpRegister>(
+        reg_name, kFRegisterAliases[i]));
+  }
+  // Register trap monitor.
+  state_->set_on_trap(absl::bind_front(&CheriotTestRig::OnTrap, this));
+  // Allocate data buffers.
+  db1_ = db_factory_.Allocate<uint8_t>(1);
+  db2_ = db_factory_.Allocate<uint16_t>(1);
+  db4_ = db_factory_.Allocate<uint32_t>(1);
+  db8_ = db_factory_.Allocate<uint64_t>(1);
+  // Initialize minstret/minstreth. Bind the instruction counter to those
+  // registers.
+  auto minstret_res = state_->csr_set()->GetCsr("minstret");
+  auto minstreth_res = state_->csr_set()->GetCsr("minstreth");
+  if (!minstret_res.ok() || !minstreth_res.ok()) {
+    LOG(ERROR) << "Error while initializing minstret/minstreth";
+  }
+  auto *minstret = static_cast<RiscVCheriotMInstret *>(minstret_res.value());
+  auto *minstreth = static_cast<RiscVCheriotMInstreth *>(minstreth_res.value());
+  minstret->set_counter(&counter_num_instructions_);
+  minstreth->set_counter(&counter_num_instructions_);
+  // Set memory limits according to the memory space for TestRIG.
+  state_->set_max_physical_address(0x8000'0000ULL + 64 * 1024);
+  state_->set_min_physical_address(0x8000'0000ULL);
+  ResetArch();
+}
+
+CheriotTestRig::~CheriotTestRig() {
+  delete cheriot_decoder_;
+  delete state_;
+  delete fp_state_;
+  delete tagged_memory_;
+  delete tagged_memory_watcher_;
+  // Deallocate data buffers.
+  db1_->DecRef();
+  db2_->DecRef();
+  db4_->DecRef();
+  db8_->DecRef();
+}
+
+absl::Status CheriotTestRig::Execute(
+    const test_rig::InstructionPacket &inst_packet, int fd) {
+  switch (trace_version_) {
+    case 1:
+      return ExecuteV1(inst_packet, fd);
+      break;
+    case 2:
+      return ExecuteV2(inst_packet, fd);
+      break;
+    default:
+      return absl::UnimplementedError(
+          absl::StrCat("Trace version ", trace_version_, " is not supported"));
+  }
+}
+
+absl::Status CheriotTestRig::SetVersion(int version) {
+  if (version > 2)
+    return absl::UnimplementedError(
+        absl::StrCat("Trace version ", version, " is not supported"));
+  trace_version_ = version;
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTestRig::ExecuteV1(
+    const test_rig::InstructionPacket &inst_packet, int fd) {
+  test_rig::ExecutionPacket ep;
+  uint32_t inst_word = inst_packet.rvfi_insn;
+  ep.rvfi_halt = 0;
+  // Clear memory related fields.
+  mem_addr_ = 0;
+  mem_r_mask_ = 0;
+  mem_w_mask_ = 0;
+  std::memset(mem_r_data_, 0, sizeof(mem_r_data_));
+  std::memset(mem_w_data_, 0, sizeof(mem_w_data_));
+  // If trap was set last time around, set indicator for trap handler.
+  ep.rvfi_intr = trap_set_ ? 1 : 0;
+  trap_set_ = false;
+  uint64_t pc = pcc_->address();
+  ep.rvfi_pc_rdata = pc;
+  // Decode fills in rd_addr, rs2_addr, rs1_addr.
+  CheriotTestRigDecoder::DecodeInfo decode_info;
+  auto *inst = cheriot_decoder_->DecodeInstruction(pc, inst_word, decode_info);
+  ep.rvfi_rd_addr = decode_info.rd;
+  ep.rvfi_rs1_addr = decode_info.rs1;
+  ep.rvfi_rs2_addr = decode_info.rs2;
+  ep.rvfi_rs1_data = GetRegister(ep.rvfi_rs1_addr);
+  ep.rvfi_rs2_data = GetRegister(ep.rvfi_rs2_addr);
+  uint64_t next_pc = pc + inst->size();
+  // Execute the instruction.
+  if (!pcc_->HasPermission(PB::kPermitExecute)) {
+    state_->HandleCheriRegException(
+        inst, inst->address(), CheriotEC::kCapExPermitExecuteViolation, pcc_);
+    inst_word = 0;
+  } else if (!pcc_->IsInBounds(pc, state_->has_compact() ? sizeof(uint16_t)
+                                                         : sizeof(uint32_t))) {
+    state_->HandleCheriRegException(inst, inst->address(),
+                                    CheriotEC::kCapExBoundsViolation, pcc_);
+    inst_word = 0;
+  } else {
+    inst->Execute(nullptr);
+  }
+  // Check for trap.
+  if (trap_set_) {
+    next_pc = pcc_->address();
+    // If there's a trap, clear relevant fields.
+    ep.rvfi_trap = 1;
+    ep.rvfi_rd_addr = 0;
+    ep.rvfi_rs2_addr = 0;
+    ep.rvfi_rs1_addr = 0;
+    ep.rvfi_rd_wdata = 0;
+    ep.rvfi_rs2_data = 0;
+    ep.rvfi_rs1_data = 0;
+    ep.rvfi_mem_addr = 0;
+    ep.rvfi_mem_rdata = 0;
+    ep.rvfi_mem_wdata = 0;
+    ep.rvfi_mem_rmask = 0;
+    ep.rvfi_mem_wmask = 0;
+    // If the trap was a memory access trap, set the memory address.
+    auto res = state_->csr_set()->GetCsr("mcause");
+    auto cause = res.value()->AsUint32();
+    if (cause == *EC::kLoadAccessFault || cause == *EC::kStoreAccessFault) {
+      res = state_->csr_set()->GetCsr("mtval");
+      ep.rvfi_mem_addr = res.value()->AsUint32();
+    }
+  } else {
+    if (state_->branch()) {
+      next_pc = pcc_->address();
+    }
+    ep.rvfi_trap = 0;
+    // Update register write data.
+    ep.rvfi_rd_wdata = GetRegister(ep.rvfi_rd_addr);
+    // Update memory fields.
+    ep.rvfi_mem_addr = mem_addr_;
+    ep.rvfi_mem_rdata = mem_r_data_[0];
+    ep.rvfi_mem_wdata = mem_w_data_[0];
+    ep.rvfi_mem_rmask = mem_r_mask_;
+    ep.rvfi_mem_wmask = mem_w_mask_;
+  }
+  state_->set_branch(false);
+  counter_num_instructions_.Increment(1);
+  ep.rvfi_insn = inst_word;
+  ep.rvfi_pc_wdata = next_pc;
+  ep.rvfi_order = counter_num_instructions_.GetValue();
+  pcc_->set_address(next_pc);
+  inst->DecRef();
+  auto res = write(fd, &ep, sizeof(ep));
+  if (res != sizeof(ep)) {
+    LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+    return absl::InternalError("Error writing to trace socket");
+  }
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTestRig::ExecuteV2(
+    const test_rig::InstructionPacket &inst_packet, int fd) {
+  test_rig::ExecutionPacketExtInteger ep_ext_integer;
+  test_rig::ExecutionPacketExtMemAccess ep_ext_mem_access;
+  test_rig::ExecutionPacketV2 ep_v2;
+  test_rig::ExecutionPacketMetaData &ep_metadata = ep_v2.basic_data;
+  test_rig::ExecutionPacketPC &ep_pc = ep_v2.pc_data;
+
+  uint32_t inst_word = inst_packet.rvfi_insn;
+  ep_metadata.rvfi_halt = 0;
+  // Clear memory related fields.
+  mem_addr_ = 0;
+  mem_r_mask_ = 0;
+  mem_w_mask_ = 0;
+  std::memset(mem_r_data_, 0, sizeof(mem_r_data_));
+  std::memset(mem_w_data_, 0, sizeof(mem_w_data_));
+  // If trap was set last time around, set indicator for trap handler.
+  ep_metadata.rvfi_intr = trap_set_ ? 1 : 0;
+  trap_set_ = false;
+  uint64_t pc = pcc_->address();
+  ep_pc.rvfi_pc_rdata = pc;
+  // Decode fills in rd_addr, rs2_addr, rs1_addr.
+  CheriotTestRigDecoder::DecodeInfo decode_info;
+  auto *inst = cheriot_decoder_->DecodeInstruction(pc, inst_word, decode_info);
+  ep_ext_integer.rvfi_rd_addr = decode_info.rd;
+  ep_ext_integer.rvfi_rs1_addr = decode_info.rs1;
+  ep_ext_integer.rvfi_rs2_addr = decode_info.rs2;
+  // Get register source values.
+  ep_ext_integer.rvfi_rs1_rdata = GetRegister(ep_ext_integer.rvfi_rs1_addr);
+  ep_ext_integer.rvfi_rs2_rdata = GetRegister(ep_ext_integer.rvfi_rs2_addr);
+  uint64_t next_pc = pc + inst->size();
+  // Execute the instruction.
+  if (!pcc_->tag()) {
+    state_->HandleCheriRegException(inst, inst->address(),
+                                    CheriotEC::kCapExTagViolation, pcc_);
+    inst_word = 0;
+  } else if (!pcc_->HasPermission(PB::kPermitExecute)) {
+    state_->HandleCheriRegException(
+        inst, inst->address(), CheriotEC::kCapExPermitExecuteViolation, pcc_);
+    inst_word = 0;
+  } else if (!pcc_->IsInBounds(pc, state_->has_compact() ? sizeof(uint16_t)
+                                                         : sizeof(uint32_t))) {
+    state_->HandleCheriRegException(inst, inst->address(),
+                                    CheriotEC::kCapExBoundsViolation, pcc_);
+    inst_word = 0;
+  } else {
+    inst->Execute(nullptr);
+  }
+
+  // Check for trap.
+  if (trap_set_) {
+    next_pc = pcc_->address();
+    // If there's a trap, clear relevant fields.
+    ep_metadata.rvfi_trap = 1;
+    ep_ext_integer.rvfi_rd_addr = 0;
+    ep_ext_integer.rvfi_rs2_addr = 0;
+    ep_ext_integer.rvfi_rs1_addr = 0;
+    ep_ext_integer.rvfi_rd_wdata = 0;
+    ep_ext_integer.rvfi_rs2_rdata = 0;
+    ep_ext_integer.rvfi_rs1_rdata = 0;
+    ep_ext_mem_access.rvfi_mem_addr = 0;
+    std::memset(ep_ext_mem_access.rvfi_mem_rdata, 0,
+                sizeof(ep_ext_mem_access.rvfi_mem_rdata));
+    std::memset(ep_ext_mem_access.rvfi_mem_wdata, 0,
+                sizeof(ep_ext_mem_access.rvfi_mem_wdata));
+    ep_ext_mem_access.rvfi_mem_rmask = 0;
+    ep_ext_mem_access.rvfi_mem_wmask = 0;
+    // If the trap was a memory access trap, set the memory address.
+    auto res = state_->csr_set()->GetCsr("mcause");
+    auto cause = res.value()->AsUint32();
+    if (cause == *EC::kLoadAccessFault || cause == *EC::kStoreAccessFault) {
+      res = state_->csr_set()->GetCsr("mtval");
+      ep_ext_mem_access.rvfi_mem_addr = res.value()->AsUint32();
+    }
+  } else {
+    if (state_->branch()) {
+      next_pc = pcc_->address();
+    }
+    ep_metadata.rvfi_trap = 0;
+    // Update register write data.
+    ep_ext_integer.rvfi_rd_wdata = GetRegister(ep_ext_integer.rvfi_rd_addr);
+    // Update memory fields.
+    ep_ext_mem_access.rvfi_mem_addr = mem_addr_;
+    std::memcpy(ep_ext_mem_access.rvfi_mem_rdata, mem_r_data_,
+                sizeof(ep_ext_mem_access.rvfi_mem_rdata));
+    std::memcpy(ep_ext_mem_access.rvfi_mem_wdata, mem_w_data_,
+                sizeof(ep_ext_mem_access.rvfi_mem_wdata));
+    ep_ext_mem_access.rvfi_mem_rmask = mem_r_mask_;
+    ep_ext_mem_access.rvfi_mem_wmask = mem_w_mask_;
+  }
+  state_->set_branch(false);
+  counter_num_instructions_.Increment(1);
+  ep_metadata.rvfi_mode = test_rig::kMachineMode;
+  ep_metadata.rvfi_ixl = test_rig::kXL32;
+  ep_metadata.rvfi_insn = inst_word;
+  ep_pc.rvfi_pc_wdata = next_pc;
+  ep_metadata.rvfi_order = counter_num_instructions_.GetValue();
+  ep_metadata.rvfi_valid = 1;
+  pcc_->set_address(next_pc);
+  inst->DecRef();
+  ep_v2.trace_size = sizeof(ep_v2);
+  ep_v2.available_fields = 0;
+  // Check to see if the memory access extension should be added.
+  if ((ep_ext_mem_access.rvfi_mem_rmask != 0) ||
+      (ep_ext_mem_access.rvfi_mem_wmask != 0) ||
+      (ep_ext_mem_access.rvfi_mem_addr != 0)) {
+    ep_v2.trace_size += sizeof(ep_ext_mem_access);
+    ep_v2.available_fields |= test_rig::kMemoryAccess;
+  }
+  // Check to see if the integer data extension should be added.
+  if ((ep_ext_integer.rvfi_rd_addr != 0) ||
+      (ep_ext_integer.rvfi_rs1_addr != 0) ||
+      (ep_ext_integer.rvfi_rs2_addr != 0)) {
+    ep_v2.trace_size += sizeof(ep_ext_integer);
+    ep_v2.available_fields |= test_rig::kIntegerData;
+  }
+
+  // Write out the execution packet.
+  auto res = write(fd, &ep_v2, sizeof(ep_v2));
+  if (res != sizeof(ep_v2)) {
+    LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+    return absl::InternalError("Error writing to trace socket");
+  }
+  // Write out the extension packets.
+  if (ep_v2.available_fields & test_rig::kIntegerData) {
+    auto res = write(fd, &ep_ext_integer, sizeof(ep_ext_integer));
+    if (res != sizeof(ep_ext_integer)) {
+      LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+      return absl::InternalError("Error writing to trace socket");
+    }
+  }
+  if (ep_v2.available_fields & test_rig::kMemoryAccess) {
+    auto res = write(fd, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+    if (res != sizeof(ep_ext_mem_access)) {
+      LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+      return absl::InternalError("Error writing to trace socket");
+    }
+  }
+  return absl::OkStatus();
+}
+
+// Reset state.
+void CheriotTestRig::ResetArch() {
+  // Reset state.
+  state_->Reset();
+  // Reset pcc.
+  pcc_->ResetExecuteRoot();
+  pcc_->set_address(0x8000'0000);
+  // Clear 64KB memory.
+  db8_->Set<uint64_t>(0, 0);
+  for (uint64_t addr = 0x8000'0000ULL; addr < 0x8001'0000; addr += 8) {
+    tagged_memory_->Store(addr, db8_);
+  }
+  // Reset instruction counter.
+  counter_num_instructions_.SetValue(0);
+  // Set all capability registers to memory root capability.
+  for (auto const &name :
+       {"c1",  "c2",  "c3",  "c4",  "c5",  "c6",  "c7",  "c8",
+        "c9",  "c10", "c11", "c12", "c13", "c14", "c15", "c16",
+        "c17", "c18", "c19", "c20", "c21", "c22", "c23", "c24",
+        "c25", "c26", "c27", "c28", "c29", "c30", "c31"}) {
+    auto *cap_reg = state_->GetRegister<CheriotRegister>(name).first;
+    cap_reg->ResetMemoryRoot();
+  }
+}
+
+absl::Status CheriotTestRig::Reset(uint8_t halt, int fd) {
+  ResetArch();
+  trap_set_ = false;
+  // Write the appropriate trace packet out.
+  switch (trace_version_) {
+    case 1:
+      return ResetV1(halt, fd);
+      break;
+    case 2:
+      return ResetV2(halt, fd);
+
+    default:
+      return absl::UnimplementedError(
+          absl::StrCat("Trace version ", trace_version_, " is not supported"));
+  }
+}
+
+absl::Status CheriotTestRig::ResetV1(uint8_t halt, int fd) {
+  test_rig::ExecutionPacket ep;
+  std::memset(&ep, 0, sizeof(ep));
+  ep.rvfi_halt = halt;
+  auto res = write(fd, &ep, sizeof(ep));
+  if (res != sizeof(ep)) {
+    LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+    return absl::InternalError("Error writing to trace socket");
+  }
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTestRig::ResetV2(uint8_t halt, int fd) {
+  test_rig::ExecutionPacketV2 ep_v2;
+  ep_v2.trace_size = sizeof(ep_v2);
+  ep_v2.available_fields = 0;
+  std::memset(&ep_v2.pc_data, 0, sizeof(ep_v2.pc_data));
+  std::memset(&ep_v2.basic_data, 0, sizeof(ep_v2.basic_data));
+  ep_v2.basic_data.rvfi_halt = halt;
+  auto res = write(fd, &ep_v2, sizeof(ep_v2));
+  if (res != sizeof(ep_v2)) {
+    LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+    return absl::InternalError("Error writing to trace socket");
+  }
+  return absl::OkStatus();
+}
+
+// Just capture that a trap occurred.
+bool CheriotTestRig::OnTrap(bool is_interrupt, uint64_t trap_value,
+                            uint64_t exception_code, uint64_t epc,
+                            const Instruction *inst) {
+  trap_set_ = true;
+  return false;
+}
+
+// Capture load information.
+void CheriotTestRig::OnLoad(uint64_t address, int size) {
+  mem_addr_ = address;
+  switch (size) {
+    case 1:
+      tagged_memory_->Load(address, db1_, nullptr, nullptr);
+      mem_r_mask_ = 0x1ULL;
+      mem_r_data_[0] = db1_->Get<uint8_t>(0);
+      break;
+    case 2:
+      tagged_memory_->Load(address, db2_, nullptr, nullptr);
+      mem_r_mask_ = 0x3ULL;
+      mem_r_data_[0] = db2_->Get<uint16_t>(0);
+      break;
+    case 4:
+      tagged_memory_->Load(address, db4_, nullptr, nullptr);
+      mem_r_mask_ = 0xfULL;
+      mem_r_data_[0] = db4_->Get<uint32_t>(0);
+      break;
+    case 8:
+      tagged_memory_->Load(address, db8_, nullptr, nullptr);
+      mem_r_mask_ = 0xffULL;
+      mem_r_data_[0] = db8_->Get<uint64_t>(0);
+      break;
+    default:
+      break;
+  }
+}
+
+// Capture store information.
+void CheriotTestRig::OnStore(uint64_t address, int size) {
+  mem_addr_ = address;
+  switch (size) {
+    case 1:
+      tagged_memory_->Load(address, db1_, nullptr, nullptr);
+      mem_w_mask_ = 0x1ULL;
+      mem_w_data_[0] = db1_->Get<uint8_t>(0);
+      break;
+    case 2:
+      tagged_memory_->Load(address, db2_, nullptr, nullptr);
+      mem_w_mask_ = 0x3ULL;
+      mem_w_data_[0] = db2_->Get<uint16_t>(0);
+      break;
+    case 4:
+      tagged_memory_->Load(address, db4_, nullptr, nullptr);
+      mem_w_mask_ = 0xfULL;
+      mem_w_data_[0] = db4_->Get<uint32_t>(0);
+      break;
+    case 8:
+      tagged_memory_->Load(address, db8_, nullptr, nullptr);
+      mem_w_mask_ = 0xffULL;
+      mem_w_data_[0] = db8_->Get<uint64_t>(0);
+      break;
+    default:
+      break;
+  }
+}
+
+// Get the register value.
+uint32_t CheriotTestRig::GetRegister(uint32_t reg_id) {
+  auto reg_name = absl::StrCat(CheriotState::kXregPrefix, reg_id);
+  auto ptr = state_->registers()->find(reg_name);
+  if (ptr == state_->registers()->end()) {
+    return 0;
+  } else {
+    auto *reg = ptr->second;
+    auto *creg = static_cast<CheriotRegister *>(reg);
+    return creg->address();
+  }
+}
+
+}  // namespace mpact::sim::cheriot
diff --git a/cheriot/cheriot_test_rig.h b/cheriot/cheriot_test_rig.h
new file mode 100644
index 0000000..48f04d3
--- /dev/null
+++ b/cheriot/cheriot_test_rig.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_TEST_RIG_H_
+#define MPACT_CHERIOT__CHERIOT_TEST_RIG_H_
+
+#include <cstdint>
+
+#include "absl/status/status.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/cheriot_test_rig_decoder.h"
+#include "cheriot/riscv_cheriot_fp_state.h"
+#include "cheriot/test_rig_packets.h"
+#include "mpact/sim/generic/component.h"
+#include "mpact/sim/generic/counters.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+
+// This file defines the class used to execute instructions provided by TestRig.
+// TestRig is a framework for testing RiscV processors with Random Instruction
+// Generation. See. http://github.com/CTSRD-CHERI/TestRIG.
+
+namespace mpact::sim::cheriot {
+
+class CheriotTestRig : public generic::Component {
+ public:
+  CheriotTestRig();
+  ~CheriotTestRig() override;
+  // Execute the instruction word specified in the instruction packet. Fill out
+  // the fields in the execution packet accordingly.
+  absl::Status Execute(const test_rig::InstructionPacket &inst_packet, int fd);
+  // Return the highest version of RVFI supported.
+  int GetMaxSupportedVersion() { return 2; }
+  // Set the version of RVFI to use.
+  absl::Status SetVersion(int version);
+
+  // Reset the execution state and write out end packet.
+  absl::Status Reset(uint8_t halt, int fd);
+
+ private:
+  // Version specific instances of execute and reset.
+  absl::Status ExecuteV1(const test_rig::InstructionPacket &inst_packet,
+                         int fd);
+  absl::Status ExecuteV2(const test_rig::InstructionPacket &inst_packet,
+                         int fd);
+  absl::Status ResetV1(uint8_t halt, int fd);
+  absl::Status ResetV2(uint8_t halt, int fd);
+  // Perform necessary architectural reset.
+  void ResetArch();
+  // Callback when a trap is encountered.
+  bool OnTrap(bool is_interrupt, uint64_t trap_value, uint64_t exception_code,
+              uint64_t epc, const Instruction *inst);
+  // Callback when a memory load/store encountered.
+  void OnLoad(uint64_t address, int size);
+  void OnStore(uint64_t address, int size);
+  // Return the value of the register with the given id.
+  uint32_t GetRegister(uint32_t reg_id);
+
+  int trace_version_ = 1;
+  CheriotState *state_;
+  RiscVCheriotFPState *fp_state_;
+  CheriotRegister *pcc_;
+  CheriotTestRigDecoder *cheriot_decoder_ = nullptr;
+  util::TaggedMemoryInterface *tagged_memory_ = nullptr;
+  util::TaggedMemoryWatcher *tagged_memory_watcher_ = nullptr;
+  // Instruction counter.
+  generic::SimpleCounter<uint64_t> counter_num_instructions_;
+  // Fields for capturing information during execution of an instruction that
+  // then can be filled into the execution packet.
+  bool trap_set_ = false;
+  uint64_t mem_addr_;
+  uint64_t mem_r_mask_;
+  uint64_t mem_w_mask_;
+  uint64_t mem_r_data_[4];
+  uint64_t mem_w_data_[4];
+  // Handling data buffers for ld/st.
+  generic::DataBufferFactory db_factory_;
+  generic::DataBuffer *db1_ = nullptr;
+  generic::DataBuffer *db2_ = nullptr;
+  generic::DataBuffer *db4_ = nullptr;
+  generic::DataBuffer *db8_ = nullptr;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__CHERIOT_TEST_RIG_H_
diff --git a/cheriot/cheriot_test_rig_decoder.cc b/cheriot/cheriot_test_rig_decoder.cc
new file mode 100644
index 0000000..5038397
--- /dev/null
+++ b/cheriot/cheriot_test_rig_decoder.cc
@@ -0,0 +1,274 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_test_rig_decoder.h"
+
+#include <cstdint>
+#include <string>
+
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_bin_decoder.h"
+#include "cheriot/riscv_cheriot_decoder.h"
+#include "cheriot/riscv_cheriot_encoding.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/program_error.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::cheriot::encoding::FormatEnum;
+using RV_EC = ::mpact::sim::riscv::ExceptionCode;
+using ::mpact::sim::cheriot::isa32::OpcodeEnum;  // NOLINT: is used below.
+
+CheriotTestRigDecoder::CheriotTestRigDecoder(CheriotState *state)
+    : state_(state) {
+  // Get a handle to the internal error in the program error controller.
+  decode_error_ = state->program_error_controller()->GetProgramError(
+      generic::ProgramErrorController::kInternalErrorName);
+  // Allocate the isa factory class, the top level isa decoder instance, and
+  // the encoding parser.
+  cheriot_isa_factory_ = new CheriotTestRigIsaFactory();
+  cheriot_isa_ =
+      new isa32::RiscVCheriotInstructionSet(state, cheriot_isa_factory_);
+  cheriot_encoding_ = new isa32::RiscVCheriotEncoding(state);
+}
+
+CheriotTestRigDecoder::~CheriotTestRigDecoder() {
+  delete cheriot_isa_;
+  delete cheriot_encoding_;
+  delete cheriot_isa_factory_;
+}
+
+generic::Instruction *CheriotTestRigDecoder::DecodeInstruction(
+    uint64_t address, uint32_t inst_word, DecodeInfo &decode_info) {
+  uint16_t inst_word16 = static_cast<uint16_t>(inst_word & 0xffff);
+  // First check that the address is aligned properly. If not, create and return
+  // an instruction object that will raise an exception.
+  if (address & 0x1) {
+    auto *inst = new generic::Instruction(0, state_);
+    inst->set_size(1);
+    inst->SetDisassemblyString("Misaligned instruction address");
+    inst->set_opcode(*isa32::OpcodeEnum::kNone);
+    inst->set_address(address);
+    inst->set_semantic_function([this](generic::Instruction *inst) {
+      state_->Trap(/*is_interrupt*/ false, inst->address(),
+                   *RV_EC::kInstructionAddressMisaligned, inst->address() ^ 0x1,
+                   inst);
+    });
+    return inst;
+  }
+  // Parse the instruction in the encoding parser.
+  cheriot_encoding_->ParseInstruction(inst_word);
+  auto format = cheriot_encoding_->GetFormat(SlotEnum::kRiscv32Cheriot, 0);
+  auto opcode = cheriot_encoding_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0);
+
+  // Extract the numerical register specifies of the instruction.
+  int rd = 0;
+  int rs1 = 0;
+  int rs2 = 0;
+  switch (format) {
+    case FormatEnum::kAType:
+      // Atomic Instructions. All use rd, rs1, and rs2.
+      rd = encoding::a_type::ExtractRd(inst_word);
+      rs1 = encoding::a_type::ExtractRs1(inst_word);
+      rs2 = encoding::a_type::ExtractRs2(inst_word);
+      break;
+    case FormatEnum::kBType:
+      // 32 bit branch type instructions. All use rs1 and rs2.
+      // beq, bne, blt, bge, bltu, bgeu
+      rd = 0;
+      rs1 = encoding::b_type::ExtractRs1(inst_word);
+      rs2 = encoding::b_type::ExtractRs2(inst_word);
+      break;
+    case FormatEnum::kIType:  // 2 reg operands: rd and rs1.
+      // addi, slti, sltiu, xori, ori, andi
+      // cincaddrimm, cjalr, crj, lc, setboundsimm
+      // lb, lh, lw, lb, lhu
+      // csrrw/s/c, csrr[swc]_n[rw]
+      rd = encoding::i_type::ExtractRd(inst_word);
+      rs1 = encoding::i_type::ExtractRs1(inst_word);
+      rs2 = 0;
+      break;
+    case FormatEnum::kI2Type:  // 1 register operand: rd.
+      // cssr[wsc]i, csrr[wsc]_n[rw]
+      rd = encoding::i2_type::ExtractRd(inst_word);
+      rs1 = 0;
+      rs2 = 0;
+      break;
+    case FormatEnum::kI5Type:  // 2 reg operands.
+      rd = encoding::i5_type::ExtractRd(inst_word);
+      rs1 = encoding::i5_type::ExtractRs1(inst_word);
+      rs2 = 0;
+      break;
+    case FormatEnum::kJType: {  // Jump type - immediate.
+      rd = encoding::j_type::ExtractRd(inst_word);
+      rs1 = 0;
+      rs2 = 0;
+      break;
+    }
+    case FormatEnum::kR4Type:  // 4 reg operands, rd, rs1, rs3, and rs4.
+      rd = encoding::r4_type::ExtractRd(inst_word);
+      rs1 = encoding::r4_type::ExtractRs1(inst_word);
+      rs2 = encoding::r4_type::ExtractRs2(inst_word);
+      break;
+    case FormatEnum::kRType:  // 3 reg operands: rd, rs1, and rs2.
+      rd = encoding::r_type::ExtractRd(inst_word);
+      rs1 = encoding::r_type::ExtractRs1(inst_word);
+      rs2 = encoding::r_type::ExtractRs2(inst_word);
+      break;
+    case FormatEnum::kR2Type:
+      rd = encoding::r_type::ExtractRd(inst_word);
+      rs1 = encoding::r_type::ExtractRs1(inst_word);
+      rs2 = 0;
+      break;
+    case FormatEnum::kSType:  // 2 reg operands: rs1 and rs2.
+      // sb, sh, sw, csc.
+      rd = 0;
+      rs1 = encoding::s_type::ExtractRs1(inst_word);
+      rs2 = encoding::s_type::ExtractRs2(inst_word);
+      break;
+    case FormatEnum::kUType:
+      // lui, cauicgp, cauipcc.
+      rd = encoding::u_type::ExtractRd(inst_word);
+      rs1 = 0;
+      rs2 = 0;
+      break;
+    case FormatEnum::kCA:  // 3 reg operands: rd, rs1, and rs2.
+      // csub, cxor, cor, cand.
+      rd = encoding::c_a::ExtractRd(inst_word16);
+      rs1 = encoding::c_a::ExtractRs1(inst_word16);
+      rs2 = encoding::c_a::ExtractRs2(inst_word16);
+      break;
+    case FormatEnum::kCSH:  // rs1 is source and dest.
+      // csrli, csrai, candi.
+      rd = encoding::c_s_h::ExtractRd(inst_word16);
+      rs1 = encoding::c_s_h::ExtractRs1(inst_word16);
+      rs2 = 0;
+      break;
+    case FormatEnum::kCB:  // rs1 is source.
+      // cbeqz, cbnez.
+      rd = 0;
+      rs1 = encoding::c_b::ExtractRs1(inst_word16);
+      rs2 = 0;
+      break;
+    case FormatEnum::kCI:  // 2 reg operands: rd, rs1.
+      // cnop, caddi, cli, caddi16sp, clui, cslli, clwsp, cldsp.
+      rd = encoding::c_i::ExtractRd(inst_word16);
+      if ((opcode == OpcodeEnum::kClwsp) || (opcode == OpcodeEnum::kCldsp)) {
+        rs1 = 2;
+      } else {
+        rs1 = encoding::c_i::ExtractRs1(inst_word16);
+      }
+      rs2 = 0;
+      break;
+    case FormatEnum::kCIW:  // 1 reg operand: rd.
+      // caddi4spn.
+      rd = encoding::c_i_w::ExtractRd(inst_word16);
+      rs1 = 2;
+      rs2 = 0;
+      break;
+    case FormatEnum::kCJ:  // Depends on opcode. jal/jalr use x1.
+      // cj, cjal.
+      rd = opcode == OpcodeEnum::kCheriotCj ? 0 : 1;
+      rs1 = 0;
+      rs2 = 0;
+      break;
+    case FormatEnum::kCL:  // 2 reg operands: cl_rs1 and cl_rd.
+      // clw, cld.
+      rd = encoding::c_l::ExtractRd(inst_word16);
+      rs1 = encoding::c_l::ExtractRs1(inst_word16);
+      rs2 = 0;
+      break;
+    case FormatEnum::kCR:  // 3 reg operands: rd(s), crs2, and rd(d).
+      // cmv, cebreak, cadd, cheriot_cjr, cheriot_cjalr.
+      switch (opcode) {
+        case OpcodeEnum::kCmv:
+          rd = encoding::c_r::ExtractRd(inst_word16);
+          rs1 = 0;
+          rs2 = encoding::c_r::ExtractRs2(inst_word16);
+          break;
+        case OpcodeEnum::kCebreak:
+          rd = 0;
+          rs1 = 0;
+          rs2 = 0;
+          break;
+        case OpcodeEnum::kCadd:
+          rd = encoding::c_r::ExtractRd(inst_word16);
+          rs1 = encoding::c_r::ExtractRs1(inst_word16);
+          rs2 = encoding::c_r::ExtractRs2(inst_word16);
+          break;
+        case OpcodeEnum::kCheriotCjr:
+          rd = 0;
+          rs1 = encoding::c_r::ExtractRs1(inst_word16);
+          rs2 = 0;
+          break;
+        case OpcodeEnum::kCheriotCjalr:
+          rd = 1;
+          rs1 = encoding::c_r::ExtractRs1(inst_word16);
+          rs2 = 0;
+          break;
+        default:
+          break;
+      }
+      break;
+    case FormatEnum::kCS:  // 2 reg operands: rs1p and rs2p.
+      // sw, sd.
+      rd = 0;
+      rs1 = encoding::c_s::ExtractRs1(inst_word16);
+      rs2 = encoding::c_s::ExtractRs2(inst_word16);
+      break;
+    case FormatEnum::kCSS:  // 1 reg operand: rs2.
+      // cswsp, csdsp
+      rd = 0;
+      rs1 = 0;
+      rs2 = encoding::c_s_s::ExtractRs2(inst_word16);
+      break;
+    default:
+      break;
+  }
+  // Call the isa decoder to obtain a new instruction object for the instruction
+  // word that was parsed above.
+  auto *instruction = cheriot_isa_->Decode(address, cheriot_encoding_);
+  // For these formats, sail does not populate the rs1/rs2 address fields.
+  if (format == FormatEnum::kIType || format == FormatEnum::kI5Type ||
+      format == FormatEnum::kR2Type || format == FormatEnum::kCB ||
+      format == FormatEnum::kCSH || format == FormatEnum::kCIW ||
+      format == FormatEnum::kCI) {
+    rs1 = 0;
+  }
+  if (format == FormatEnum::kRType || format == FormatEnum::kBType ||
+      format == FormatEnum::kCR || format == FormatEnum::kCA ||
+      format == FormatEnum::kSType) {
+    rs1 = 0;
+    rs2 = 0;
+  }
+  if (opcode == OpcodeEnum::kCslli) {
+    rs1 = 0;
+  }
+  decode_info.rd = rd;
+  decode_info.rs1 = rs1;
+  decode_info.rs2 = rs2;
+  return instruction;
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_test_rig_decoder.h b/cheriot/cheriot_test_rig_decoder.h
new file mode 100644
index 0000000..46fe4b1
--- /dev/null
+++ b/cheriot/cheriot_test_rig_decoder.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_TEST_RIG_DECODER_H_
+#define MPACT_CHERIOT__CHERIOT_TEST_RIG_DECODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_decoder.h"
+#include "cheriot/riscv_cheriot_encoding.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "cheriot/test_rig_packets.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/program_error.h"
+
+// This is a specialized instruction decoder class for CherIoT TestRIG. It is
+// specialized due to the fact that the instruction words for TestRIG are
+// supplied from a socket, and not from an image stored in memory. The decode
+// function has also been extended to capture register source and destination
+// numbers.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+class CheriotTestRigIsaFactory
+    : public isa32::RiscVCheriotInstructionSetFactory {
+ public:
+  std::unique_ptr<isa32::Riscv32CheriotSlot> CreateRiscv32CheriotSlot(
+      generic::ArchState *state) override {
+    return std::make_unique<isa32::Riscv32CheriotSlot>(state);
+  }
+};
+
+class CheriotTestRigDecoder {
+ public:
+  using SlotEnum = isa32::SlotEnum;
+  using OpcodeEnum = isa32::OpcodeEnum;
+
+  struct DecodeInfo {
+    int rd;
+    int rs1;
+    int rs2;
+  };
+
+  explicit CheriotTestRigDecoder(CheriotState *state);
+  CheriotTestRigDecoder() = delete;
+  CheriotTestRigDecoder(const CheriotTestRigDecoder &) = delete;
+  CheriotTestRigDecoder &operator=(const CheriotTestRigDecoder &) = delete;
+  virtual ~CheriotTestRigDecoder();
+
+  // Decode a single instruction and fill in decode time information in the
+  // TestRIG execution packet.
+  generic::Instruction *DecodeInstruction(uint64_t address, uint32_t inst_word,
+                                          DecodeInfo &decode_info);
+
+ private:
+  CheriotState *state_;
+  std::unique_ptr<generic::ProgramError> decode_error_;
+  isa32::RiscVCheriotEncoding *cheriot_encoding_;
+  isa32::RiscVCheriotInstructionSetFactory *cheriot_isa_factory_;
+  isa32::RiscVCheriotInstructionSet *cheriot_isa_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_TEST_RIG_DECODER_H_
diff --git a/cheriot/cheriot_test_rig_main.cc b/cheriot/cheriot_test_rig_main.cc
new file mode 100644
index 0000000..cf7fcae
--- /dev/null
+++ b/cheriot/cheriot_test_rig_main.cc
@@ -0,0 +1,168 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <cerrno>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/flags/usage.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "cheriot/cheriot_test_rig.h"
+#include "cheriot/test_rig_packets.h"
+
+using ::mpact::sim::cheriot::CheriotTestRig;
+using ::mpact::sim::cheriot::test_rig::InstructionPacket;
+using ::mpact::sim::cheriot::test_rig::TraceCommand;
+using ::mpact::sim::cheriot::test_rig::VersionPacket;
+
+ABSL_FLAG(int, trace_port, 0, "Trace port number");
+
+int main(int argc, char **argv) {
+  absl::SetProgramUsageMessage(argv[0]);
+  auto arg_vec = absl::ParseCommandLine(argc, argv);
+
+  // Verify that the port number has been set.
+  if (absl::GetFlag(FLAGS_trace_port) == 0) {
+    LOG(ERROR) << "No trace target port specified\n";
+    return -1;
+  }
+
+  // Connect to the sockets.
+  auto trace_socket = socket(AF_INET, SOCK_STREAM, 0);
+  if (trace_socket == -1) {
+    LOG(ERROR) << "Error creating socket\n";
+    return -1;
+  }
+  // Set socket option SO_REUSEADDR and SO_REUSEPORT.
+  int reuseaddr = 1;
+  int reuseport = 1;
+  if (setsockopt(trace_socket, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
+                 sizeof(reuseaddr)) < 0) {
+    LOG(ERROR) << "Failed to set socket option SO_REUSEADDR\n";
+    return -1;
+  }
+  if (setsockopt(trace_socket, SOL_SOCKET, SO_REUSEPORT, &reuseport,
+                 sizeof(reuseport)) < 0) {
+    LOG(ERROR) << "Failed to set socket option SO_REUSEPORT\n";
+    return -1;
+  }
+  if (struct timeval t = {0, 0};
+      setsockopt(trace_socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) < 0) {
+    LOG(ERROR) << "Failed to set socket option SO_RCVTIMEO\n";
+    return -1;
+  }
+  // Bind the socket.
+  const sockaddr_in trace_address_in = {
+      AF_INET, htons(absl::GetFlag(FLAGS_trace_port)), {INADDR_ANY}};
+  int res =
+      bind(trace_socket, reinterpret_cast<const sockaddr *>(&trace_address_in),
+           sizeof(trace_address_in));
+  if (res != 0) {
+    LOG(ERROR) << "Error connecting to trace_socket (" << res << ")\n";
+    return -1;
+  }
+  // Accept connection.
+  res = listen(trace_socket, 1);
+  int trace_fd = accept(trace_socket, nullptr, nullptr);
+
+  // Test rig engine.
+  auto test_rig = std::make_unique<CheriotTestRig>();
+
+  CHECK_OK(test_rig->SetVersion(1));
+  InstructionPacket inst_packet;
+  VersionPacket version_packet;
+
+  bool error = false;
+  uint32_t trace_version = 0;
+  while (true) {
+    auto res = read(trace_fd, &inst_packet, sizeof(inst_packet));
+    // Check for error.
+    if (res < 0) {
+      LOG(ERROR) << "Error reading from trace socket (" << errno << ")\n";
+      error = true;
+      break;
+    }
+    // Zero bytes indicates end of file.
+    if (res == 0) break;
+    if (res != sizeof(inst_packet)) {
+      LOG(ERROR) << "Error reading insufficient bytes from trace socket ("
+                 << res << " != " << sizeof(inst_packet) << ")\n";
+      error = true;
+      break;
+    }
+    switch (inst_packet.rvfi_cmd) {
+      case TraceCommand::kEndOfTrace: {
+        // First check if this is a version negotiation packet.
+        uint8_t halt;
+        if (inst_packet.rvfi_insn == 0x56455253) {
+          auto version = test_rig->GetMaxSupportedVersion();
+          halt = 1 | version;
+        } else {  // End of trace packet.
+          halt = 1;
+        }
+        auto status = test_rig->Reset(halt, trace_fd);
+        if (!status.ok()) {
+          LOG(ERROR) << "Error: " << status.message() << "\n";
+          error = true;
+        }
+        break;
+      }
+      case TraceCommand::kInstruction: {
+        // Execute the trace packet.
+        auto status = test_rig->Execute(inst_packet, trace_fd);
+        if (!status.ok()) {
+          LOG(ERROR) << "Error executing trace packet (" << status.message()
+                     << ")\n";
+          error = true;
+        }
+        break;
+      }
+      case TraceCommand::kSetVersion: {
+        // Set the trace version to write.
+        trace_version = inst_packet.rvfi_insn;
+        auto status = test_rig->SetVersion(trace_version);
+        if (!status.ok()) {
+          LOG(ERROR) << "Error setting trace version (" << status.message()
+                     << ")\n";
+          error = true;
+          break;
+        }
+        version_packet.version = trace_version;
+        res = write(trace_fd, &version_packet, sizeof(version_packet));
+        if (res != sizeof(version_packet)) {
+          LOG(ERROR) << "Error writing to trace socket (" << res << ")\n";
+          error = true;
+        }
+        break;
+      }
+      default:
+        LOG(ERROR) << "Unknown command (ignored): " << (int)inst_packet.rvfi_cmd
+                   << "\n";
+        break;
+    }
+    if (error) break;
+  }
+
+  // Shutdown the socket.
+  res = shutdown(trace_fd, SHUT_RDWR);
+  res = shutdown(trace_socket, SHUT_RDWR);
+}
diff --git a/cheriot/cheriot_top.cc b/cheriot/cheriot_top.cc
new file mode 100644
index 0000000..7bdbc8d
--- /dev/null
+++ b/cheriot/cheriot_top.cc
@@ -0,0 +1,1047 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_top.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/functional/bind_front.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/numeric/bits.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/synchronization/notification.h"
+#include "cheriot/cheriot_debug_interface.h"
+#include "cheriot/cheriot_decoder.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "cheriot/riscv_cheriot_fp_state.h"
+#include "cheriot/riscv_cheriot_register_aliases.h"
+#include "mpact/sim/generic/component.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/decode_cache.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/atomic_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/memory_watcher.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+#include "re2/re2.h"
+#include "riscv//riscv_action_point.h"
+#include "riscv//riscv_breakpoint.h"
+#include "riscv//riscv_csr.h"
+#include "riscv//riscv_register.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+constexpr char kCheriotName[] = "CherIoT";
+
+using EC = ::mpact::sim::cheriot::ExceptionCode;
+using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
+
+CheriotTop::CheriotTop(std::string name)
+    : Component(name),
+      owns_memory_(true),
+      counter_num_instructions_("num_instructions", 0),
+      counter_num_cycles_("num_cycles", 0),
+      counter_pc_("pc", 0),
+      cap_reg_re_{
+          R"((\w+)\.(top|base|length|tag|permissions|object_type|reserved))"} {
+  data_memory_ = new util::TaggedFlatDemandMemory(8);
+  inst_memory_ = data_memory_;
+  Initialize();
+}
+
+CheriotTop::CheriotTop(std::string name, util::TaggedMemoryInterface *memory)
+    : Component(name),
+      inst_memory_(memory),
+      data_memory_(memory),
+      atomic_memory_(nullptr),
+      owns_memory_(false),
+      counter_num_instructions_("num_instructions", 0),
+      counter_num_cycles_("num_cycles", 0),
+      counter_pc_("pc", 0),
+      cap_reg_re_{
+          R"((\w+)\.(top|base|length|tag|permissions|object_type|reserved))"} {
+  Initialize();
+}
+
+CheriotTop::CheriotTop(std::string name, util::MemoryInterface *inst_memory,
+                       util::TaggedMemoryInterface *data_memory)
+    : Component(name),
+      inst_memory_(inst_memory),
+      data_memory_(data_memory),
+      atomic_memory_(nullptr),
+      owns_memory_(false),
+      counter_num_instructions_("num_instructions", 0),
+      counter_num_cycles_("num_cycles", 0),
+      counter_pc_("pc", 0),
+      cap_reg_re_{
+          R"((\w+)\.(top|base|length|tag|permissions|object_type|reserved))"} {
+  Initialize();
+}
+
+CheriotTop::CheriotTop(std::string name, util::TaggedMemoryInterface *memory,
+                       util::MemoryInterface *atomic_memory_if)
+    : Component(name),
+      inst_memory_(memory),
+      data_memory_(memory),
+      atomic_memory_if_(atomic_memory_if),
+      owns_memory_(false),
+      counter_num_instructions_("num_instructions", 0),
+      counter_num_cycles_("num_cycles", 0),
+      counter_pc_("pc", 0),
+      cap_reg_re_{
+          R"((\w+)\.(top|base|length|tag|permissions|object_type|reserved))"} {
+  Initialize();
+}
+
+CheriotTop::CheriotTop(std::string name, util::MemoryInterface *inst_memory,
+                       util::TaggedMemoryInterface *data_memory,
+                       util::MemoryInterface *atomic_memory_if)
+    : Component(name),
+      inst_memory_(inst_memory),
+      data_memory_(data_memory),
+      atomic_memory_if_(atomic_memory_if),
+      owns_memory_(false),
+      counter_num_instructions_("num_instructions", 0),
+      counter_num_cycles_("num_cycles", 0),
+      counter_pc_("pc", 0),
+      cap_reg_re_{
+          R"((\w+)\.(top|base|length|tag|permissions|object_type|reserved))"} {
+  Initialize();
+}
+
+CheriotTop::~CheriotTop() {
+  // If the simulator is still running, request a halt (set halted_ to true),
+  // and wait until the simulator finishes before continuing the destructor.
+  if (run_status_ == RunStatus::kRunning) {
+    run_halted_->WaitForNotification();
+    delete run_halted_;
+    run_halted_ = nullptr;
+  }
+
+  if (branch_trace_db_ != nullptr) branch_trace_db_->DecRef();
+
+  delete rv_bp_manager_;
+  delete cheriot_decode_cache_;
+  delete cheriot_decoder_;
+  delete state_;
+  delete fp_state_;
+  delete tagged_watcher_;
+  delete atomic_watcher_;
+  delete atomic_memory_;
+  if (owns_memory_) delete inst_memory_;
+}
+
+void CheriotTop::Initialize() {
+  // Create the simulation state.
+  tagged_watcher_ = new util::TaggedMemoryWatcher(data_memory_);
+  atomic_watcher_ = new util::MemoryWatcher(atomic_memory_if_);
+  atomic_memory_ = new util::AtomicMemory(atomic_watcher_);
+  state_ = new CheriotState(kCheriotName, tagged_watcher_, atomic_memory_);
+  fp_state_ = new RiscVCheriotFPState(state_);
+  state_->set_rv_fp(fp_state_);
+  pcc_ = static_cast<CheriotRegister *>(
+      state_->registers()->at(CheriotState::kPcName));
+  // Set up the decoder and decode cache.
+  cheriot_decoder_ = new CheriotDecoder(state_, inst_memory_);
+  // Register instruction opcode counters.
+  for (int i = 0; i < static_cast<int>(isa32::OpcodeEnum::kPastMaxValue); i++) {
+    counter_opcode_[i].Initialize(absl::StrCat("num_", isa32::kOpcodeNames[i]),
+                                  0);
+    CHECK_OK(AddCounter(&counter_opcode_[i]))
+        << absl::StrCat("Failed to register opcode counter for :'",
+                        isa32::kOpcodeNames[i], "'");
+  }
+  cheriot_decode_cache_ =
+      generic::DecodeCache::Create({16 * 1024, 2}, cheriot_decoder_);
+  // Register instruction counter.
+  CHECK_OK(AddCounter(&counter_num_instructions_))
+      << "Failed to register instruction counter";
+  // Register pc counter.
+  CHECK_OK(AddCounter(&counter_pc_)) << "Failed to register pc counter";
+
+  // Breakpoints.
+  rv_action_point_manager_ = new riscv::RiscVActionPointManager(
+      inst_memory_, absl::bind_front(&generic::DecodeCache::Invalidate,
+                                     cheriot_decode_cache_));
+  rv_bp_manager_ = new riscv::RiscVBreakpointManager(
+      rv_action_point_manager_,
+      [this](HaltReason halt_reason) { RequestHalt(halt_reason, nullptr); });
+  // Set the software breakpoint callback.
+  state_->AddEbreakHandler([this](const Instruction *inst) {
+    if (rv_action_point_manager_->IsActionPointActive(inst->address())) {
+      // Need to request a halt so that the action point can be stepped past
+      // after executing the actions. However, an action may override the
+      // particular halt reason (e.g., breakpoints).
+      RequestHalt(HaltReason::kActionPoint, inst);
+      rv_action_point_manager_->PerformActions(inst->address());
+      return true;
+    }
+    return false;
+  });
+
+  // Make sure the architectural and abi register aliases are added.
+  std::string reg_name;
+  std::string xreg_name;
+  for (int i = 0; i < 32; i++) {
+    reg_name = absl::StrCat(CheriotState::kCregPrefix, i);
+    (void)state_->AddRegister<CheriotRegister>(reg_name);
+    (void)state_->AddRegisterAlias<CheriotRegister>(reg_name,
+                                                    kCRegisterAliases[i]);
+    (void)state_->AddRegisterAlias<CheriotRegister>(reg_name,
+                                                    kXRegisterAliases[i]);
+    xreg_name = absl::StrCat(CheriotState::kXregPrefix, i);
+    (void)state_->AddRegisterAlias<CheriotRegister>(reg_name, xreg_name);
+  }
+  for (int i = 0; i < 32; i++) {
+    reg_name = absl::StrCat(CheriotState::kFregPrefix, i);
+    (void)state_->AddRegister<riscv::RVFpRegister>(reg_name);
+    (void)state_->AddRegisterAlias<riscv::RVFpRegister>(reg_name,
+                                                        kFRegisterAliases[i]);
+  }
+  // Branch trace.
+  branch_trace_db_ = db_factory_.Allocate<BranchTraceEntry>(kBranchTraceSize);
+  branch_trace_ =
+      reinterpret_cast<BranchTraceEntry *>(branch_trace_db_->raw_ptr());
+  for (int i = 0; i < kBranchTraceSize; i++) {
+    branch_trace_[i] = {0, 0, 0};
+  }
+}
+
+bool CheriotTop::ExecuteInstruction(Instruction *inst) {
+  if (!pcc_->tag()) {
+    state_->HandleCheriRegException(inst, inst->address(),
+                                    EC::kCapExTagViolation, pcc_);
+    return true;
+  }
+  if (!pcc_->HasPermission(PB::kPermitExecute)) {
+    state_->HandleCheriRegException(inst, inst->address(),
+                                    EC::kCapExPermitExecuteViolation, pcc_);
+    return true;
+  }
+  if (!pcc_->IsInBounds(inst->address(), inst->size())) {
+    state_->HandleCheriRegException(inst, inst->address(),
+                                    EC::kCapExBoundsViolation, pcc_);
+    return true;
+  }
+  inst->Execute(nullptr);
+  counter_pc_.SetValue(inst->address());
+  // Comment out instruction logging during execution.
+  // LOG(INFO) << "[" << std::hex << inst->address() << "] " <<
+  // inst->AsString();
+  return true;
+}
+
+absl::Status CheriotTop::Halt() {
+  // If it is already halted, just return.
+  if (run_status_ == RunStatus::kHalted) {
+    return absl::OkStatus();
+  }
+  // If it is not running, then there's an error.
+  if (run_status_ != RunStatus::kRunning) {
+    return absl::FailedPreconditionError(
+        "CheriotTop::Halt: Core is not running");
+  }
+  halt_reason_ = *HaltReason::kUserRequest;
+  halted_ = true;
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTop::StepPastBreakpoint() {
+  uint64_t pc = state_->pc_operand()->AsUint64(0);
+  uint64_t bpt_pc = pc;
+  // Disable the breakpoint.
+  rv_action_point_manager_->WriteOriginalInstruction(pc);
+  // Execute the real instruction.
+  auto real_inst = cheriot_decode_cache_->GetDecodedInstruction(pc);
+  real_inst->IncRef();
+  uint64_t next_pc = pc + real_inst->size();
+  bool executed = false;
+  do {
+    executed = ExecuteInstruction(real_inst);
+    counter_num_cycles_.Increment(1);
+    state_->AdvanceDelayLines();
+  } while (!executed);
+  // Increment counters.
+  counter_opcode_[real_inst->opcode()].Increment(1);
+  counter_num_instructions_.Increment(1);
+  real_inst->DecRef();
+  // Re-enable the breakpoint.
+  // Re-enable the breakpoint.
+  rv_action_point_manager_->WriteBreakpointInstruction(bpt_pc);
+  // Get the next pc value.
+  uint64_t pcc_val = pcc_->data_buffer()->Get<uint32_t>(0);
+  if (state_->branch()) {
+    state_->set_branch(false);
+    AddToBranchTrace(pc, pcc_val);
+    next_pc = pcc_val;
+    if (break_on_control_flow_change_) {
+      halted_ = true;
+      halt_reason_ = *HaltReason::kHardwareBreakpoint;
+    }
+  }
+  SetPc(next_pc);
+  return absl::OkStatus();
+}
+
+absl::StatusOr<int> CheriotTop::Step(int num) {
+  if (num <= 0) {
+    return absl::InvalidArgumentError("Step count must be > 0");
+  }
+  // If the simulator is running, return with an error.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError(
+        "CheriotTop::Step: Core must be halted");
+  }
+  run_status_ = RunStatus::kSingleStep;
+  int count = 0;
+  halted_ = false;
+  // First check to see if the previous halt was due to a breakpoint. If so,
+  // verify that the breakpoint is there, then step over the breakpoint.
+  if (need_to_step_over_) {
+    need_to_step_over_ = false;
+    auto status = StepPastBreakpoint();
+    if (!status.ok()) return status;
+    count++;
+  }
+
+  // Step the simulator forward until the number of steps have been achieved, or
+  // there is a halt request.
+
+  // This holds the value of the current pc, and post-loop, the address of
+  // the most recently executed instruction.
+  uint64_t pc;
+  // At the top of the loop this holds the address of the instruction to be
+  // executed next. Post-loop it holds the address of the next instruction to
+  // be executed.
+  uint64_t next_pc = pcc_->data_buffer()->Get<uint32_t>(0);
+  while (!halted_ && (count < num)) {
+    pc = next_pc;
+    SetPc(pc);
+    auto *inst = cheriot_decode_cache_->GetDecodedInstruction(pc);
+    // Set the next_pc to the next sequential instruction.
+    next_pc += inst->size();
+    bool executed = false;
+    do {
+      executed = ExecuteInstruction(inst);
+      counter_num_cycles_.Increment(1);
+      state_->AdvanceDelayLines();
+      // Check for interrupt.
+      if (state_->is_interrupt_available()) {
+        uint64_t epc = pc;
+        if (executed) {
+          epc = state_->branch() ? pcc_->data_buffer()->Get<uint32_t>(0)
+                                 : next_pc;
+        }
+        state_->TakeAvailableInterrupt(epc);
+      }
+    } while (!executed);
+    count++;
+    // Update counters.
+    counter_opcode_[inst->opcode()].Increment(1);
+    counter_num_instructions_.Increment(1);
+    // Get the next pc value.
+    uint64_t pcc_val = pcc_->data_buffer()->Get<uint32_t>(0);
+    if (state_->branch()) {
+      state_->set_branch(false);
+      AddToBranchTrace(pc, pcc_val);
+      next_pc = pcc_val;
+      if (break_on_control_flow_change_) {
+        halted_ = true;
+        halt_reason_ = *HaltReason::kHardwareBreakpoint;
+      }
+    }
+    if (!halted_) continue;
+    // If it's an action point, just step over and continue.
+    if (halt_reason_ == *HaltReason::kActionPoint) {
+      auto status = StepPastBreakpoint();
+      if (!status.ok()) return status;
+      // Reset the halt reason and continue;
+      halted_ = false;
+      halt_reason_ = *HaltReason::kNone;
+      need_to_step_over_ = false;
+      continue;
+    }
+    break;
+  }
+  // Update the pc register, now that it can be read.
+  if (halt_reason_ == *HaltReason::kSoftwareBreakpoint) {
+    // If at a breakpoint, keep the pc at the current value.
+    SetPc(pc);
+  } else {
+    // Otherwise set it to point to the next instruction.
+    SetPc(next_pc);
+  }
+  // If there is no halt request, there is no specific halt reason.
+  if (!halted_) {
+    halt_reason_ = *HaltReason::kNone;
+  }
+  run_status_ = RunStatus::kHalted;
+  return count;
+}
+
+absl::Status CheriotTop::Run() {
+  // Verify that the core isn't running already.
+  if (run_status_ == RunStatus::kRunning) {
+    return absl::FailedPreconditionError(
+        "CheriotTop::Run: core is already running");
+  }
+  // First check to see if the previous halt was due to a breakpoint. If so,
+  // need to step over the breakpoint.
+  if (need_to_step_over_) {
+    need_to_step_over_ = false;
+    auto status = StepPastBreakpoint();
+    if (!status.ok()) return status;
+  }
+  run_status_ = RunStatus::kRunning;
+  halted_ = false;
+
+  // The simulator is now run in a separate thread so as to allow a user
+  // interface to continue operating. Allocate a new run_halted_ Notification
+  // object, as they are single use only.
+  run_halted_ = new absl::Notification();
+  // The thread is detached so it executes without having to be joined.
+  std::thread([this]() {
+    // This holds the value of the current pc, and post-loop, the address of
+    // the most recently executed instruction.
+    uint64_t pc;
+    // At the top of the loop this holds the address of the instruction to be
+    // executed next. Post-loop it holds the address of the next instruction to
+    // be executed.
+    uint64_t next_pc = pcc_->data_buffer()->Get<uint32_t>(0);
+    while (!halted_) {
+      pc = next_pc;
+      auto *inst = cheriot_decode_cache_->GetDecodedInstruction(pc);
+      SetPc(pc);
+      // Set the PC destination operand to next_seq_pc. Any branch that is
+      // executed will overwrite this.
+      next_pc += inst->size();
+      bool executed = false;
+      do {
+        // Try executing the instruction. If it fails, advance a cycle
+        // and try again.
+        executed = ExecuteInstruction(inst);
+        counter_num_cycles_.Increment(1);
+        state_->AdvanceDelayLines();
+        // Check for interrupt.
+        if (state_->is_interrupt_available()) {
+          uint64_t epc = pc;
+          if (executed) {
+            epc = state_->branch() ? pcc_->data_buffer()->Get<uint32_t>(0)
+                                   : next_pc;
+          }
+          state_->TakeAvailableInterrupt(epc);
+        }
+      } while (!executed);
+      // Update counters.
+      counter_opcode_[inst->opcode()].Increment(1);
+      counter_num_instructions_.Increment(1);
+      // Get the next pc value.
+      // Get the next pc value.
+      uint64_t pcc_val = pcc_->data_buffer()->Get<uint32_t>(0);
+      if (state_->branch()) {
+        state_->set_branch(false);
+        AddToBranchTrace(pc, pcc_val);
+        next_pc = pcc_val;
+        if (break_on_control_flow_change_) {
+          halted_ = true;
+          halt_reason_ = *HaltReason::kHardwareBreakpoint;
+        }
+      }
+      if (!halted_) continue;
+      // If it's an action point, just step over and continue executing, as
+      // this is not a full breakpoint.
+      if (halt_reason_ == *HaltReason::kActionPoint) {
+        auto status = StepPastBreakpoint();
+        if (!status.ok()) {
+          // If there is an error, signal a simulator error.
+          halt_reason_ = *HaltReason::kSimulatorError;
+          break;
+        };
+        // Reset the halt reason and continue;
+        halted_ = false;
+        halt_reason_ = *HaltReason::kNone;
+        continue;
+      }
+      break;
+    }
+    // Update the pc register, now that it can be read.
+    if (halt_reason_ == *HaltReason::kSoftwareBreakpoint) {
+      // If at a breakpoint, keep the pc at the current value.
+      SetPc(pc);
+    } else {
+      // Otherwise set it to point to the next instruction.
+      SetPc(next_pc);
+    }
+    run_status_ = RunStatus::kHalted;
+    // Notify that the run has completed.
+    run_halted_->Notify();
+  }).detach();
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTop::Wait() {
+  // If the simulator isn't running, then just return after deleting
+  // the notification object.
+  if (run_status_ != RunStatus::kRunning) {
+    delete run_halted_;
+    run_halted_ = nullptr;
+    return absl::OkStatus();
+  }
+
+  // Wait for the simulator to finish - i.e., a notification on run_halted_.
+  run_halted_->WaitForNotification();
+  // Now delete the notification object - it is single use only.
+  delete run_halted_;
+  run_halted_ = nullptr;
+  return absl::OkStatus();
+}
+
+absl::StatusOr<CheriotTop::RunStatus> CheriotTop::GetRunStatus() {
+  return run_status_;
+}
+
+absl::StatusOr<CheriotTop::HaltReasonValueType>
+CheriotTop::GetLastHaltReason() {
+  return halt_reason_;
+}
+
+absl::StatusOr<uint64_t> CheriotTop::ReadRegister(const std::string &name) {
+  auto iter = state_->registers()->find(name);
+  // If the register was not found, see if it refers to a capability component.
+  // Capability components are named c<n>.top, c<n>.base, etc.
+  if (iter == state_->registers()->end()) {
+    std::string component;
+    std::string cap_reg_name;
+    if (RE2::FullMatch(name, *cap_reg_re_, &cap_reg_name, &component)) {
+      iter = state_->registers()->find(cap_reg_name);
+      if (iter == state_->registers()->end()) {
+        return absl::NotFoundError(
+            absl::StrCat("Register '", name, "' not found"));
+      }
+      auto *cap_reg = static_cast<CheriotRegister *>(iter->second);
+      if (component == "top") return cap_reg->top();
+      if (component == "base") return cap_reg->base();
+      if (component == "length") return cap_reg->length();
+      if (component == "tag") return cap_reg->tag();
+      if (component == "permissions") return cap_reg->permissions();
+      if (component == "object_type") return cap_reg->object_type();
+      if (component == "reserved") return cap_reg->reserved();
+      return absl::NotFoundError(
+          absl::StrCat("Register '", name, "' not found"));
+    }
+  }
+  // Was the register found? If not try CSRs.
+  if (iter == state_->registers()->end()) {
+    auto result = state_->csr_set()->GetCsr(name);
+    if (result.ok()) {
+      auto *csr = *result;
+      return csr->GetUint32();
+    }
+    // See if it is $branch_trace_head.
+    if (name == "$branch_trace_head") return branch_trace_head_;
+    if (name == "$branch_trace_size") return branch_trace_size_;
+    if (!result.ok()) {
+      return absl::NotFoundError(
+          absl::StrCat("Register '", name, "' not found"));
+    }
+  }
+
+  auto *db = (iter->second)->data_buffer();
+  uint64_t value;
+  switch (db->size<uint8_t>()) {
+    case 1:
+      value = static_cast<uint64_t>(db->Get<uint8_t>(0));
+      break;
+    case 2:
+      value = static_cast<uint64_t>(db->Get<uint16_t>(0));
+      break;
+    case 4:
+      value = static_cast<uint64_t>(db->Get<uint32_t>(0));
+      break;
+    case 8:
+      value = static_cast<uint64_t>(db->Get<uint64_t>(0));
+      break;
+    default:
+      return absl::InternalError("Register size is not 1, 2, 4, or 8 bytes");
+  }
+  return value;
+}
+
+absl::Status CheriotTop::WriteRegister(const std::string &name,
+                                       uint64_t value) {
+  // The registers aren't protected by a mutex, so let's not write them while
+  // the simulator is running.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError("WriteRegister: Core must be halted");
+  }
+  auto iter = state_->registers()->find(name);
+  // If the register was not found, see if it refers to a capability component.
+  // Capability components are named c<n>.top, c<n>.base, etc.
+  if (iter == state_->registers()->end()) {
+    std::string component;
+    std::string cap_reg_name;
+    if (RE2::FullMatch(name, *cap_reg_re_, &cap_reg_name, &component)) {
+      auto *cap_reg = static_cast<CheriotRegister *>(iter->second);
+      if (component == "top") {
+        value = std::min<uint64_t>(value, 0x1'0000'0000ULL);
+        if (value < cap_reg->base()) {
+          return absl::InvalidArgumentError("Top must be greater than base");
+        }
+        cap_reg->SetBounds(cap_reg->base(), value - cap_reg->base());
+        return absl::OkStatus();
+      }
+      if (component == "base") {
+        value = std::min<uint64_t>(value, 0xffff'ffffULL);
+        if (value > cap_reg->top()) {
+          return absl::InvalidArgumentError("Base must be less than top");
+        }
+        cap_reg->SetBounds(value, cap_reg->top() - value);
+        return absl::OkStatus();
+      }
+      if (component == "length") {
+        value = std::min<uint64_t>(value, 0x1'0000'0000ULL);
+        cap_reg->SetBounds(cap_reg->base(), value);
+        return absl::OkStatus();
+      }
+      if (component == "tag") {
+        cap_reg->set_tag(static_cast<bool>(value));
+        return absl::OkStatus();
+      }
+      if (component == "permissions") {
+        cap_reg->set_permissions(value & PB::kPermitMask);
+        return absl::OkStatus();
+      }
+      if (component == "object_type") {
+        cap_reg->set_object_type(value);
+        return absl::OkStatus();
+      }
+      if (component == "reserved") {
+        cap_reg->set_reserved(value);
+        return absl::OkStatus();
+      }
+      return absl::NotFoundError(
+          absl::StrCat("Register '", name, "' not found"));
+    }
+  }
+  // Was the register found? If not try CSRs.
+  if (iter == state_->registers()->end()) {
+    auto result = state_->csr_set()->GetCsr(name);
+    if (name == "$branch_trace_size") {
+      return ResizeBranchTrace(value);
+    }
+    if (!result.ok()) {
+      return absl::NotFoundError(
+          absl::StrCat("Register '", name, "' not found"));
+    }
+    auto *csr = *result;
+    csr->Set(static_cast<uint32_t>(value));
+  }
+
+  // If stopped at a software breakpoint and the pc is changed, change the
+  // halt reason, since the next instruction won't be were we stopped.
+  if (((name == "pcc") || (name == "pc")) &&
+      (halt_reason_ == *HaltReason::kSoftwareBreakpoint)) {
+    halt_reason_ = *HaltReason::kNone;
+  }
+
+  auto *db = (iter->second)->data_buffer();
+  switch (db->size<uint8_t>()) {
+    case 1:
+      db->Set<uint8_t>(0, static_cast<uint8_t>(value));
+      break;
+    case 2:
+      db->Set<uint16_t>(0, static_cast<uint16_t>(value));
+      break;
+    case 4:
+      db->Set<uint32_t>(0, static_cast<uint32_t>(value));
+      break;
+    case 8:
+      db->Set<uint64_t>(0, static_cast<uint64_t>(value));
+      break;
+    default:
+      return absl::InternalError("Register size is not 1, 2, 4, or 8 bytes");
+  }
+  return absl::OkStatus();
+}
+
+absl::StatusOr<DataBuffer *> CheriotTop::GetRegisterDataBuffer(
+    const std::string &name) {
+  // The registers aren't protected by a mutex, so let's not access them while
+  // the simulator is running.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError(
+        "GetRegisterDataBuffer: Core must be halted");
+  }
+  if (name == "$branch_trace") return branch_trace_db_;
+  auto iter = state_->registers()->find(name);
+  if (iter == state_->registers()->end()) {
+    return absl::NotFoundError(absl::StrCat("Register '", name, "' not found"));
+  }
+  return iter->second->data_buffer();
+}
+
+absl::StatusOr<size_t> CheriotTop::ReadMemory(uint64_t address, void *buffer,
+                                              size_t length) {
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError("ReadMemory: Core must be halted");
+  }
+  if (address > state_->max_physical_address()) {
+    return absl::InvalidArgumentError("Invalid memory address");
+  }
+  length = std::min(length, state_->max_physical_address() - address + 1);
+  auto *db = db_factory_.Allocate(length);
+  // Load bypassing any watch points/semihosting.
+  state_->tagged_memory()->Load(address, db, nullptr, nullptr);
+  std::memcpy(buffer, db->raw_ptr(), length);
+  db->DecRef();
+  return length;
+}
+
+absl::StatusOr<size_t> CheriotTop::ReadTagMemory(uint64_t address, void *buf,
+                                                 size_t length) {
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError("ReadTagMemory: Core must be halted");
+  }
+  if (address > state_->max_physical_address()) {
+    return absl::InvalidArgumentError("Invalid memory address");
+  }
+  length = std::min(length, state_->max_physical_address() - address + 1);
+  auto *tag_db = db_factory_.Allocate<uint8_t>(length);
+  state_->tagged_memory()->Load(address, nullptr, tag_db, nullptr, nullptr);
+  std::memcpy(buf, tag_db->raw_ptr(), length);
+  return length;
+}
+
+absl::StatusOr<size_t> CheriotTop::WriteMemory(uint64_t address,
+                                               const void *buffer,
+                                               size_t length) {
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError("WriteMemory: Core must be halted");
+  }
+  if (address > state_->max_physical_address()) {
+    return absl::InvalidArgumentError("Invalid memory address");
+  }
+  length = std::min(length, state_->max_physical_address() - address + 1);
+  auto *db = db_factory_.Allocate(length);
+  std::memcpy(db->raw_ptr(), buffer, length);
+  // Store bypassing any watch points/semihosting.
+  state_->tagged_memory()->Store(address, db);
+  db->DecRef();
+  return length;
+}
+
+bool CheriotTop::HasBreakpoint(uint64_t address) {
+  return rv_bp_manager_->HasBreakpoint(address);
+}
+
+absl::Status CheriotTop::SetSwBreakpoint(uint64_t address) {
+  // Don't try if the simulator is running.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError(
+        "SetSwBreakpoint: Core must be halted");
+  }
+  // If there is no breakpoint manager, return an error.
+  if (rv_bp_manager_ == nullptr) {
+    return absl::InternalError("Breakpoints are not enabled");
+  }
+  // Try setting the breakpoint.
+  return rv_bp_manager_->SetBreakpoint(address);
+}
+
+absl::Status CheriotTop::ClearSwBreakpoint(uint64_t address) {
+  // Don't try if the simulator is running.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError(
+        "ClearSwBreakpoint: Core must be halted");
+  }
+  if (rv_bp_manager_ == nullptr) {
+    return absl::InternalError("Breakpoints are not enabled");
+  }
+  return rv_bp_manager_->ClearBreakpoint(address);
+}
+
+absl::Status CheriotTop::ClearAllSwBreakpoints() {
+  // Don't try if the simulator is running.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError(
+        "ClearAllSwBreakpoints: Core must be halted");
+  }
+  if (rv_bp_manager_ == nullptr) {
+    return absl::InternalError("Breakpoints are not enabled");
+  }
+  rv_bp_manager_->ClearAllBreakpoints();
+  return absl::OkStatus();
+}
+
+// Methods for Action points forward to the rv_action_point_manager_ methods.
+
+absl::StatusOr<int> CheriotTop::SetActionPoint(
+    uint64_t address, absl::AnyInvocable<void(uint64_t, int)> action) {
+  if (rv_action_point_manager_ == nullptr) {
+    return absl::InternalError("Action points are not enabled");
+  }
+  auto res = rv_action_point_manager_->SetAction(address, std::move(action));
+  if (!res.ok()) return res;
+  return res.value();
+}
+
+absl::Status CheriotTop::ClearActionPoint(uint64_t address, int id) {
+  if (rv_action_point_manager_ == nullptr) {
+    return absl::InternalError("Action points are not enabled");
+  }
+  return rv_action_point_manager_->ClearAction(address, id);
+}
+
+absl::Status CheriotTop::EnableAction(uint64_t address, int id) {
+  if (rv_action_point_manager_ == nullptr) {
+    return absl::InternalError("Action points are not enabled");
+  }
+  return rv_action_point_manager_->EnableAction(address, id);
+}
+
+absl::Status CheriotTop::DisableAction(uint64_t address, int id) {
+  if (rv_action_point_manager_ == nullptr) {
+    return absl::InternalError("Action points are not enabled");
+  }
+  return rv_action_point_manager_->DisableAction(address, id);
+}
+
+// Set a data watchpoint for the given address range and access type.
+absl::Status CheriotTop::SetDataWatchpoint(uint64_t address, size_t length,
+                                           AccessType access_type) {
+  if ((access_type == AccessType::kLoad) ||
+      (access_type == AccessType::kLoadStore)) {
+    auto rd_tagged_status = tagged_watcher_->SetLoadWatchCallback(
+        util::TaggedMemoryWatcher::AddressRange(address, address + length - 1),
+        [this](uint64_t address, int size) {
+          set_halt_string(absl::StrFormat(
+              "Watchpoint triggered due to load from %08x", address));
+          RequestHalt(*HaltReason::kDataWatchPoint, nullptr);
+        });
+    if (!rd_tagged_status.ok()) return rd_tagged_status;
+
+    auto rd_atomic_status = atomic_watcher_->SetLoadWatchCallback(
+        util::MemoryWatcher::AddressRange(address, address + length - 1),
+        [this](uint64_t address, int size) {
+          set_halt_string(absl::StrFormat(
+              "Watchpoint triggered due to load from %08x", address));
+          RequestHalt(*HaltReason::kDataWatchPoint, nullptr);
+        });
+    if (!rd_atomic_status.ok()) {
+      // Error recovery - ignore return value.
+      (void)tagged_watcher_->ClearLoadWatchCallback(address);
+      return rd_atomic_status;
+    }
+  }
+  if ((access_type == AccessType::kStore) ||
+      (access_type == AccessType::kLoadStore)) {
+    auto wr_tagged_status = tagged_watcher_->SetStoreWatchCallback(
+        util::TaggedMemoryWatcher::AddressRange(address, address + length - 1),
+        [this](uint64_t address, int size) {
+          set_halt_string(absl::StrFormat(
+              "Watchpoint triggered due to store to %08x", address));
+          RequestHalt(*HaltReason::kDataWatchPoint, nullptr);
+        });
+    if (!wr_tagged_status.ok()) {
+      if (access_type == AccessType::kLoadStore) {
+        // Error recovery - ignore return value.
+        (void)tagged_watcher_->ClearLoadWatchCallback(address);
+        (void)atomic_watcher_->ClearLoadWatchCallback(address);
+      }
+      return wr_tagged_status;
+    }
+
+    auto wr_atomic_status = atomic_watcher_->SetStoreWatchCallback(
+        util::MemoryWatcher::AddressRange(address, address + length - 1),
+        [this](uint64_t address, int size) {
+          set_halt_string(absl::StrFormat(
+              "Watchpoint triggered due to store to %08x", address));
+          RequestHalt(*HaltReason::kDataWatchPoint, nullptr);
+        });
+    if (!wr_atomic_status.ok()) {
+      // Error recovery - ignore return value.
+      (void)tagged_watcher_->ClearStoreWatchCallback(address);
+      if (access_type == AccessType::kLoadStore) {
+        (void)tagged_watcher_->ClearLoadWatchCallback(address);
+        (void)atomic_watcher_->ClearLoadWatchCallback(address);
+      }
+      return wr_atomic_status;
+    }
+  }
+  return absl::OkStatus();
+}
+
+absl::Status CheriotTop::ClearDataWatchpoint(uint64_t address,
+                                             AccessType access_type) {
+  if ((access_type == AccessType::kLoad) ||
+      (access_type == AccessType::kLoadStore)) {
+    auto rd_tagged_status = tagged_watcher_->ClearLoadWatchCallback(address);
+    if (!rd_tagged_status.ok()) return rd_tagged_status;
+
+    auto rd_atomic_status = atomic_watcher_->ClearLoadWatchCallback(address);
+    if (!rd_atomic_status.ok()) return rd_atomic_status;
+  }
+  if ((access_type == AccessType::kStore) ||
+      (access_type == AccessType::kLoadStore)) {
+    auto wr_tagged_status = tagged_watcher_->ClearStoreWatchCallback(address);
+    if (!wr_tagged_status.ok()) return wr_tagged_status;
+
+    auto wr_atomic_status = atomic_watcher_->ClearStoreWatchCallback(address);
+    if (!wr_atomic_status.ok()) return wr_atomic_status;
+  }
+  return absl::OkStatus();
+}
+
+void CheriotTop::SetBreakOnControlFlowChange(bool value) {
+  break_on_control_flow_change_ = value;
+}
+
+absl::StatusOr<Instruction *> CheriotTop::GetInstruction(uint64_t address) {
+  auto inst = cheriot_decode_cache_->GetDecodedInstruction(address);
+  return inst;
+}
+
+absl::StatusOr<std::string> CheriotTop::GetDisassembly(uint64_t address) {
+  // Don't try if the simulator is running.
+  if (run_status_ != RunStatus::kHalted) {
+    return absl::FailedPreconditionError("GetDissasembly: Core must be halted");
+  }
+
+  Instruction *inst = nullptr;
+  // If requesting the disassembly for an instruction at an action point, we
+  // need to write the original instruction back to memory before getting the
+  // disassembly.
+  bool inst_swap = rv_action_point_manager_->IsActionPointActive(address);
+  if (inst_swap) {
+    rv_action_point_manager_->WriteOriginalInstruction(address);
+  }
+  // Get the decoded instruction.
+  inst = cheriot_decode_cache_->GetDecodedInstruction(address);
+  auto disasm = inst != nullptr ? inst->AsString() : "Invalid instruction";
+  // Swap back if required.
+  if (inst_swap) {
+    rv_action_point_manager_->WriteBreakpointInstruction(address);
+  }
+  return disasm;
+}
+
+void CheriotTop::RequestHalt(HaltReasonValueType halt_reason,
+                             const Instruction *inst) {
+  // First set the halt_reason_, then the halt flag.
+  halt_reason_ = halt_reason;
+  halted_ = true;
+  // If the halt reason is either sw breakpoint or action point, set
+  // need_to_step_over to true.
+  if ((halt_reason_ == *HaltReason::kSoftwareBreakpoint) ||
+      (halt_reason_ == *HaltReason::kActionPoint)) {
+    need_to_step_over_ = true;
+  }
+}
+
+void CheriotTop::RequestHalt(HaltReason halt_reason, const Instruction *inst) {
+  RequestHalt(*halt_reason, inst);
+}
+
+void CheriotTop::SetPc(uint64_t value) {
+  if (pcc_->data_buffer()->size<uint8_t>() == 4) {
+    pcc_->data_buffer()->Set<uint32_t>(0, static_cast<uint32_t>(value));
+  } else {
+    pcc_->data_buffer()->Set<uint64_t>(0, value);
+  }
+}
+
+absl::Status CheriotTop::ResizeBranchTrace(size_t size) {
+  if (absl::popcount(size) != 1) {
+    return absl::InvalidArgumentError("Invalid size - must be a power of 2");
+  }
+  auto *new_db = db_factory_.Allocate<BranchTraceEntry>(size);
+  auto *new_trace = reinterpret_cast<BranchTraceEntry *>(new_db->raw_ptr());
+  if (new_db == nullptr) {
+    return absl::InternalError("Failed to allocate new branch trace buffer");
+  }
+  // Copy entries from the old buffer to the new buffer, but do it so that
+  // the most recent entry of the old buffer is at the end of the newly
+  // allocated buffer. That way, if the new buffer is smaller, we don't have to
+  // do too much special handling.
+  int new_index = size - 1;
+  int old_index = branch_trace_head_;
+  while ((new_index >= 0) && (branch_trace_[old_index].count > 0)) {
+    new_trace[new_index] = branch_trace_[old_index];
+    new_index--;
+    old_index--;
+    if (old_index < 0) {
+      old_index = branch_trace_size_ - 1;
+    }
+    // Stop if we get to the beginning of the old trace.
+    if (old_index == branch_trace_head_) break;
+  }
+  while (new_index >= 0) {
+    new_trace[new_index] = {0, 0, 0};
+    new_index--;
+  }
+  branch_trace_db_->DecRef();
+  branch_trace_db_ = new_db;
+  branch_trace_ = new_trace;
+  branch_trace_size_ = size;
+  branch_trace_mask_ = branch_trace_size_ - 1;
+  branch_trace_head_ = branch_trace_mask_;
+  return absl::OkStatus();
+}
+
+void CheriotTop::AddToBranchTrace(uint64_t from, uint64_t to) {
+  // Get the most recent entry.
+  auto &entry = branch_trace_[branch_trace_head_];
+  // If the branch is the same as the previous, just increment its count.
+  if ((from == entry.from) && (to == entry.to)) {
+    entry.count++;
+    return;
+  }
+  branch_trace_head_ = (branch_trace_head_ + 1) & branch_trace_mask_;
+  branch_trace_[branch_trace_head_] = {static_cast<uint32_t>(from),
+                                       static_cast<uint32_t>(to), 1};
+}
+
+void CheriotTop::EnableStatistics() {
+  for (auto &[unused, counter_ptr] : counter_map()) {
+    if (counter_ptr->GetName() == "pc") continue;
+    counter_ptr->SetIsEnabled(true);
+  }
+}
+
+void CheriotTop::DisableStatistics() {
+  for (auto &[unused, counter_ptr] : counter_map()) {
+    if (counter_ptr->GetName() == "pc") continue;
+    counter_ptr->SetIsEnabled(false);
+  }
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/cheriot_top.h b/cheriot/cheriot_top.h
new file mode 100644
index 0000000..e07838f
--- /dev/null
+++ b/cheriot/cheriot_top.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__CHERIOT_TOP_H_
+#define MPACT_CHERIOT__CHERIOT_TOP_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/synchronization/notification.h"
+#include "cheriot/cheriot_debug_interface.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "cheriot/riscv_cheriot_fp_state.h"
+#include "mpact/sim/generic/component.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/counters.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/decode_cache.h"
+#include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/memory_watcher.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+#include "re2/re2.h"
+#include "riscv//riscv_action_point.h"
+#include "riscv//riscv_breakpoint.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+struct BranchTraceEntry {
+  uint32_t from;
+  uint32_t to;
+  uint32_t count;
+};
+
+// Top level class for the CherIoT simulator. This is the main interface for
+// interacting and controlling execution of programs running on the simulator.
+// This class brings together the decoder, the architecture state, and control.
+class CheriotTop : public generic::Component, public CheriotDebugInterface {
+ public:
+  static constexpr int kBranchTraceSize = 16;
+  using RunStatus = generic::CoreDebugInterface::RunStatus;
+  using HaltReason = generic::CoreDebugInterface::HaltReason;
+
+  // Simple constructor, the memories are created and owned by the CheriotTop
+  // instance.
+  explicit CheriotTop(std::string name);
+  // Constructors without the atomic memory interface. Either one (unified),
+  // or two (inst and data) interfaces are passed in.
+  CheriotTop(std::string name, util::TaggedMemoryInterface *memory);
+  CheriotTop(std::string name, util::MemoryInterface *inst_memory,
+             util::TaggedMemoryInterface *data_memory);
+  // Constructors with the memory interface to be used with atomic memory
+  // operations. Either one (unified), or two (inst and data) interfaces are
+  // passed in.
+  CheriotTop(std::string name, util::TaggedMemoryInterface *memory,
+             util::MemoryInterface *atomic_memory_if);
+  CheriotTop(std::string name, util::MemoryInterface *inst_memory,
+             util::TaggedMemoryInterface *data_memory,
+             util::MemoryInterface *atomic_memory_if);
+  ~CheriotTop() override;
+
+  // Methods inherited from CoreDebugInterface.
+  absl::Status Halt() override;
+  absl::StatusOr<int> Step(int num) override;
+  absl::Status Run() override;
+  absl::Status Wait() override;
+
+  absl::StatusOr<RunStatus> GetRunStatus() override;
+  absl::StatusOr<HaltReasonValueType> GetLastHaltReason() override;
+
+  // Register access by register name.
+  absl::StatusOr<uint64_t> ReadRegister(const std::string &name) override;
+  absl::Status WriteRegister(const std::string &name, uint64_t value) override;
+  absl::StatusOr<generic::DataBuffer *> GetRegisterDataBuffer(
+      const std::string &name) override;
+  // Read and Write memory methods bypass any semihosting.
+  absl::StatusOr<size_t> ReadMemory(uint64_t address, void *buf,
+                                    size_t length) override;
+  absl::StatusOr<size_t> WriteMemory(uint64_t address, const void *buf,
+                                     size_t length) override;
+  absl::StatusOr<size_t> ReadTagMemory(uint64_t address, void *buf,
+                                       size_t length) override;
+
+  // Breakpoints.
+  bool HasBreakpoint(uint64_t address) override;
+  absl::Status SetSwBreakpoint(uint64_t address) override;
+  absl::Status ClearSwBreakpoint(uint64_t address) override;
+  absl::Status ClearAllSwBreakpoints() override;
+
+  // Action points.
+  absl::StatusOr<int> SetActionPoint(
+      uint64_t address,
+      absl::AnyInvocable<void(uint64_t, int)> action) override;
+  absl::Status ClearActionPoint(uint64_t address, int id) override;
+  absl::Status EnableAction(uint64_t address, int id) override;
+  absl::Status DisableAction(uint64_t address, int id) override;
+  // Watch points.
+  absl::Status SetDataWatchpoint(uint64_t address, size_t length,
+                                 AccessType access_type) override;
+  absl::Status ClearDataWatchpoint(uint64_t address,
+                                   AccessType access_type) override;
+  void SetBreakOnControlFlowChange(bool value) override;
+  absl::StatusOr<Instruction *> GetInstruction(uint64_t address) override;
+  absl::StatusOr<std::string> GetDisassembly(uint64_t address) override;
+
+  // Called when a halt is requested.
+  void RequestHalt(HaltReason halt_reason, const Instruction *inst);
+  void RequestHalt(HaltReasonValueType halt_reason, const Instruction *inst);
+
+  // Resize branch trace.
+  absl::Status ResizeBranchTrace(size_t size);
+
+  // Enable/disable the registered statistics counters.
+  void EnableStatistics();
+  void DisableStatistics();
+
+  // Accessors.
+  CheriotState *state() const { return state_; }
+  util::TaggedMemoryInterface *data_memory() const { return data_memory_; }
+  util::MemoryInterface *inst_memory() const { return inst_memory_; }
+  // The following are not const as callers may need to call non-const methods
+  // of the counter.
+  generic::SimpleCounter<uint64_t> *counter_num_instructions() {
+    return &counter_num_instructions_;
+  }
+  generic::SimpleCounter<uint64_t> *counter_num_cycles() {
+    return &counter_num_cycles_;
+  }
+  generic::SimpleCounter<uint64_t> *counter_pc() { return &counter_pc_; }
+  // Memory watchers used for data watch points.
+  util::TaggedMemoryWatcher *tagged_watcher() { return tagged_watcher_; }
+  util::MemoryWatcher *atomic_watcher() { return atomic_watcher_; }
+
+  const std::string &halt_string() const { return halt_string_; }
+  void set_halt_string(std::string halt_string) { halt_string_ = halt_string; }
+
+ private:
+  // Initialize the top.
+  void Initialize();
+  // Execute instruction. Returns true if the instruction was executed (or
+  // an exception was triggered).
+  bool ExecuteInstruction(Instruction *inst);
+  // Helper method to step past a breakpoint.
+  absl::Status StepPastBreakpoint();
+  // Set the pc value.
+  void SetPc(uint64_t value);
+  // Branch tracing.
+  void AddToBranchTrace(uint64_t from, uint64_t to);
+  // The DB factory is used to manage data buffers for memory read/writes.
+  generic::DataBufferFactory db_factory_;
+  // Current status and last halt reasons.
+  RunStatus run_status_ = RunStatus::kHalted;
+  HaltReasonValueType halt_reason_ = *HaltReason::kNone;
+  // Halting flag. This is set to true when execution must halt.
+  bool halted_ = false;
+  absl::Notification *run_halted_ = nullptr;
+  // The local CherIoT state.
+  CheriotState *state_;
+  RiscVCheriotFPState *fp_state_;
+  // Flag that indicates an instruction needs to be stepped over.
+  bool need_to_step_over_ = false;
+  // Action point manager.
+  riscv::RiscVActionPointManager *rv_action_point_manager_ = nullptr;
+  // Breakpoint manager.
+  riscv::RiscVBreakpointManager *rv_bp_manager_ = nullptr;
+  // Textual description of halt reason.
+  std::string halt_string_;
+  // The pc register instance.
+  CheriotRegister *pcc_;
+  // RiscV32 decoder instance.
+  generic::DecoderInterface *cheriot_decoder_ = nullptr;
+  // Decode cache, memory and memory watcher.
+  generic::DecodeCache *cheriot_decode_cache_ = nullptr;
+  util::MemoryInterface *inst_memory_ = nullptr;
+  util::TaggedMemoryInterface *data_memory_ = nullptr;
+  util::MemoryInterface *atomic_memory_if_ = nullptr;
+  util::AtomicMemoryOpInterface *atomic_memory_ = nullptr;
+  bool owns_memory_ = false;
+  util::TaggedMemoryWatcher *tagged_watcher_ = nullptr;
+  util::MemoryWatcher *atomic_watcher_ = nullptr;
+  // Branch trace info - uses a circular buffer. The size is defined by the
+  // constant kBranchTraceSize in the .cc file.
+  BranchTraceEntry *branch_trace_;
+  // Data buffer used to hold the branch trace info. This is used so that it
+  // can be returned to the debug command shell using the GetRegisterDataBuffer
+  // call.
+  DataBuffer *branch_trace_db_ = nullptr;
+  // Points to the most recently written entry in the circular buffer.
+  int branch_trace_head_ = 0;
+  int branch_trace_mask_ = kBranchTraceSize - 1;
+  int branch_trace_size_ = kBranchTraceSize;
+  // Counter for the number of instructions simulated.
+  generic::SimpleCounter<uint64_t>
+      counter_opcode_[*isa32::OpcodeEnum::kPastMaxValue];
+  generic::SimpleCounter<uint64_t> counter_num_instructions_;
+  generic::SimpleCounter<uint64_t> counter_num_cycles_;
+  // Counter used for profiling by connecting it to a profiler. This allows
+  // the pc to be written to the counter, and the profiling can be enabled/
+  // disabled with the other counters.
+  generic::SimpleCounter<uint64_t> counter_pc_;
+  absl::flat_hash_map<uint32_t, std::string> register_id_map_;
+  // RegEx for parsing capability register component name.
+  LazyRE2 cap_reg_re_;
+  // Flag for breaking on a control flow change.
+  bool break_on_control_flow_change_ = false;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__CHERIOT_TOP_H_
diff --git a/cheriot/debug_command_shell.cc b/cheriot/debug_command_shell.cc
new file mode 100644
index 0000000..3b24c1d
--- /dev/null
+++ b/cheriot/debug_command_shell.cc
@@ -0,0 +1,1663 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/debug_command_shell.h"
+
+#include <cstdint>
+#include <cstring>
+#include <fstream>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "cheriot/cheriot_debug_interface.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_top.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "cheriot/riscv_cheriot_register_aliases.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "re2/re2.h"
+#include "riscv//stoull_wrapper.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
+
+using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
+
+// The constructor initializes all the regular expressions and the help string.
+DebugCommandShell::DebugCommandShell()
+    : quit_re_{R"(\s*quit\s*)"},
+      core_re_{R"(\s*core\s+(\d+)\s*)"},
+      run_re_{R"(\s*(?:run|c)\s*)"},
+      run_free_re_{R"(\s*run\s+free\s*)"},
+      wait_re_{R"(\s*wait\s*)"},
+      step_1_re_{R"(\s*step\s*)"},
+      step_n_re_{R"(\s*step\s+(\d+)\s*)"},
+      halt_re_{R"(\s*halt\s*)"},
+      next_re_{R"(\s*next\s*)"},
+      read_reg_re_{R"(\s*reg\s+get\s+(\$?(\w|\.)+)(\s+[foduxX]\d+)?\s*)"},
+      read_reg2_re_{R"(\s*reg\s+(\$?(\w|\.)+)(\s+[foduxX]\d+)?\s*)"},
+      write_reg_re_{R"(\s*reg\s+set\s+(\$?\w+)\s+(\w+)\s*)"},
+      rd_vreg_re_{R"(\s*vreg(?:\s+get)?\s+(\$?\w+)(?:(?:\s*\:(\d+))?)"
+                  R"((?:\s+(?:([oduxX])(8|16|32|64))?)?)?\s*)"},
+      read_mem_re_{R"(\s*mem\s+get\s+(\w+)(?:\s+([foduxX]\d+)?)?\s*)"},
+      read_mem2_re_{R"(\s*mem\s+(\w+)(?:\s+([foduxX]\d+)?)?\s*)"},
+      write_mem_re_{R"(\s*mem\s+set\s+(\w+)\s+([oduxX]\d+)?\s+(\w+)\s*)"},
+      set_break_re_{R"(\s*break\s+set\s+(\$?\w+)\s*)"},
+      set_break2_re_{R"(\s*break\s+(\$?\w+)\s*)"},
+      set_break_n_re_{R"(\s*break\s+(set\s+)?\#(\d+)\s*)"},
+      list_break_re_{R"(\s*break\s*)"},
+      clear_break_n_re_{R"(\s*break\s+clear\s+\#(\d+)\s*)"},
+      clear_break_re_{R"(\s*break\s+clear\s+(\$?\w+)\s*)"},
+      clear_all_break_re_{R"(\s*break\s+clear-all\s*)"},
+      set_watch_re_{R"(\s*watch\s+set\s+(\w+)\s+(\w+)(\s+r|\s+w|\s+rw)?\s*)"},
+      set_watch2_re_{R"(\s*watch\s+(\w+)\s+(\w+)(\s+r|\s+w|\s+rw)?\s*)"},
+      set_watch_n_re_{R"(\s*watch\s+(set\s+)?\#(\d+)\s*)"},
+      list_watch_re_{R"(\s*watch\s*)"},
+      clear_watch_re_{R"(\s*watch\s+clear\s+(\w+)(\s+r|\s+w|\s+rw)?\s*)"},
+      clear_watch_n_re_{R"(\s*watch\s+clear\s+\#(\d+)\s*)"},
+      clear_all_watch_re_{R"(\s*watch\s+clear-all\s*)"},
+      list_action_re_{R"(\s*action\*)"},
+      enable_action_n_re_{R"(\s*action\s+enable\s+\*(\d+)\s*)"},
+      disable_action_n_re_{R"(\s*action\s+disable\s+\*(\d+)\s*)"},
+      clear_action_n_re_{R"(\s*action\s+clear\s+\*(\d+)\s*)"},
+      clear_all_action_re_{R"(\s*action\s+clear-all\s*)"},
+      branch_trace_re_{R"(\s*branch-trace\s*)"},
+      exec_re_{R"(\s*exec\s+(.+)\s*)"},
+      empty_re_{R"(\s*(?:\#.*)?)"},
+      help_re_{R"(\s*help\s*)"} {
+  help_message_ =
+      R"raw(  quit                             - exit command shell.
+  core [N]                         - direct subsequent commands to core N
+                                     (default: 0).
+  run                              - run program from current pcc until a
+                                     breakpoint or exit. Wait until halted.
+  run free                         - run program in background from current pcc
+                                     until breakpoint or exit.
+  wait                             - wait for any free run to complete.
+  step [N]                         - step [N] instructions (default: 1).
+  next                             - step 1 instruction (stepping over calls).
+  halt                             - halt a running program.
+  reg get NAME [FORMAT]            - get the value or register NAME.
+  reg NAME [FORMAT]                - get the value of register NAME.
+  reg set NAME VALUE               - set register NAME to VALUE.
+  reg set NAME SYMBOL              - set register NAME to value of SYMBOL.
+  mem get VALUE [FORMAT]           - get memory from location VALUE according to
+                                     format. The format is a letter (o, d, u, x,
+                                     or X) followed by width (8, 16, 32, 64).
+                                     The default format is x32.
+  mem get SYMBOL [FORMAT]          - get memory from location SYMBOL and format
+                                     according to FORMAT (see above).
+  mem SYMBOL [FORMAT]              - get memory from location SYMBOL and format
+                                     according to FORMAT (see above).
+  mem set VALUE [FORMAT] VALUE     - set memory at location VALUE(1) to VALUE(2)
+                                     according to FORMAT. Default format is x32.
+  mem set SYMBOL [FORMAT] VALUE    - set memory at location SYMBOL to VALUE
+                                     according to FORMAT. Default format is x32.
+  break [set] VALUE                - set breakpoint at address VALUE.
+  break [set] SYMBOL               - set breakpoint at value of SYMBOL.
+  break set #<N>                   - reactivate breakpoint index N.
+  break #<N>                       - reactivate breakpoint index N.
+  break clear VALUE                - clear breakpoint at address VALUE.
+  break clear SYMBOL               - clear breakpoint at value of SYMBOL.
+  break clear #<N>                 - clear breakpoint index N.
+  break clear-all                  - remove all breakpoints.
+  break                            - list breakpoints.
+  watch [set] VALUE len [r|w|rw]   - set watchpoint at value (read, write, or
+                                     readwrite) - default is write.
+  watch [set] SYMBOL len [r|w|rw]  - set watchpoint at value (read, write, or
+                                     readwrite) - default is write.
+  watch set #<N>                   - reactivate watchpoint index N.
+  watch clear VALUE [r|w|rw]       - clear watchpoint at value (read, write, or
+                                     readwrite) - default is write.
+  watch clear SYMBOL [r|w|rw]      - clear watchpoint at symbol (read, write or
+                                     readwrite) - default is write.
+  watch clear #<N>                 - clear watchpoint index N.
+  watch clear-all                  - remove all watchpoints.
+  watch                            - list watchpoints.
+  action enable #<N>               - enable action point with index N.
+  action disable #<N>              - disable action point with index N.
+  action clear #<N>                - clear action point with index N.
+  action clear-all                 - clear all action points.
+  action                           - list action points.
+  branch-trace                     - list the control flow change (includes
+                                     interrupts) w/out repetitions due to loops.
+  exec    NAME                     - load commands from file 'NAME' and execute
+                                     each line as a command. Lines starting with
+                                     a '#' are treated as comments.
+  help                             - display this message.
+
+  Special register names:
+  $all                             - core set of registers (e.g., reg $all).
+)raw";
+  // Insert known capability registers
+  for (int i = 0; i < 16; i++) {
+    reg_vector_.push_back(kCRegisterAliases[i]);
+    capability_registers_.insert(absl::StrCat("c", i));
+    capability_registers_.insert(kCRegisterAliases[i]);
+  }
+  capability_registers_.insert("pcc");
+  capability_registers_.insert("mtcc");
+  capability_registers_.insert("mtdc");
+  capability_registers_.insert("mscratchc");
+  capability_registers_.insert("mepcc");
+  reg_vector_.push_back("pcc");
+  reg_vector_.push_back("mepcc");
+  reg_vector_.push_back("mtcc");
+  reg_vector_.push_back("mtdc");
+  reg_vector_.push_back("mscratchc");
+}
+
+void DebugCommandShell::AddCore(const CoreAccess &core_access) {
+  core_access_.push_back(core_access);
+  core_action_point_id_.push_back(0);
+  core_action_point_info_.emplace_back();
+}
+
+void DebugCommandShell::AddCores(const std::vector<CoreAccess> &core_access) {
+  for (const auto &core_access : core_access) {
+    AddCore(core_access);
+  }
+}
+
+// NOLINTBEGIN(readability/fn_size)
+void DebugCommandShell::Run(std::istream &is, std::ostream &os) {
+  // TODO(torerik): refactor this function.
+  // Assumes the max linesize is 512.
+  command_streams_.push_back(&is);
+  constexpr int kLineSize = 512;
+  char line[kLineSize];
+  std::string previous_line;
+  current_core_ = 0;
+  absl::string_view line_view;
+  bool halt_reason = false;
+  while (true) {
+    // Prompt and read in the next command.
+    auto pcc_result =
+        core_access_[current_core_].debug_interface->ReadRegister("pcc");
+    std::string prompt;
+    if (halt_reason) {
+      halt_reason = false;
+      auto halt_reason =
+          core_access_[current_core_].debug_interface->GetLastHaltReason();
+      if (halt_reason.ok()) {
+        switch (halt_reason.value()) {
+          case *HaltReason::kSoftwareBreakpoint:
+            absl::StrAppend(&prompt, "Stopped at software breakpoint\n");
+            break;
+          case *HaltReason::kUserRequest:
+            absl::StrAppend(&prompt, "Stopped at user request\n");
+            break;
+          case *HaltReason::kDataWatchPoint:
+            absl::StrAppend(&prompt, "Stopped at data watchpoint\n");
+            break;
+          case *HaltReason::kProgramDone:
+            absl::StrAppend(&prompt, "Program done\n");
+            break;
+          default:
+            if ((halt_reason.value() >= *HaltReason::kUserSpecifiedMin) &&
+                (halt_reason.value() <= *HaltReason::kUserSpecifiedMax)) {
+              absl::StrAppend(&prompt, "Stopped for custom halt reason\n");
+            }
+            break;
+        }
+      }
+    }
+    if (pcc_result.ok()) {
+      if (core_access_[current_core_].loader != nullptr) {
+        auto symbol_result =
+            core_access_[current_core_].loader->GetFcnSymbolName(
+                pcc_result.value());
+        if (symbol_result.ok()) {
+          absl::StrAppend(&prompt, symbol_result.value(), ":\n");
+        }
+      }
+      absl::StrAppend(&prompt,
+                      absl::Hex(pcc_result.value(), absl::PadSpec::kZeroPad8));
+      auto disasm_result =
+          core_access_[current_core_].debug_interface->GetDisassembly(
+              pcc_result.value());
+      if (disasm_result.ok()) {
+        absl::StrAppend(&prompt, "   ", disasm_result.value());
+      }
+      absl::StrAppend(&prompt, "\n");
+    }
+    absl::StrAppend(&prompt, "[", current_core_, "] > ");
+    while (!command_streams_.empty()) {
+      auto &current_is = *command_streams_.back();
+      // Ignore comments or empty lines.
+      bool is_file = command_streams_.size() > 1;
+      // Read a command from the input stream. If it's from a file, then ignore
+      // empty lines and comments.
+      do {
+        if (command_streams_.size() == 1) os << prompt;
+        current_is.getline(line, kLineSize);
+      } while ((is_file && RE2::FullMatch(line, *empty_re_)) &&
+               !current_is.bad() && !current_is.eof());
+
+      // If the current is at eof or gone bad, pop the stream and try the next.
+      if (current_is.bad() || current_is.eof()) {
+        // If it's not the only stream, delete the stream since it was allocated
+        // for an exec command.
+        if (is_file) {
+          delete command_streams_.back();
+        }
+        command_streams_.pop_back();
+        previous_line = previous_commands_.back();
+        previous_commands_.pop_back();
+        continue;
+      }
+      // We have a valid command.
+      break;
+    }
+
+    if (command_streams_.empty()) {
+      os << "Error: input end of file or bad stream state\n" << std::endl;
+      os.flush();
+      return;
+    }
+
+    if (line[0] != '\0') {
+      previous_line = line;
+    }
+    line_view = absl::string_view(previous_line);
+
+    // Start matching commands.
+
+    // First try any added custom commands.
+    bool executed = false;
+    for (auto &fcn : command_functions_) {
+      std::string output;
+      executed = fcn(line_view, core_access_[current_core_], output);
+      if (executed) {
+        os << output << std::endl;
+        break;
+      }
+    }
+    if (executed) continue;
+
+    // quit
+    if (RE2::FullMatch(line_view, *quit_re_)) return;
+
+    // core N
+    if (int new_core;
+        RE2::FullMatch(line_view, *core_re_, RE2::CRadix(&new_core))) {
+      if (new_core >= core_access_.size()) {
+        os << absl::StrCat("Error: core number must be less than ",
+                           core_access_.size())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      current_core_ = new_core;
+      continue;
+    }
+
+    // run
+    if (RE2::FullMatch(line_view, *run_re_)) {
+      auto run_result = core_access_[current_core_].debug_interface->Run();
+      if (!run_result.ok()) {
+        os << "Error: " << run_result.message() << std::endl;
+        os.flush();
+      }
+      auto wait_result = core_access_[current_core_].debug_interface->Wait();
+      if (!wait_result.ok()) {
+        os << "Error: " << wait_result.message() << std::endl;
+        os.flush();
+      }
+      halt_reason = true;
+      continue;
+    }
+
+    // run free
+    if (RE2::FullMatch(line_view, *run_free_re_)) {
+      auto result = core_access_[current_core_].debug_interface->Run();
+      if (!result.ok()) {
+        os << "Error: " << result.message() << std::endl;
+        os.flush();
+      }
+      halt_reason = true;
+      continue;
+    }
+
+    // wait
+    if (RE2::FullMatch(line_view, *wait_re_)) {
+      auto result = core_access_[current_core_].debug_interface->Wait();
+      if (!result.ok()) {
+        os << "Error: " << result.message() << std::endl;
+        os.flush();
+      }
+      continue;
+    }
+
+    // step
+    if (RE2::FullMatch(line_view, *step_1_re_)) {
+      auto result = core_access_[current_core_].debug_interface->Step(1);
+      if (!result.status().ok()) {
+        os << "Error: " << result.status().message() << std::endl;
+        os.flush();
+        continue;
+      }
+      if (result.value() != 1) {
+        os << result.value() << " instructions executed" << std::endl;
+        os.flush();
+      }
+      continue;
+    }
+
+    // step N
+    if (int count;
+        RE2::FullMatch(line_view, *step_n_re_, RE2::CRadix(&count))) {
+      auto result = core_access_[current_core_].debug_interface->Step(count);
+      if (!result.status().ok()) {
+        os << "Error: " << result.status().message() << std::endl;
+        os.flush();
+        continue;
+      }
+      if (result.value() != count) {
+        os << result.value() << " instructions executed" << std::endl;
+        os.flush();
+      }
+      continue;
+    }
+
+    // halt
+    if (RE2::FullMatch(line_view, *halt_re_)) {
+      auto result = core_access_[current_core_].debug_interface->Halt();
+      if (!result.ok()) {
+        os << "Error: " << result.message() << std::endl;
+        os.flush();
+      }
+      halt_reason = true;
+      continue;
+    }
+
+    // reg read NAME
+    if (std::string name, format;
+        RE2::FullMatch(line_view, *read_reg_re_, &name, &format)) {
+      // If it is the simulator 'register' $all, print all registers.
+      if (name == "$all") {
+        os << FormatAllRegisters(current_core_);
+        continue;
+      }
+      os << FormatRegister(current_core_, name) << std::endl;
+      os.flush();
+      continue;
+    }
+
+    // reg write NAME = VALUE
+    if (std::string name, value;
+        RE2::FullMatch(line_view, *write_reg_re_, &name, &value)) {
+      auto result = GetValueFromString(current_core_, value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", value, "' ", result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      auto write_result =
+          core_access_[current_core_].debug_interface->WriteRegister(
+              name, result.value());
+      if (!write_result.ok()) {
+        os << "Error: " << write_result.message() << std::endl;
+        os.flush();
+      }
+      continue;
+    }
+
+    // mem get VALUE | SYMBOL [FORMAT]
+
+    if (std::string str_value, format;
+        RE2::FullMatch(line_view, *read_mem_re_, &str_value, &format)) {
+      os << ReadMemory(current_core_, str_value, format) << std::endl;
+      continue;
+    }
+
+    if (std::string str_value1, format, str_value2; RE2::FullMatch(
+            line_view, *write_mem_re_, &str_value1, &format, &str_value2)) {
+      os << WriteMemory(current_core_, str_value1, format, str_value2)
+         << std::endl;
+      continue;
+    }
+
+    // break set VALUE | SYMBOL
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *set_break_re_, &str_value)) {
+      if (str_value == "$branch") {
+        auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+            core_access_[current_core_].debug_interface);
+        cheriot_interface->SetBreakOnControlFlowChange(true);
+        continue;
+      }
+      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", str_value, "' ",
+                           result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      auto cmd_result =
+          core_access_[current_core_].debug_interface->SetSwBreakpoint(
+              result.value());
+      if (!cmd_result.ok()) {
+        os << "Error:  " << cmd_result.message() << std::endl;
+        os.flush();
+        continue;
+      }
+      core_access_[current_core_]
+          .breakpoint_map[core_access_[current_core_].breakpoint_index++] =
+          result.value();
+      os << absl::StrCat("Breakpoint set at 0x",
+                         absl::Hex(result.value(), absl::PadSpec::kZeroPad8))
+         << std::endl;
+      continue;
+    }
+
+    // break set #<N>
+    if (std::string str_value, num_value;
+        RE2::FullMatch(line_view, *set_break_n_re_, &str_value, &num_value)) {
+      int index;
+      if (!absl::SimpleAtoi(num_value, &index)) {
+        os << absl::StrCat("Error: cannot parse '", str_value,
+                           "' as a breakpoint index\n");
+        continue;
+      }
+      auto iter = core_access_[current_core_].breakpoint_map.find(index);
+      if (iter == core_access_[current_core_].breakpoint_map.end()) {
+        os << absl::StrCat("Error: no breakpoint with index ", index, "\n");
+        continue;
+      }
+      uint64_t address = iter->second;
+      if (!core_access_[current_core_].debug_interface->HasBreakpoint(
+              address)) {
+        auto status =
+            core_access_[current_core_].debug_interface->SetSwBreakpoint(
+                address);
+        if (!status.ok()) {
+          os << absl::StrCat("Error: ", status.message(), "\n");
+        }
+      } else {
+        os << "Breakpoint already active\n";
+      }
+      continue;
+    }
+
+    // break clear #<N>
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *clear_break_n_re_, &str_value)) {
+      int index;
+      if (!absl::SimpleAtoi(str_value, &index)) {
+        os << absl::StrCat("Error: cannot parse '", str_value,
+                           "' as a breakpoint index\n");
+        continue;
+      }
+      auto iter = core_access_[current_core_].breakpoint_map.find(index);
+      if (iter == core_access_[current_core_].breakpoint_map.end()) {
+        os << absl::StrCat("Error: no breakpoint with index ", index, "\n");
+        continue;
+      }
+      uint64_t address = iter->second;
+      if (core_access_[current_core_].debug_interface->HasBreakpoint(address)) {
+        auto status =
+            core_access_[current_core_].debug_interface->ClearSwBreakpoint(
+                address);
+        if (!status.ok()) {
+          os << absl::StrCat("Error: ", status.message(), "\n");
+        }
+      }
+      continue;
+    }
+
+    // break clear-all
+    if (RE2::FullMatch(line_view, *clear_all_break_re_)) {
+      auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+          core_access_[current_core_].debug_interface);
+      cheriot_interface->SetBreakOnControlFlowChange(false);
+      auto result =
+          core_access_[current_core_].debug_interface->ClearAllSwBreakpoints();
+      if (!result.ok()) {
+        os << absl::StrCat("Error: ", result.message()) << std::endl;
+        os.flush();
+        continue;
+      }
+      os << "All breakpoints removed" << std::endl;
+      continue;
+    }
+
+    // break clear VALUE | SYMBOL
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *clear_break_re_, &str_value)) {
+      if (str_value == "$branch") {
+        auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+            core_access_[current_core_].debug_interface);
+        cheriot_interface->SetBreakOnControlFlowChange(false);
+        continue;
+      }
+      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", str_value, "' ",
+                           result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      auto cmd_result =
+          core_access_[current_core_].debug_interface->ClearSwBreakpoint(
+              result.value());
+      if (!cmd_result.ok()) {
+        os << "Error:  " << cmd_result.message() << std::endl;
+        os.flush();
+        continue;
+      }
+      os << absl::StrCat("Breakpoint removed from 0x",
+                         absl::Hex(result.value(), absl::PadSpec::kZeroPad8))
+         << std::endl;
+      continue;
+    }
+
+    // break list
+    if (RE2::FullMatch(line_view, *list_break_re_)) {
+      std::string bp_list;
+      for (auto [index, address] : core_access_[current_core_].breakpoint_map) {
+        bool active =
+            core_access_[current_core_].debug_interface->HasBreakpoint(address);
+        std::string symbol;
+        if (core_access_[current_core_].loader != nullptr) {
+          auto res =
+              core_access_[current_core_].loader->GetFcnSymbolName(address);
+          if (res.ok()) symbol = std::move(res.value());
+        }
+        absl::StrAppend(&bp_list,
+                        absl::StrFormat("  %3d   %-8s   0x%08x   %s\n", index,
+                                        active ? "active" : "inactive", address,
+                                        symbol.empty() ? "-" : symbol));
+      }
+      os << absl::StrCat("Breakpoints:\n", bp_list, "\n");
+      continue;
+    }
+
+    // help
+    if (RE2::FullMatch(line_view, *help_re_)) {
+      for (auto const &usage : command_usage_) {
+        os << usage << std::endl;
+      }
+      os << help_message_;
+      os.flush();
+      continue;
+    }
+
+    // reg NAME
+    if (std::string name, format;
+        RE2::FullMatch(line_view, *read_reg2_re_, &name, &format)) {
+      if (name == "$all") {
+        os << FormatAllRegisters(current_core_) << std::endl;
+        continue;
+      }
+      os << FormatRegister(current_core_, name) << std::endl;
+      os.flush();
+      continue;
+    }
+
+    // break SYMBOL | VALUE
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *set_break2_re_, &str_value)) {
+      if (str_value == "$branch") {
+        auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+            core_access_[current_core_].debug_interface);
+        cheriot_interface->SetBreakOnControlFlowChange(true);
+        continue;
+      }
+      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", str_value, "' ",
+                           result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      auto cmd_result =
+          core_access_[current_core_].debug_interface->SetSwBreakpoint(
+              result.value());
+      if (!cmd_result.ok()) {
+        os << "Error:  " << cmd_result.message() << std::endl;
+        os.flush();
+        continue;
+      }
+      core_access_[current_core_]
+          .breakpoint_map[core_access_[current_core_].breakpoint_index++] =
+          result.value();
+      os << absl::StrCat("Breakpoint set at 0x",
+                         absl::Hex(result.value(), absl::PadSpec::kZeroPad8))
+         << std::endl;
+      continue;
+    }
+
+    // watch set SYMBOL | VALUE  <length> [r|w|rw]
+    if (std::string str_value, length_value, rw_value; RE2::FullMatch(
+            line_view, *set_watch_re_, &str_value, &length_value, &rw_value)) {
+      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", str_value, "' ",
+                           result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      if (!rw_value.empty()) {
+        rw_value = rw_value.substr(rw_value.find_first_not_of(' '));
+      }
+      AccessType access_type = AccessType::kStore;
+      if (rw_value == "r") {
+        access_type = AccessType::kLoad;
+      } else if (rw_value == "rw") {
+        access_type = AccessType::kStore;
+      }
+
+      uint64_t address = result.value();
+      result = GetValueFromString(current_core_, length_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: cannot parse '", length_value,
+                           "' as a length\n");
+        os.flush();
+        continue;
+      }
+      size_t length = result.value();
+      auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+          core_access_[current_core_].debug_interface);
+      auto cmd_result =
+          cheriot_interface->SetDataWatchpoint(address, length, access_type);
+      if (!cmd_result.ok()) {
+        os << "Error:  " << cmd_result.message() << std::endl;
+        os.flush();
+        continue;
+      }
+      core_access_[current_core_]
+          .watchpoint_map[core_access_[current_core_].watchpoint_index++] = {
+          address, length, access_type, /*active=*/true};
+      os << absl::StrCat("Watchpoint set at 0x",
+                         absl::Hex(address, absl::PadSpec::kZeroPad8))
+         << std::endl;
+      continue;
+    }
+
+    // watch set #<N>
+    if (std::string str_value, num_value;
+        RE2::FullMatch(line_view, *set_watch_n_re_, &str_value, &num_value)) {
+      int index;
+      if (!absl::SimpleAtoi(num_value, &index)) {
+        os << absl::StrCat("Error: cannot parse '", str_value,
+                           "' as a watchpoint index\n");
+        continue;
+      }
+      auto iter = core_access_[current_core_].watchpoint_map.find(index);
+      if (iter == core_access_[current_core_].watchpoint_map.end()) {
+        os << absl::StrCat("Error: no watchpoint with index ", index, "\n");
+        continue;
+      }
+      if (iter->second.active) {
+        os << "Watchpoint already active\n";
+        continue;
+      }
+      uint64_t address = iter->second.address;
+      size_t length = iter->second.length;
+      AccessType access_type = iter->second.access_type;
+      auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+          core_access_[current_core_].debug_interface);
+      auto status =
+          cheriot_interface->SetDataWatchpoint(address, length, access_type);
+      if (!status.ok()) {
+        os << absl::StrCat("Error: ", status.message(), "\n");
+        continue;
+      }
+      iter->second.active = true;
+      continue;
+    }
+
+    // watch clear #<N>
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *clear_watch_n_re_, &str_value)) {
+      int index;
+      if (!absl::SimpleAtoi(str_value, &index)) {
+        os << absl::StrCat("Error: cannot parse '", str_value,
+                           "' as a watchpoint index\n");
+        continue;
+      }
+      auto iter = core_access_[current_core_].watchpoint_map.find(index);
+      if (iter == core_access_[current_core_].watchpoint_map.end()) {
+        os << absl::StrCat("Error: no watchpoint with index ", index, "\n");
+        continue;
+      }
+      if (!iter->second.active) continue;
+      uint64_t address = iter->second.address;
+      auto access_type = iter->second.access_type;
+      auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+          core_access_[current_core_].debug_interface);
+      auto status =
+          cheriot_interface->ClearDataWatchpoint(address, access_type);
+      if (!status.ok()) {
+        os << absl::StrCat("Error: ", status.message(), "\n");
+        continue;
+      }
+      iter->second.active = false;
+      continue;
+    }
+
+    // watch clear-all
+    if (RE2::FullMatch(line_view, *clear_all_watch_re_)) {
+      for (auto &[index, info] : core_access_[current_core_].watchpoint_map) {
+        if (!info.active) continue;
+
+        uint64_t address = info.address;
+        auto access_type = info.access_type;
+        auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+            core_access_[current_core_].debug_interface);
+        auto status =
+            cheriot_interface->ClearDataWatchpoint(address, access_type);
+        if (!status.ok()) {
+          os << absl::StrCat("Error: ", status.message(), "\n");
+          continue;
+        }
+        info.active = false;
+      }
+      os << "All watchpoints removed" << std::endl;
+      continue;
+    }
+
+    // watch clear VALUE | SYMBOL [r|w|rw]
+    if (std::string str_value, rw_value;
+        RE2::FullMatch(line_view, *clear_watch_re_, &str_value, &rw_value)) {
+      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", str_value, "' ",
+                           result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      if (!rw_value.empty()) {
+        rw_value = rw_value.substr(rw_value.find_first_not_of(' '));
+      }
+      auto access_type = AccessType::kStore;
+      if (rw_value == "r") {
+        access_type = AccessType::kLoad;
+      } else if (rw_value == "rw") {
+        access_type = AccessType::kLoadStore;
+      }
+      bool done = false;
+      for (auto &[index, info] : core_access_[current_core_].watchpoint_map) {
+        if ((info.address == result.value()) &&
+            (info.access_type == access_type)) {
+          auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+              core_access_[current_core_].debug_interface);
+          auto cmd_result = cheriot_interface->ClearDataWatchpoint(
+              result.value(), access_type);
+          if (!cmd_result.ok()) {
+            os << "Error:  " << cmd_result.message() << std::endl;
+            os.flush();
+            break;
+          }
+          info.active = false;
+          done = true;
+          break;
+        }
+      }
+      if (!done) {
+        continue;
+      }
+      os << absl::StrCat("Watchpoint removed from 0x",
+                         absl::Hex(result.value(), absl::PadSpec::kZeroPad8))
+         << std::endl;
+      continue;
+    }
+
+    // watch SYMBOL | VALUE [r|w|rw]
+    if (std::string str_value, length_value, rw_value; RE2::FullMatch(
+            line_view, *set_watch2_re_, &str_value, &length_value, &rw_value)) {
+      auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: '", str_value, "' ",
+                           result.status().message())
+           << std::endl;
+        os.flush();
+        continue;
+      }
+      AccessType access_type = AccessType::kStore;
+      if (!rw_value.empty()) {
+        rw_value = rw_value.substr(rw_value.find_first_not_of(' '));
+      }
+      if (rw_value == "r") {
+        access_type = AccessType::kLoad;
+      } else if (rw_value == "rw") {
+        access_type = AccessType::kLoadStore;
+      }
+      uint64_t address = result.value();
+      result = GetValueFromString(current_core_, length_value, /*radix=*/0);
+      if (!result.ok()) {
+        os << absl::StrCat("Error: cannot parse '", length_value,
+                           "' as a length\n");
+        os.flush();
+        continue;
+      }
+      size_t length = result.value();
+      auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+          core_access_[current_core_].debug_interface);
+      auto cmd_result =
+          cheriot_interface->SetDataWatchpoint(address, length, access_type);
+      if (!cmd_result.ok()) {
+        os << "Error:  " << cmd_result.message() << std::endl;
+        os.flush();
+        continue;
+      }
+      core_access_[current_core_]
+          .watchpoint_map[core_access_[current_core_].watchpoint_index++] = {
+          address, length, access_type, /*active=*/true};
+      os << absl::StrCat("Watchpoint set at 0x",
+                         absl::Hex(address, absl::PadSpec::kZeroPad8))
+         << std::endl;
+      continue;
+    }
+
+    // watch list
+    if (RE2::FullMatch(line_view, *list_watch_re_)) {
+      std::string bp_list;
+      for (auto const &[index, info] :
+           core_access_[current_core_].watchpoint_map) {
+        std::string symbol;
+        if (core_access_[current_core_].loader != nullptr) {
+          auto res = core_access_[current_core_].loader->GetFcnSymbolName(
+              info.address);
+          if (res.ok()) symbol = std::move(res.value());
+        }
+        std::string access_type;
+        switch (info.access_type) {
+          case AccessType::kStore:
+            access_type = "w";
+            break;
+          case AccessType::kLoad:
+            access_type = "r";
+            break;
+          case AccessType::kLoadStore:
+            access_type = "rw";
+        }
+        absl::StrAppend(
+            &bp_list,
+            absl::StrFormat("  %3d   %-8s   0x%08x   %3d   %2s   %s\n", index,
+                            info.active ? "active" : "inactive", info.address,
+                            info.length, access_type,
+                            symbol.empty() ? "-" : symbol));
+      }
+      os << absl::StrCat("Watchpoints:\n", bp_list, "\n");
+      continue;
+    }
+
+    // mem get VALUE | SYMBOL [FORMAT]
+    if (std::string str_value, format;
+        RE2::FullMatch(line_view, *read_mem2_re_, &str_value, &format)) {
+      os << ReadMemory(current_core_, str_value, format) << std::endl;
+      continue;
+    }
+
+    // Action points.
+    if (RE2::FullMatch(line_view, *list_action_re_)) {
+      os << ListActionPoints();
+      continue;
+    }
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *enable_action_n_re_, &str_value)) {
+      os << EnableActionPointN(str_value);
+      continue;
+    }
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *disable_action_n_re_, &str_value)) {
+      os << DisableActionPointN(str_value);
+      continue;
+    }
+    if (std::string str_value;
+        RE2::FullMatch(line_view, *clear_action_n_re_, &str_value)) {
+      os << ClearActionPointN(str_value);
+      continue;
+    }
+    if (RE2::FullMatch(line_view, *clear_all_action_re_)) {
+      os << ClearAllActionPoints();
+      continue;
+    }
+    // branch-trace.
+    // This prints out a list of the last 16 pairs of <from, to> control flow
+    // changes (including interrupts), with no repetitions for loops.
+    if (RE2::FullMatch(line_view, *branch_trace_re_)) {
+      // Get the index of the head of the queue.
+      auto head_result =
+          core_access_[current_core_].debug_interface->ReadRegister(
+              "$branch_trace_head");
+      if (!head_result.ok()) {
+        os << "Error: " << head_result.status().message() << "\n";
+        continue;
+      }
+      // Adjust by one, as the head points to the most recent valid entry.
+      auto head = head_result.value() + 1;
+      // Get the branch trace data buffer.
+      auto result =
+          core_access_[current_core_].debug_interface->GetRegisterDataBuffer(
+              "$branch_trace");
+      if (!result.ok()) {
+        os << "Error: " << result.status().message() << "\n";
+        continue;
+      }
+      auto *db = result.value();
+      // Check for null data buffer.
+      if (db == nullptr) {
+        os << "Error: register '$branch_trace' has no data buffer\n";
+        os.flush();
+        continue;
+      }
+      // Get a span for the branch trace.
+      auto trace_span = db->Get<BranchTraceEntry>();
+      auto size = trace_span.size();
+      os << absl::StrFormat("     %-8s      %-8s     %8s\n", "From", "To",
+                            "Count");
+      for (int i = 0; i < size; ++i) {
+        auto index = (head + i) % size;
+        auto [from, to, count] = trace_span[index];
+        // Ignore 0 -> 0 entries. Those are the initial values.
+        if (count == 0) continue;
+        os << absl::StrFormat("   0x%08x -> 0x%08x     %8u\n", from, to, count);
+      }
+      os.flush();
+      continue;
+    }
+
+    // Step (over function call).
+    if (RE2::FullMatch(line_view, *next_re_)) {
+      auto res = StepOverCall(current_core_, os);
+      if (!res.ok()) {
+        os << "Error: " << res.message() << "\n";
+      }
+      continue;
+    }
+
+    if (std::string file_name;
+        RE2::FullMatch(line_view, *exec_re_, &file_name)) {
+      auto *ifile = new std::ifstream(file_name);
+      if (!ifile->is_open() || !ifile->good()) {
+        os << "Error: unable to open '" << file_name << "'\n";
+        os.flush();
+        continue;
+      }
+      previous_commands_.push_back(previous_line);
+      command_streams_.push_back(ifile);
+      continue;
+    }
+
+    // At this point a valid command should have been matched, so assume an
+    // error.
+    os << absl::StrCat("Error: unrecognized command '", line, "'") << std::endl;
+    os.flush();
+  }
+}
+// NOLINTEND(readability/fn_size)
+
+void DebugCommandShell::AddCommand(absl::string_view usage,
+                                   CommandFunction command_function) {
+  command_usage_.emplace_back(usage);
+  command_functions_.emplace_back(std::move(command_function));
+}
+
+namespace {
+
+constexpr absl::string_view kHexFormatNone = "%x";
+constexpr absl::string_view kHexFormatCapNone = "%X";
+constexpr absl::string_view kHexFormatUint8 = "%02x";
+constexpr absl::string_view kHexFormatCapUint8 = "%02X";
+constexpr absl::string_view kHexFormatUint16 = "%04x";
+constexpr absl::string_view kHexFormatCapUint16 = "%04X";
+constexpr absl::string_view kHexFormatUint32 = "%08x";
+constexpr absl::string_view kHexFormatCapUint32 = "%08X";
+constexpr absl::string_view kHexFormatUint64 = "%016x";
+constexpr absl::string_view kHexFormatCapUint64 = "%016X";
+
+constexpr absl::string_view kHexFormat[] = {
+    kHexFormatNone, kHexFormatUint8,  kHexFormatUint16,
+    kHexFormatNone, kHexFormatUint32, kHexFormatNone,
+    kHexFormatNone, kHexFormatNone,   kHexFormatUint64};
+
+constexpr absl::string_view kHexFormatCap[] = {
+    kHexFormatCapNone, kHexFormatCapUint8,  kHexFormatCapUint16,
+    kHexFormatCapNone, kHexFormatCapUint32, kHexFormatCapNone,
+    kHexFormatCapNone, kHexFormatCapNone,   kHexFormatCapUint64};
+
+// Templated helper function to help format integer values of different widths.
+template <typename T>
+std::string FormatDbValue(generic::DataBuffer *db, absl::string_view format,
+                          int index) {
+  std::string output;
+  if (index < 0 || index >= db->size<T>()) return "Error: index out of range";
+  T value = db->Get<T>(index);
+  switch (format[0]) {
+    case 'd':
+      output += absl::StrFormat("%d", value);
+      break;
+    case 'o':
+      output += absl::StrFormat("%o", value);
+      break;
+    case 'u':
+      output += absl::StrFormat("%u", value);
+      break;
+    case 'x':
+      output += absl::StrFormat(kHexFormat[sizeof(T)], value);
+      break;
+    case 'X':
+      output += absl::StrFormat(kHexFormatCap[sizeof(T)], value);
+      break;
+    default:
+      output = absl::StrCat("Error: invalid '", format, "'");
+      break;
+  }
+  return output;
+}
+
+template <typename T>
+absl::Status WriteDbValue(absl::string_view str_value, absl::string_view format,
+                          int index, generic::DataBuffer *db) {
+  if (index < 0 || index >= db->size<T>())
+    return absl::OutOfRangeError("Error: index out of range");
+  if (format[0] == 'd') {
+    int64_t value;
+    if (!absl::SimpleAtoi(str_value, &value)) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Error: could not convert '", str_value, "' to number"));
+    }
+    db->Set<T>(index, static_cast<T>(value));
+    return absl::OkStatus();
+  }
+  if (format[0] == 'u') {
+    uint64_t value;
+    if (!absl::SimpleAtoi(str_value, &value)) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Error: could not convert '", str_value, "' to number"));
+    }
+    db->Set<T>(index, static_cast<T>(value));
+    return absl::OkStatus();
+  }
+  if (format[0] == 'x' || format[0] == 'X') {
+    uint64_t value;
+    if (!absl::SimpleHexAtoi(str_value, &value)) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Error: could not convert '", str_value, "' to number"));
+    }
+    db->Set<T>(index, static_cast<T>(value));
+    return absl::OkStatus();
+  }
+  return absl::InternalError(absl::StrCat("Unsupported format '", format, "'"));
+}
+
+}  // namespace
+
+std::string DebugCommandShell::FormatSingleDbValue(generic::DataBuffer *db,
+                                                   const std::string &format,
+                                                   int width, int index) const {
+  switch (width) {
+    case 8:
+      return FormatDbValue<uint8_t>(db, format, index);
+    case 16:
+      return FormatDbValue<uint16_t>(db, format, index);
+    case 32:
+      return FormatDbValue<uint32_t>(db, format, index);
+    case 64:
+      return FormatDbValue<uint64_t>(db, format, index);
+    default:
+      return absl::StrCat("Error: illegal width '", width, "'");
+  }
+}
+
+std::string DebugCommandShell::FormatAllDbValues(generic::DataBuffer *db,
+                                                 const std::string &format,
+                                                 int width) const {
+  std::string output;
+  std::string sep;
+  switch (width) {
+    case 8:
+      for (int i = 0; i < db->size<uint8_t>(); ++i) {
+        output += sep + FormatDbValue<uint8_t>(db, format, i);
+        sep = ":";
+      }
+      break;
+    case 16:
+      for (int i = 0; i < db->size<uint16_t>(); ++i) {
+        output += sep + FormatDbValue<uint16_t>(db, format, i);
+        sep = ":";
+      }
+      break;
+    case 32:
+      for (int i = 0; i < db->size<uint32_t>(); ++i) {
+        output += sep + FormatDbValue<uint32_t>(db, format, i);
+        sep = ":";
+      }
+      break;
+    case 64:
+      for (int i = 0; i < db->size<uint64_t>(); ++i) {
+        output += sep + FormatDbValue<uint64_t>(db, format, i);
+        sep = ":";
+      }
+      break;
+    default:
+      output = absl::StrCat("Error: illegal width '", width, "'");
+      break;
+  }
+  return output;
+}
+
+absl::Status DebugCommandShell::WriteSingleValueToDb(
+    absl::string_view str_value, generic::DataBuffer *db, std::string format,
+    int width, int index) const {
+  switch (width) {
+    case 8:
+      return WriteDbValue<uint8_t>(str_value, format, index, db);
+    case 16:
+      return WriteDbValue<uint16_t>(str_value, format, index, db);
+    case 32:
+      return WriteDbValue<uint32_t>(str_value, format, index, db);
+    case 64:
+      return WriteDbValue<uint64_t>(str_value, format, index, db);
+    default:
+      return absl::InternalError("Error");
+  }
+  return absl::OkStatus();
+}
+
+std::string DebugCommandShell::ReadMemory(int core,
+                                          const std::string &str_value,
+                                          absl::string_view format) {
+  int size = 0;
+  char format_char = 'x';
+  int bit_width = 32;
+
+  if (!format.empty()) {
+    // Check the format specification.
+    auto pos = format.find_first_not_of(' ');
+    format_char = format[pos];
+    auto status = absl::SimpleAtoi(format.substr(pos + 1), &bit_width);
+    if (!status) {
+      return absl::StrCat("Error '", format.substr(pos + 1),
+                          "': ", "unable to convert to int");
+    }
+    if ((bit_width != 8) && (bit_width != 16) && (bit_width != 32) &&
+        (bit_width != 64)) {
+      return absl::StrCat("Illegal bit width specification: ", bit_width);
+    }
+  }
+
+  // Get the address.
+  auto result = GetValueFromString(current_core_, str_value, /*radix=*/0);
+  if (!result.ok()) {
+    return absl::StrCat("Error: '", str_value, "' ", result.status().message());
+  }
+  auto address = result.value();
+
+  // Perform the memory access.
+  size = bit_width / 8;
+  if (size > kMemBufferSize) size = kMemBufferSize;
+  auto mem_result = core_access_[current_core_].debug_interface->ReadMemory(
+      address, mem_buffer_, size);
+  if (!mem_result.ok()) {
+    return absl::StrCat("Error: ", mem_result.status().message());
+  }
+  // Perform tag memory access.
+  // There is a tag per each 8 bytes of data. So compute the number of tags
+  // that need to be loaded, taking into account misalignment.
+  auto tag_address = address & ~0x7ULL;
+  int tag_size = (address + size - tag_address + 7) / 8;
+  auto *cheriot_interface = reinterpret_cast<CheriotDebugInterface *>(
+      core_access_[current_core_].debug_interface);
+  auto tag_result =
+      cheriot_interface->ReadTagMemory(tag_address, tag_buffer_, tag_size);
+  if (!tag_result.ok()) {
+    return absl::StrCat("Error: ", tag_result.status().message());
+  }
+  std::string tag_string;
+  std::string sep = "[";
+  for (int i = 0; i < tag_size; ++i) {
+    absl::StrAppend(&tag_string, sep, tag_buffer_[i]);
+    sep = ", ";
+  }
+  absl::StrAppend(&tag_string, "]");
+
+  // Get the result and format it.
+  void *void_buffer = mem_buffer_;
+  std::string output;
+  if ((format_char == 'f') && (bit_width >= 32)) {
+    switch (bit_width) {
+      case 32:
+        output = absl::StrCat(*reinterpret_cast<float *>(void_buffer));
+        break;
+      case 64:
+        output = absl::StrCat(*reinterpret_cast<double *>(void_buffer));
+        break;
+      default:
+        break;
+    }
+  } else if (format_char == 'd') {
+    switch (bit_width) {
+      case 8:
+        output = absl::StrCat(*static_cast<int8_t *>(void_buffer));
+        break;
+      case 16:
+        output = absl::StrCat(*static_cast<int16_t *>(void_buffer));
+        break;
+      case 32:
+        output = absl::StrCat(*static_cast<int32_t *>(void_buffer));
+        break;
+      case 64:
+        output = absl::StrCat(*static_cast<int64_t *>(void_buffer));
+        break;
+      default:
+        break;
+    }
+  } else {
+    uint64_t val = 0;
+    auto pad = absl::PadSpec::kNoPad;
+    switch (bit_width) {
+      case 8:
+        val = *static_cast<uint8_t *>(void_buffer);
+        pad = absl::PadSpec::kZeroPad2;
+        break;
+      case 16:
+        val = *static_cast<uint16_t *>(void_buffer);
+        pad = absl::PadSpec::kZeroPad4;
+        break;
+      case 32:
+        val = *static_cast<uint32_t *>(void_buffer);
+        pad = absl::PadSpec::kZeroPad8;
+        break;
+      case 64:
+        val = *static_cast<uint64_t *>(void_buffer);
+        pad = absl::PadSpec::kZeroPad16;
+        break;
+    }
+    std::string format_string;
+    if ((format_char == 'x') || (format_char == 'X')) {
+      output = absl::StrCat(absl::Hex(val, pad));
+    } else if (format_char == 'u') {
+      output = absl::StrCat(val);
+    } else {
+      output = absl::StrFormat("%o", val);
+    }
+  }
+  return absl::StrCat("[", absl::Hex(address, absl::PadSpec::kZeroPad8),
+                      "] = ", output, "  ", tag_string);
+}
+
+std::string DebugCommandShell::WriteMemory(int core,
+                                           const std::string &str_value1,
+                                           const std::string &format,
+                                           const std::string &str_value2) {
+  int size = 0;
+  char format_char = '\0';
+  int radix = 0;
+  int bit_width = 32;
+  if (!format.empty()) {
+    // Check the format specification.
+    auto pos = format.find_first_not_of(' ');
+    format_char = format[pos];
+    if ((format_char == 'x') || (format_char == 'X')) {
+      radix = 16;
+    } else if ((format_char == 'd') || (format_char == 'u')) {
+      radix = 10;
+    } else if (format_char == 'o') {
+      radix = 8;
+    }
+    auto status = riscv::internal::stoull(format.substr(pos + 1));
+    bit_width = 0;
+    if (!status.ok()) {
+      return absl::StrCat("Error '", format.substr(pos + 1),
+                          "': ", status.status().message());
+    }
+    bit_width = status.value();
+  }
+
+  // Determine the zero padding for hex number.
+  auto pad = absl::kNoPad;
+  if (bit_width == 8) {
+    pad = absl::kZeroPad2;
+  } else if (bit_width == 16) {
+    pad = absl::kZeroPad4;
+  } else if (bit_width == 32) {
+    pad = absl::kZeroPad8;
+  } else if (bit_width == 64) {
+    pad = absl::kZeroPad16;
+  } else {
+    return absl::StrCat("Illegal bit width specification: ", bit_width);
+  }
+
+  // Get the address.
+  auto result = GetValueFromString(current_core_, str_value1, /*radix=*/0);
+  if (!result.ok()) {
+    return absl::StrCat("Error: '", str_value1, "' ",
+                        result.status().message());
+  }
+  auto address = result.value();
+
+  // Get the value to be stored.
+  auto value_result = GetValueFromString(current_core_, str_value2, radix);
+  if (!value_result.ok()) {
+    return absl::StrCat("Error: '", str_value2, "' ",
+                        result.status().message());
+  }
+  int64_t mem_value = value_result.value();
+
+  // Perform the memory access.
+  size = bit_width / 8;
+  if (size > kMemBufferSize) size = kMemBufferSize;
+  std::memcpy(mem_buffer_, &mem_value, size);
+  auto mem_result = core_access_[current_core_].debug_interface->WriteMemory(
+      address, mem_buffer_, size);
+  if (!mem_result.ok()) {
+    return absl::StrCat("Error: ", mem_result.status().message());
+  }
+  return absl::StrCat("[", absl::Hex(address, absl::kZeroPad8),
+                      "] = ", absl::Hex(mem_value, pad));
+}
+
+absl::StatusOr<uint64_t> DebugCommandShell::GetValueFromString(
+    int core, const std::string &str_value, int radix) {
+  size_t index;
+  // Attempt to convert to a number.
+  auto convert_result = riscv::internal::stoull(str_value, &index, radix);
+  // If successful and the entire string was consumed, the number is good.
+  if (convert_result.ok() && (index >= str_value.size())) {
+    return convert_result.value();
+  }
+  // If it's out of range, signal an error.
+  if (convert_result.status().code() == absl::StatusCode::kOutOfRange) {
+    return convert_result.status();
+  }
+  // If all else fails, let's see if it's a symbol.
+  if (core_access_[current_core_].loader == nullptr)
+    return absl::NotFoundError("Symbol not found");
+  auto result = core_access_[current_core_].loader->GetSymbol(str_value);
+  if (!result.ok()) return result.status();
+  return result.value().first;
+}
+
+absl::Status DebugCommandShell::StepOverCall(int core, std::ostream &os) {
+  auto pcc_result =
+      core_access_[current_core_].debug_interface->ReadRegister("pcc");
+  if (!pcc_result.ok()) {
+    return absl::UnavailableError(pcc_result.status().message());
+  }
+  auto pcc = pcc_result.value();
+  auto inst_res =
+      core_access_[current_core_].debug_interface->GetInstruction(pcc);
+  if (!inst_res.ok()) {
+    return absl::UnavailableError(inst_res.status().message());
+  }
+  auto *inst = inst_res.value();
+  // If it's not a jump-and-link, it's a single step.
+  if ((inst->opcode() != *isa32::OpcodeEnum::kCheriotJal) &&
+      (inst->opcode() != *isa32::OpcodeEnum::kCheriotJalr) &&
+      (inst->opcode() != *isa32::OpcodeEnum::kCheriotCjal) &&
+      (inst->opcode() != *isa32::OpcodeEnum::kCheriotCjalr)) {
+    return core_access_[current_core_].debug_interface->Step(1).status();
+  }
+  // If it is a jump-and-link, we have to set a breakpoint on the instruction
+  // following the jump-and-link, then run until it halts, which may even be
+  // on another breakpoint. Either way, we then remove the breakpoint we
+  // inserted and return.
+  uint64_t bp_address = pcc + inst->size();
+  bool bp_set = false;
+  // See if there is a bp on that address already, if so, don't try to set
+  // another one.
+  if (!core_access_[current_core_].debug_interface->HasBreakpoint(bp_address)) {
+    bp_set = true;
+    auto bp_set_res =
+        core_access_[current_core_].debug_interface->SetSwBreakpoint(
+            bp_address);
+    if (!bp_set_res.ok()) {
+      return bp_set_res;
+    }
+  }
+  auto run_res = core_access_[current_core_].debug_interface->Run();
+  if (!run_res.ok()) {
+    // First remove the breakpoint, then return error.
+    (void)core_access_[current_core_].debug_interface->ClearSwBreakpoint(
+        bp_address);
+    return run_res;
+  }
+  (void)core_access_[current_core_].debug_interface->Wait();
+  auto bp_clr_res =
+      core_access_[current_core_].debug_interface->ClearSwBreakpoint(
+          bp_address);
+  pcc_result = core_access_[current_core_].debug_interface->ReadRegister("pcc");
+  pcc = pcc_result.value();
+  if (pcc != bp_address) {
+    os << absl::StrCat(
+        "Warning: Stopped at instruction other than the expected: [",
+        absl::Hex(bp_address, absl::kZeroPad8), "]\n");
+  }
+  return bp_clr_res;
+}
+
+std::string DebugCommandShell::FormatCapabilityRegister(
+    int core, const std::string &reg_name) const {
+  std::string output;
+  std::vector<uint64_t> values;
+  auto res =
+      core_access_[current_core_].debug_interface->ReadRegister(reg_name);
+  if (!res.ok()) {
+    return absl::StrCat("Error reading '", reg_name,
+                        "': ", res.status().message());
+  }
+  values.push_back(res.value());
+  // Read the capability components.
+  for (auto const &suffix :
+       {"tag", "base", "top", "length", "object_type", "permissions"}) {
+    res = core_access_[current_core_].debug_interface->ReadRegister(
+        absl::StrCat(reg_name, ".", suffix));
+    if (!res.ok()) {
+      return absl::StrCat("Error reading '", reg_name, ".", suffix,
+                          "': ", res.status().message());
+    }
+    values.push_back(res.value());
+  }
+  // Format the capability register components into a readable format.
+  uint64_t obj_type =
+      values[5] |
+      ((values[5] && !(values[6] & PB::kPermitExecute)) ? 0x8 : 0x0);
+  std::string permissions =
+      absl::StrCat(values[6] & PB::kPermitGlobal ? "G " : "- ",
+                   values[6] & PB::kPermitLoad ? "R" : "-",
+                   values[6] & PB::kPermitStore ? "W" : "-",
+                   values[6] & PB::kPermitLoadStoreCapability ? "c" : "-",
+                   values[6] & PB::kPermitLoadMutable ? "m" : "-",
+                   values[6] & PB::kPermitLoadGlobal ? "g" : "-",
+                   values[6] & PB::kPermitStoreLocalCapability ? "l " : "- ",
+                   values[6] & PB::kPermitExecute ? "X" : "-",
+                   values[6] & PB::kPermitAccessSystemRegisters ? "a " : "- ",
+                   values[6] & PB::kPermitSeal ? "S" : "-",
+                   values[6] & PB::kPermitUnseal ? "U" : "-",
+                   values[6] & PB::kUserPerm0 ? "0)" : "-)");
+  absl::StrAppend(
+      &output,
+      absl::StrFormat(
+          "%-5s = 0x%08x (v: %1x 0x%08x-0x%09x l: 0x%09x o: 0x%x p: %s)",
+          reg_name, values[0], values[1], values[2], values[3], values[4],
+          obj_type, permissions));
+  return output;
+}
+
+bool DebugCommandShell::IsCapabilityRegister(
+    const std::string &reg_name) const {
+  return capability_registers_.contains(reg_name);
+}
+
+std::string DebugCommandShell::FormatRegister(
+    int core, const std::string &reg_name) const {
+  if (IsCapabilityRegister(reg_name)) {
+    return FormatCapabilityRegister(current_core_, reg_name);
+  }
+  std::string output;
+  auto result =
+      core_access_[current_core_].debug_interface->ReadRegister(reg_name);
+  if (result.ok()) {
+    absl::StrAppend(&output, reg_name, " = ", absl::Hex(result.value()));
+  } else {
+    absl::StrAppend(&output, "Error reading '", reg_name,
+                    "': ", result.status().message());
+  }
+  return output;
+}
+
+std::string DebugCommandShell::FormatAllRegisters(int core) const {
+  std::string output;
+  for (auto const &reg_name : reg_vector_) {
+    absl::StrAppend(&output, FormatRegister(current_core_, reg_name), "\n");
+  }
+  return output;
+}
+
+// Action point methods.
+std::string DebugCommandShell::ListActionPoints() {
+  std::string output;
+  auto &action_map = core_action_point_info_[current_core_];
+  for (auto const &[local_id, info] : action_map) {
+    absl::StrAppend(
+        &output,
+        absl::StrFormat("%02d  [0x%08lx] %8s  %s\n", local_id, info.address,
+                        info.is_enabled ? "enabled" : "disabled", info.name));
+  }
+  return output;
+}
+
+std::string DebugCommandShell::EnableActionPointN(
+    const std::string &index_str) {
+  auto res = riscv::internal::stoull(index_str, nullptr, 10);
+  if (!res.ok()) {
+    return std::string(res.status().message());
+  }
+  auto &action_map = core_action_point_info_[current_core_];
+  int index = res.value();
+  auto it = action_map.find(index);
+  if (it == action_map.end()) {
+    return absl::StrCat("Action point ", index, " not found");
+  }
+  auto &info = it->second;
+  if (info.is_enabled) {
+    return absl::StrCat("Action point ", index, " is already enabled");
+  }
+  info.is_enabled = true;
+  auto *dbg_if = core_access_[current_core_].debug_interface;
+  auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if);
+  auto status = cheriot_dbg_if->EnableAction(info.address, info.id);
+  if (!status.ok()) {
+    return absl::StrCat("Error: ", status.message());
+  }
+  return "";
+}
+
+std::string DebugCommandShell::DisableActionPointN(
+    const std::string &index_str) {
+  auto res = riscv::internal::stoull(index_str, nullptr, 10);
+  if (!res.ok()) {
+    return std::string(res.status().message());
+  }
+  auto &action_map = core_action_point_info_[current_core_];
+  int index = res.value();
+  auto it = action_map.find(index);
+  if (it == action_map.end()) {
+    return absl::StrCat("Action point ", index, " not found");
+  }
+  auto &info = it->second;
+  if (!info.is_enabled) {
+    return absl::StrCat("Action point ", index, " is already disabled");
+  }
+  info.is_enabled = false;
+  auto *dbg_if = core_access_[current_core_].debug_interface;
+  auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if);
+  auto status = cheriot_dbg_if->DisableAction(info.address, info.id);
+  if (!status.ok()) {
+    return absl::StrCat("Error: ", status.message());
+  }
+  return "";
+}
+
+std::string DebugCommandShell::ClearActionPointN(const std::string &index_str) {
+  auto res = riscv::internal::stoull(index_str, nullptr, 10);
+  if (!res.ok()) {
+    return std::string(res.status().message());
+  }
+  auto &action_map = core_action_point_info_[current_core_];
+  int index = res.value();
+  auto it = action_map.find(index);
+  if (it == action_map.end()) {
+    return absl::StrCat("Action point ", index, " not found");
+  }
+  auto &info = it->second;
+  auto *dbg_if = core_access_[current_core_].debug_interface;
+  auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if);
+  auto status = cheriot_dbg_if->ClearActionPoint(info.address, info.id);
+  if (!status.ok()) {
+    return absl::StrCat("Error: ", status.message());
+  }
+  action_map.erase(it);
+  return "";
+}
+
+std::string DebugCommandShell::ClearAllActionPoints() {
+  std::string output;
+  auto *dbg_if = core_access_[current_core_].debug_interface;
+  auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if);
+  for (auto &[local_id, info] : core_action_point_info_[current_core_]) {
+    auto status = cheriot_dbg_if->ClearActionPoint(info.address, info.id);
+    if (!status.ok()) {
+      absl::StrAppend(&output, "Error: ", status.message());
+    }
+  }
+  return output;
+}
+
+absl::Status DebugCommandShell::SetActionPoint(
+    uint64_t address, std::string name,
+    absl::AnyInvocable<void(uint64_t, int)> function) {
+  auto *dbg_if = core_access_[current_core_].debug_interface;
+  auto *cheriot_dbg_if = static_cast<CheriotDebugInterface *>(dbg_if);
+  auto result = cheriot_dbg_if->SetActionPoint(address, std::move(function));
+  if (!result.ok()) {
+    return absl::InternalError(result.status().message());
+  }
+  int id = result.value();
+  int local_id = core_action_point_id_[current_core_]++;
+  auto &action_map = core_action_point_info_[current_core_];
+  action_map.emplace(local_id, ActionPointInfo{address, id, name, true});
+  return absl::OkStatus();
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/debug_command_shell.h b/cheriot/debug_command_shell.h
new file mode 100644
index 0000000..0a3b218
--- /dev/null
+++ b/cheriot/debug_command_shell.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__DEBUG_COMMAND_SHELL_H_
+#define MPACT_CHERIOT__DEBUG_COMMAND_SHELL_H_
+
+#include <cstdint>
+#include <deque>
+#include <fstream>
+#include <iostream>
+#include <istream>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "absl/container/btree_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/functional/any_invocable.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/debug_command_shell_interface.h"
+#include "re2/re2.h"
+
+namespace mpact::sim::generic {
+class DataBuffer;
+}  // namespace mpact::sim::generic
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::DebugCommandShellInterface;
+
+// This class implements an interactive command shell for a set of cores
+// simulated by the MPact simulator using the CoreDebugInterface.
+class DebugCommandShell : public DebugCommandShellInterface {
+ public:
+  // Default constructor is deleted.
+  DebugCommandShell();
+
+  // Add core access to the system. All cores must be added before calling Run.
+  void AddCore(const CoreAccess &core_access) override;
+  void AddCores(const std::vector<CoreAccess> &core_access) override;
+
+  // The run method is the command interpreter. It parses the command strings,
+  // executes the corresponding commands, displays results and error messages.
+  void Run(std::istream &is, std::ostream &os) override;
+
+  // This adds a custom command to the command interpreter. Usage will be added
+  // to the standard command usage. The callable will be called before the
+  // standard commands are processed.
+  void AddCommand(absl::string_view usage,
+                  CommandFunction command_function) override;
+
+  // This adds an action point at the given address with 'function' as the
+  // action. The string 'name' is used to give it an identifier to use when
+  // listed.
+  absl::Status SetActionPoint(uint64_t address, std::string name,
+                              absl::AnyInvocable<void(uint64_t, int)> function);
+
+ private:
+  // Struct to track action point information.
+  struct ActionPointInfo {
+    uint64_t address;
+    int id;
+    std::string name;
+    bool is_enabled;
+  };
+
+  // Helper method for formatting single data buffer value.
+  std::string FormatSingleDbValue(generic::DataBuffer *db,
+                                  const std::string &format, int width,
+                                  int index) const;
+  // Helper method for formatting multiple data buffer values.
+  std::string FormatAllDbValues(generic::DataBuffer *db,
+                                const std::string &format, int width) const;
+  // Helper method for writing single data buffer value.
+  absl::Status WriteSingleValueToDb(absl::string_view str_value,
+                                    generic::DataBuffer *db, std::string format,
+                                    int width, int index) const;
+
+  // Helper method for processing read memory command.
+  std::string ReadMemory(int core, const std::string &str_value,
+                         absl::string_view format);
+  // Helper method for processing write memory command.
+  std::string WriteMemory(int core, const std::string &str_value1,
+                          const std::string &format,
+                          const std::string &str_value2);
+  // Helper method used to parse a numeric string or use the string as a
+  // symbol name for lookup in the loader.
+  absl::StatusOr<uint64_t> GetValueFromString(int core,
+                                              const std::string &str_value,
+                                              int radix);
+
+  // Method to step over call instructions.
+  absl::Status StepOverCall(int core, std::ostream &os);
+
+  // Returns true if the given register name is a known capability register.
+  bool IsCapabilityRegister(const std::string &reg_name) const;
+  // Reads and formats a capability register.
+  std::string FormatCapabilityRegister(int core,
+                                       const std::string &reg_name) const;
+  // Reads and formats a register.
+  std::string FormatRegister(int core, const std::string &reg_name) const;
+  // Reads and formats $all registers - stored in reg_vec_.
+  std::string FormatAllRegisters(int core) const;
+
+  // Action point handling.
+  std::string ListActionPoints();
+  std::string EnableActionPointN(const std::string &index_str);
+  std::string DisableActionPointN(const std::string &index_str);
+  std::string ClearActionPointN(const std::string &index_str);
+  std::string ClearAllActionPoints();
+
+  std::vector<CoreAccess> core_access_;
+  // Help message displayed for command 'help'.
+  std::string help_message_;
+
+  // Regular expression variables used to parse commands in the shell.
+  LazyRE2 quit_re_;
+  LazyRE2 core_re_;
+  // Run control commands.
+  LazyRE2 run_re_;
+  LazyRE2 run_free_re_;
+  LazyRE2 wait_re_;
+  LazyRE2 step_1_re_;
+  LazyRE2 step_n_re_;
+  LazyRE2 halt_re_;
+  LazyRE2 next_re_;
+  // Read/write registers.
+  LazyRE2 read_reg_re_;
+  LazyRE2 read_reg2_re_;
+  LazyRE2 write_reg_re_;
+  LazyRE2 rd_vreg_re_;
+  LazyRE2 wr_vreg_re_;
+  // Read/write memory commands.
+  LazyRE2 read_mem_re_;
+  LazyRE2 read_mem2_re_;
+  LazyRE2 write_mem_re_;
+  // Breakpoint commands.
+  LazyRE2 set_break_re_;
+  LazyRE2 set_break2_re_;
+  LazyRE2 set_break_n_re_;
+  LazyRE2 list_break_re_;
+  LazyRE2 clear_break_n_re_;
+  LazyRE2 clear_break_re_;
+  LazyRE2 clear_all_break_re_;
+  // Watch point commands.
+  LazyRE2 set_watch_re_;
+  LazyRE2 set_watch2_re_;
+  LazyRE2 set_watch_n_re_;
+  LazyRE2 list_watch_re_;
+  LazyRE2 clear_watch_re_;
+  LazyRE2 clear_watch_n_re_;
+  LazyRE2 clear_all_watch_re_;
+  // Action point commands.
+  LazyRE2 list_action_re_;
+  LazyRE2 enable_action_n_re_;
+  LazyRE2 disable_action_n_re_;
+  LazyRE2 clear_action_n_re_;
+  LazyRE2 clear_all_action_re_;
+  // Branch trace
+  LazyRE2 branch_trace_re_;
+  // Execute commands from a file.
+  LazyRE2 exec_re_;
+  // Empty command.
+  LazyRE2 empty_re_;
+  // Help command.
+  LazyRE2 help_re_;
+
+  int current_core_;
+  uint8_t mem_buffer_[kMemBufferSize];
+  uint8_t tag_buffer_[kMemBufferSize >> 3];
+  std::vector<CommandFunction> command_functions_;
+  std::vector<std::string> command_usage_;
+  absl::flat_hash_set<std::string> capability_registers_;
+  std::vector<std::string> reg_vector_;
+  absl::flat_hash_set<std::string> exec_file_names_;
+  std::deque<std::istream *> command_streams_;
+  std::deque<std::string> previous_commands_;
+  std::vector<absl::btree_map<int, ActionPointInfo>> core_action_point_info_;
+  std::vector<int> core_action_point_id_;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__DEBUG_COMMAND_SHELL_H_
diff --git a/cheriot/memory_use_profiler.cc b/cheriot/memory_use_profiler.cc
new file mode 100644
index 0000000..199b0aa
--- /dev/null
+++ b/cheriot/memory_use_profiler.cc
@@ -0,0 +1,223 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/memory_use_profiler.h"
+
+#include <cstdint>
+#include <cstring>
+#include <ostream>
+#include <utility>
+
+#include "absl/container/btree_map.h"
+#include "absl/numeric/bits.h"
+#include "absl/strings/str_format.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/memory_watcher.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+
+namespace mpact::sim::cheriot {
+
+namespace internal {
+
+MemoryUseTracker::~MemoryUseTracker() {
+  for (auto &[unused, bits] : memory_use_map_) {
+    delete[] bits;
+  }
+  memory_use_map_.clear();
+}
+
+// Static helper function.
+static inline void MarkUsedBits(uint64_t byte_offset, int mask, uint8_t *bits) {
+  // Shift right by two, so that every byte offset becomes a word offset.
+  uint64_t word_offset = byte_offset >> 2;
+  // Byte offs
+  uint64_t byte_index = word_offset >> 3;
+  int bit_index = word_offset & 0b111;
+
+  bits[byte_index] |= (mask << bit_index);
+}
+
+void MemoryUseTracker::MarkUsed(uint64_t address, int size) {
+  // The profiling is done on a word boundary, so word or smaller is marked by
+  // a single bit, double words by 2 bits.
+  uint8_t mask = size == 8 ? 0b11 : 0b1;
+  if ((address >= last_start_) && (address + size - 1 <= last_end_)) {
+    return MarkUsedBits(address - last_start_, mask, last_used_);
+  }
+  auto it = memory_use_map_.find(AddressRange{address, address + size - 1});
+  if (it == memory_use_map_.end()) {
+    // Compute new base and top addresses.
+    uint64_t start = address & ~kBaseMask;
+    uint64_t end = start + kSegmentSize - 1;
+    auto *bits = new uint8_t[kBitsSize];
+    std::memset(bits, 0, kBitsSize);
+
+    it = memory_use_map_.insert(std::make_pair(AddressRange{start, end}, bits))
+             .first;
+  }
+  last_start_ = it->first.start;
+  last_end_ = it->first.end;
+  last_used_ = it->second;
+  MarkUsedBits(address - last_start_, mask, it->second);
+}
+
+// Write out ranges of words that have been used.
+void MemoryUseTracker::WriteUseProfile(std::ostream &os) const {
+  // Current range info.
+  uint64_t range_start = 0;
+  uint64_t range_end = 0;
+  bool range_started = false;
+  for (auto const &[range, bits] : memory_use_map_) {
+    auto base = range.start;
+    int byte_index = 0;
+    uint8_t byte = bits[byte_index];
+    while (byte_index < kBitsSize) {
+      if (range_started) {
+        // If we have a range started and the accesses are contiguous, increment
+        // the byte index and continue.
+        if (byte == 0xff) {
+          byte_index++;
+          if (byte_index < kBitsSize) byte = bits[byte_index];
+          continue;
+        }
+        // Compute the end of the current range by counting the number of
+        // consecutive ones starting from the lsb.
+        int bit_indx = absl::countr_one(byte) - 1;
+        range_end = base + (byte_index * 8 + bit_indx) * kGranularity;
+        // Output range.
+        os << absl::StrFormat("0x%llx,0x%llx,%llu\n", range_start, range_end,
+                              range_end - range_start + 4);
+        // Clear those bits from the current byte, then we start looking for
+        // a new range.
+        byte &= ~((1 << (bit_indx + 1)) - 1);
+        range_started = false;
+      }
+      // If we are here, range_started is false.
+      if (byte == 0) {
+        byte_index++;
+        if (byte_index < kBitsSize) byte = bits[byte_index];
+        continue;
+      }
+      // At this point byte != 0, and we need to start a new range.
+      int bit_indx = absl::countr_zero(byte);
+      range_start = base + (byte_index * 8 + bit_indx) * kGranularity;
+      // Set the low bits in the current byte so that we can determine the end
+      // of this range, in case it is contained in this byte.
+      byte |= ((1 << bit_indx) - 1);
+      range_started = true;
+    }
+  }
+}
+
+}  // namespace internal
+
+MemoryUseProfiler::MemoryUseProfiler() : MemoryUseProfiler(nullptr) {}
+
+MemoryUseProfiler::MemoryUseProfiler(MemoryInterface *memory)
+    : memory_(memory) {}
+
+void MemoryUseProfiler::Load(uint64_t address, DataBuffer *db,
+                             Instruction *inst, ReferenceCount *context) {
+  if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
+  if (memory_) memory_->Load(address, db, inst, context);
+}
+
+void MemoryUseProfiler::Load(DataBuffer *address_db, DataBuffer *mask_db,
+                             int el_size, DataBuffer *db, Instruction *inst,
+                             ReferenceCount *context) {
+  if (is_enabled_) {
+    for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
+      if (mask_db->Get<uint8_t>(i)) {
+        tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
+      }
+    }
+  }
+  if (memory_) memory_->Load(address_db, mask_db, el_size, db, inst, context);
+}
+
+void MemoryUseProfiler::Store(uint64_t address, DataBuffer *db) {
+  if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
+  if (memory_) memory_->Store(address, db);
+}
+
+void MemoryUseProfiler::Store(DataBuffer *address_db, DataBuffer *mask_db,
+                              int el_size, DataBuffer *db) {
+  if (is_enabled_) {
+    for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
+      if (mask_db->Get<uint8_t>(i)) {
+        tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
+      }
+    }
+  }
+  if (memory_) memory_->Store(address_db, mask_db, el_size, db);
+}
+
+TaggedMemoryUseProfiler::TaggedMemoryUseProfiler()
+    : TaggedMemoryUseProfiler(nullptr) {}
+
+TaggedMemoryUseProfiler::TaggedMemoryUseProfiler(
+    TaggedMemoryInterface *tagged_memory)
+    : tagged_memory_(tagged_memory) {}
+
+void TaggedMemoryUseProfiler::Load(uint64_t address, DataBuffer *db,
+                                   Instruction *inst, ReferenceCount *context) {
+  if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
+  if (tagged_memory_) tagged_memory_->Load(address, db, inst, context);
+}
+
+void TaggedMemoryUseProfiler::Load(DataBuffer *address_db, DataBuffer *mask_db,
+                                   int el_size, DataBuffer *db,
+                                   Instruction *inst, ReferenceCount *context) {
+  if (is_enabled_) {
+    for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
+      if (mask_db->Get<uint8_t>(i)) {
+        tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
+      }
+    }
+  }
+  if (tagged_memory_)
+    tagged_memory_->Load(address_db, mask_db, el_size, db, inst, context);
+}
+
+void TaggedMemoryUseProfiler::Load(uint64_t address, DataBuffer *db,
+                                   DataBuffer *tags, Instruction *inst,
+                                   ReferenceCount *context) {
+  if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
+  if (tagged_memory_) tagged_memory_->Load(address, db, tags, inst, context);
+}
+
+void TaggedMemoryUseProfiler::Store(uint64_t address, DataBuffer *db) {
+  if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
+  if (tagged_memory_) tagged_memory_->Store(address, db);
+}
+
+void TaggedMemoryUseProfiler::Store(DataBuffer *address_db, DataBuffer *mask_db,
+                                    int el_size, DataBuffer *db) {
+  if (is_enabled_) {
+    for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
+      if (mask_db->Get<uint8_t>(i)) {
+        tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
+      }
+    }
+  }
+  if (tagged_memory_) tagged_memory_->Store(address_db, mask_db, el_size, db);
+}
+
+void TaggedMemoryUseProfiler::Store(uint64_t address, DataBuffer *db,
+                                    DataBuffer *tags) {
+  if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
+  if (tagged_memory_) tagged_memory_->Store(address, db, tags);
+}
+
+}  // namespace mpact::sim::cheriot
diff --git a/cheriot/memory_use_profiler.h b/cheriot/memory_use_profiler.h
new file mode 100644
index 0000000..c7ebf83
--- /dev/null
+++ b/cheriot/memory_use_profiler.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__MEMORY_USE_PROFILER_H_
+#define MPACT_CHERIOT__MEMORY_USE_PROFILER_H_
+
+#include <cstdint>
+#include <ostream>
+
+#include "absl/container/btree_map.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/ref_count.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/memory_watcher.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+
+namespace mpact::sim::cheriot {
+
+using AddressRange = util::MemoryWatcher::AddressRange;
+using AddressRangeLess = util::MemoryWatcher::AddressRangeLess;
+using generic::DataBuffer;
+using generic::Instruction;
+using generic::ReferenceCount;
+using util::MemoryInterface;
+using util::TaggedMemoryInterface;
+
+namespace internal {
+
+// This class is used to track the use of word addresses. It dynamically
+// allocates tracking memory as needed, and marks a bit for each word that is
+// accessed.
+class MemoryUseTracker {
+ public:
+  MemoryUseTracker() = default;
+  ~MemoryUseTracker();
+
+  // Memory use is tracked on word granularity.
+  static constexpr int kGranularity = sizeof(uint32_t);
+  // The segment size is the size of the address range for each segment.
+  static constexpr int kSegmentSize = 128 * 1024;
+  static constexpr int kBaseMask = kSegmentSize - 1;
+  // Size of each 'use' bit store.
+  static constexpr int kBitsSize = kSegmentSize / (kGranularity * 8);
+  void MarkUsed(uint64_t address, int size);
+  void WriteUseProfile(std::ostream &os) const;
+
+ private:
+  uint64_t last_start_ = 0;
+  uint64_t last_end_ = 0;
+  uint8_t *last_used_ = nullptr;
+  absl::btree_map<AddressRange, uint8_t *, AddressRangeLess> memory_use_map_;
+};
+
+}  // namespace internal
+
+// Use profiler for the MemoryInterface.
+class MemoryUseProfiler : public MemoryInterface {
+ public:
+  // The default constructor does not set up memory forwarding.
+  MemoryUseProfiler();
+  explicit MemoryUseProfiler(MemoryInterface *memory);
+  ~MemoryUseProfiler() override = default;
+
+  // Inherited from memory interfaces.
+  void Load(uint64_t address, DataBuffer *db, Instruction *inst,
+            ReferenceCount *context) override;
+  void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+            DataBuffer *db, Instruction *inst,
+            ReferenceCount *context) override;
+  void Store(uint64_t address, DataBuffer *db) override;
+  void Store(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+             DataBuffer *db) override;
+
+  void WriteProfile(std::ostream &os) const { tracker_.WriteUseProfile(os); }
+
+  // Accessor.
+  void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
+
+ private:
+  bool is_enabled_ = false;
+  MemoryInterface *memory_;
+  internal::MemoryUseTracker tracker_;
+};
+
+// Use profiler for the TaggedMemoryInterface.
+class TaggedMemoryUseProfiler : public TaggedMemoryInterface {
+ public:
+  // The default constructor does not set up memory forwarding.
+  TaggedMemoryUseProfiler();
+  explicit TaggedMemoryUseProfiler(TaggedMemoryInterface *tagged_memory);
+  ~TaggedMemoryUseProfiler() override = default;
+
+  // Inherited from memory interfaces.
+  void Load(uint64_t address, DataBuffer *db, Instruction *inst,
+            ReferenceCount *context) override;
+  void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+            DataBuffer *db, Instruction *inst,
+            ReferenceCount *context) override;
+  void Load(uint64_t address, DataBuffer *db, DataBuffer *tags,
+            Instruction *inst, ReferenceCount *context) override;
+  void Store(uint64_t address, DataBuffer *db) override;
+  void Store(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+             DataBuffer *db) override;
+  void Store(uint64_t address, DataBuffer *db, DataBuffer *tags) override;
+
+  void WriteProfile(std::ostream &os) const { tracker_.WriteUseProfile(os); }
+
+  // Accessor.
+  void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
+
+ private:
+  bool is_enabled_ = false;
+  TaggedMemoryInterface *tagged_memory_;
+  internal::MemoryUseTracker tracker_;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__MEMORY_USE_PROFILER_H_
diff --git a/cheriot/mpact_cheriot.cc b/cheriot/mpact_cheriot.cc
new file mode 100644
index 0000000..51673c2
--- /dev/null
+++ b/cheriot/mpact_cheriot.cc
@@ -0,0 +1,545 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <signal.h>
+
+#include <cstdint>
+#include <cstdlib>
+#include <fstream>
+#include <ios>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/flags/usage.h"
+#include "absl/functional/any_invocable.h"
+#include "absl/functional/bind_front.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "cheriot/cheriot_instrumentation_control.h"
+#include "cheriot/cheriot_top.h"
+#include "cheriot/debug_command_shell.h"
+#include "cheriot/memory_use_profiler.h"
+#include "cheriot/profiler.h"
+#include "cheriot/riscv_cheriot_minstret.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/counters.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/proto/component_data.pb.h"
+#include "mpact/sim/util/memory/atomic_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/memory_watcher.h"
+#include "mpact/sim/util/memory/single_initiator_router.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+#include "mpact/sim/util/other/simple_uart.h"
+#include "mpact/sim/util/program_loader/elf_program_loader.h"
+#include "re2/re2.h"
+#include "riscv//riscv_arm_semihost.h"
+#include "riscv//riscv_clint.h"
+#include "src/google/protobuf/text_format.h"
+
+using ::mpact::sim::proto::ComponentData;
+using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
+using ::mpact::sim::cheriot::CheriotInstrumentationControl;
+using ::mpact::sim::cheriot::Profiler;
+using ::mpact::sim::cheriot::TaggedMemoryUseProfiler;
+
+// Flags for specifying interactive mode.
+ABSL_FLAG(bool, i, false, "Interactive mode");
+ABSL_FLAG(bool, interactive, false, "Interactive mode");
+// Flag for destination directory of proto file.
+ABSL_FLAG(std::string, output_dir, "", "Output directory");
+// The following defines the optional flag for setting the stack size. If the
+// stack size is not set using the flag, then the simulator will look in the
+// executable to see if the GNU_STACK segment exists (assuming gcc RiscV
+// compiler), and use that size. If not, it will use the value of the symbol
+// __stack_size in the executable. If no such symbol exists, the stack size will
+// be 32KB.
+//
+// A symbol may be defined in a C/C++ source file using asm, such as:
+// asm(".global __stack_size\n"
+//     ".equ __stack_size, 32 * 1024\n");
+// The asm statement need not be inside a function body.
+//
+// The program header entry may be generated by adding the following to the
+// gcc/g++ command line: -Wl,z,stack-size=N
+//
+ABSL_FLAG(std::optional<uint64_t>, stack_size, 32 * 1024,
+          "Size of software stack");
+// Optional flag for setting the location of the end of the stack (bottom). The
+// beginning stack pointer is the value stack_end + stack_size. If this option
+// is not set, it will use the value of the symbol __stack_end in the
+// executable. If no such symbol exists, stack pointer initialization will not
+// be performed by the simulator, and an appropriate crt0 library has to be
+// used.
+//
+// A symbol may be defined in a C/C++ source file using asm, such as:
+// asm(".global __stack_end\n"
+//     ".equ __stack_end, 0x200000\n");
+// The asm statement need not be inside a function body.
+ABSL_FLAG(std::optional<uint64_t>, stack_end, 0,
+          "Lowest valid address of software stack. "
+          "Top of stack is stack_end + stack_size.");
+
+// The following macro can be used in source code to define both the stack size
+// and location:
+//
+// #define __STACK(addr, size) \
+//  asm(".global __stack_size\n.equ __stack_size, " #size "\n"); \
+//  asm(".global __stack_end\n.equ __stack_end, " #addr "\n");
+//
+// E.g.
+//
+// #include <stdio>
+//
+// __STACK(0x20000, 32 * 1024);
+//
+// int main(int, char **) {
+//   printf("Hello World\n");
+//   return 0;
+// }
+//
+
+// Flag to exit on any exception and print the relevant exception information.
+ABSL_FLAG(bool, exit_on_exception, false, "Exit on exception");
+
+// Enable instruction profiling.
+ABSL_FLAG(bool, inst_profile, false, "Enable instruction profiling");
+
+// Enable memory use profiling.
+ABSL_FLAG(bool, mem_profile, false, "Enable memory use profiling");
+
+constexpr char kStackEndSymbolName[] = "__stack_end";
+constexpr char kStackSizeSymbolName[] = "__stack_size";
+
+constexpr int kCapabilityGranule = 8;
+
+using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
+using ::mpact::sim::cheriot::CheriotTop;
+using ::mpact::sim::cheriot::RiscVCheriotMInstret;
+using ::mpact::sim::cheriot::RiscVCheriotMInstreth;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::riscv::RiscVArmSemihost;
+using ::mpact::sim::riscv::RiscVClint;
+using ::mpact::sim::util::AtomicMemoryOpInterface;
+using ::mpact::sim::util::MemoryInterface;
+using ::mpact::sim::util::SimpleUart;
+using ::mpact::sim::util::TaggedMemoryInterface;
+using ::mpact::sim::util::TaggedMemoryWatcher;
+
+// Static pointer to the top instance. Used by the control-C handler.
+static CheriotTop *top = nullptr;
+
+// Control-c handler to interrupt any running simulation.
+static void sim_sigint_handler(int arg) {
+  if (top != nullptr) {
+    (void)top->Halt();
+    return;
+  } else {
+    exit(-1);
+  }
+}
+
+// This is an example custom command that is added to the interactive
+// debug command shell.
+static bool PrintRegisters(
+    absl::string_view input,
+    const mpact::sim::cheriot::DebugCommandShell::CoreAccess &core_access,
+    std::string &output) {
+  static const LazyRE2 reg_info_re{R"(\s*xyzreg\s+info\s*)"};
+  if (!RE2::FullMatch(input, *reg_info_re)) return false;
+
+  std::string output_str;
+  for (int i = 0; i < 32; i++) {
+    std::string reg_name = absl::StrCat("x", i);
+    auto result = core_access.debug_interface->ReadRegister(reg_name);
+    if (!result.ok()) {
+      output = absl::StrCat("Failed to read register '", reg_name, "'");
+      return true;
+    }
+    output_str +=
+        absl::StrCat("x", absl::Dec(i, absl::kZeroPad2), " = [",
+                     absl::Hex(result.value(), absl::kZeroPad8), "]\n");
+  }
+  output = output_str;
+  return true;
+}
+
+// Trap handler.
+bool HandleSimulatorTrap(bool is_interrupt, uint64_t trap_value, uint64_t ec,
+                         uint64_t epc, const Instruction *instruction) {
+  if (is_interrupt) return false;
+  std::cerr << absl::StrCat(
+      "Exception\n"
+      " trapvalue: ",
+      absl::Hex(trap_value, absl::kZeroPad8),
+      "\n"
+      " code: ",
+      absl::Hex(ec, absl::kZeroPad8),
+      "\n"
+      " epc: ",
+      absl::Hex(epc, absl::kZeroPad8),
+      "\n"
+      " inst: ",
+      instruction == nullptr ? "nullptr" : instruction->AsString(), "\n");
+  // Halt the simulation.
+  if (top != nullptr) (void)top->Halt();
+  return false;
+}
+
+// Main function for the simulator.
+int main(int argc, char **argv) {
+  absl::SetProgramUsageMessage(argv[0]);
+  auto arg_vec = absl::ParseCommandLine(argc, argv);
+
+  if (arg_vec.size() > 2) {
+    std::cerr << "Only a single input file allowed" << std::endl;
+    return -1;
+  }
+  if (arg_vec.size() < 2) {
+    std::cerr << "Must specify input file" << std::endl;
+    return -1;
+  }
+  std::string full_file_name = arg_vec[1];
+  std::string file_name =
+      full_file_name.substr(full_file_name.find_last_of('/') + 1);
+  std::string file_basename = file_name.substr(0, file_name.find_first_of('.'));
+
+  auto *tagged_memory =
+      new mpact::sim::util::TaggedFlatDemandMemory(kCapabilityGranule);
+  // Load the elf segments into memory.
+  mpact::sim::util::ElfProgramLoader elf_loader(tagged_memory);
+  auto load_result = elf_loader.LoadProgram(full_file_name);
+  if (!load_result.ok()) {
+    std::cerr << "Error while loading '" << full_file_name
+              << "': " << load_result.status().message();
+    return -1;
+  }
+  auto *router = new mpact::sim::util::SingleInitiatorRouter("router");
+  TaggedMemoryInterface *data_memory =
+      static_cast<TaggedMemoryInterface *>(router);
+  TaggedMemoryUseProfiler *memory_use_profiler = nullptr;
+  // Check to see if memory use profiling is enabled, and if so, set it up.
+  if (absl::GetFlag(FLAGS_mem_profile)) {
+    memory_use_profiler = new TaggedMemoryUseProfiler(data_memory);
+    // Disable until program execution.
+    memory_use_profiler->set_is_enabled(false);
+    data_memory = memory_use_profiler;
+  }
+  CheriotTop cheriot_top("Cheriot", static_cast<MemoryInterface *>(router),
+                         data_memory, static_cast<MemoryInterface *>(router));
+
+  // Enable instruction profiling if the flag is set.
+  Profiler *inst_profiler = nullptr;
+  if (absl::GetFlag(FLAGS_inst_profile)) {
+    inst_profiler = new Profiler(elf_loader, 2);
+    cheriot_top.counter_pc()->AddListener(inst_profiler);
+  } else {
+    cheriot_top.counter_pc()->SetIsEnabled(false);
+  }
+
+  mpact::sim::generic::DataBuffer *db = nullptr;
+
+  // If tohost exists, add a memory watcher to look for exit signal.
+  auto tohost_res = elf_loader.GetSymbol("tohost");
+  uint64_t tohost_addr = 0;
+  if (tohost_res.ok()) {
+    // tohost is declared as uint32_t tohost[2]. Writing an lsb of 1
+    // terminates the simulation. The upper 31 bits can pass extra metadata.
+    // Use all 0s to indicate success.
+    tohost_addr = tohost_res.value().first;
+    // Add to_host watchpoint.
+    db = cheriot_top.state()->db_factory()->Allocate<uint32_t>(2);
+    auto status = cheriot_top.tagged_watcher()->SetStoreWatchCallback(
+        TaggedMemoryWatcher::AddressRange{
+            tohost_addr, tohost_addr + 2 * sizeof(uint32_t) - 1},
+        [tagged_memory, tohost_addr, &db, &cheriot_top](uint64_t, int) {
+          if (db == nullptr) return;
+          tagged_memory->Load(tohost_addr, db, nullptr, nullptr);
+          uint32_t code = db->Get<uint32_t>(0);
+          if (code & 0x1) {
+            code >>= 1;
+            std::cerr << absl::StrCat("Simulation halted: exit ",
+                                      absl::Hex(code), "\n");
+            (void)cheriot_top.Halt();
+            db->DecRef();
+            db = nullptr;
+          }
+        });
+    if (!status.ok()) {
+      LOG(ERROR) << "Failed to set 'tohost' watchpoint";
+      exit(-1);
+    }
+  }
+
+  // Initialize minstret/minstreth. Bind the instruction counter to those
+  // registers.
+  auto minstret_res = cheriot_top.state()->csr_set()->GetCsr("minstret");
+  auto minstreth_res = cheriot_top.state()->csr_set()->GetCsr("minstreth");
+  if (!minstret_res.ok() || !minstreth_res.ok()) {
+    std::cerr << "Error while initializing minstret/minstreth";
+    return -1;
+  }
+  auto *minstret = static_cast<RiscVCheriotMInstret *>(minstret_res.value());
+  auto *minstreth = static_cast<RiscVCheriotMInstreth *>(minstreth_res.value());
+  minstret->set_counter(cheriot_top.counter_num_instructions());
+  minstreth->set_counter(cheriot_top.counter_num_instructions());
+
+  // Set up the memory router with the appropriate targets.
+  ::mpact::sim::util::AtomicMemory *atomic_memory = nullptr;
+  atomic_memory = new mpact::sim::util::AtomicMemory(tagged_memory);
+
+  auto *uart = new SimpleUart(cheriot_top.state());
+
+  CHECK_OK(
+      router->AddTarget<MemoryInterface>(uart, 0x1000'0000ULL, 0x1000'00ffULL));
+  auto *clint = new RiscVClint(/*period=*/100, cheriot_top.state()->mip());
+  cheriot_top.counter_num_cycles()->AddListener(clint);
+  CHECK_OK(router->AddTarget<AtomicMemoryOpInterface>(
+      atomic_memory, 0x0000'0000ULL, 0x01ff'ffffULL));
+  CHECK_OK(router->AddTarget<TaggedMemoryInterface>(
+      tagged_memory, 0x0000'0000ULL, 0x01ff'ffffULL));
+  CHECK_OK(router->AddTarget<MemoryInterface>(clint, 0x0200'0000ULL,
+                                              0x0200'ffffULL));
+  CHECK_OK(router->AddTarget<AtomicMemoryOpInterface>(
+      atomic_memory, 0x02001'0000ULL, 0xffff'ffffULL));
+  CHECK_OK(router->AddTarget<TaggedMemoryInterface>(
+      tagged_memory, 0x0201'0000ULL, 0xffff'ffffULL));
+
+  // Set up a dummy WFI handler.
+  cheriot_top.state()->set_on_wfi([](const Instruction *) { return true; });
+  cheriot_top.state()->set_on_ecall([](const Instruction *) { return false; });
+  // Initialize the PC to the entry point.
+  uint32_t entry_point = load_result.value();
+  auto pcc_write = cheriot_top.WriteRegister("pcc", entry_point);
+  if (!pcc_write.ok()) {
+    std::cerr << "Error writing to pcc: " << pcc_write.message();
+    return -1;
+  }
+
+  // Set up semihosting.
+  auto *semihost = new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord32,
+                                        cheriot_top.inst_memory(),
+                                        cheriot_top.data_memory());
+  cheriot_top.state()->AddEbreakHandler([semihost](const Instruction *inst) {
+    if (semihost->IsSemihostingCall(inst)) {
+      semihost->OnEBreak(inst);
+      return true;
+    }
+    return false;
+  });
+  semihost->set_exit_callback([&cheriot_top]() {
+    cheriot_top.RequestHalt(HaltReason::kSemihostHaltRequest, nullptr);
+  });
+
+  // Initializing the stack pointer.
+
+  // First see if there is a stack location defined, if not, do not initialize
+  // the stack pointer.
+  bool initialize_stack = false;
+  uint64_t stack_end = 0;
+
+  // Is the __stack_end symbol defined?
+  auto res = elf_loader.GetSymbol(kStackEndSymbolName);
+  if (res.ok()) {
+    stack_end = res.value().first;
+    initialize_stack = true;
+  }
+
+  // The stack_end flag overrides the __stack_end symbol.
+  if (absl::GetFlag(FLAGS_stack_end).has_value()) {
+    stack_end = absl::GetFlag(FLAGS_stack_end).value();
+    initialize_stack = true;
+  }
+
+  // If there is a stack location, get the stack size, and write the sp.
+  if (initialize_stack) {
+    // Default size is 32KB.
+    uint64_t stack_size = 32 * 1024;
+
+    // Does the executable have a valid GNU_STACK segment? If so, override the
+    // default
+    auto loader_res = elf_loader.GetStackSize();
+    if (loader_res.ok()) {
+      stack_end = loader_res.value();
+    }
+
+    // If the __stack_size symbol is defined then override.
+    auto res = elf_loader.GetSymbol(kStackSizeSymbolName);
+    if (res.ok()) {
+      stack_size = res.value().first;
+    }
+
+    // If the flag is set, then override.
+    if (absl::GetFlag(FLAGS_stack_size).has_value()) {
+      stack_size = absl::GetFlag(FLAGS_stack_size).value();
+    }
+
+    auto sp_write = cheriot_top.WriteRegister("sp", stack_end + stack_size);
+    if (!sp_write.ok()) {
+      std::cerr << "Error writing to sp: " << sp_write.message();
+      return -1;
+    }
+  }
+
+  mpact::sim::generic::SimpleCounter<double> counter_sec("simulation_time_sec",
+                                                         0.0);
+
+  CHECK_OK(cheriot_top.AddCounter(&counter_sec));
+  // Set up control-c handling.
+  top = &cheriot_top;
+  struct sigaction sa;
+  sa.sa_flags = 0;
+  sigemptyset(&sa.sa_mask);
+  sigaddset(&sa.sa_mask, SIGINT);
+  sa.sa_handler = &sim_sigint_handler;
+  sigaction(SIGINT, &sa, nullptr);
+
+  // If exit on exception is set, set up an exception handler that terminates
+  // the simulation and prints exception information. In interactive mode a
+  // message is printed and any run is stopped.
+  if (absl::GetFlag(FLAGS_exit_on_exception)) {
+    cheriot_top.state()->set_on_trap(&HandleSimulatorTrap);
+  }
+
+  if (memory_use_profiler) memory_use_profiler->set_is_enabled(true);
+
+  // Determine if this is being run interactively or as a batch job.
+  bool interactive = absl::GetFlag(FLAGS_i) || absl::GetFlag(FLAGS_interactive);
+  CheriotInstrumentationControl *cheriot_instrumentation_control = nullptr;
+  if (interactive) {
+    mpact::sim::cheriot::DebugCommandShell cmd_shell;
+    cmd_shell.AddCore({&cheriot_top, &elf_loader});
+    cheriot_instrumentation_control = new CheriotInstrumentationControl(
+        &cmd_shell, &cheriot_top, memory_use_profiler);
+    // Add custom command to interactive debug command shell.
+    cmd_shell.AddCommand(
+        "    reg info                       - print all scalar regs",
+        PrintRegisters);
+    cmd_shell.AddCommand(
+        cheriot_instrumentation_control->Usage(),
+        absl::bind_front(&CheriotInstrumentationControl::PerformShellCommand,
+                         cheriot_instrumentation_control));
+    cmd_shell.Run(std::cin, std::cout);
+  } else {
+    std::cerr << "Starting simulation\n";
+
+    auto t0 = absl::Now();
+
+    auto run_status = cheriot_top.Run();
+    if (!run_status.ok()) {
+      std::cerr << run_status.message() << std::endl;
+    }
+
+    auto wait_status = cheriot_top.Wait();
+    if (!wait_status.ok()) {
+      std::cerr << wait_status.message() << std::endl;
+    }
+
+    auto t1 = absl::Now();
+    absl::Duration duration = t1 - t0;
+    double sec = static_cast<double>(duration / absl::Milliseconds(100)) / 10;
+    counter_sec.SetValue(sec);
+
+    std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec);
+  }
+
+  // Write out memory use profile.
+  if (memory_use_profiler != nullptr) {
+    std::cerr << "Writing out memory use profile\n";
+    std::string memory_use_profile_file_name;
+    if (FLAGS_output_dir.CurrentValue().empty()) {
+      memory_use_profile_file_name =
+          "./" + file_basename + "_memory_use_profile.csv";
+    } else {
+      memory_use_profile_file_name = FLAGS_output_dir.CurrentValue() + "/" +
+                                     file_basename + "_memory_use_profile.csv";
+    }
+    std::fstream memory_use_profile_file(memory_use_profile_file_name.c_str(),
+                                         std::ios_base::out);
+    if (!memory_use_profile_file.good()) {
+      LOG(ERROR) << "Failed to write memory use profile to file";
+    } else {
+      memory_use_profiler->WriteProfile(memory_use_profile_file);
+    }
+  }
+
+  // Write out instruction profile.
+  if (inst_profiler != nullptr) {
+    std::cerr << "Writing out instruction profile\n";
+    std::string inst_profile_file_name;
+    if (FLAGS_output_dir.CurrentValue().empty()) {
+      inst_profile_file_name = "./" + file_basename + "_inst_profile.csv";
+    } else {
+      inst_profile_file_name = FLAGS_output_dir.CurrentValue() + "/" +
+                               file_basename + "_inst_profile.csv";
+    }
+    std::fstream inst_profile_file(inst_profile_file_name.c_str(),
+                                   std::ios_base::out);
+    if (!inst_profile_file.good()) {
+      LOG(ERROR) << "Failed to write profile to file";
+    } else {
+      inst_profiler->WriteProfile(inst_profile_file);
+    }
+  }
+
+  // Export counters.
+  std::cerr << "Exporting counters\n";
+  auto component_proto = std::make_unique<ComponentData>();
+  CHECK_OK(cheriot_top.Export(component_proto.get()))
+      << "Failed to export proto";
+  std::string proto_file_name;
+  if (FLAGS_output_dir.CurrentValue().empty()) {
+    proto_file_name = "./" + file_basename + "_counters.proto";
+  } else {
+    proto_file_name = FLAGS_output_dir.CurrentValue() + "/" + file_basename +
+                      "_counters.proto";
+  }
+  std::fstream proto_file(proto_file_name.c_str(), std::ios_base::out);
+  std::string serialized;
+  if (!proto_file.good() || !google::protobuf::TextFormat::PrintToString(
+                                *component_proto.get(), &serialized)) {
+    LOG(ERROR) << "Failed to write proto to file";
+  } else {
+    proto_file << serialized;
+    proto_file.close();
+  }
+
+  // Cleanup.
+  auto bp_status = cheriot_top.ClearAllSwBreakpoints();
+  if (!bp_status.ok()) {
+    LOG(ERROR) << "Error in ClearAllSwBreakpoints: " << bp_status.message();
+  }
+  delete cheriot_instrumentation_control;
+  delete inst_profiler;
+  delete atomic_memory;
+  delete tagged_memory;
+  delete memory_use_profiler;
+  delete semihost;
+  if (db != nullptr) db->DecRef();
+}
diff --git a/cheriot/profiler.cc b/cheriot/profiler.cc
new file mode 100644
index 0000000..c20bdc8
--- /dev/null
+++ b/cheriot/profiler.cc
@@ -0,0 +1,130 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/profiler.h"
+
+#include <cstdint>
+#include <cstring>
+#include <ostream>
+#include <utility>
+
+#include "absl/log/log.h"
+#include "absl/numeric/bits.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "elfio/elf_types.hpp"
+
+namespace mpact::sim::cheriot {
+
+Profiler::Profiler(ElfProgramLoader &elf_loader, unsigned granularity)
+    : elf_loader_(&elf_loader) {
+  if (!absl::has_single_bit(granularity)) {
+    LOG(ERROR) << absl::StrCat("Invalid granularity: ", granularity,
+                               ",  must be a power of 2");
+  }
+  shift_ = absl::bit_width(granularity) - 1;
+  SetElfLoader(&elf_loader);
+}
+
+Profiler::Profiler(unsigned granularity) : elf_loader_(nullptr) {
+  if (!absl::has_single_bit(granularity)) {
+    LOG(ERROR) << absl::StrCat("Invalid granularity: ", granularity,
+                               ",  must be a power of 2");
+  }
+  shift_ = absl::bit_width(granularity) - 1;
+}
+
+Profiler::~Profiler() {
+  for (auto const &[unused, counters] : profile_ranges_) {
+    delete[] counters;
+  }
+  profile_ranges_.clear();
+}
+
+void Profiler::AddSampleInternal(uint64_t sample) {
+  if (elf_loader_ == nullptr) return;
+  // Look up a new range.
+  auto it = profile_ranges_.find({sample, sample});
+  if (it == profile_ranges_.end()) {
+    LOG(WARNING) << absl::StrCat("Profile sample out of range: ",
+                                 absl::Hex(sample << shift_));
+    return;
+  }
+  // Save the range info and increment the counter.
+  last_profile_range_ = it->second;
+  last_start_ = it->first.start;
+  last_end_ = it->first.end;
+  last_profile_range_[sample - last_start_]++;
+}
+
+void Profiler::WriteProfile(std::ostream &os) {
+  os << "Address,Count" << "\n";
+  for (auto const &[range, counters] : profile_ranges_) {
+    uint64_t size = range.end - range.start;
+    for (auto i = 0; i < size; ++i) {
+      if (counters[i] == 0) continue;
+      os << absl::StrFormat("0x%llx,%llu\n", (range.start + i) << shift_,
+                            counters[i]);
+    }
+  }
+}
+
+void Profiler::SetElfLoader(ElfProgramLoader *elf_loader) {
+  elf_loader_ = elf_loader;
+  uint64_t begin = 0;
+  uint64_t end = 0;
+  // Iterate through the elf segments (assumes they are in order), and
+  // coalesces ranges that are spaced by less than 0x1'000 units of granularity.
+  // This reduces the number of ranges in the map and improves performance
+  // during simulation.
+  for (auto const &segment : elf_loader_->elf_reader()->segments) {
+    // Only consider segments that are loaded, executable, and with size > 0.
+    if (segment->get_type() != PT_LOAD) continue;
+    if ((segment->get_flags() & PF_X) == 0) continue;
+    uint64_t size = segment->get_memory_size() >> shift_;
+    if (size == 0) continue;
+
+    uint64_t vaddr_begin = segment->get_virtual_address() >> shift_;
+    // If it's the first time we see a segment, just get the start and end
+    // values.
+    if (begin == 0 && end == 0) {
+      begin = vaddr_begin;
+      end = vaddr_begin + size;
+      continue;
+    };
+    // If the segment is close enough to the current, just coalesce.
+    if (vaddr_begin - end < 0x1000) {
+      end = vaddr_begin + size;
+      continue;
+    }
+    // Otherwise, create a entry from the previously accumulated ranges, and
+    // start a new range.
+    size = end - begin - 1;
+    uint64_t *counters = new uint64_t[size];
+    ::memset(counters, 0, size * sizeof(uint64_t));
+    profile_ranges_.insert(std::make_pair(AddressRange{begin, end}, counters));
+    begin = vaddr_begin;
+    end = vaddr_begin + size;
+  }
+  // Make the last entry.
+  if (begin != 0 || end != 0) {
+    uint64_t size = end - begin - 1;
+    uint64_t *counters = new uint64_t[size];
+    ::memset(counters, 0, size * sizeof(uint64_t));
+    profile_ranges_.insert(
+        std::make_pair(AddressRange{begin, end - 1}, counters));
+  }
+}
+
+}  // namespace mpact::sim::cheriot
diff --git a/cheriot/profiler.h b/cheriot/profiler.h
new file mode 100644
index 0000000..8076836
--- /dev/null
+++ b/cheriot/profiler.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__PROFILER_H_
+#define MPACT_CHERIOT__PROFILER_H_
+
+#include <cstdint>
+#include <ostream>
+
+#include "absl/container/btree_map.h"
+#include "mpact/sim/generic/counters_base.h"
+#include "mpact/sim/util/memory/memory_watcher.h"
+#include "mpact/sim/util/program_loader/elf_program_loader.h"
+
+// This file contains a class definition for a profiler. It connects to a
+// counter, and whenever that counter's value is changed, the profiler takes
+// the new value using the CounterValueSetInterface SetValue() and adds that
+// sample to the profile. Instruction profiling is implemented by connecting
+// a profiler instance to a counter that has pc values written to it.
+
+namespace mpact::sim::cheriot {
+
+using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
+using AddressRangeLess = mpact::sim::util::MemoryWatcher::AddressRangeLess;
+
+using ::mpact::sim::generic::CounterValueSetInterface;
+using ::mpact::sim::util::ElfProgramLoader;
+
+class Profiler : public CounterValueSetInterface<uint64_t> {
+ public:
+  // Currently the only constructor works from text ranges in an elf file.
+  // TODO(torerik): Add constructors for other sets of ranges.
+  // The granularity is a power of two and determines the value difference
+  // between two adjacent sample buckets. For instruction profiling this is
+  // the smallest instruction size in bytes.
+  Profiler(ElfProgramLoader &elf_loader, unsigned granularity);
+  explicit Profiler(unsigned granularity);
+  ~Profiler() override;
+
+  // Inherited from CounterValueSetInterface. This will connect to a counter
+  // that is assigned the value to profile. The most recently used range is
+  // cached for performance. If it doesn't match, call AddSampleInternal().
+  void SetValue(const uint64_t &value) override {
+    // See if the previously referenced range applies.
+    uint64_t sample = value >> shift_;
+    if ((sample >= last_start_) && (sample <= last_end_)) {
+      last_profile_range_[sample - last_start_]++;
+      return;
+    }
+    AddSampleInternal(sample);
+  }
+
+  // Write the profile to the given stream in csv format.
+  void WriteProfile(std::ostream &os);
+
+  // If the elf loader wasn't set in the constructor, use this method to set
+  // it once the elf file is available.
+  void SetElfLoader(ElfProgramLoader *elf_loader);
+
+ private:
+  void AddSampleInternal(uint64_t sample);
+  int shift_ = 0;
+  ElfProgramLoader *elf_loader_ = nullptr;
+  absl::btree_map<AddressRange, uint64_t *, AddressRangeLess> profile_ranges_;
+  uint64_t last_start_ = 0xffff'ffff'ffff'ffffULL;
+  uint64_t last_end_ = 0xffff'ffff'ffff'ffffULL;
+  uint64_t *last_profile_range_ = nullptr;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__PROFILER_H_
diff --git a/cheriot/riscv_cheriot_a_instructions.cc b/cheriot/riscv_cheriot_a_instructions.cc
new file mode 100644
index 0000000..26d79cb
--- /dev/null
+++ b/cheriot/riscv_cheriot_a_instructions.cc
@@ -0,0 +1,130 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_a_instructions.h"
+
+#include <cstdint>
+
+#include "absl/status/status.h"
+#include "cheriot/cheriot_state.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+// This file contains the definitions of the semantic functions for the A, or
+// atomic, subset of the RiscV architecture. Each semantic function calls the
+// helper function which does all the heavy lifting.
+
+using Operation = util::AtomicMemoryOpInterface::Operation;
+using RV_EC = ::mpact::sim::riscv::ExceptionCode;
+
+// Helper function for the atomic memory operation semantic functions.
+template <typename T>
+static inline void AInstructionHelper(Instruction *inst, Operation op,
+                                      bool has_store_value) {
+  auto *state = static_cast<CheriotState *>(inst->state());
+  auto *atomic = state->atomic_tagged_memory();
+  // If the atomic memory operation interface is nullptr, this is an illegal
+  // instruction.
+  if (atomic == nullptr) {
+    state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
+                *RV_EC::kIllegalInstruction, inst->address(), inst);
+    return;
+  }
+  // Submit the memory operation.
+  auto address = generic::GetInstructionSource<uint64_t>(inst, 0);
+  auto *db = inst->state()->db_factory()->Allocate<T>(1);
+  db->set_latency(0);
+  // Only access the operand if there is a value to be read.
+  if (has_store_value) {
+    db->template Set<T>(0, generic::GetInstructionSource<T>(inst, 1));
+  }
+  // This transfers ownership of db to context. Don't DecRef.
+  auto *context = new riscv::LoadContext(db);
+  auto status = state->atomic_tagged_memory()->PerformMemoryOp(
+      address, op, db, inst->child(), context);
+  // If the operation is unimplemented, this is an illegal instruction.
+  if (absl::IsUnimplemented(status)) {
+    state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
+                *RV_EC::kIllegalInstruction, inst->address(), inst);
+    return;
+  }
+  context->DecRef();
+}
+
+void ALrw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kLoadLinked,
+                               /*has_store_value*/ false);
+}
+
+void AScw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kStoreConditional,
+                               /*has_store_value*/ true);
+}
+
+void AAmoswapw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicSwap,
+                               /*has_store_value*/ true);
+}
+
+void AAmoaddw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicAdd,
+                               /*has_store_value*/ true);
+}
+
+void AAmoandw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicAnd,
+                               /*has_store_value*/ true);
+}
+
+void AAmoorw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicOr,
+                               /*has_store_value*/ true);
+}
+
+void AAmoxorw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicXor,
+                               /*has_store_value*/ true);
+}
+
+void AAmomaxw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicMax,
+                               /*has_store_value*/ true);
+}
+
+void AAmomaxuw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicMaxu,
+                               /*has_store_value*/ true);
+}
+
+void AAmominw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicMin,
+                               /*has_store_value*/ true);
+}
+
+void AAmominuw(Instruction *instruction) {
+  AInstructionHelper<uint32_t>(instruction, Operation::kAtomicMinu,
+                               /*has_store_value*/ true);
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_a_instructions.h b/cheriot/riscv_cheriot_a_instructions.h
new file mode 100644
index 0000000..55babb7
--- /dev/null
+++ b/cheriot/riscv_cheriot_a_instructions.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_A_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_A_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::Instruction;
+
+// This file declares the set of semantic functions used in the A (or atomic)
+// subset of the RiscV instruction set. The *w functions are used in the
+// 32 bit version, and both *w and *d are used in the 64 bit version of RiscV.
+
+// The following instruction takes 3 source operands and one destination
+// operand. The first source is the register holding the address of the
+// memory to access, the second is the acquire bit, and the third, the release
+// bit. The instruction has a single destination operand which is the register
+// to write the result to.
+void ALrw(Instruction *instruction);
+// The following instructions take 4 source operands and one destination
+// operand. The first source is the register holding the address of the
+// memory to access, the second is the register value used in the memory
+// operation (to store, swap, etc), the third is the acquire bit, and the
+// fourth, the release bit. The instruction has a single destination operand
+// which is the register to write the result to.
+void AScw(Instruction *instruction);
+void AAmoswapw(Instruction *instruction);
+void AAmoaddw(Instruction *instruction);
+void AAmoandw(Instruction *instruction);
+void AAmoorw(Instruction *instruction);
+void AAmoxorw(Instruction *instruction);
+void AAmomaxw(Instruction *instruction);
+void AAmomaxuw(Instruction *instruction);
+void AAmominw(Instruction *instruction);
+void AAmominuw(Instruction *instruction);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_A_INSTRUCTIONS_H_
diff --git a/cheriot/riscv_cheriot_csr_enum.h b/cheriot/riscv_cheriot_csr_enum.h
new file mode 100644
index 0000000..3988611
--- /dev/null
+++ b/cheriot/riscv_cheriot_csr_enum.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_CSR_ENUM_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_CSR_ENUM_H_
+
+#include <any>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/operand_interface.h"
+
+// This file contains CHERIoT specific definitions for classes used to model the
+// RiscV control and status registers. For now, these are not modeled as actual
+// register state, instead, they're tied into the RiscV machine state a bit
+// more. This is so that side-effects from reads/writes can more easily be
+// handled.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+enum class RiscVCheriotCsrEnum {
+  // User trap setup.
+  kUStatus = 0x000,
+  kUIe = 0x004,
+  kUTvec = 0x005,
+  // User vector CSRs.
+  kVstart = 0x008,
+  kVxsat = 0x009,
+  kVxrm = 0x00a,
+  kVcsr = 0x00f,
+  // User trap handling.
+  kUScratch = 0x040,
+  kUEpc = 0x041,
+  kUCause = 0x042,
+  kUTval = 0x043,
+  kUIp = 0x044,
+  // User floating point CSRs.
+  kFFlags = 0x001,
+  kFrm = 0x002,
+  kFCsr = 0x003,
+  // User counters/timers.
+  kCycle = 0xc00,
+  kTime = 0xc01,
+  kInstret = 0xc02,
+
+  // Ignoring perf monitoring counters for now.
+
+  kCycleH = 0xc80,
+  kTimeH = 0xc81,
+  kInstretH = 0x82,
+
+  // Ignoring high bits of perf monitoring counters for now.
+
+  // Supervisor trap setup.
+  kSStatus = 0x100,
+  kSEDeleg = 0x102,
+  kSIDeleg = 0x103,
+  kSIe = 0x104,
+  kSTvec = 0x105,
+  kSCounteren = 0x106,
+  // Supervisor trap handling.
+  kSScratch = 0x140,  // Scratch register for supervisor trap handlers.
+  kSEpc = 0x141,      // Supervisor exception program counter.
+  kSCause = 0x142,    // Supervisor trap cause.
+  kSTval = 0x143,     // Supervisor bad address or instruction.
+  kSIp = 0x144,       // Supervisor interrupt pending.
+
+  // Supervisor protection and translation.
+  kSAtp = 0x180,  // Supervisor address translation and protection.
+
+  // Machine information registers.
+  kMVendorId = 0xf11,  // Vendor ID.
+  kMArchId = 0xf12,    // Architecture ID.
+  kMImpId = 0xf13,     // Implementation ID.
+  kMHartId = 0xf14,    // Hardware thread ID.
+  // Machine trap setup.
+  kMStatus = 0x300,     // Machine status register.
+  kMIsa = 0x301,        // ISA and extensions.
+  kMEDeleg = 0x302,     // Machine exception delegation register.
+  kMIDeleg = 0x303,     // Machine interrupt delegation register.
+  kMIe = 0x304,         // Machine interrupt-enable register.
+  kMTvec = 0x305,       // Machine trap-handler base address.
+  kMCounterEn = 0x306,  // Machine counter enable.
+  // Machine trap handling.
+  kMScratch = 0x340,  // Scratch register for machine trap handlers.
+  kMEpc = 0x341,      // Machine exception program counter.
+  kMCause = 0x342,    // Machine trap cause.
+  kMTval = 0x343,     // Machine bad address or instruction.
+  kMIp = 0x344,       // Machine interrupt pending.
+
+  // Ignoring machine memory protection for now.
+
+  kMCycle = 0xb00,    // Machine cycle counter.
+  kMInstret = 0xb02,  // Machine instructions-retired counter.
+
+  // Permformance counters.
+  kMHpmcounter3 = 0xb03,
+  kMHpmcounter4 = 0xb04,
+  kMHpmcounter5 = 0xb05,
+  kMHpmcounter6 = 0xb06,
+  kMHpmcounter7 = 0xb07,
+  kMHpmcounter8 = 0xb08,
+  kMHpmcounter9 = 0xb09,
+  kMHpmcounter10 = 0xb0a,
+  kMHpmcounter11 = 0xb0b,
+  kMHpmcounter12 = 0xb0c,
+  kMHpmcounter13 = 0xb0d,
+  kMHpmcounter14 = 0xb0e,
+  kMHpmcounter15 = 0xb0f,
+  kMHpmcounter16 = 0xb10,
+  kMHpmcounter17 = 0xb11,
+  kMHpmcounter18 = 0xb12,
+  kMHpmcounter19 = 0xb13,
+  kMHpmcounter20 = 0xb14,
+  kMHpmcounter21 = 0xb15,
+  kMHpmcounter22 = 0xb16,
+  kMHpmcounter23 = 0xb17,
+  kMHpmcounter24 = 0xb18,
+  kMHpmcounter25 = 0xb19,
+  kMHpmcounter26 = 0xb1a,
+  kMHpmcounter27 = 0xb1b,
+  kMHpmcounter28 = 0xb1c,
+  kMHpmcounter29 = 0xb1d,
+  kMHpmcounter30 = 0xb1e,
+  kMHpmcounter31 = 0xb1f,
+
+  kVl = 0xc20,     // Vector length.
+  kVtype = 0xc21,  // Vector type.
+  kVlenb = 0xc22,  // Vector length in bytes.
+
+  kMCycleH = 0xb80,    // Upper 32 bits of mcycle.
+  kMInstretH = 0xb82,  // Upper 32 bits of MInstret
+
+  // Performance counters (high).
+  kMHpmcounter3H = 0xb83,
+  kMHpmcounter4H = 0xb84,
+  kMHpmcounter5H = 0xb85,
+  kMHpmcounter6H = 0xb86,
+  kMHpmcounter7H = 0xb87,
+  kMHpmcounter8H = 0xb88,
+  kMHpmcounter9H = 0xb89,
+  kMHpmcounter10H = 0xb8a,
+  kMHpmcounter11H = 0xb8b,
+  kMHpmcounter12H = 0xb8c,
+  kMHpmcounter13H = 0xb8d,
+  kMHpmcounter14H = 0xb8e,
+  kMHpmcounter15H = 0xb8f,
+  kMHpmcounter16H = 0xb90,
+  kMHpmcounter17H = 0xb91,
+  kMHpmcounter18H = 0xb92,
+  kMHpmcounter19H = 0xb93,
+  kMHpmcounter20H = 0xb94,
+  kMHpmcounter21H = 0xb95,
+  kMHpmcounter22H = 0xb96,
+  kMHpmcounter23H = 0xb97,
+  kMHpmcounter24H = 0xb98,
+  kMHpmcounter25H = 0xb99,
+  kMHpmcounter26H = 0xb9a,
+  kMHpmcounter27H = 0xb9b,
+  kMHpmcounter28H = 0xb9c,
+  kMHpmcounter29H = 0xb9d,
+  kMHpmcounter30H = 0xb9e,
+  kMHpmcounter31H = 0xb9f,
+
+  // CHERI mccsr register.
+  kMCcsr = 0xbc0,
+
+  // Ignoring debug trace registers.
+
+  // Ignoring debug mode registers.
+
+  // CherIoT specific registers.
+  kMshwm = 0xbc1,   // Stack high water mark.
+  kMshwmb = 0xbc2,  // Stack high water mark base.
+
+  // Simulator specific CSRs. These are numbered 0x1800-0x18ff.
+  kSimMode = 0x1800,
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_CSR_ENUM_H_
diff --git a/cheriot/riscv_cheriot_encoding.cc b/cheriot/riscv_cheriot_encoding.cc
new file mode 100644
index 0000000..d3c66eb
--- /dev/null
+++ b/cheriot/riscv_cheriot_encoding.cc
@@ -0,0 +1,543 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_encoding.h"
+
+#include <cstdint>
+#include <string>
+
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_bin_decoder.h"
+#include "cheriot/riscv_cheriot_decoder.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "cheriot/riscv_cheriot_register_aliases.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/literal_operand.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_register.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+namespace isa32 {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+using ::mpact::sim::riscv::RVFpRegister;
+
+// Generic helper functions to create register operands.
+template <typename RegType>
+inline DestinationOperandInterface *GetRegisterDestinationOp(
+    CheriotState *state, std::string name, int latency) {
+  auto *reg = state->GetRegister<RegType>(name).first;
+  return reg->CreateDestinationOperand(latency);
+}
+
+template <typename RegType>
+inline DestinationOperandInterface *GetRegisterDestinationOp(
+    CheriotState *state, std::string name, int latency, std::string op_name) {
+  auto *reg = state->GetRegister<RegType>(name).first;
+  return reg->CreateDestinationOperand(latency, op_name);
+}
+
+template <typename T>
+inline DestinationOperandInterface *GetCSRSetBitsDestinationOp(
+    CheriotState *state, std::string name, int latency, std::string op_name) {
+  auto result = state->csr_set()->GetCsr(name);
+  if (!result.ok()) {
+    LOG(ERROR) << "No such CSR '" << name << "'";
+    return nullptr;
+  }
+  auto *csr = result.value();
+  auto *op = csr->CreateSetDestinationOperand(latency, op_name);
+  return op;
+}
+
+template <typename RegType>
+inline SourceOperandInterface *GetRegisterSourceOp(CheriotState *state,
+                                                   std::string name) {
+  auto *reg = state->GetRegister<RegType>(name).first;
+  auto *op = reg->CreateSourceOperand();
+  return op;
+}
+
+template <typename RegType>
+inline SourceOperandInterface *GetRegisterSourceOp(CheriotState *state,
+                                                   std::string name,
+                                                   std::string op_name) {
+  auto *reg = state->GetRegister<RegType>(name).first;
+  auto *op = reg->CreateSourceOperand(op_name);
+  return op;
+}
+
+RiscVCheriotEncoding::RiscVCheriotEncoding(CheriotState *state)
+    : state_(state) {
+  InitializeSourceOperandGetters();
+  InitializeDestinationOperandGetters();
+}
+
+void RiscVCheriotEncoding::InitializeSourceOperandGetters() {
+  // Source operand getters.
+  source_op_getters_.emplace(
+      *SourceOpEnum::kAAq, [this]() -> SourceOperandInterface * {
+        if (encoding::inst32_format::ExtractAq(inst_word_)) {
+          return new generic::IntLiteralOperand<1>();
+        }
+        return new generic::IntLiteralOperand<0>();
+      });
+  source_op_getters_.emplace(
+      *SourceOpEnum::kARl, [this]() -> SourceOperandInterface * {
+        if (encoding::inst32_format::ExtractRl(inst_word_)) {
+          return new generic::IntLiteralOperand<1>();
+        }
+        return new generic::IntLiteralOperand<0>();
+      });
+  source_op_getters_.emplace(*SourceOpEnum::kBImm12, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst32_format::ExtractBImm(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kC2, [this]() {
+    return GetRegisterSourceOp<CheriotRegister>(state_, "c2", "csp");
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kC3cs1, [this]() {
+    auto num = encoding::c_s::ExtractRs1(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num),
+        kCRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kC3cs2, [this]() {
+    auto num = encoding::c_s::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num),
+        kCRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kC3rs1, [this]() {
+    auto num = encoding::c_s::ExtractRs1(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kC3rs2, [this]() {
+    auto num = encoding::c_s::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCcs2, [this]() {
+    auto num = encoding::c_s_s::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num),
+        kCRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCgp, [this]() {
+    return GetRegisterSourceOp<CheriotRegister>(state_, "c3", "c3");
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCSRUimm5, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst32_format::ExtractIUimm5(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCfrs2, [this]() {
+    auto num = encoding::c_r::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<RVFpRegister>(
+        state_, absl::StrCat(CheriotState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCrs1, [this]() {
+    auto num = encoding::c_r::ExtractRs1(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCrs2, [this]() {
+    auto num = encoding::c_r::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCs1, [this]() {
+    auto num = encoding::r_type::ExtractRs1(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num),
+        kCRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCs2, [this]() {
+    auto num = encoding::r_type::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num),
+        kCRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kCsr, [this]() {
+    auto csr_indx = encoding::i_type::ExtractUImm12(inst_word_);
+    auto res = state_->csr_set()->GetCsr(csr_indx);
+    if (!res.ok()) {
+      return new generic::ImmediateOperand<uint32_t>(csr_indx);
+    }
+    auto *csr = res.value();
+    return new generic::ImmediateOperand<uint32_t>(csr_indx, csr->name());
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kFrs1, [this]() {
+    int num = encoding::r_type::ExtractRs1(inst_word_);
+    return GetRegisterSourceOp<RVFpRegister>(
+        state_, absl::StrCat(CheriotState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kFrs2, [this]() {
+    int num = encoding::r_type::ExtractRs2(inst_word_);
+    return GetRegisterSourceOp<RVFpRegister>(
+        state_, absl::StrCat(CheriotState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kFrs3, [this]() {
+    int num = encoding::r4_type::ExtractRs3(inst_word_);
+    return GetRegisterSourceOp<RVFpRegister>(
+        state_, absl::StrCat(CheriotState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICbImm8, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst16_format::ExtractBimm(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiImm6, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::c_i::ExtractImm6(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiImm612, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst16_format::ExtractImm18(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiUimm6, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractUimm6(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiUimm6x4, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractCiImmW(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiImm6x16, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst16_format::ExtractCiImm10(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiUimm6x8, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractCiImmD(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICiwUimm8x4, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractCiwImm10(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICjImm11, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst16_format::ExtractJimm(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kIClUimm5x4, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractClImmW(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kIClUimm5x8, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractClImmD(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICshUimm6, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::c_s_h::ExtractUimm6(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICshImm6, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::c_s_h::ExtractImm6(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICssUimm6x4, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractCssImmW(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kICssUimm6x8, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst16_format::ExtractCssImmD(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kIImm12, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst32_format::ExtractImm12(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kIUimm5, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::i5_type::ExtractRUimm5(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kIUimm12, [this]() {
+    return new generic::ImmediateOperand<uint32_t>(
+        encoding::inst32_format::ExtractUImm12(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kJImm12, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst32_format::ExtractImm12(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kJImm20, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst32_format::ExtractJImm(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kPcc, [this]() {
+    return GetRegisterSourceOp<CheriotRegister>(state_, "pcc", "pcc");
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kRm,
+                             [this]() -> SourceOperandInterface * {
+                               uint32_t rm = (inst_word_ >> 12) & 0x7;
+                               switch (rm) {
+                                 case 0:
+                                   return new generic::IntLiteralOperand<0>();
+                                 case 1:
+                                   return new generic::IntLiteralOperand<1>();
+                                 case 2:
+                                   return new generic::IntLiteralOperand<2>();
+                                 case 3:
+                                   return new generic::IntLiteralOperand<3>();
+                                 case 4:
+                                   return new generic::IntLiteralOperand<4>();
+                                 case 5:
+                                   return new generic::IntLiteralOperand<5>();
+                                 case 6:
+                                   return new generic::IntLiteralOperand<6>();
+                                 case 7:
+                                   return new generic::IntLiteralOperand<7>();
+                                 default:
+                                   return nullptr;
+                               }
+                             });
+  source_op_getters_.emplace(
+      *SourceOpEnum::kRd, [this]() -> SourceOperandInterface * {
+        int num = encoding::r_type::ExtractRd(inst_word_);
+        if (num == 0) return new generic::IntLiteralOperand<0>({1});
+        return GetRegisterSourceOp<CheriotRegister>(
+            state_, absl::StrCat(CheriotState::kXregPrefix, num),
+            kXRegisterAliases[num]);
+      });
+  source_op_getters_.emplace(
+      *SourceOpEnum::kRs1, [this]() -> SourceOperandInterface * {
+        int num = encoding::r_type::ExtractRs1(inst_word_);
+        if (num == 0) return new generic::IntLiteralOperand<0>({1});
+        return GetRegisterSourceOp<CheriotRegister>(
+            state_, absl::StrCat(CheriotState::kXregPrefix, num),
+            kXRegisterAliases[num]);
+      });
+  source_op_getters_.emplace(
+      *SourceOpEnum::kRs2, [this]() -> SourceOperandInterface * {
+        int num = encoding::r_type::ExtractRs2(inst_word_);
+        if (num == 0) return new generic::IntLiteralOperand<0>({1});
+        return GetRegisterSourceOp<CheriotRegister>(
+            state_, absl::StrCat(CheriotState::kXregPrefix, num),
+            kXRegisterAliases[num]);
+      });
+  source_op_getters_.emplace(*SourceOpEnum::kSImm12, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::s_type::ExtractSImm(inst_word_));
+  });
+  source_op_getters_.emplace(
+      *SourceOpEnum::kScr, [this]() -> SourceOperandInterface * {
+        int csr_indx = encoding::r_type::ExtractRs2(inst_word_);
+        std::string csr_name;
+        switch (csr_indx) {
+          case 28:
+            csr_name = "mtcc";
+            break;
+          case 29:
+            csr_name = "mtdc";
+            break;
+          case 30:
+            csr_name = "mscratchc";
+            break;
+          case 31:
+            csr_name = "mepcc";
+            break;
+          default:
+            return nullptr;
+        }
+        auto res = state_->csr_set()->GetCsr(csr_name);
+        if (!res.ok()) {
+          return GetRegisterSourceOp<CheriotRegister>(state_, csr_name,
+                                                      csr_name);
+        }
+        auto *csr = res.value();
+        auto *op = csr->CreateSourceOperand();
+        return op;
+      });
+  source_op_getters_.emplace(*SourceOpEnum::kSImm20, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::u_type::ExtractSImm(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kUImm20, [this]() {
+    return new generic::ImmediateOperand<int32_t>(
+        encoding::inst32_format::ExtractUImm(inst_word_));
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kX0, []() {
+    return new generic::IntLiteralOperand<0>({1});
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kX2, [this]() {
+    return GetRegisterSourceOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, 2),
+        kXRegisterAliases[2]);
+  });
+  source_op_getters_.emplace(*SourceOpEnum::kNone, []() { return nullptr; });
+}
+
+void RiscVCheriotEncoding::InitializeDestinationOperandGetters() {
+  // Destination operand getters.
+  dest_op_getters_.emplace(*DestOpEnum::kC2, [this](int latency) {
+    return GetRegisterDestinationOp<CheriotRegister>(state_, "c2", latency,
+                                                     "csp");
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kC3cd, [this](int latency) {
+    int num = encoding::c_l::ExtractRd(inst_word_);
+    return GetRegisterDestinationOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num), latency,
+        kCRegisterAliases[num]);
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kC3rd, [this](int latency) {
+    int num = encoding::c_l::ExtractRd(inst_word_);
+    if (num == 0) {
+      return GetRegisterDestinationOp<CheriotRegister>(state_, "X0Dest",
+                                                       latency);
+    }
+    return GetRegisterDestinationOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, num), latency,
+        kXRegisterAliases[num]);
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kC3rs1, [this](int latency) {
+    int num = encoding::c_l::ExtractRs1(inst_word_);
+    return GetRegisterDestinationOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, num), latency,
+        kXRegisterAliases[num]);
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kCd, [this](int latency) {
+    int num = encoding::r_type::ExtractRd(inst_word_);
+    if (num == 0) {
+      return GetRegisterDestinationOp<CheriotRegister>(state_, "X0Dest",
+                                                       latency);
+    }
+    return GetRegisterDestinationOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kCregPrefix, num), latency,
+        kCRegisterAliases[num]);
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kCsr, [this](int latency) {
+    return GetRegisterDestinationOp<CheriotRegister>(
+        state_, CheriotState::kCsrName, latency);
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kFrd, [this](int latency) {
+    int num = encoding::r_type::ExtractRd(inst_word_);
+    return GetRegisterDestinationOp<RVFpRegister>(
+        state_, absl::StrCat(CheriotState::kFregPrefix, num), latency,
+        kFRegisterAliases[num]);
+  });
+  dest_op_getters_.emplace(
+      *DestOpEnum::kScr, [this](int latency) -> DestinationOperandInterface * {
+        int csr_indx = encoding::r_type::ExtractRs2(inst_word_);
+        std::string csr_name;
+        switch (csr_indx) {
+          case 28:
+            csr_name = "mtcc";
+            break;
+          case 29:
+            csr_name = "mtdc";
+            break;
+          case 30:
+            csr_name = "mscratchc";
+            break;
+          case 31:
+            csr_name = "mepcc";
+            break;
+          default:
+            return nullptr;
+        }
+        auto res = state_->csr_set()->GetCsr(csr_name);
+        if (!res.ok()) {
+          return GetRegisterDestinationOp<CheriotRegister>(state_, csr_name,
+                                                           latency);
+        }
+        auto *csr = res.value();
+        auto *op = csr->CreateWriteDestinationOperand(latency, csr_name);
+        return op;
+      });
+  dest_op_getters_.emplace(
+      *DestOpEnum::kRd, [this](int latency) -> DestinationOperandInterface * {
+        int num = encoding::r_type::ExtractRd(inst_word_);
+        if (num == 0) {
+          return GetRegisterDestinationOp<CheriotRegister>(state_, "X0Dest", 0);
+        } else {
+          return GetRegisterDestinationOp<RVFpRegister>(
+              state_, absl::StrCat(CheriotState::kXregPrefix, num), latency,
+              kXRegisterAliases[num]);
+        }
+      });
+  dest_op_getters_.emplace(*DestOpEnum::kX1, [this](int latency) {
+    return GetRegisterDestinationOp<CheriotRegister>(
+        state_, absl::StrCat(CheriotState::kXregPrefix, 1), latency,
+        kXRegisterAliases[1]);
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kFflags, [this](int latency) {
+    return GetCSRSetBitsDestinationOp<uint32_t>(state_, "fflags", latency, "");
+  });
+  dest_op_getters_.emplace(*DestOpEnum::kNone,
+                           [](int latency) { return nullptr; });
+}
+
+// Parse the instruction word to determine the opcode.
+void RiscVCheriotEncoding::ParseInstruction(uint32_t inst_word) {
+  inst_word_ = inst_word;
+  if ((inst_word_ & 0x3) == 3) {
+    auto [opcode, format] =
+        mpact::sim::cheriot::encoding::DecodeRiscVCheriotInst32WithFormat(
+            inst_word_);
+    opcode_ = opcode;
+    format_ = format;
+    return;
+  }
+
+  auto [opcode, format] =
+      mpact::sim::cheriot::encoding::DecodeRiscVCheriotInst16WithFormat(
+          static_cast<uint16_t>(inst_word_ & 0xffff));
+  opcode_ = opcode;
+  format_ = format;
+}
+
+DestinationOperandInterface *RiscVCheriotEncoding::GetDestination(
+    SlotEnum, int, OpcodeEnum opcode, DestOpEnum dest_op, int dest_no,
+    int latency) {
+  int index = static_cast<int>(dest_op);
+  auto iter = dest_op_getters_.find(index);
+  if (iter == dest_op_getters_.end()) {
+    LOG(ERROR) << absl::StrCat("No getter for destination op enum value ",
+                               index, " for instruction ",
+                               kOpcodeNames[static_cast<int>(opcode)]);
+    return nullptr;
+  }
+  return (iter->second)(latency);
+}
+
+SourceOperandInterface *RiscVCheriotEncoding::GetSource(SlotEnum, int,
+                                                        OpcodeEnum opcode,
+                                                        SourceOpEnum source_op,
+                                                        int source_no) {
+  int index = static_cast<int>(source_op);
+  auto iter = source_op_getters_.find(index);
+  if (iter == source_op_getters_.end()) {
+    LOG(ERROR) << absl::StrCat("No getter for source op enum value ", index,
+                               " for instruction ",
+                               kOpcodeNames[static_cast<int>(opcode)]);
+    return nullptr;
+  }
+  return (iter->second)();
+}
+
+}  // namespace isa32
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_encoding.h b/cheriot/riscv_cheriot_encoding.h
new file mode 100644
index 0000000..de50ca2
--- /dev/null
+++ b/cheriot/riscv_cheriot_encoding.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_ENCODING_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_ENCODING_H_
+
+#include <cstdint>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/functional/any_invocable.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_bin_decoder.h"
+#include "cheriot/riscv_cheriot_decoder.h"
+#include "cheriot/riscv_cheriot_enums.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+namespace isa32 {
+
+using ::mpact::sim::cheriot::encoding::FormatEnum;
+
+// This class provides the interface between the generated instruction decoder
+// framework (which is agnostic of the actual bit representation of
+// instructions) and the instruction representation. This class provides methods
+// to return the opcode, source operands, and destination operands for
+// instructions according to the operand fields in the encoding.
+class RiscVCheriotEncoding : public RiscVCheriotEncodingBase {
+ public:
+  explicit RiscVCheriotEncoding(CheriotState *state);
+
+  // Parses an instruction and determines the opcode.
+  void ParseInstruction(uint32_t inst_word);
+
+  // RiscV32 CHERIoT has a single slot type and single entry, so the following
+  // methods ignore those parameters.
+
+  // Returns the opcode in the current instruction representation.
+  OpcodeEnum GetOpcode(SlotEnum, int) override { return opcode_; }
+
+  // Returns the instruction format in the current instruction representation.
+  FormatEnum GetFormat(SlotEnum, int) { return format_; }
+
+  // There is no predicate, so return nullptr.
+  PredicateOperandInterface *GetPredicate(SlotEnum, int, OpcodeEnum,
+                                          PredOpEnum) override {
+    return nullptr;
+  }
+
+  // Currently no resources modeled for RiscV CHERIoT.
+  ResourceOperandInterface *GetSimpleResourceOperand(
+      SlotEnum, int, OpcodeEnum, SimpleResourceVector &resource_vec,
+      int end) override {
+    return nullptr;
+  }
+
+  ResourceOperandInterface *GetComplexResourceOperand(
+      SlotEnum, int, OpcodeEnum, ComplexResourceEnum resource, int begin,
+      int end) override {
+    return nullptr;
+  }
+
+  // The following method returns a source operand that corresponds to the
+  // particular operand field.
+  SourceOperandInterface *GetSource(SlotEnum, int, OpcodeEnum, SourceOpEnum op,
+                                    int source_no) override;
+
+  // The following method returns a destination operand that corresponds to the
+  // particular operand field.
+  DestinationOperandInterface *GetDestination(SlotEnum, int, OpcodeEnum,
+                                              DestOpEnum op, int dest_no,
+                                              int latency) override;
+  // This method returns latency for any destination operand for which the
+  // latency specifier in the .isa file is '*'. Since there are none, just
+  // return 0.
+  int GetLatency(SlotEnum, int, OpcodeEnum, DestOpEnum, int) override {
+    return 0;
+  }
+
+ private:
+  using SourceOpGetterMap =
+      absl::flat_hash_map<int, absl::AnyInvocable<SourceOperandInterface *()>>;
+  using DestOpGetterMap = absl::flat_hash_map<
+      int, absl::AnyInvocable<DestinationOperandInterface *(int)>>;
+
+  // These two methods initialize the source and destination operand getter
+  // arrays.
+  void InitializeSourceOperandGetters();
+  void InitializeDestinationOperandGetters();
+
+  SourceOpGetterMap source_op_getters_;
+  DestOpGetterMap dest_op_getters_;
+  CheriotState *state_;
+  uint32_t inst_word_;
+  OpcodeEnum opcode_;
+  FormatEnum format_;
+};
+
+}  // namespace isa32
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_ENCODING_H_
diff --git a/cheriot/riscv_cheriot_f_instructions.cc b/cheriot/riscv_cheriot_f_instructions.cc
new file mode 100644
index 0000000..a5d9b15
--- /dev/null
+++ b/cheriot/riscv_cheriot_f_instructions.cc
@@ -0,0 +1,452 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_f_instructions.h"
+
+#include <cmath>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <tuple>
+#include <type_traits>
+
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_instruction_helpers.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_register.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::riscv::LoadContext;
+
+// The following instruction semantic functions implement the single precision
+// floating point instructions in the RiscV architecture. They all utilize the
+// templated helper functions in riscv_instruction_helpers.h to implement
+// the boiler plate code.
+
+using XRegister = CheriotRegister;
+using FPRegister = riscv::RVFpRegister;
+using RegUInt =
+    typename std::make_unsigned<riscv::RVFpRegister::ValueType>::type;
+
+// These types are used instead of uint32_t and int32_t to represent the
+// integer type of equal value to float when values of these types are
+// really reinterpreted float values.
+using FPUInt = FPTypeInfo<float>::UIntType;
+using FPSInt = FPTypeInfo<float>::IntType;
+
+// Note, for any SP operation on values in 64-bit DP registers, the input
+// values have to be properly NaN-boxed. If not, the value is treated as
+// a canonical NaN.
+
+// Templated helper functions.
+
+namespace internal {
+
+// Convert float to signed 32 bit integer.
+template <typename XInt>
+static inline void RVFCvtWs(const Instruction *instruction) {
+  RiscVConvertFloatWithFflagsOp<CheriotRegister, XInt, float, int32_t>(
+      instruction);
+}
+
+// Convert float to unsigned 32 bit integer.
+template <typename XInt>
+static inline void RVFCvtWus(const Instruction *instruction) {
+  RiscVConvertFloatWithFflagsOp<CheriotRegister, XInt, float, uint32_t>(
+      instruction);
+}
+
+// Single precision compare equal.
+template <typename XRegister>
+static inline void RVFCmpeq(const Instruction *instruction) {
+  RVCheriotBinaryNaNBoxOp<typename XRegister::ValueType,
+                          typename XRegister::ValueType, float>(
+      instruction,
+      [instruction](float a, float b) -> typename XRegister::ValueType {
+        if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
+          auto *db = instruction->Destination(1)->AllocateDataBuffer();
+          db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          db->Submit();
+        }
+        return a == b;
+      });
+}
+
+// Single precicion compare less than.
+template <typename XRegister>
+static inline void RVFCmplt(const Instruction *instruction) {
+  RVCheriotBinaryNaNBoxOp<typename XRegister::ValueType,
+                          typename XRegister::ValueType, float>(
+      instruction,
+      [instruction](float a, float b) -> typename XRegister::ValueType {
+        if (FPTypeInfo<float>::IsNaN(a) || FPTypeInfo<float>::IsNaN(b)) {
+          auto *db = instruction->Destination(1)->AllocateDataBuffer();
+          db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          db->Submit();
+        }
+        return a < b;
+      });
+}
+
+// Single precision compare less than or equal.
+template <typename XRegister>
+static inline void RVFCmple(const Instruction *instruction) {
+  RVCheriotBinaryNaNBoxOp<typename XRegister::ValueType,
+                          typename XRegister::ValueType, float>(
+      instruction,
+      [instruction](float a, float b) -> typename XRegister::ValueType {
+        if (FPTypeInfo<float>::IsNaN(a) || FPTypeInfo<float>::IsNaN(b)) {
+          auto *db = instruction->Destination(1)->AllocateDataBuffer();
+          db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          db->Submit();
+        }
+        return a <= b;
+      });
+}
+
+template <typename T>
+static inline T CanonicalizeNaN(T value) {
+  if (!std::isnan(value)) return value;
+  auto nan_value = FPTypeInfo<T>::kCanonicalNaN;
+  return *reinterpret_cast<T *>(&nan_value);
+}
+
+}  // namespace internal
+
+// Load child instruction.
+void RiscVIFlwChild(const Instruction *instruction) {
+  LoadContext *context = static_cast<LoadContext *>(instruction->context());
+  auto value = context->value_db->Get<FPUInt>(0);
+  auto *reg =
+      static_cast<generic::RegisterDestinationOperand<FPRegister::ValueType> *>(
+          instruction->Destination(0))
+          ->GetRegister();
+  if (sizeof(FPRegister::ValueType) > sizeof(FPUInt)) {
+    // NaN box the loaded value.
+    auto reg_value = std::numeric_limits<FPRegister::ValueType>::max();
+    reg_value <<= sizeof(FPUInt) * 8;
+    reg_value |= value;
+    reg->data_buffer()->Set<FPRegister::ValueType>(0, reg_value);
+    return;
+  }
+  reg->data_buffer()->Set<FPRegister::ValueType>(0, value);
+}
+
+// Basic arithmetic instructions.
+void RiscVFAdd(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
+      instruction, [](float a, float b) { return a + b; });
+}
+
+void RiscVFSub(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
+      instruction, [](float a, float b) { return a - b; });
+}
+
+void RiscVFMul(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
+      instruction, [](float a, float b) { return a * b; });
+}
+
+void RiscVFDiv(const Instruction *instruction) {
+  RiscVBinaryFloatNaNBoxOp<FPRegister::ValueType, float, float>(
+      instruction, [](float a, float b) { return a / b; });
+}
+
+// Square root uses the library square root.
+void RiscVFSqrt(const Instruction *instruction) {
+  RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, FPRegister::ValueType, float,
+                          float>(instruction, [](float a) -> float {
+    float res = sqrt(a);
+    if (std::isnan(res))
+      return *reinterpret_cast<const float *>(
+          &FPTypeInfo<float>::kCanonicalNaN);
+    return res;
+  });
+}
+
+// If either operand is NaN return the other.
+void RiscVFMin(const Instruction *instruction) {
+  RiscVBinaryOp<FPRegister, float, float>(
+      instruction, [instruction](float a, float b) -> float {
+        if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
+          auto *db = instruction->Destination(1)->AllocateDataBuffer();
+          db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          db->Submit();
+        }
+        if (FPTypeInfo<float>::IsNaN(a)) {
+          if (FPTypeInfo<float>::IsNaN(b)) {
+            FPTypeInfo<float>::UIntType not_a_number =
+                FPTypeInfo<float>::kCanonicalNaN;
+            return *reinterpret_cast<float *>(&not_a_number);
+          }
+          return b;
+        }
+        if (FPTypeInfo<float>::IsNaN(b)) return a;
+        // If both are zero, return the negative zero if there is one.
+        if ((a == 0.0) && (b == 0.0)) return (std::signbit(a)) ? a : b;
+        return (a > b) ? b : a;
+      });
+}
+
+// If either operand is NaN return the other.
+void RiscVFMax(const Instruction *instruction) {
+  RiscVBinaryOp<FPRegister, float, float>(
+      instruction, [instruction](float a, float b) -> float {
+        if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
+          auto *db = instruction->Destination(1)->AllocateDataBuffer();
+          db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          db->Submit();
+        }
+        if (FPTypeInfo<float>::IsNaN(a)) {
+          if (FPTypeInfo<float>::IsNaN(b)) {
+            FPTypeInfo<float>::UIntType not_a_number =
+                FPTypeInfo<float>::kCanonicalNaN;
+            return *reinterpret_cast<float *>(&not_a_number);
+          }
+          return b;
+        }
+        if (FPTypeInfo<float>::IsNaN(b)) return a;
+        // If both are zero, return the positive zero if there is one.
+        if ((a == 0.0) && (b == 0.0)) return (std::signbit(b)) ? a : b;
+        return (a < b) ? b : a;
+      });
+}
+
+// Four flavors of multiply-accumulate.
+// Multiply-add (a * b) + c
+// Multiply-subtract (a * b) - c
+// Negated multiply-add -((a * b) + c)
+// Negated multiply-subtract -((a * b) - c)
+
+void RiscVFMadd(const Instruction *instruction) {
+  using T = float;
+  RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
+      instruction, [instruction](T a, T b, T c) -> T {
+        // Propagate any NaNs.
+        if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
+        if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
+        if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
+          auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
+          flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          flag_db->Submit();
+        }
+        if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
+        if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return c;
+        if (c == 0.0) {
+          if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
+            FPUInt c_sign = *reinterpret_cast<FPUInt *>(&c) >>
+                            (FPTypeInfo<T>::kBitSize - 1);
+            FPUInt ua = *reinterpret_cast<FPUInt *>(&a);
+            FPUInt ub = *reinterpret_cast<FPUInt *>(&b);
+            FPUInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
+            if (prod_sign != c_sign) return 0.0;
+            return c;
+          }
+          return internal::CanonicalizeNaN(a * b);
+        }
+        return internal::CanonicalizeNaN((a * b) + c);
+      });
+}
+
+void RiscVFMsub(const Instruction *instruction) {
+  using T = float;
+  RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
+      instruction, [instruction](T a, T b, T c) -> T {
+        if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
+        if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
+        if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
+          auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
+          flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          flag_db->Submit();
+        }
+        if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
+        if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return -c;
+        if (c == 0.0) {
+          if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
+            FPUInt c_sign = -*reinterpret_cast<FPUInt *>(&c) >>
+                            (FPTypeInfo<T>::kBitSize - 1);
+            FPUInt ua = *reinterpret_cast<FPUInt *>(&a);
+            FPUInt ub = *reinterpret_cast<FPUInt *>(&b);
+            FPUInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
+            if (prod_sign == c_sign) return 0.0;
+            return -c;
+          }
+          return internal::CanonicalizeNaN(a * b);
+        }
+        return internal::CanonicalizeNaN((a * b) - c);
+      });
+}
+
+void RiscVFNmadd(const Instruction *instruction) {
+  using T = float;
+  RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
+      instruction, [instruction](T a, T b, T c) -> T {
+        if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
+        if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
+        if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
+          auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
+          flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          flag_db->Submit();
+        }
+        if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
+        if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return -c;
+        if (c == 0.0) {
+          if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
+            FPUInt c_sign = *reinterpret_cast<FPUInt *>(&c) >>
+                            (FPTypeInfo<T>::kBitSize - 1);
+            FPUInt ua = *reinterpret_cast<FPUInt *>(&a);
+            FPUInt ub = *reinterpret_cast<FPUInt *>(&b);
+            FPUInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
+            if (prod_sign != c_sign) return 0.0;
+            return -c;
+          }
+          return internal::CanonicalizeNaN(-a * b);
+        }
+        return internal::CanonicalizeNaN(-((a * b) + c));
+      });
+}
+
+void RiscVFNmsub(const Instruction *instruction) {
+  using T = float;
+  RiscVTernaryFloatNaNBoxOp<FPRegister::ValueType, T, T>(
+      instruction, [instruction](T a, T b, T c) -> T {
+        if (FPTypeInfo<T>::IsNaN(a)) return internal::CanonicalizeNaN(a);
+        if (FPTypeInfo<T>::IsNaN(b)) return internal::CanonicalizeNaN(b);
+        if ((std::isinf(a) && (b == 0.0)) || ((std::isinf(b) && (a == 0.0)))) {
+          auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
+          flag_db->Set<uint32_t>(0, *FPExceptions::kInvalidOp);
+          flag_db->Submit();
+        }
+        if (FPTypeInfo<T>::IsNaN(c)) return internal::CanonicalizeNaN(c);
+        if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) return c;
+        if (c == 0.0) {
+          if ((a == 0.0 && !std::isinf(b)) || (b == 0.0 && !std::isinf(a))) {
+            FPUInt c_sign = -*reinterpret_cast<FPUInt *>(&c) >>
+                            (FPTypeInfo<T>::kBitSize - 1);
+            FPUInt ua = *reinterpret_cast<FPUInt *>(&a);
+            FPUInt ub = *reinterpret_cast<FPUInt *>(&b);
+            FPUInt prod_sign = (ua ^ ub) >> (FPTypeInfo<T>::kBitSize - 1);
+            if (prod_sign != c_sign) return 0.0;
+            return c;
+          }
+          return internal::CanonicalizeNaN(-a * b);
+        }
+        return internal::CanonicalizeNaN(-((a * b) - c));
+      });
+}
+
+// Set sign of the first operand to that of the second.
+void RiscVFSgnj(const Instruction *instruction) {
+  RiscVBinaryNaNBoxOp<FPRegister::ValueType, FPUInt, FPUInt>(
+      instruction,
+      [](FPUInt a, FPUInt b) { return (a & 0x7fff'ffff) | (b & 0x8000'0000); });
+}
+
+// Set the sign of the first operand to the opposite of the second.
+void RiscVFSgnjn(const Instruction *instruction) {
+  RiscVBinaryNaNBoxOp<FPRegister::ValueType, FPUInt, FPUInt>(
+      instruction, [](FPUInt a, FPUInt b) {
+        return (a & 0x7fff'ffff) | (~b & 0x8000'0000);
+      });
+}
+
+// Set the sign of the first operand to the xor of the signs of the two
+// operands.
+void RiscVFSgnjx(const Instruction *instruction) {
+  RiscVBinaryNaNBoxOp<FPRegister::ValueType, FPUInt, FPUInt>(
+      instruction, [](FPUInt a, FPUInt b) {
+        return (a & 0x7fff'ffff) | ((a ^ b) & 0x8000'0000);
+      });
+}
+
+// Convert signed 32 bit integer to float.
+void RiscVFCvtSw(const Instruction *instruction) {
+  RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, uint32_t, float, int32_t>(
+      instruction, [](int32_t a) -> float { return static_cast<float>(a); });
+}
+
+// Convert unsigned 32 bit integer to float.
+void RiscVFCvtSwu(const Instruction *instruction) {
+  RiscVUnaryFloatNaNBoxOp<FPRegister::ValueType, uint32_t, float, uint32_t>(
+      instruction, [](uint32_t a) -> float { return static_cast<float>(a); });
+}
+
+// Single precision move instruction from integer to fp register file.
+void RiscVFMvwx(const Instruction *instruction) {
+  RiscVUnaryNaNBoxOp<FPRegister::ValueType, uint32_t, uint32_t, uint32_t>(
+      instruction, [](uint32_t a) -> uint32_t { return a; });
+}
+
+using XRegister = CheriotRegister;
+using XUint = typename std::make_unsigned<XRegister::ValueType>::type;
+using XInt = typename std::make_signed<XRegister::ValueType>::type;
+
+void RiscVFSw(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  if (state->mstatus()->fs() == 0) return;
+  RVCheriotStore<CheriotRegister, int32_t>(instruction);
+}
+
+// Single precision conversion instructions.
+
+// Convert float to signed 32 bit integer.
+void RiscVFCvtWs(const Instruction *instruction) {
+  internal::RVFCvtWs<XInt>(instruction);
+}
+
+// Convert float to unsigned 32 bit integer.
+void RiscVFCvtWus(const Instruction *instruction) {
+  internal::RVFCvtWus<XInt>(instruction);
+}
+
+// Single precision move instruction to integer register file, with
+// sign-extension.
+void RiscVFMvxw(const Instruction *instruction) {
+  RVCheriotUnaryOp<XRegister, int32_t, int32_t>(instruction,
+                                                [](int32_t a) { return a; });
+}
+
+// Single precision compare equal.
+void RiscVFCmpeq(const Instruction *instruction) {
+  internal::RVFCmpeq<XRegister>(instruction);
+}
+
+// Single precicion compare less than.
+void RiscVFCmplt(const Instruction *instruction) {
+  internal::RVFCmplt<XRegister>(instruction);
+}
+
+// Single precision compare less than or equal.
+void RiscVFCmple(const Instruction *instruction) {
+  internal::RVFCmple<XRegister>(instruction);
+}
+
+// Single precision fp class instruction.
+void RiscVFClass(const Instruction *instruction) {
+  RVCheriotUnaryOp<XRegister, uint32_t, float>(
+      instruction,
+      [](float a) -> uint32_t { return static_cast<uint32_t>(ClassifyFP(a)); });
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_f_instructions.h b/cheriot/riscv_cheriot_f_instructions.h
new file mode 100644
index 0000000..12c30f9
--- /dev/null
+++ b/cheriot/riscv_cheriot_f_instructions.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_F_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_F_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+// This file declares the semantic functions that implement the scalar
+// single precision floating point instructions in the RiscV architecture.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using generic::Instruction;
+
+// Single precision arithmetic instructions each take 3 source operands and
+// 1 destination register operand. Source 0 and 1 are the data sources to the
+// operation, source 2 is the rounding mode specifier in the instruction.
+void RiscVFAdd(const Instruction *instruction);
+void RiscVFSub(const Instruction *instruction);
+void RiscVFMul(const Instruction *instruction);
+void RiscVFDiv(const Instruction *instruction);
+// Single precision load child instruction takes a single destination operand.
+void RiscVIFlwChild(const Instruction *instruction);
+// Single precision square root takes 2 source operands, the source register
+// and the rounding mode, and 1 destination register operand.
+void RiscVFSqrt(const Instruction *instruction);
+// The single precision Min and Max instructions each take 2 source register
+// operands and 1 destination register operand.
+void RiscVFMin(const Instruction *instruction);
+void RiscVFMax(const Instruction *instruction);
+// The following four multiply-accumulate instruction each take 3 source
+// register operands (0, 1, 2) and the rounding mode (3), and one destination
+// register operand.
+void RiscVFMadd(const Instruction *instruction);
+void RiscVFMsub(const Instruction *instruction);
+void RiscVFNmadd(const Instruction *instruction);
+void RiscVFNmsub(const Instruction *instruction);
+// The single precision sign manipulation instructions take 2 source register
+// operands and a single destination register operand.
+void RiscVFSgnj(const Instruction *instruction);
+void RiscVFSgnjn(const Instruction *instruction);
+void RiscVFSgnjx(const Instruction *instruction);
+// The single precision conversion instructions each take 1 source register
+// operand, the rounding mode, and 1 destination register operand.
+void RiscVFCvtSw(const Instruction *instruction);
+void RiscVFCvtSwu(const Instruction *instruction);
+void RiscVFCvtSl(const Instruction *instruction);
+void RiscVFCvtSlu(const Instruction *instruction);
+// The move instruction takes a single register source operand and a single
+void RiscVFMvwx(const Instruction *instruction);
+
+// Store float instruction semantic function, source operand 0 is the base
+// register, source operand 1 is the offset, while source operand 2 is the value
+// to be stored referred to by rs2.
+void RiscVFSw(const Instruction *instruction);
+// Single precision load child instruction takes a single destination operand.
+// The single precision conversion instructions each take 1 source register
+// operand, the rounding mode, and 1 destination register operand.
+void RiscVFCvtWs(const Instruction *instruction);
+void RiscVFCvtWus(const Instruction *instruction);
+// The move instruction takes a single register source operand and a single
+// destination register operand.
+void RiscVFMvxw(const Instruction *instruction);
+// The single precision compare instructions take 2 source register operands
+// and a single destination register operand.
+void RiscVFCmpeq(const Instruction *instruction);
+void RiscVFCmplt(const Instruction *instruction);
+void RiscVFCmple(const Instruction *instruction);
+// The single precision class instruction takes a single source register operand
+// and a single destination register operand.
+void RiscVFClass(const Instruction *instruction);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_F_INSTRUCTIONS_H_
diff --git a/cheriot/riscv_cheriot_fp_state.cc b/cheriot/riscv_cheriot_fp_state.cc
new file mode 100644
index 0000000..1dcbf52
--- /dev/null
+++ b/cheriot/riscv_cheriot_fp_state.cc
@@ -0,0 +1,182 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_fp_state.h"
+
+#include <cstdint>
+
+#include "absl/log/log.h"
+#include "cheriot/cheriot_state.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_csr.h"
+#include "riscv//riscv_fp_host.h"
+#include "riscv//riscv_fp_info.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::riscv::FPRoundingMode;
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+using ::mpact::sim::riscv::GetHostFloatingPointInterface;
+using ::mpact::sim::riscv::RiscVCsrEnum;
+
+constexpr char kFcsrName[] = "fcsr";
+constexpr uint32_t kFcsrInitial = 0b000'0'0'0'0'0;
+constexpr uint32_t kFcsrReadMask = 0b111'1'1111;
+constexpr uint32_t kFcsrWriteMask = 0b111'1'1111;
+
+constexpr char kFflagsName[] = "fflags";
+constexpr uint32_t kFflagsInitial = 0b0'0000;
+constexpr uint32_t kFflagsReadMask = 0b1'1111;
+constexpr uint32_t kFflagsWriteMask = 0b1'1111;
+
+constexpr char kFrmName[] = "frm";
+constexpr uint32_t kFrmInitial = 0b000;
+constexpr uint32_t kFrmReadMask = 0b111;
+constexpr uint32_t kFrmWriteMask = 0b111;
+
+// Helper function to avoid some extra code below.
+static inline void LogIfError(absl::Status status) {
+  if (status.ok()) return;
+  LOG(ERROR) << status.message();
+}
+
+RiscVCheriotFPState::RiscVCheriotFPState(CheriotState *rv_state)
+    : rv_state_(rv_state) {
+  fcsr_ = new RiscVFcsr(this);
+  frm_ = new RiscVFrm(this);
+  fflags_ = new RiscVFflags(this);
+  host_fp_interface_ = GetHostFloatingPointInterface();
+
+  LogIfError(rv_state_->csr_set()->AddCsr(fcsr_));
+  LogIfError(rv_state_->csr_set()->AddCsr(frm_));
+  LogIfError(rv_state_->csr_set()->AddCsr(fflags_));
+}
+
+RiscVCheriotFPState::~RiscVCheriotFPState() {
+  delete fcsr_;
+  delete frm_;
+  delete fflags_;
+  delete host_fp_interface_;
+}
+
+FPRoundingMode RiscVCheriotFPState::GetRoundingMode() const {
+  return rounding_mode_;
+}
+
+void RiscVCheriotFPState::SetRoundingMode(FPRoundingMode mode) {
+  if (rounding_mode_ != mode) {
+    switch (mode) {
+      case FPRoundingMode::kRoundToNearestTiesToMax:
+      case FPRoundingMode::kRoundToNearest:
+        break;
+      case FPRoundingMode::kRoundTowardsZero:
+        break;
+      case FPRoundingMode::kRoundDown:
+        break;
+      case FPRoundingMode::kRoundUp:
+        break;
+      default:
+        rounding_mode_valid_ = false;
+        LOG(ERROR) << "Illegal rounding mode: " << static_cast<int>(mode);
+        return;
+    }
+    rounding_mode_ = mode;
+    frm_->Write(*mode);
+    rounding_mode_valid_ = true;
+  }
+}
+
+// The RiscV fp csr.
+RiscVFcsr::RiscVFcsr(RiscVCheriotFPState *fp_state)
+    : RiscVSimpleCsr<uint32_t>(kFcsrName, RiscVCsrEnum::kFCsr, kFcsrInitial,
+                               kFcsrReadMask, kFcsrWriteMask,
+                               fp_state->rv_state()),
+      fp_state_(fp_state) {}
+
+// The status value is computed from the most recent x86 status value.
+uint32_t RiscVFcsr::AsUint32() {
+  uint32_t status_value = fp_state_->host_fp_interface()->GetRiscVFcsr();
+  auto csr_value = GetUint32();
+  auto value = ((csr_value & ~0x1f) | (status_value & 0x1f)) & read_mask();
+  return value;
+}
+
+uint64_t RiscVFcsr::AsUint64() { return AsUint32(); }
+
+// The status value is converted to x86 and stored for use in next fp
+// instruction.
+void RiscVFcsr::Write(uint32_t value) {
+  auto wr_value = value & write_mask();
+  Set(wr_value);
+  fp_state_->host_fp_interface()->SetRiscVFcsr(wr_value);
+  fp_state_->SetRoundingMode(
+      static_cast<FPRoundingMode>((wr_value >> 5) & 0b111));
+}
+
+void RiscVFcsr::Write(uint64_t value) { Write(static_cast<uint32_t>(value)); }
+
+// RiscVFflags translates reads and writes into reads and writes of fcsr.
+RiscVFflags::RiscVFflags(RiscVCheriotFPState *fp_state)
+    : RiscVSimpleCsr<uint32_t>(kFflagsName, RiscVCsrEnum::kFFlags,
+                               kFflagsInitial, kFflagsReadMask,
+                               kFflagsWriteMask, fp_state->rv_state()),
+      fp_state_(fp_state) {}
+
+uint32_t RiscVFflags::AsUint32() {
+  uint32_t value = fp_state_->fcsr()->AsUint32();
+  value &= 0b1'1111;
+  return value;
+}
+
+void RiscVFflags::Write(uint32_t value) {
+  uint32_t current = fp_state_->fcsr()->AsUint32();
+  uint32_t new_value = (current & ~write_mask()) | (value & write_mask());
+  if (new_value == current) return;
+  fp_state_->fcsr()->Write(new_value);
+}
+
+uint32_t RiscVFflags::GetUint32() { return AsUint32(); }
+
+void RiscVFflags::Set(uint32_t value) { Write(value); }
+
+// RiscV rm (rounding mode) csr translates reads and writes into reads
+// and writes of fcsr.
+RiscVFrm::RiscVFrm(RiscVCheriotFPState *fp_state)
+    : RiscVSimpleCsr<uint32_t>(kFrmName, RiscVCsrEnum::kFrm, kFrmInitial,
+                               kFrmReadMask, kFrmWriteMask,
+                               fp_state->rv_state()),
+      fp_state_(fp_state) {}
+
+uint32_t RiscVFrm::AsUint32() {
+  uint32_t value = fp_state_->fcsr()->AsUint32();
+  uint32_t rm = (value >> 5) & read_mask();
+  return rm;
+}
+
+void RiscVFrm::Write(uint32_t value) {
+  uint32_t wr_value = value & write_mask();
+  uint32_t fcsr = fp_state_->fcsr()->AsUint32();
+  uint32_t new_fcsr = ((fcsr & 0b1'1111) | (wr_value << 5));
+  fp_state_->fcsr()->Write(new_fcsr);
+}
+
+uint32_t RiscVFrm::GetUint32() { return AsUint32(); }
+
+void RiscVFrm::Set(uint32_t value) { Write(value); }
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_fp_state.h b/cheriot/riscv_cheriot_fp_state.h
new file mode 100644
index 0000000..138e139
--- /dev/null
+++ b/cheriot/riscv_cheriot_fp_state.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_FP_STATE_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_FP_STATE_H_
+
+#include <cstdint>
+
+#include "riscv//riscv_csr.h"
+#include "riscv//riscv_fp_host.h"
+#include "riscv//riscv_fp_info.h"
+
+// This file contains code that manages the fp state of the RiscV processor.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+class RiscVCheriotFPState;
+class CheriotState;
+
+using ::mpact::sim::riscv::FPRoundingMode;
+using ::mpact::sim::riscv::HostFloatingPointInterface;
+using ::mpact::sim::riscv::RiscVSimpleCsr;
+
+// Floating point CSR.
+class RiscVFcsr : public RiscVSimpleCsr<uint32_t> {
+ public:
+  RiscVFcsr() = delete;
+  explicit RiscVFcsr(RiscVCheriotFPState *fp_state);
+  ~RiscVFcsr() override = default;
+
+  // Overrides.
+  uint32_t AsUint32() override;
+  uint64_t AsUint64() override;
+  void Write(uint32_t value) override;
+  void Write(uint64_t value) override;
+
+ private:
+  RiscVCheriotFPState *fp_state_;
+};
+
+// Floating point rounding mode csr.
+class RiscVFrm : public RiscVSimpleCsr<uint32_t> {
+ public:
+  RiscVFrm() = delete;
+  explicit RiscVFrm(RiscVCheriotFPState *fp_state);
+  ~RiscVFrm() override = default;
+
+  // Overrides.
+  uint32_t AsUint32() override;
+  uint64_t AsUint64() override { return AsUint32(); }
+  void Write(uint32_t value) override;
+  void Write(uint64_t value) override { Write(static_cast<uint32_t>(value)); }
+  uint32_t GetUint32() override;
+  uint64_t GetUint64() override { return GetUint32(); }
+  void Set(uint32_t value) override;
+  void Set(uint64_t value) override { Set(static_cast<uint32_t>(value)); }
+
+ private:
+  RiscVCheriotFPState *fp_state_;
+};
+
+// Floating point status flags csr.
+class RiscVFflags : public RiscVSimpleCsr<uint32_t> {
+ public:
+  RiscVFflags() = delete;
+  explicit RiscVFflags(RiscVCheriotFPState *fp_state);
+  ~RiscVFflags() override = default;
+
+  // Overrides.
+  uint32_t AsUint32() override;
+  uint64_t AsUint64() override { return AsUint32(); }
+  void Write(uint32_t value) override;
+  void Write(uint64_t value) override { Write(static_cast<uint32_t>(value)); }
+  uint32_t GetUint32() override;
+  uint64_t GetUint64() override { return GetUint32(); }
+  void Set(uint32_t value) override;
+  void Set(uint64_t value) override { Set(static_cast<uint32_t>(value)); }
+
+ private:
+  RiscVCheriotFPState *fp_state_;
+};
+
+class RiscVCheriotFPState {
+ public:
+  RiscVCheriotFPState() = delete;
+  RiscVCheriotFPState(const RiscVCheriotFPState &) = delete;
+  explicit RiscVCheriotFPState(CheriotState *rv_state);
+  ~RiscVCheriotFPState();
+
+  FPRoundingMode GetRoundingMode() const;
+
+  void SetRoundingMode(FPRoundingMode mode);
+
+  bool rounding_mode_valid() const { return rounding_mode_valid_; }
+
+  // FP CSRs.
+  RiscVFcsr *fcsr() const { return fcsr_; }
+  RiscVFrm *frm() const { return frm_; }
+  RiscVFflags *fflags() const { return fflags_; }
+  // Parent state.
+  CheriotState *rv_state() const { return rv_state_; }
+  // Host interface.
+  HostFloatingPointInterface *host_fp_interface() const {
+    return host_fp_interface_;
+  }
+
+ private:
+  CheriotState *rv_state_;
+  RiscVFcsr *fcsr_ = nullptr;
+  RiscVFrm *frm_ = nullptr;
+  RiscVFflags *fflags_ = nullptr;
+  HostFloatingPointInterface *host_fp_interface_;
+
+  bool rounding_mode_valid_ = true;
+  FPRoundingMode rounding_mode_ = FPRoundingMode::kRoundToNearest;
+};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_FP_STATE_H_
diff --git a/cheriot/riscv_cheriot_i_instructions.cc b/cheriot/riscv_cheriot_i_instructions.cc
new file mode 100644
index 0000000..16bf8cc
--- /dev/null
+++ b/cheriot/riscv_cheriot_i_instructions.cc
@@ -0,0 +1,246 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_i_instructions.h"
+
+#include <cstdint>
+#include <functional>
+#include <ios>
+#include <type_traits>
+
+#include "absl/log/log.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_instruction_helpers.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using EC = ::mpact::sim::riscv::ExceptionCode;
+
+void RiscVIllegalInstruction(const Instruction *inst) {
+  auto *state = static_cast<CheriotState *>(inst->state());
+  // Get instruction word, as it needs to be used as trap value.
+  uint64_t address = inst->address();
+  auto db = state->db_factory()->Allocate<uint32_t>(1);
+  state->DbgLoadMemory(address, db);
+  uint32_t inst_word = db->Get<uint32_t>(0);
+  db->DecRef();
+  // See if the instruction is interpreted as 32 or 16 bit instruction.
+  if ((inst_word & 0b11) != 0b11) inst_word &= 0xffff;
+  LOG(INFO) << "RiscVIllegalInstruction: " << std::hex << inst_word;
+  state->Trap(/*is_interrupt=*/false, /*trap_value=*/inst_word,
+              *EC::kIllegalInstruction,
+              /*epc=*/inst->address(), inst);
+}
+
+// The following instruction semantic functions implement basic alu operations.
+// They are used for both register-register and register-immediate versions of
+// the corresponding instructions.
+
+using RegisterType = CheriotRegister;
+
+// Register width integer types. These are preferred to uint32_t, etc, for
+// those instructions that operate on the entire instruction width.
+using UIntReg =
+    typename std::make_unsigned<typename RegisterType::ValueType>::type;
+using IntReg = typename std::make_signed<UIntReg>::type;
+
+void RiscVIAdd(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a + b; });
+}
+
+void RiscVISub(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a - b; });
+}
+
+void RiscVISlt(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, IntReg, IntReg>(
+      instruction, [](IntReg a, IntReg b) { return a < b; });
+}
+
+void RiscVISltu(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a < b; });
+}
+
+void RiscVIAnd(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a & b; });
+}
+
+void RiscVIOr(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a | b; });
+}
+
+void RiscVIXor(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a ^ b; });
+}
+
+void RiscVISll(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a << (b & 0x1f); });
+}
+
+void RiscVISrl(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, UIntReg, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a >> (b & 0x1f); });
+}
+
+void RiscVISra(const Instruction *instruction) {
+  RVCheriotBinaryOp<RegisterType, IntReg, IntReg>(
+      instruction, [](IntReg a, IntReg b) { return a >> (b & 0x1f); });
+}
+
+// Load upper immediate. It is assumed that the decoder already shifted the
+// immediate. Operates on 32 bit quantities, not XLEN bits.
+void RiscVILui(const Instruction *instruction) {
+  RVCheriotUnaryOp<RegisterType, uint32_t, uint32_t>(
+      instruction, [](uint32_t lhs) { return lhs & ~0xfff; });
+}
+
+// RiscVIJal and RiscVIJalr are superseded by the capability versions CJal and
+// CJalr.
+
+void RiscVINop(const Instruction *instruction) {}
+
+// Register width integer types.
+using UIntReg =
+    typename std::make_unsigned<typename RegisterType::ValueType>::type;
+using IntReg = typename std::make_signed<UIntReg>::type;
+
+void RiscVIBeq(const Instruction *instruction) {
+  RVCheriotBranchConditional<RegisterType, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a == b; });
+}
+
+void RiscVIBne(const Instruction *instruction) {
+  RVCheriotBranchConditional<RegisterType, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a != b; });
+}
+
+void RiscVIBlt(const Instruction *instruction) {
+  RVCheriotBranchConditional<RegisterType, IntReg>(
+      instruction, [](IntReg a, IntReg b) { return a < b; });
+}
+
+void RiscVIBltu(const Instruction *instruction) {
+  RVCheriotBranchConditional<RegisterType, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a < b; });
+}
+
+void RiscVIBge(const Instruction *instruction) {
+  RVCheriotBranchConditional<RegisterType, IntReg>(
+      instruction, [](IntReg a, IntReg b) { return a >= b; });
+}
+
+void RiscVIBgeu(const Instruction *instruction) {
+  RVCheriotBranchConditional<RegisterType, UIntReg>(
+      instruction, [](UIntReg a, UIntReg b) { return a >= b; });
+}
+
+void RiscVILd(const Instruction *instruction) {
+  RVCheriotLoad<RegisterType, uint64_t>(instruction);
+}
+
+void RiscVILw(const Instruction *instruction) {
+  RVCheriotLoad<RegisterType, int32_t>(instruction);
+}
+
+void RiscVILwChild(const Instruction *instruction) {
+  RVCheriotLoadChild<RegisterType, int32_t>(instruction);
+}
+
+void RiscVILh(const Instruction *instruction) {
+  RVCheriotLoad<RegisterType, int16_t>(instruction);
+}
+
+void RiscVILhChild(const Instruction *instruction) {
+  RVCheriotLoadChild<RegisterType, int16_t>(instruction);
+}
+
+void RiscVILhu(const Instruction *instruction) {
+  RVCheriotLoad<RegisterType, uint16_t>(instruction);
+}
+
+void RiscVILhuChild(const Instruction *instruction) {
+  RVCheriotLoadChild<RegisterType, uint16_t>(instruction);
+}
+
+void RiscVILb(const Instruction *instruction) {
+  RVCheriotLoad<RegisterType, int8_t>(instruction);
+}
+
+void RiscVILbChild(const Instruction *instruction) {
+  RVCheriotLoadChild<RegisterType, int8_t>(instruction);
+}
+
+void RiscVILbu(const Instruction *instruction) {
+  RVCheriotLoad<RegisterType, uint8_t>(instruction);
+}
+
+void RiscVILbuChild(const Instruction *instruction) {
+  RVCheriotLoadChild<RegisterType, uint8_t>(instruction);
+}
+
+void RiscVISd(const Instruction *instruction) {
+  RVCheriotStore<RegisterType, uint64_t>(instruction);
+}
+
+void RiscVISw(const Instruction *instruction) {
+  RVCheriotStore<RegisterType, uint32_t>(instruction);
+}
+
+void RiscVISh(const Instruction *instruction) {
+  RVCheriotStore<RegisterType, uint16_t>(instruction);
+}
+
+void RiscVISb(const Instruction *instruction) {
+  RVCheriotStore<RegisterType, uint8_t>(instruction);
+}
+
+void RiscVIFence(const Instruction *instruction) {
+  uint32_t bits = instruction->Source(0)->AsUint32(0);
+  int fm = (bits >> 8) & 0xf;
+  int predecessor = (bits >> 4) & 0xf;
+  int successor = bits & 0xf;
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  state->Fence(instruction, fm, predecessor, successor);
+}
+
+void RiscVIEcall(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  state->ECall(instruction);
+}
+
+void RiscVIEbreak(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  state->EBreak(instruction);
+}
+
+void RiscVWFI(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  state->WFI(instruction);
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_i_instructions.h b/cheriot/riscv_cheriot_i_instructions.h
new file mode 100644
index 0000000..f5f7c4d
--- /dev/null
+++ b/cheriot/riscv_cheriot_i_instructions.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_I_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_I_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+// This file contains the declarations of the instruction semantic functions
+// for the RiscV i instructions. The 32 bit instruction versions are in the
+// RV32 namespace, whereas the 64 bit versions are in the RV64 namespace.
+// Instructions that are the same across 32 and 64 bits are in the riscv
+// namespace.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::Instruction;
+
+void RiscVIllegalInstruction(const Instruction *inst);
+
+// No operands necessary for Nop. For now, all hint instructions should be
+// decoded as Nop.
+void RiscVINop(const Instruction *instruction);
+
+// For the following, source operand 0 refers to the register specified in rs1,
+// and source operand 1 refers to either the register specified in rs2, or the
+// immediate. Destination operand 0 refers to the register specified in rd.
+void RiscVIAdd(const Instruction *instruction);
+void RiscVISub(const Instruction *instruction);
+void RiscVISlt(const Instruction *instruction);
+void RiscVISltu(const Instruction *instruction);
+void RiscVIAnd(const Instruction *instruction);
+void RiscVIOr(const Instruction *instruction);
+void RiscVIXor(const Instruction *instruction);
+void RiscVISll(const Instruction *instruction);
+void RiscVISrl(const Instruction *instruction);
+void RiscVISra(const Instruction *instruction);
+// For the following semantic function, source operand 0 refers to the
+// immediate value, and destination 0 the register specified in rd. Note, the
+// value of the immediate shall be properly shifted.
+void RiscVILui(const Instruction *instruction);
+// Source operand 0 contains the immediate value. Destination operand 0 refers
+// to the pc destination operand, wheras destination operand 1 refers to the
+// link register specified in rd.
+void RiscVIBeq(const Instruction *instruction);
+void RiscVIBne(const Instruction *instruction);
+void RiscVIBlt(const Instruction *instruction);
+void RiscVIBltu(const Instruction *instruction);
+void RiscVIBge(const Instruction *instruction);
+void RiscVIBgeu(const Instruction *instruction);
+// Each of the load instructions are modeled by a pair of semantic instruction
+// functions. The "main" function computes the effective address and initiates
+// the load, the "child" function processes the load result and writes it back
+// to the destination register.
+// For the "main" semantic function, source operand 0 is the base register,
+// source operand 1 the offset. Destination operand 0 is the register specified
+// by rd. The "child" semantic function will get a copy of the destination
+// operand.
+void RiscVILd(const Instruction *instruction);
+void RiscVILw(const Instruction *instruction);
+void RiscVILwChild(const Instruction *instruction);
+void RiscVILh(const Instruction *instruction);
+void RiscVILhChild(const Instruction *instruction);
+void RiscVILhu(const Instruction *instruction);
+void RiscVILhuChild(const Instruction *instruction);
+void RiscVILb(const Instruction *instruction);
+void RiscVILbChild(const Instruction *instruction);
+void RiscVILbu(const Instruction *instruction);
+void RiscVILbuChild(const Instruction *instruction);
+// For each store instruction semantic function, source operand 0 is the base
+// register, source operand 1 is the offset, while source operand 2 is the value
+// to be stored referred to by rs2.
+void RiscVISd(const Instruction *instruction);
+void RiscVISw(const Instruction *instruction);
+void RiscVISh(const Instruction *instruction);
+void RiscVISb(const Instruction *instruction);
+
+// The Fence instruction takes a single source operand (index 0) which consists
+// of an immediate value containing the right justified concatenation of the FM,
+// predecessor, and successor bit fields of the instruction.
+void RiscVIFence(const Instruction *instruction);
+// Ecall and EBreak take no source or destination operands.
+void RiscVIEcall(const Instruction *instruction);
+void RiscVIEbreak(const Instruction *instruction);
+// Trap doesn't implement any specific instruction, but can be called as an
+// instruction, for instance for unknown instructions.
+void RiscVITrap(bool is_interrupt, uint32_t cause, Instruction *instruction);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_I_INSTRUCTIONS_H_
diff --git a/cheriot/riscv_cheriot_instruction_helpers.h b/cheriot/riscv_cheriot_instruction_helpers.h
new file mode 100644
index 0000000..74b6e44
--- /dev/null
+++ b/cheriot/riscv_cheriot_instruction_helpers.h
@@ -0,0 +1,750 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTION_HELPERS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTION_HELPERS_H_
+
+#include <any>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <tuple>
+#include <type_traits>
+
+#include "absl/log/log.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_fp_state.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_fp_host.h"
+#include "riscv//riscv_fp_info.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::operator*;
+using ::mpact::sim::generic::FPTypeInfo;
+using ::mpact::sim::generic::RegisterBase;
+using ::mpact::sim::riscv::FPExceptions;
+using ::mpact::sim::riscv::FPRoundingMode;
+using ::mpact::sim::riscv::ScopedFPRoundingMode;
+using ::mpact::sim::riscv::ScopedFPStatus;
+using CapReg = CheriotRegister;
+using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
+
+// Get the destination capability register.
+static inline CapReg *GetCapDest(const Instruction *instruction, int i) {
+  return static_cast<CapReg *>(
+      std::any_cast<RegisterBase *>(instruction->Destination(i)->GetObject()));
+}
+
+// Writing an integer result requires invalidating the capability and setting
+// it to null.
+template <typename Result>
+static inline void WriteCapIntResult(const Instruction *instruction, int i,
+                                     Result value) {
+  auto *cap_reg = GetCapDest(instruction, i);
+  cap_reg->data_buffer()->Set<Result>(0, value);
+  cap_reg->Invalidate();
+  cap_reg->set_is_null();
+}
+
+// Templated helper function for convert instruction semantic functions.
+template <typename From, typename To>
+inline std::tuple<To, uint32_t> CvtHelper(From value) {
+  constexpr From kMax = static_cast<From>(std::numeric_limits<To>::max());
+  constexpr From kMin = static_cast<From>(std::numeric_limits<To>::min());
+
+  if (FPTypeInfo<From>::IsNaN(value)) {
+    return std::make_tuple(std::numeric_limits<To>::max(),
+                           *FPExceptions::kInvalidOp);
+  }
+  if (value > kMax) {
+    return std::make_tuple(std::numeric_limits<To>::max(),
+                           *FPExceptions::kInvalidOp);
+  }
+  if (value < kMin) {
+    if (std::is_unsigned<To>::value && (value > -1.0)) {
+      using SignedTo = typename std::make_signed<To>::type;
+      SignedTo signed_val = static_cast<SignedTo>(value);
+      if (signed_val == 0) {
+        return std::make_tuple(0, *FPExceptions::kInexact);
+      }
+    }
+    return std::make_tuple(std::numeric_limits<To>::min(),
+                           *FPExceptions::kInvalidOp);
+  }
+
+  auto output_value = static_cast<To>(value);
+  return std::make_tuple(output_value, 0);
+}
+
+// TODO(torerik): Modify as needed if used to produce values in capability
+// registers.
+// Generic helper function for floating op instructions that do not
+// require NaN boxing since they produce non fp-values, but set fflags.
+template <typename CapRegType, typename Result, typename From, typename To>
+inline void RiscVConvertFloatWithFflagsOp(const Instruction *instruction) {
+  constexpr From kMax = static_cast<From>(std::numeric_limits<To>::max());
+  constexpr From kMin = static_cast<From>(std::numeric_limits<To>::min());
+
+  From lhs = generic::GetInstructionSource<From>(instruction, 0);
+
+  uint32_t flags = 0;
+  uint32_t rm = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  To value = 0;
+  if (FPTypeInfo<From>::IsNaN(lhs)) {
+    value = std::numeric_limits<To>::max();
+    flags = *FPExceptions::kInvalidOp;
+  } else if (lhs >= kMax) {
+    value = std::numeric_limits<To>::max();
+    flags = *FPExceptions::kInvalidOp;
+  } else if (lhs < kMin) {
+    bool is_set = false;
+    if (std::is_unsigned<To>::value && (lhs > -1.0)) {
+      using SignedTo = typename std::make_signed<To>::type;
+      SignedTo signed_val = static_cast<SignedTo>(lhs);
+      if (signed_val == 0) {
+        value = 0;
+        flags = *FPExceptions::kInexact;
+        is_set = true;
+      }
+    }
+    if (!is_set) {
+      value = std::numeric_limits<To>::min();
+      flags = *FPExceptions::kInvalidOp;
+      is_set = true;
+    }
+  } else if (lhs == 0.0) {
+    value = 0;
+  } else {
+    // static_cast<>() doesn't necessarily round, so will have to force
+    // rounding before converting to the integer type if necessary.
+    using FromUint = typename FPTypeInfo<From>::UIntType;
+    auto constexpr kBias = FPTypeInfo<From>::kExpBias;
+    auto constexpr kExpMask = FPTypeInfo<From>::kExpMask;
+    auto constexpr kSigSize = FPTypeInfo<From>::kSigSize;
+    auto constexpr kSigMask = FPTypeInfo<From>::kSigMask;
+    FromUint lhs_u = *reinterpret_cast<FromUint *>(&lhs);
+    FromUint exp = kExpMask & lhs_u;
+    int exp_value = exp >> kSigSize;
+    int unbiased_exp = exp_value - kBias;
+    FromUint sig = kSigMask & lhs_u;
+    // If the number of bits in the significand is greater or equal to
+    // unbiased exponent, and there is a 1 among the extra bits, we need to
+    // perform rounding.
+    if (unbiased_exp < 0) {
+      flags = *FPExceptions::kInexact;
+    } else if (unbiased_exp <= kSigSize) {
+      FromUint mask = (1ULL << (kSigSize - unbiased_exp)) - 1;
+      if ((sig & mask) != 0) {
+        flags = *FPExceptions::kInexact;
+        FromUint sign = lhs_u & (1ULL << (FPTypeInfo<From>::kBitSize - 1));
+        // Turn the value into a denormal.
+        constexpr FromUint hidden_bit = 1ULL << (kSigSize - 1);
+        FromUint tmp_u = sign | hidden_bit | (sig >> 1);
+        From tmp = *reinterpret_cast<From *>(&tmp_u);
+        // Divide so that only the bits we care about are left in the
+        // significand.
+        int shift = kBias + kSigSize - exp_value - 1;
+        FromUint div_exp = shift + kBias;
+        FromUint div_u = div_exp << kSigSize;
+        From div = *reinterpret_cast<From *>(&div_u);
+        auto *rv_fp =
+            static_cast<CheriotState *>(instruction->state())->rv_fp();
+        auto *host_fp_interface = rv_fp->host_fp_interface();
+        {
+          // The rounding happens during this division.
+          ScopedFPRoundingMode set_fp_rm(host_fp_interface, rm);
+          tmp /= div;
+        }
+        // Convert back to normalized number, by using the original sign
+        // and exponent, and the normalized and significand from the division.
+        tmp_u = *reinterpret_cast<FromUint *>(&tmp);
+        lhs_u = sign | exp | ((tmp_u << (shift + 1)) & kSigMask);
+        lhs = *reinterpret_cast<From *>(&lhs_u);
+      }
+    }
+    value = static_cast<To>(lhs);
+  }
+  using SignedTo = typename std::make_signed<To>::type;
+  // The final value is sign-extended to the register width, even if it's
+  // conversion to an unsigned value.
+  SignedTo signed_value = static_cast<SignedTo>(value);
+  Result dest_value = static_cast<Result>(signed_value);
+  WriteCapIntResult(instruction, 0, dest_value);
+  if (flags) {
+    auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
+    flag_db->Set<uint32_t>(0, flags);
+    flag_db->Submit();
+  }
+}
+
+// Helper function to read a NaN boxed source value, converting it to NaN if
+// it isn't formatted properly.
+template <typename RegValue, typename Argument>
+inline Argument GetNaNBoxedSource(const Instruction *instruction, int arg) {
+  if (sizeof(RegValue) <= sizeof(Argument)) {
+    return generic::GetInstructionSource<Argument>(instruction, arg);
+  } else {
+    using SInt = typename std::make_signed<RegValue>::type;
+    using UInt = typename std::make_unsigned<RegValue>::type;
+    SInt val = generic::GetInstructionSource<SInt>(instruction, arg);
+    UInt uval = static_cast<UInt>(val);
+    UInt mask = std::numeric_limits<UInt>::max() << (sizeof(Argument) * 8);
+    if (((mask & uval) != mask)) {
+      return *reinterpret_cast<const Argument *>(
+          &FPTypeInfo<Argument>::kCanonicalNaN);
+    }
+    return generic::GetInstructionSource<Argument>(instruction, arg);
+  }
+}
+
+template <typename Register, typename Result, typename Argument>
+inline void RiscVBinaryOp(const Instruction *instruction,
+                          std::function<Result(Argument, Argument)> operation) {
+  using RegValue = typename Register::ValueType;
+  Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
+  Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
+  Result dest_value = operation(lhs, rhs);
+  auto *reg = static_cast<generic::RegisterDestinationOperand<RegValue> *>(
+                  instruction->Destination(0))
+                  ->GetRegister();
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+}
+
+// Generic helper functions for binary instructions. Clears the tag bit for
+// the capability register and sets it to null.
+template <typename Register, typename Result, typename Argument1,
+          typename Argument2>
+inline void RVCheriotBinaryOp(
+    const Instruction *instruction,
+    std::function<Result(Argument1, Argument2)> operation) {
+  Argument1 lhs = generic::GetInstructionSource<Argument1>(instruction, 0);
+  Argument2 rhs = generic::GetInstructionSource<Argument2>(instruction, 1);
+  Result dest_value = operation(lhs, rhs);
+  WriteCapIntResult(instruction, 0, dest_value);
+}
+
+template <typename Register, typename Result, typename Argument>
+inline void RVCheriotBinaryOp(
+    const Instruction *instruction,
+    std::function<Result(Argument, Argument)> operation) {
+  Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
+  Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
+  Result dest_value = operation(lhs, rhs);
+  WriteCapIntResult(instruction, 0, dest_value);
+}
+
+template <typename Register, typename Result>
+inline void RVCheriotBinaryOp(const Instruction *instruction,
+                              std::function<Result(Result, Result)> operation) {
+  Result lhs = generic::GetInstructionSource<Result>(instruction, 0);
+  Result rhs = generic::GetInstructionSource<Result>(instruction, 1);
+  Result dest_value = operation(lhs, rhs);
+  WriteCapIntResult(instruction, 0, dest_value);
+}
+
+// Generic helper function for unary instructions.
+template <typename Register, typename Result, typename Argument>
+inline void RVCheriotUnaryOp(const Instruction *instruction,
+                             std::function<Result(Argument)> operation) {
+  auto lhs = generic::GetInstructionSource<Argument>(instruction, 0);
+  Result dest_value = operation(lhs);
+  WriteCapIntResult(instruction, 0, dest_value);
+}
+
+// Helper function for conditional branches.
+template <typename RegisterType, typename ValueType>
+static inline void RVCheriotBranchConditional(
+    const Instruction *instruction,
+    std::function<bool(ValueType, ValueType)> cond) {
+  using UIntType =
+      typename std::make_unsigned<typename RegisterType::ValueType>::type;
+  ValueType a = generic::GetInstructionSource<ValueType>(instruction, 0);
+  ValueType b = generic::GetInstructionSource<ValueType>(instruction, 1);
+  if (cond(a, b)) {
+    UIntType offset = generic::GetInstructionSource<UIntType>(instruction, 2);
+    UIntType target = offset + instruction->address();
+    auto *state = static_cast<CheriotState *>(instruction->state());
+    auto *pcc = state->pcc();
+    if (!pcc->HasPermission(PB::kPermitExecute)) {
+      state->HandleCheriRegException(
+          instruction, pcc->address(),
+          ExceptionCode::kCapExPermitExecuteViolation, pcc);
+      return;
+    }
+    pcc->set_address(target);
+    state->set_branch(true);
+  }
+}
+
+// Generic helper function for load instructions.
+template <typename Register, typename ValueType>
+inline void RVCheriotLoad(const Instruction *instruction) {
+  using RegVal = typename Register::ValueType;
+  using URegVal = typename std::make_unsigned<RegVal>::type;
+  // Bet the capability.
+  auto *cap_reg = static_cast<CheriotRegister *>(
+      static_cast<generic::RegisterSourceOperand<RegVal> *>(
+          instruction->Source(0))
+          ->GetRegister());
+  URegVal base = cap_reg->address();
+  RegVal offset = generic::GetInstructionSource<RegVal>(instruction, 1);
+  URegVal address = base + offset;
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  // Check for tag unset.
+  if (!cap_reg->tag()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExTagViolation, cap_reg);
+    return;
+  }
+  // Check for sealed.
+  if (cap_reg->IsSealed()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExSealViolation, cap_reg);
+    return;
+  }
+  // Check for permissions.
+  if (!cap_reg->HasPermission(CheriotRegister::kPermitLoad)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExPermitLoadViolation,
+                                   cap_reg);
+    return;
+  }
+  // Check for bounds.
+  if (!cap_reg->IsInBounds(address, sizeof(ValueType))) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExBoundsViolation,
+                                   cap_reg);
+    return;
+  }
+  auto *value_db =
+      instruction->state()->db_factory()->Allocate(sizeof(ValueType));
+  value_db->set_latency(0);
+  auto *context = new riscv::LoadContext(value_db);
+  state->LoadMemory(instruction, address, value_db, instruction->child(),
+                    context);
+  context->DecRef();
+}
+
+// Generic helper function for load instructions' "child instruction".
+template <typename Register, typename ValueType>
+inline void RVCheriotLoadChild(const Instruction *instruction) {
+  using RegVal = typename Register::ValueType;
+  using URegVal = typename std::make_unsigned<RegVal>::type;
+  using SRegVal = typename std::make_signed<URegVal>::type;
+  riscv::LoadContext *context =
+      static_cast<riscv::LoadContext *>(instruction->context());
+  if (std::is_signed<ValueType>::value) {
+    SRegVal value = static_cast<SRegVal>(context->value_db->Get<ValueType>(0));
+    WriteCapIntResult(instruction, 0, value);
+  } else {
+    URegVal value = static_cast<URegVal>(context->value_db->Get<ValueType>(0));
+    WriteCapIntResult(instruction, 0, value);
+  }
+}
+
+// Generic helper function for store instructions.
+template <typename RegisterType, typename ValueType>
+inline void RVCheriotStore(const Instruction *instruction) {
+  using URegVal =
+      typename std::make_unsigned<typename RegisterType::ValueType>::type;
+  using SRegVal = typename std::make_signed<URegVal>::type;
+  ValueType value = generic::GetInstructionSource<ValueType>(instruction, 2);
+  auto *cap_reg = static_cast<CheriotRegister *>(
+      static_cast<
+          generic::RegisterSourceOperand<typename RegisterType::ValueType> *>(
+          instruction->Source(0))
+          ->GetRegister());
+  URegVal base = cap_reg->address();
+  SRegVal offset = generic::GetInstructionSource<SRegVal>(instruction, 1);
+  URegVal address = base + offset;
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  // Check for tag unset.
+  if (!cap_reg->tag()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExTagViolation, cap_reg);
+    return;
+  }
+  // Check for sealed.
+  if (cap_reg->IsSealed()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExSealViolation, cap_reg);
+    return;
+  }
+  // Check for permissions.
+  if (!cap_reg->HasPermission(CheriotRegister::kPermitStore)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExPermitStoreViolation,
+                                   cap_reg);
+    return;
+  }
+  // Check for bounds.
+  if (!cap_reg->IsInBounds(address, sizeof(ValueType))) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   ExceptionCode::kCapExBoundsViolation,
+                                   cap_reg);
+    return;
+  }
+  auto *db = state->db_factory()->Allocate(sizeof(ValueType));
+  db->Set<ValueType>(0, value);
+  state->StoreMemory(instruction, address, db);
+  db->DecRef();
+}
+
+// Generic helper function for binary instructions that take NaN boxed sources
+// but produce the result in a capability register.
+template <typename RegValue, typename Result, typename Argument>
+inline void RVCheriotBinaryNaNBoxOp(
+    const Instruction *instruction,
+    std::function<Result(Argument, Argument)> operation) {
+  Argument lhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 0);
+  Argument rhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 1);
+  Result dest_value = operation(lhs, rhs);
+  // Check to see if we need to NaN box the result.
+  if (sizeof(RegValue) > sizeof(Result)) {
+    // If the floating point value is narrower than the register, the upper
+    // bits have to be set to all ones.
+    using UReg = typename std::make_unsigned<RegValue>::type;
+    using UInt = typename FPTypeInfo<Result>::UIntType;
+    auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
+    UReg reg_value = std::numeric_limits<UReg>::max();
+    int shift = 8 * (sizeof(RegValue) - sizeof(Result));
+    reg_value = (reg_value << shift) | dest_u_value;
+    WriteCapIntResult(instruction, 0, reg_value);
+    return;
+  }
+  WriteCapIntResult(instruction, 0, dest_value);
+}
+
+// Generic helper function for binary instructions with NaN boxing. This is
+// used for those instructions that produce results in fp registers, but are
+// not really executing an fp operation that requires rounding.
+template <typename RegValue, typename Result, typename Argument>
+inline void RiscVBinaryNaNBoxOp(
+    const Instruction *instruction,
+    std::function<Result(Argument, Argument)> operation) {
+  Argument lhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 0);
+  Argument rhs = GetNaNBoxedSource<RegValue, Argument>(instruction, 1);
+  Result dest_value = operation(lhs, rhs);
+  auto *reg = static_cast<generic::RegisterDestinationOperand<RegValue> *>(
+                  instruction->Destination(0))
+                  ->GetRegister();
+  // Check to see if we need to NaN box the result.
+  if (sizeof(RegValue) > sizeof(Result)) {
+    // If the floating point value is narrower than the register, the upper
+    // bits have to be set to all ones.
+    using UReg = typename std::make_unsigned<RegValue>::type;
+    using UInt = typename FPTypeInfo<Result>::UIntType;
+    auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
+    UReg reg_value = std::numeric_limits<UReg>::max();
+    int shift = 8 * (sizeof(RegValue) - sizeof(Result));
+    reg_value = (reg_value << shift) | dest_u_value;
+    reg->data_buffer()->template Set<RegValue>(0, reg_value);
+    return;
+  }
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+}
+
+// Generic helper function for unary instructions with NaN boxing.
+template <typename DstRegValue, typename SrcRegValue, typename Result,
+          typename Argument>
+inline void RiscVUnaryNaNBoxOp(const Instruction *instruction,
+                               std::function<Result(Argument)> operation) {
+  Argument lhs = GetNaNBoxedSource<SrcRegValue, Argument>(instruction, 0);
+  Result dest_value = operation(lhs);
+  // Check to see if we need to NaN box the result.
+  if (sizeof(DstRegValue) > sizeof(Result)) {
+    // If the floating point value is narrower than the register, the upper
+    // bits have to be set to all ones.
+    using UReg = typename std::make_unsigned<DstRegValue>::type;
+    using UInt = typename FPTypeInfo<Result>::UIntType;
+    auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
+    UReg reg_value = std::numeric_limits<UReg>::max();
+    int shift = 8 * (sizeof(DstRegValue) - sizeof(Result));
+    reg_value = (reg_value << shift) | dest_u_value;
+    WriteCapIntResult(instruction, 0, reg_value);
+    return;
+  }
+  WriteCapIntResult(instruction, 0, dest_value);
+}
+
+// TODO(torerik): Modify as needed if used to produce values in capability
+// registers.
+// Generic helper function for unary floating point instructions. The main
+// difference is that it handles rounding mode and performs NaN boxing.
+template <typename DstRegValue, typename SrcRegValue, typename Result,
+          typename Argument>
+inline void RiscVUnaryFloatNaNBoxOp(const Instruction *instruction,
+                                    std::function<Result(Argument)> operation) {
+  using ResUint = typename FPTypeInfo<Result>::UIntType;
+  Argument lhs = GetNaNBoxedSource<SrcRegValue, Argument>(instruction, 0);
+  // Get the rounding mode.
+  int rm_value = generic::GetInstructionSource<int>(instruction, 1);
+
+  // If the rounding mode is dynamic, read it from the current state.
+  auto *rv_fp = static_cast<CheriotState *>(instruction->state())->rv_fp();
+  if (rm_value == *FPRoundingMode::kDynamic) {
+    if (!rv_fp->rounding_mode_valid()) {
+      LOG(ERROR) << "Invalid rounding mode";
+      return;
+    }
+    rm_value = *(rv_fp->GetRoundingMode());
+  }
+  Result dest_value;
+  {
+    ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
+    dest_value = operation(lhs);
+  }
+  if (std::isnan(dest_value) && std::signbit(dest_value)) {
+    ResUint res_value = *reinterpret_cast<ResUint *>(&dest_value);
+    res_value &= FPTypeInfo<Result>::kInfMask;
+    dest_value = *reinterpret_cast<Result *>(&res_value);
+  }
+  auto *dest = instruction->Destination(0);
+  auto *reg_dest =
+      static_cast<generic::RegisterDestinationOperand<DstRegValue> *>(dest);
+  auto *reg = reg_dest->GetRegister();
+  // Check to see if we need to NaN box the result.
+  if (sizeof(DstRegValue) > sizeof(Result)) {
+    // If the floating point Value is narrower than the register, the upper
+    // bits have to be set to all ones.
+    using UReg = typename std::make_unsigned<DstRegValue>::type;
+    using UInt = typename FPTypeInfo<Result>::UIntType;
+    auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
+    UReg reg_value = std::numeric_limits<UReg>::max();
+    int shift = 8 * (sizeof(DstRegValue) - sizeof(Result));
+    reg_value = (reg_value << shift) | dest_u_value;
+    reg->data_buffer()->template Set<DstRegValue>(0, reg_value);
+    return;
+  }
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+}
+
+// TODO(torerik): Modify as needed if used to produce values in capability
+// registers.
+// Generic helper function for floating op instructions that do not require
+// NaN boxing since they produce non fp-values.
+template <typename Result, typename Argument>
+inline void RiscVUnaryFloatOp(const Instruction *instruction,
+                              std::function<Result(Argument)> operation) {
+  Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
+  // Get the rounding mode.
+  int rm_value = generic::GetInstructionSource<int>(instruction, 1);
+
+  auto *rv_fp = static_cast<CheriotState *>(instruction->state())->rv_fp();
+  // If the rounding mode is dynamic, read it from the current state.
+  if (rm_value == *FPRoundingMode::kDynamic) {
+    if (!rv_fp->rounding_mode_valid()) {
+      LOG(ERROR) << "Invalid rounding mode";
+      return;
+    }
+    rm_value = *rv_fp->GetRoundingMode();
+  }
+  Result dest_value;
+  {
+    ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
+    dest_value = operation(lhs);
+  }
+  auto *dest = instruction->Destination(0);
+  using UInt = typename FPTypeInfo<Result>::UIntType;
+  auto *reg_dest =
+      static_cast<generic::RegisterDestinationOperand<UInt> *>(dest);
+  auto *reg = reg_dest->GetRegister();
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+}
+
+// TODO(torerik): Modify as needed if used to produce values in capability
+// registers.
+// Generic helper function for floating op instructions that do not require
+// NaN boxing since they produce non fp-values, but set fflags.
+template <typename Result, typename Argument>
+inline void RiscVUnaryFloatWithFflagsOp(
+    const Instruction *instruction,
+    std::function<Result(Argument, uint32_t &)> operation) {
+  Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
+  // Get the rounding mode.
+  int rm_value = generic::GetInstructionSource<int>(instruction, 1);
+
+  auto *rv_fp = static_cast<CheriotState *>(instruction->state())->rv_fp();
+  // If the rounding mode is dynamic, read it from the current state.
+  if (rm_value == *FPRoundingMode::kDynamic) {
+    if (!rv_fp->rounding_mode_valid()) {
+      LOG(ERROR) << "Invalid rounding mode";
+      return;
+    }
+    rm_value = *rv_fp->GetRoundingMode();
+  }
+  uint32_t flag = 0;
+  Result dest_value;
+  {
+    ScopedFPStatus set_fp_status(rv_fp->host_fp_interface(), rm_value);
+    dest_value = operation(lhs, flag);
+  }
+  auto *dest = instruction->Destination(0);
+  using UInt = typename FPTypeInfo<Result>::UIntType;
+  auto *reg_dest =
+      static_cast<generic::RegisterDestinationOperand<UInt> *>(dest);
+  auto *reg = reg_dest->GetRegister();
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+  auto *flag_db = instruction->Destination(1)->AllocateDataBuffer();
+  flag_db->Set<uint32_t>(0, flag);
+  flag_db->Submit();
+}
+
+// TODO(torerik): Modify as needed if used to produce values in capability
+// registers.
+// Generic helper function for binary floating point instructions. The main
+// difference is that it handles rounding mode.
+template <typename Register, typename Result, typename Argument>
+inline void RiscVBinaryFloatNaNBoxOp(
+    const Instruction *instruction,
+    std::function<Result(Argument, Argument)> operation) {
+  Argument lhs = GetNaNBoxedSource<Register, Argument>(instruction, 0);
+  Argument rhs = GetNaNBoxedSource<Register, Argument>(instruction, 1);
+  // Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
+  // Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
+
+  // Get the rounding mode.
+  int rm_value = generic::GetInstructionSource<int>(instruction, 2);
+
+  auto *rv_fp = static_cast<CheriotState *>(instruction->state())->rv_fp();
+  // If the rounding mode is dynamic, read it from the current state.
+  if (rm_value == *FPRoundingMode::kDynamic) {
+    if (!rv_fp->rounding_mode_valid()) {
+      LOG(ERROR) << "Invalid rounding mode";
+      return;
+    }
+    rm_value = *rv_fp->GetRoundingMode();
+  }
+  Result dest_value;
+  {
+    ScopedFPStatus fp_status(rv_fp->host_fp_interface(), rm_value);
+    dest_value = operation(lhs, rhs);
+  }
+  if (std::isnan(dest_value)) {
+    *reinterpret_cast<typename FPTypeInfo<Result>::UIntType *>(&dest_value) =
+        FPTypeInfo<Result>::kCanonicalNaN;
+  }
+  auto *reg = static_cast<generic::RegisterDestinationOperand<Register> *>(
+                  instruction->Destination(0))
+                  ->GetRegister();
+  // Check to see if we need to NaN box the result.
+  if (sizeof(Register) > sizeof(Result)) {
+    // If the floating point value is narrower than the register, the upper
+    // bits have to be set to all ones.
+    using UReg = typename std::make_unsigned<Register>::type;
+    using UInt = typename FPTypeInfo<Result>::UIntType;
+    auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
+    UReg reg_value = std::numeric_limits<UReg>::max();
+    int shift = 8 * (sizeof(Register) - sizeof(Result));
+    reg_value = (reg_value << shift) | dest_u_value;
+    reg->data_buffer()->template Set<Register>(0, reg_value);
+    return;
+  }
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+}
+
+// TODO(torerik): Modify as needed if used to produce values in capability
+// registers.
+// Generic helper function for ternary floating point instructions.
+template <typename Register, typename Result, typename Argument>
+inline void RiscVTernaryFloatNaNBoxOp(
+    const Instruction *instruction,
+    std::function<Result(Argument, Argument, Argument)> operation) {
+  Argument rs1 = generic::GetInstructionSource<Argument>(instruction, 0);
+  Argument rs2 = generic::GetInstructionSource<Argument>(instruction, 1);
+  Argument rs3 = generic::GetInstructionSource<Argument>(instruction, 2);
+  // Get the rounding mode.
+  int rm_value = generic::GetInstructionSource<int>(instruction, 3);
+
+  auto *rv_fp = static_cast<CheriotState *>(instruction->state())->rv_fp();
+  // If the rounding mode is dynamic, read it from the current state.
+  if (rm_value == *FPRoundingMode::kDynamic) {
+    if (!rv_fp->rounding_mode_valid()) {
+      LOG(ERROR) << "Invalid rounding mode";
+      return;
+    }
+    rm_value = *rv_fp->GetRoundingMode();
+  }
+  Result dest_value;
+  {
+    ScopedFPStatus fp_status(rv_fp->host_fp_interface(), rm_value);
+    dest_value = operation(rs1, rs2, rs3);
+  }
+  auto *reg = static_cast<generic::RegisterDestinationOperand<Register> *>(
+                  instruction->Destination(0))
+                  ->GetRegister();
+  // Check to see if we need to NaN box the result.
+  if (sizeof(Register) > sizeof(Result)) {
+    // If the floating point value is narrower than the register, the upper
+    // bits have to be set to all ones.
+    using UReg = typename std::make_unsigned<Register>::type;
+    using UInt = typename FPTypeInfo<Result>::UIntType;
+    auto dest_u_value = *reinterpret_cast<UInt *>(&dest_value);
+    UReg reg_value = std::numeric_limits<UReg>::max();
+    int shift = 8 * (sizeof(Register) - sizeof(Result));
+    reg_value = (reg_value << shift) | dest_u_value;
+    reg->data_buffer()->template Set<Register>(0, reg_value);
+    return;
+  }
+  reg->data_buffer()->template Set<Result>(0, dest_value);
+}
+
+// Helper function to classify floating point values.
+template <typename T>
+typename FPTypeInfo<T>::UIntType ClassifyFP(T val) {
+  using UIntType = typename FPTypeInfo<T>::UIntType;
+  auto int_value = *reinterpret_cast<UIntType *>(&val);
+  UIntType sign = int_value >> (FPTypeInfo<T>::kBitSize - 1);
+  UIntType exp_mask = (1 << FPTypeInfo<T>::kExpSize) - 1;
+  UIntType exp = (int_value >> FPTypeInfo<T>::kSigSize) & exp_mask;
+  UIntType sig =
+      int_value & ((static_cast<UIntType>(1) << FPTypeInfo<T>::kSigSize) - 1);
+  if (exp == 0) {    // The number is denormal or zero.
+    if (sig == 0) {  // The number is zero.
+      return sign ? 1 << 3 : 1 << 4;
+    } else {  // subnormal.
+      return sign ? 1 << 2 : 1 << 5;
+    }
+  } else if (exp == exp_mask) {  //  The number is infinity or NaN.
+    if (sig == 0) {              // infinity
+      return sign ? 1 : 1 << 7;
+    } else {
+      if ((sig >> (FPTypeInfo<T>::kSigSize - 1)) != 0) {  // Quiet NaN.
+        return 1 << 9;
+      } else {  // signaling NaN.
+        return 1 << 8;
+      }
+    }
+  }
+  return sign ? 1 << 1 : 1 << 6;
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTION_HELPERS_H_
diff --git a/cheriot/riscv_cheriot_instructions.cc b/cheriot/riscv_cheriot_instructions.cc
new file mode 100644
index 0000000..09ccc3d
--- /dev/null
+++ b/cheriot/riscv_cheriot_instructions.cc
@@ -0,0 +1,660 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_instructions.h"
+
+#include <any>
+#include <bit>
+#include <cstdint>
+#include <type_traits>
+
+#include "absl/log/log.h"
+#include "absl/numeric/bits.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_register.h"
+#include "riscv//riscv_state.h"
+
+// This file contains the implementations of the RiscV CHERIoT instruction
+// semantic functions. These instructions are defined in section 9 in the
+// Microsoft Tech Report MSR-TR-2023-6 "CHERIoT: Rethinking security for
+// low-cost embedded systems"."
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+using CapReg = CheriotRegister;
+using EC = ::mpact::sim::cheriot::ExceptionCode;
+using RV32Register = ::mpact::sim::riscv::RV32Register;
+using ::mpact::sim::generic::RegisterBase;
+
+// Helpers to get capability register source and destination registers.
+static inline CapReg *GetCapSource(const Instruction *instruction, int i) {
+  return static_cast<CapReg *>(
+      std::any_cast<RegisterBase *>(instruction->Source(i)->GetObject()));
+}
+
+static inline CapReg *GetCapDest(const Instruction *instruction, int i) {
+  return static_cast<CapReg *>(
+      std::any_cast<RegisterBase *>(instruction->Destination(i)->GetObject()));
+}
+// Writing an integer result requires invalidating the capability and setting
+// it to null.
+template <typename Result>
+static inline void WriteCapIntResult(const Instruction *instruction, int i,
+                                     Result value) {
+  auto *cap_reg = GetCapDest(instruction, i);
+  cap_reg->data_buffer()->Set<Result>(0, value);
+  cap_reg->Invalidate();
+  cap_reg->set_is_null();
+}
+
+// Sign extension helper function.
+template <typename T>
+static inline T SignExtend(T value, int size) {
+  using ST = typename std::make_signed<T>::type;
+  ST svalue = value;
+  int shift_amount = sizeof(T) * 8 - size;
+  svalue = (svalue << shift_amount) >> shift_amount;
+  return static_cast<T>(svalue);
+}
+
+// Instruction semantic function bodies.
+
+void CheriotAuicap(const Instruction *instruction) {
+  // TODO(torerik): fix bug here.
+  auto *cap_src = GetCapSource(instruction, 0);
+  auto offset = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cap_dest = GetCapDest(instruction, 0);
+  cap_dest->CopyFrom(*cap_src);
+  uint32_t address = cap_src->address() + offset;
+  cap_dest->data_buffer()->Set<uint32_t>(0, address);
+  if (cap_dest->IsSealed()) {
+    cap_dest->Invalidate();
+  }
+  if (!cap_dest->IsRepresentable()) cap_dest->Invalidate();
+}
+
+void CheriotCAndPerm(const Instruction *instruction) {
+  auto *cap_src = GetCapSource(instruction, 0);
+  auto perms = cap_src->permissions();
+  auto perms_to_keep = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto new_perms = perms & perms_to_keep;
+  auto *cap_dest = GetCapDest(instruction, 0);
+  bool valid = !cap_src->IsSealed();
+  cap_dest->CopyFrom(*cap_src);
+  cap_dest->ClearPermissions(perms ^ new_perms);
+  if (!valid) cap_dest->Invalidate();
+}
+
+void CheriotCClearTag(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cd = GetCapDest(instruction, 0);
+  if (cd != cs1) cd->CopyFrom(*cs1);
+  cd->Invalidate();
+}
+
+void CheriotCGetAddr(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  WriteCapIntResult<uint32_t>(instruction, 0, cs1->address());
+}
+
+void CheriotCGetBase(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto [base, unused] = cs1->ComputeBounds();
+  WriteCapIntResult(instruction, 0, base);
+}
+
+void CheriotCGetHigh(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  WriteCapIntResult<uint32_t>(instruction, 0, cs1->Compress());
+}
+
+void CheriotCGetLen(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto [base, top] = cs1->ComputeBounds();
+  uint64_t length = top - base;
+  if (length == 0x1'0000'0000ULL) length = 0xffff'ffff;
+  WriteCapIntResult<uint32_t>(instruction, 0, length);
+}
+
+void CheriotCGetPerm(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  WriteCapIntResult<uint32_t>(instruction, 0, cs1->permissions());
+}
+
+void CheriotCGetTag(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  WriteCapIntResult<uint32_t>(instruction, 0, cs1->tag());
+}
+
+void CheriotCGetTop(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto [unused, top] = cs1->ComputeBounds();
+  // auto top = cs1->top();
+  if (top == 0x1'0000'0000ULL) {
+    top = 0xffff'ffff;
+  }
+  WriteCapIntResult<uint32_t>(instruction, 0, top);
+}
+
+void CheriotCGetType(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  uint32_t object_type = cs1->object_type();
+  object_type &= 0b0111;
+  if ((object_type != 0) && (!cs1->HasPermission(CapReg::kPermitExecute))) {
+    object_type |= 0b1000;
+  }
+  WriteCapIntResult<uint32_t>(instruction, 0, object_type);
+}
+
+void CheriotCIncAddr(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto inc = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  uint32_t new_addr = cs1->address() + inc;
+  bool valid = true;
+  if (cs1->IsSealed()) valid = false;
+  cd->CopyFrom(*cs1);
+  cd->SetAddress(new_addr);
+  if (!cd->IsRepresentable() || !valid) cd->Invalidate();
+}
+
+// Helper function to check for exceptions for Jal and J.
+static bool CheriotCJChecks(const Instruction *instruction, uint64_t new_pc,
+                            const CheriotRegister *pcc) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  if (!state->has_compact() && (new_pc & 0b10)) {
+    state->Trap(/*is_interrupt*/ false, new_pc,
+                *riscv::ExceptionCode::kInstructionAddressMisaligned,
+                instruction->address(), instruction);
+    return false;
+  }
+  return true;
+}
+
+void CheriotCJal(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto offset = generic::GetInstructionSource<uint32_t>(instruction, 0);
+  uint64_t new_pc = offset + instruction->address();
+  auto *pcc = state->pcc();
+  if (!CheriotCJChecks(instruction, new_pc, pcc)) return;
+  // Update link register.
+  auto *cd = GetCapDest(instruction, 0);
+  cd->CopyFrom(*pcc);
+  cd->set_address(instruction->address() + instruction->size());
+  bool interrupt_enable = state->mstatus()->mie();
+  (void)cd->Seal(*state->sealing_root(),
+                 interrupt_enable ? CapReg::kInterruptEnablingSentry
+                                  : CapReg::kInterruptDisablingSentry);
+  // Update pcc.
+  pcc->set_address(new_pc);
+  state->set_branch(true);
+}
+
+void CheriotCJ(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto offset = generic::GetInstructionSource<uint32_t>(instruction, 0);
+  uint64_t new_pc = offset + instruction->address();
+  auto *pcc = state->pcc();
+  if (!CheriotCJChecks(instruction, new_pc, pcc)) return;
+  // Update pcc.
+  pcc->set_address(new_pc);
+  state->set_branch(true);
+}
+
+// Helper function to check for exceptions for Jr and Jalr.
+static bool CheriotCJrCheck(const Instruction *instruction, uint64_t new_pc,
+                            uint32_t offset, const CheriotRegister *cs1) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  if (!cs1->tag()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExTagViolation, cs1);
+    return false;
+  }
+  if (cs1->IsSealed() && (!cs1->IsSentry() || offset != 0)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExSealViolation, cs1);
+    return false;
+  }
+  if (!cs1->HasPermission(CapReg::kPermitExecute)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExPermitExecuteViolation, cs1);
+    return false;
+  }
+  if (!state->has_compact() && (new_pc & 0b10)) {
+    state->Trap(/*is_interrupt*/ false, new_pc,
+                *riscv::ExceptionCode::kInstructionAddressMisaligned,
+                instruction->address(), instruction);
+    return false;
+  }
+  return true;
+}
+
+void CheriotCJalr(const Instruction *instruction) {
+  // TODO(torerik): fix this mess.
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto offset = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *pcc = state->pcc();
+  auto new_pc = offset + cs1->address();
+  new_pc &= ~0b1ULL;
+  if (!CheriotCJrCheck(instruction, new_pc, offset, cs1)) {
+    return;
+  }
+  // Update link register.
+  state->temp_reg()->CopyFrom(*pcc);
+  state->temp_reg()->set_address(instruction->address() + instruction->size());
+  auto *mstatus = state->mstatus();
+  bool interrupt_enable = (mstatus->GetUint32() & 0b1000) != 0;
+  auto status = state->temp_reg()->Seal(
+      *state->sealing_root(), interrupt_enable
+                                  ? CapReg::kInterruptEnablingSentry
+                                  : CapReg::kInterruptDisablingSentry);
+  if (!status.ok()) {
+    LOG(ERROR) << "Failed to seal: " << status;
+    return;
+  }
+  // Update pcc.
+  pcc->CopyFrom(*cs1);
+  // If the new pcc is a sentry, unseal and set/clear mie accordingly.
+  if (pcc->IsSentry()) {
+    if (pcc->object_type() != CapReg::kSentry) {
+      interrupt_enable = pcc->object_type() == CapReg::kInterruptEnablingSentry;
+      mstatus->set_mie(interrupt_enable);
+      mstatus->Submit();
+    }
+    (void)pcc->Unseal(*state->sealing_root(), pcc->object_type());
+  }
+  pcc->set_address(new_pc);
+  state->set_branch(true);
+  auto *cd = GetCapDest(instruction, 0);
+  cd->CopyFrom(*state->temp_reg());
+}
+
+void CheriotCJr(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto offset = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *pcc = state->pcc();
+  auto new_pc = offset + cs1->address();
+  new_pc &= ~0b1ULL;
+  if (!CheriotCJrCheck(instruction, new_pc, offset, cs1)) return;
+  // Update pcc.
+  pcc->CopyFrom(*cs1);
+  // If the new pcc is a sentry, unseal and set/clear mie accordingly.
+  if (pcc->IsSentry()) {
+    if (pcc->object_type() != CapReg::kSentry) {
+      bool interrupt_enable =
+          pcc->object_type() == CapReg::kInterruptEnablingSentry;
+      auto *mstatus = state->mstatus();
+      mstatus->set_mie(interrupt_enable);
+      mstatus->Submit();
+    }
+    (void)pcc->Unseal(*state->sealing_root(), pcc->object_type());
+  }
+  pcc->set_address(new_pc);
+  state->set_branch(true);
+}
+
+void CheriotCLc(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto offset = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cs1 = GetCapSource(instruction, 0);
+  uint32_t address = cs1->address() + offset;
+  if (!cs1->tag()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExTagViolation, cs1);
+    return;
+  }
+  if (cs1->IsSealed()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExSealViolation, cs1);
+    return;
+  }
+  if (!cs1->HasPermission(CapReg::kPermitLoad)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExPermitLoadViolation, cs1);
+    return;
+  }
+  if (!cs1->IsInBounds(address, CapReg::kCapabilitySizeInBytes)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExBoundsViolation, cs1);
+    return;
+  }
+  if ((address & ((1 << CapReg::kGranuleShift) - 1)) != 0) {
+    state->Trap(/*is_interrupt*/ false, address,
+                *riscv::ExceptionCode::kLoadAddressMisaligned,
+                instruction->address(), instruction);
+    return;
+  }
+  auto *db = state->db_factory()->Allocate(CapReg::kCapabilitySizeInBytes);
+  db->set_latency(0);
+  auto *tag_db = state->db_factory()->Allocate(1);
+  auto *context = new CapabilityLoadContext32(db, tag_db, cs1->permissions(),
+                                              /*clear_tag=*/false);
+  state->LoadCapability(instruction, address, db, tag_db, instruction->child(),
+                        context);
+  context->DecRef();
+}
+
+void CheriotCLcChild(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto *context =
+      static_cast<CapabilityLoadContext32 *>(instruction->context());
+  auto *cd = GetCapDest(instruction, 0);
+  cd->Expand(context->db->Get<uint32_t>(0), context->db->Get<uint32_t>(1),
+             context->tag_db->Get<uint8_t>(0));
+  if (cd->tag()) {
+    if ((context->permissions & CapReg::kPermitLoadGlobal) == 0) {
+      cd->ClearPermissions(CapReg::kPermitGlobal | CapReg::kPermitLoadGlobal);
+    }
+    if (!cd->IsSealed() &&
+        ((context->permissions & CapReg::kPermitLoadMutable) == 0)) {
+      cd->ClearPermissions(CapReg::kPermitStore | CapReg::kPermitLoadMutable);
+    }
+    // If the source capability did not have load/store capability, invalidate.
+    if ((context->permissions & CapReg::kPermitLoadStoreCapability) == 0) {
+      cd->Invalidate();
+    }
+    // If it's not a sealing cap, check for revocation.
+    if (cd->tag() &&
+        ((cd->permissions() & (CapReg::kPermitSeal | CapReg::kPermitUnseal |
+                               CapReg::kUserPerm0)) == 0)) {
+      if (state->MustRevoke(cd->base())) {
+        cd->Invalidate();
+      }
+    }
+  }
+}
+
+void CheriotCMove(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cd = GetCapDest(instruction, 0);
+  cd->CopyFrom(*cs1);
+}
+
+static uint32_t GetExponent(uint32_t length) {
+  constexpr uint32_t kMaxLenBase = (1 << 9) - 1;
+  if (length > kMaxLenBase * (1 << 14)) return 24;
+  // Compute the power of 2 value that is the ceiling of the rounded division
+  uint32_t alignment = absl::bit_ceil((length + kMaxLenBase - 1) / kMaxLenBase);
+  return absl::bit_width(alignment) - 1;
+}
+
+void CheriotCRepresentableAlignmentMask(const Instruction *instruction) {
+  auto rs1 = generic::GetInstructionSource<uint32_t>(instruction, 0);
+  auto exp = GetExponent(rs1);
+  WriteCapIntResult<uint32_t>(instruction, 0, 0xffff'ffffU << exp);
+}
+
+void CheriotCRoundRepresentableLength(const Instruction *instruction) {
+  auto rs1 = generic::GetInstructionSource<uint32_t>(instruction, 0);
+  auto exp = GetExponent(rs1);
+  uint32_t mask = (1 << exp) - 1;
+  uint32_t length = ((rs1 + mask) / (mask + 1)) * (mask + 1);
+  WriteCapIntResult(instruction, 0, length);
+}
+
+void CheriotCSc(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cs2 = GetCapSource(instruction, 2);
+  uint32_t imm = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  uint32_t address = cs1->address() + imm;
+  uint8_t tag = cs2->tag();
+  if (!cs1->tag()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExTagViolation, cs1);
+    return;
+  }
+  if (cs1->IsSealed()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExSealViolation, cs1);
+    return;
+  }
+  if (!cs1->HasPermission(CapReg::kPermitStore)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExPermitStoreViolation, cs1);
+    return;
+  }
+  if (!cs1->HasPermission(CapReg::kPermitLoadStoreCapability) && cs2->tag()) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExPermitStoreCapabilityViolation,
+                                   cs1);
+    return;
+  }
+  if (!cs1->HasPermission(CapReg::kPermitStoreLocalCapability) && cs2->tag() &&
+      !cs2->HasPermission(CapReg::kPermitGlobal)) {
+    tag = 0;
+  }
+  if (!cs1->IsInBounds(address, CapReg::kCapabilitySizeInBytes)) {
+    state->HandleCheriRegException(instruction, instruction->address(),
+                                   EC::kCapExBoundsViolation, cs1);
+    return;
+  }
+  if ((address & ((1 << CapReg::kGranuleShift) - 1)) != 0) {
+    state->Trap(/*is_interrupt*/ false, address,
+                *riscv::ExceptionCode::kStoreAddressMisaligned,
+                instruction->address(), instruction);
+    return;
+  }
+  auto *db = state->db_factory()->Allocate(CapReg::kCapabilitySizeInBytes);
+  auto *tag_db = state->db_factory()->Allocate(1);
+  db->Set<uint32_t>(0, cs2->address());
+  db->Set<uint32_t>(1, cs2->Compress());
+  tag_db->Set<uint8_t>(0, tag);
+  state->StoreCapability(instruction, address, db, tag_db);
+  db->DecRef();
+  tag_db->DecRef();
+}
+
+void CheriotCSeal(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cs2 = GetCapSource(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  bool valid = true;
+  // If cs1 is sealed, invalidate cd.
+  if (cs1->IsSealed()) valid = false;
+  uint32_t object_type = cs2->address();
+  bool permitted_otype = false;
+  switch (object_type) {
+    case CapReg::kSentry:
+    case CapReg::kInterruptDisablingSentry:
+    case CapReg::kInterruptEnablingSentry:
+    case CapReg::kSealedExecutable6:
+    case CapReg::kSealedExecutable7:
+      permitted_otype = cs1->HasPermission(CapReg::kPermitExecute);
+      break;
+    default:
+      permitted_otype = !cs1->HasPermission(CapReg::kPermitExecute) &&
+                        (object_type > 8) && (object_type <= 15);
+      break;
+  }
+  bool permitted = cs2->tag() && cs2->HasPermission(CapReg::kPermitSeal) &&
+                   (object_type < cs2->top()) && (object_type >= cs2->base()) &&
+                   permitted_otype && cs2->IsUnsealed();
+  auto cs2_address = cs2->address();
+  cd->CopyFrom(*cs1);
+  cd->set_object_type(
+      cs2_address &
+      ((1 << (CapReg::kObjectType[0] - CapReg::kObjectType[1] + 1))) - 1);
+  if (!permitted || !valid) cd->Invalidate();
+}
+
+void CheriotCSetAddr(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto rs2 = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  auto valid = true;
+  if (cs1->IsSealed()) valid = false;
+  cd->CopyFrom(*cs1);
+  cd->SetAddress(rs2);
+  if (!cd->IsRepresentable() || !valid) cd->Invalidate();
+}
+
+void CheriotCSetBounds(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto rs2 = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  auto cs1_address = cs1->address();
+  bool valid = true;
+  // If cs1 is sealed, then invalidate the capability.
+  if (cs1->IsSealed()) valid = false;
+  // If the bounds are such that the new requested capability is not
+  // representable, invalidate.
+  auto [cs1_base, cs1_top] = cs1->ComputeBounds();
+  auto new_top =
+      static_cast<uint64_t>(cs1_address) + static_cast<uint64_t>(rs2);
+  valid &= (cs1_address >= cs1_base) && (new_top <= cs1_top);
+  cd->CopyFrom(*cs1);
+  (void)cd->SetBounds(cs1_address, rs2);
+  if (!valid) cd->Invalidate();
+}
+
+void CheriotCSetBoundsExact(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto rs2 = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  bool valid = true;
+  auto cs1_address = cs1->address();
+  // If cs1 is sealed, then invalidate the capability.
+  if (cs1->IsSealed()) valid = false;
+  // If outside the requested representable range, invalidate.
+  auto [cs1_base, cs1_top] = cs1->ComputeBounds();
+  auto new_top =
+      static_cast<uint64_t>(cs1_address) + static_cast<uint64_t>(rs2);
+  valid &= (cs1_address >= cs1_base) && (new_top <= cs1_top);
+  // Invalidate if not exact.
+  cd->CopyFrom(*cs1);
+  bool exact = cd->SetBounds(cs1_address, rs2);
+  if (!exact || !valid) cd->Invalidate();
+}
+
+void CheriotCSetEqualExact(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cs2 = GetCapSource(instruction, 1);
+  uint32_t equal =
+      (cs1->tag() == cs2->tag()) && (cs1->Compress() == cs2->Compress());
+  WriteCapIntResult(instruction, 0, equal);
+}
+
+void CheriotCSetHigh(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto rs2 = generic::GetInstructionSource<uint32_t>(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  cd->Expand(cs1->address(), rs2, false);
+}
+
+void CheriotCSpecialR(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  // Decode will ensure that register scr is valid.
+  auto *scr = GetCapSource(instruction, 0);
+  auto *cd = GetCapDest(instruction, 0);
+  if (!state->pcc()->HasPermission(CapReg::kPermitAccessSystemRegisters)) {
+    state->HandleCheriRegException(
+        instruction, instruction->address(),
+        EC::kCapExPermitAccessSystemRegistersViolation, state->pcc());
+    return;
+  }
+  cd->CopyFrom(*scr);
+}
+
+void CheriotCSpecialRW(const Instruction *instruction) {
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  // Decode will ensure that register scr is valid.
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *scr = GetCapSource(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  if (!state->pcc()->HasPermission(CapReg::kPermitAccessSystemRegisters)) {
+    state->HandleCheriRegException(
+        instruction, instruction->address(),
+        EC::kCapExPermitAccessSystemRegistersViolation, state->pcc());
+    return;
+  }
+  auto *temp_reg = state->temp_reg();
+  temp_reg->CopyFrom(*cs1);
+  cd->CopyFrom(*scr);
+  // If it's the mepcc register, make sure to clear any lsb.
+  if (scr->name() == "mepcc") {
+    if (temp_reg->address() & 0x1ULL) {
+      temp_reg->set_address(temp_reg->address() & ~0x1);
+      temp_reg->Invalidate();
+    } else if (temp_reg->IsSealed() ||
+               !temp_reg->HasPermission(CapReg::kPermitExecute)) {
+      temp_reg->Invalidate();
+    }
+  } else if (scr->name() == "mtcc") {
+    if (temp_reg->address() & 0x3ULL) {
+      temp_reg->set_address(temp_reg->address() & ~0x3ULL);
+      temp_reg->Invalidate();
+    } else if (temp_reg->IsSealed() ||
+               !temp_reg->HasPermission(CapReg::kPermitExecute)) {
+      temp_reg->Invalidate();
+    }
+  }
+  scr->CopyFrom(*state->temp_reg());
+}
+
+void CheriotCSub(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cs2 = GetCapSource(instruction, 1);
+  WriteCapIntResult(instruction, 0, cs1->address() - cs2->address());
+}
+
+void CheriotCTestSubset(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cs2 = GetCapSource(instruction, 1);
+  auto [cs1_base, cs1_top] = cs1->ComputeBounds();
+  auto [cs2_base, cs2_top] = cs2->ComputeBounds();
+  // Verify that cs2 is a subset of cs1.
+  bool result =
+      cs1->tag() == cs2->tag() &&
+      // cs2 has a valid range smaller or equal to cs1.
+      cs1_base <= cs2_base && cs1_top >= cs2_top &&
+      // cs2 permissions are a subset of cs1 permissions.
+      ((cs1->permissions() & cs2->permissions()) == cs2->permissions());
+  WriteCapIntResult(instruction, 0, static_cast<uint32_t>(result));
+}
+
+void CheriotCUnseal(const Instruction *instruction) {
+  auto *cs1 = GetCapSource(instruction, 0);
+  auto *cs2 = GetCapSource(instruction, 1);
+  auto *cd = GetCapDest(instruction, 0);
+  bool valid = true;
+  if (!cs2->tag() || !cs1->IsSealed() || cs2->IsSealed() ||
+      (cs2->address() != cs1->object_type()) ||
+      !cs2->HasPermission(CapReg::kPermitUnseal) ||
+      (cs2->address() < cs2->base()) || (cs2->address() >= cs2->top())) {
+    valid = false;
+  }
+  auto cs2_permissions = cs2->permissions();
+  cd->CopyFrom(*cs1);
+  if ((cs2_permissions & CapReg::kPermitGlobal) == 0) {
+    cd->ClearPermissions(CapReg::kPermitGlobal);
+  }
+  cd->set_object_type(CapReg::kUnsealed);
+  if (!valid) cd->Invalidate();
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_instructions.h b/cheriot/riscv_cheriot_instructions.h
new file mode 100644
index 0000000..346bc24
--- /dev/null
+++ b/cheriot/riscv_cheriot_instructions.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+// This file declares the instruction semantic functions for the RiscV CHERIoT
+// instructions. These instructions are defined in section 9 in the Microsoft
+// Tech Report MSR-TR-2023-6 "CHERIoT: Rethinking security for low-cost embedded
+// systems"."
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using generic::Instruction;
+
+// This takes 2 source operands and one destination operand. Source 0 is the
+// source capability, source 1 is a pre-scaled immediate. Destination 0 is the
+// target capability.
+void CheriotAuicap(const Instruction *instruction);
+// This takes 2 sources and 1 destination operand. Source 0 is the source
+// capability, source 1 is an integer register value. Destination 0 is the
+// target capability.
+void CheriotCAndPerm(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is the target capability.
+void CheriotCClearTag(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetAddr(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetBase(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetHigh(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetLen(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetPerm(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetTag(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetTop(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is an integer register.
+void CheriotCGetType(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability and source 1 is the integer increment amount. Destination
+// 0 is the target capability.
+void CheriotCIncAddr(const Instruction *instruction);
+// This instruction takes 1 source (an offset), and one destination (the link
+// capability). The pcc is implied.
+void CheriotCJal(const Instruction *instruction);
+// This instruction takes 1 source (an offset). The pcc is implied.
+void CheriotCJ(const Instruction *instruction);
+// This instruction takes 2 sources and one destination operand. Source 0 is the
+// source capability, source 1 is the offset. Destination 0 is the link
+// capability. The pcc is implied.
+void CheriotCJalr(const Instruction *instruction);
+// This instruction takes 2 sources. Source 0 is the source capability, source 1
+// is the offset. The pcc is implied.
+void CheriotCJr(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// address source capability, source 1 is the integer offset. Destination 0 is
+// the target capability.
+void CheriotCLc(const Instruction *instruction);
+// Child instruction function for CheriotCLc.
+void CheriotCLcChild(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is the source
+// capability. Destination 0 is the target capability.
+void CheriotCMove(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is an integer value
+// (address of a capability). Destination 0 is an integer register.
+void CheriotCRepresentableAlignmentMask(const Instruction *instruction);
+// This takes 1 source and 1 destination operand. Source 0 is an integer value
+// (address of a capability). Destination 0 is an integer register.
+void CheriotCRoundRepresentableLength(const Instruction *instruction);
+// This takes 3 source operands. Source 0 is the address source capability,
+// source 1 is the integer offset, and source 2 is the source capability that
+// is stored to memory.
+void CheriotCSc(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is the sealing authority capability. Destination
+// 0 is the target capability.
+void CheriotCSeal(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is an integer value. Destination 0 is the
+// target capability.
+void CheriotCSetAddr(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is an integer value. Destination 0 is the
+// target capability.
+void CheriotCSetBounds(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is an integer value. Destination 0 is the
+// target capability.
+void CheriotCSetBoundsExact(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Sources 0 and 1
+// are capabilities. Destination 0 is an integer register.
+void CheriotCSetEqualExact(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is an integer value. Destination 0 is the
+// target capability.
+void CheriotCSetHigh(const Instruction *instruction);
+// This takes one source and one destination operand. Source 0 is the source
+// special capability CSR. Destination 0 is the target capability.
+void CheriotCSpecialR(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is the special capability CSR. Destination 0
+// is the target capability.
+void CheriotCSpecialRW(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Sources 0 and 1
+// are capabilities. Destination 0 is an integer register.
+void CheriotCSub(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Sources 0 and 1
+// are capabilities. Destination 0 is an integer register.
+void CheriotCTestSubset(const Instruction *instruction);
+// This takes 2 source operands and 1 destination operand. Source 0 is the
+// source capability, source 1 is the unsealing authority capability.
+// Destination 0 is the target capability.
+void CheriotCUnseal(const Instruction *instruction);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_INSTRUCTIONS_H_
diff --git a/cheriot/riscv_cheriot_m_instructions.cc b/cheriot/riscv_cheriot_m_instructions.cc
new file mode 100644
index 0000000..50c462c
--- /dev/null
+++ b/cheriot/riscv_cheriot_m_instructions.cc
@@ -0,0 +1,108 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_m_instructions.h"
+
+#include <limits>
+#include <type_traits>
+
+#include "cheriot/cheriot_register.h"
+#include "cheriot/riscv_cheriot_instruction_helpers.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::WideType;
+
+using RegType = CheriotRegister;
+using UintReg = typename std::make_unsigned<RegType::ValueType>::type;
+using WideUintReg = typename WideType<UintReg>::type;
+using IntReg = typename std::make_signed<RegType::ValueType>::type;
+using WideIntReg = typename WideType<IntReg>::type;
+
+void MMul(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, UintReg, WideIntReg>(
+      instruction, [](WideIntReg a_wide, WideIntReg b_wide) {
+        WideIntReg c_wide = a_wide * b_wide;
+        return static_cast<UintReg>(
+            (c_wide & std::numeric_limits<UintReg>::max()));
+      });
+}
+
+void MMulh(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, IntReg>(instruction,
+                                     [](WideIntReg a_wide, WideIntReg b_wide) {
+                                       WideIntReg c_wide = a_wide * b_wide;
+                                       return static_cast<IntReg>(c_wide >> 32);
+                                     });
+}
+
+void MMulhu(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, UintReg>(
+      instruction, [](WideUintReg a_wide, WideUintReg b_wide) {
+        WideUintReg c_wide = a_wide * b_wide;
+        return static_cast<UintReg>(c_wide >> 32);
+      });
+}
+
+void MMulhsu(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, UintReg, WideIntReg, WideUintReg>(
+      instruction, [](WideIntReg a_wide, WideUintReg b_wide) {
+        WideIntReg c_wide = a_wide * b_wide;
+        return static_cast<IntReg>(c_wide >> 32);
+      });
+}
+
+void MDiv(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, IntReg>(
+      instruction, [](IntReg a, IntReg b) -> IntReg {
+        if (b == 0) return -1;
+        if ((b == -1) && (a == std::numeric_limits<IntReg>::min())) {
+          return std::numeric_limits<IntReg>::min();
+        }
+        return a / b;
+      });
+}
+
+void MDivu(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, UintReg>(
+      instruction, [](UintReg a, UintReg b) -> UintReg {
+        if (b == 0) return std::numeric_limits<UintReg>::max();
+        return a / b;
+      });
+}
+
+void MRem(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, IntReg>(
+      instruction, [](IntReg a, IntReg b) -> IntReg {
+        if (b == 0) return a;
+        if ((b == -1) && (a == std::numeric_limits<IntReg>::min())) {
+          return 0;
+        }
+        return a % b;
+      });
+}
+
+void MRemu(Instruction *instruction) {
+  RVCheriotBinaryOp<RegType, UintReg>(instruction, [](UintReg a, UintReg b) {
+    if (b == 0) return a;
+    return a % b;
+  });
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_m_instructions.h b/cheriot/riscv_cheriot_m_instructions.h
new file mode 100644
index 0000000..6ddccca
--- /dev/null
+++ b/cheriot/riscv_cheriot_m_instructions.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_M_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_M_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::Instruction;
+
+// Each instruction takes rs1 as source operand 0, rs2 as source operand 1,
+// and rd as destination operand 0.
+
+void MMul(Instruction *instruction);
+void MMulh(Instruction *instruction);
+void MMulhu(Instruction *instruction);
+void MMulhsu(Instruction *instruction);
+void MDiv(Instruction *instruction);
+void MDivu(Instruction *instruction);
+void MRem(Instruction *instruction);
+void MRemu(Instruction *instruction);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_M_INSTRUCTIONS_H_
diff --git a/cheriot/riscv_cheriot_minstret.cc b/cheriot/riscv_cheriot_minstret.cc
new file mode 100644
index 0000000..6eae2dd
--- /dev/null
+++ b/cheriot/riscv_cheriot_minstret.cc
@@ -0,0 +1,73 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_minstret.h"
+
+#include <cstdint>
+#include <string>
+
+#include "cheriot/cheriot_state.h"
+#include "riscv//riscv_csr.h"
+
+namespace mpact::sim::cheriot {
+
+using ::mpact::sim::riscv::RiscVCsrEnum;
+
+RiscVCheriotMInstret::RiscVCheriotMInstret(std::string name,
+                                           CheriotState *state)
+    : RiscVSimpleCsr<uint32_t>(name, RiscVCsrEnum::kMInstret, state) {}
+
+// Read the current value of the counter and apply the offset.
+uint32_t RiscVCheriotMInstret::GetUint32() {
+  if (counter_ == nullptr) return offset_;
+  uint32_t value = GetCounterValue() + offset_;
+  return value;
+}
+
+uint64_t RiscVCheriotMInstret::GetUint64() {
+  return static_cast<uint64_t>(GetUint32());
+}
+
+void RiscVCheriotMInstret::Set(uint32_t value) {
+  offset_ = value - GetCounterValue();
+}
+
+void RiscVCheriotMInstret::Set(uint64_t value) {
+  Set(static_cast<uint32_t>(value));
+}
+
+RiscVCheriotMInstreth::RiscVCheriotMInstreth(std::string name,
+                                             CheriotState *state)
+    : RiscVSimpleCsr<uint32_t>(name, RiscVCsrEnum::kMInstretH, state) {}
+
+// Read the current value of the counter and apply the offset.
+uint32_t RiscVCheriotMInstreth::GetUint32() {
+  if (counter_ == nullptr) return offset_;
+  uint32_t value = GetCounterValue() + offset_;
+  return value;
+}
+
+uint64_t RiscVCheriotMInstreth::GetUint64() {
+  return static_cast<uint64_t>(GetUint32());
+}
+
+void RiscVCheriotMInstreth::Set(uint32_t value) {
+  offset_ = value - GetCounterValue();
+}
+
+void RiscVCheriotMInstreth::Set(uint64_t value) {
+  Set(static_cast<uint32_t>(value));
+}
+
+}  // namespace mpact::sim::cheriot
diff --git a/cheriot/riscv_cheriot_minstret.h b/cheriot/riscv_cheriot_minstret.h
new file mode 100644
index 0000000..1df90b0
--- /dev/null
+++ b/cheriot/riscv_cheriot_minstret.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_MINSTRET_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_MINSTRET_H_
+
+#include <cstdint>
+#include <string>
+
+#include "cheriot/riscv_cheriot_csr_enum.h"
+#include "mpact/sim/generic/counters.h"
+#include "riscv//riscv_csr.h"
+
+// This file provides the declarations for the CherIoT minstret/minstreth
+// CSRs. They are tied to the instruction counter of the top level of the
+// simulator. That binding is done when the simulator is instantiated. Until
+// that is done, the CSR just works like a scratch CSR.
+
+// Since this CSR is both readable and writable, but the counter value cannot
+// be changed, every time the register is written, a relative offset is computed
+// from the counter, so that the values read are relative to the most recent
+// write of the CSR.
+
+namespace mpact::sim::cheriot {
+
+using ::mpact::sim::generic::SimpleCounter;
+using ::mpact::sim::riscv::RiscVSimpleCsr;
+
+class CheriotState;
+
+class RiscVCheriotMInstret : public RiscVSimpleCsr<uint32_t> {
+ public:
+  RiscVCheriotMInstret(std::string name, CheriotState* state);
+  RiscVCheriotMInstret(const RiscVCheriotMInstret&) = delete;
+  RiscVCheriotMInstret& operator=(const RiscVCheriotMInstret&) = delete;
+  ~RiscVCheriotMInstret() override = default;
+
+  // RiscVSimpleCsr method overrides.
+  uint32_t GetUint32() override;
+  uint64_t GetUint64() override;
+
+  void Set(uint32_t) override;
+  void Set(uint64_t) override;
+
+  void set_counter(SimpleCounter<uint64_t>* counter) { counter_ = counter; }
+
+ private:
+  inline uint32_t GetCounterValue() const {
+    return static_cast<uint32_t>(counter_->GetValue() & 0xffff'ffffULL);
+  };
+
+  SimpleCounter<uint64_t>* counter_ = nullptr;
+  uint32_t offset_ = 0;
+};
+
+class RiscVCheriotMInstreth : public RiscVSimpleCsr<uint32_t> {
+ public:
+  RiscVCheriotMInstreth(std::string name, CheriotState* state);
+  RiscVCheriotMInstreth(const RiscVCheriotMInstret&) = delete;
+  RiscVCheriotMInstreth& operator=(const RiscVCheriotMInstret&) = delete;
+  ~RiscVCheriotMInstreth() override = default;
+
+  // RiscVSimpleCsr method overrides.
+  uint32_t GetUint32() override;
+  uint64_t GetUint64() override;
+
+  void Set(uint32_t) override;
+  void Set(uint64_t) override;
+
+  void set_counter(SimpleCounter<uint64_t>* counter) { counter_ = counter; }
+
+ private:
+  inline uint32_t GetCounterValue() const {
+    return static_cast<uint32_t>(counter_->GetValue() >> 32);
+  };
+
+  SimpleCounter<uint64_t>* counter_ = nullptr;
+  uint32_t offset_ = 0;
+};
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_MINSTRET_H_
diff --git a/cheriot/riscv_cheriot_priv_instructions.cc b/cheriot/riscv_cheriot_priv_instructions.cc
new file mode 100644
index 0000000..e729c5d
--- /dev/null
+++ b/cheriot/riscv_cheriot_priv_instructions.cc
@@ -0,0 +1,51 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_priv_instructions.h"
+
+#include <cstdint>
+
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "riscv//riscv_xstatus.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using UIntReg = uint32_t;
+
+void RiscVPrivMRet(const Instruction *inst) {
+  auto *state = static_cast<CheriotState *>(inst->state());
+  state->pcc()->CopyFrom(*state->mepcc());
+  state->set_branch(true);
+  auto *mstatus = state->mstatus();
+  // Set mstatus:mie to the value of mstatus:mpie.
+  mstatus->set_mie(mstatus->mpie());
+  // Set mstatus:mpie to 1.
+  mstatus->set_mpie(1);
+  mstatus->set_mpp(*PrivilegeMode::kMachine);
+  state->SignalReturnFromInterrupt();
+  mstatus->Submit();
+}
+
+void RiscVPrivWfi(const Instruction *inst) {
+  // WFI is treated as a no-op, unless the user sets a callback.
+  auto *state = static_cast<CheriotState *>(inst->state());
+  state->WFI(inst);
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_priv_instructions.h b/cheriot/riscv_cheriot_priv_instructions.h
new file mode 100644
index 0000000..c013c1c
--- /dev/null
+++ b/cheriot/riscv_cheriot_priv_instructions.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_PRIV_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_PRIV_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+// This file contains the declarations of the instruction semantic functions
+// for the RiscV 32i privileged instructions.
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::Instruction;
+
+void RiscVPrivMRet(const Instruction *inst);
+
+void RiscVPrivWfi(const Instruction *inst);
+void RiscVPrivSFenceVmaZZ(const Instruction *inst);
+void RiscVPrivSFenceVmaZN(const Instruction *inst);
+void RiscVPrivSFenceVmaNZ(const Instruction *inst);
+void RiscVPrivSFenceVmaNN(const Instruction *inst);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_PRIV_INSTRUCTIONS_H_
diff --git a/cheriot/riscv_cheriot_register_aliases.h b/cheriot/riscv_cheriot_register_aliases.h
new file mode 100644
index 0000000..3c17317
--- /dev/null
+++ b/cheriot/riscv_cheriot_register_aliases.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_REGISTER_ALIASES_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_REGISTER_ALIASES_H_
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+constexpr char kXRegisterAliases[32][6] = {
+    /* x0 */ "zero",
+    /* x1 */ "ra",
+    /* x2 */ "sp",
+    /* x3 */ "gp",
+    /* x4 */ "tp",
+    /* x5 */ "t0",
+    /* x6 */ "t1",
+    /* x7 */ "t2",
+    /* x8 */ "s0",
+    /* x9 */ "s1",
+    /* x10 */ "a0",
+    /* x11 */ "a1",
+    /* x12 */ "a2",
+    /* x13 */ "a3",
+    /* x14 */ "a4",
+    /* x15 */ "a5",
+    /* x16 */ "a6",
+    /* x17 */ "a7",
+    /* x18 */ "s2",
+    /* x19 */ "s3",
+    /* x20 */ "s4",
+    /* x21 */ "s5",
+    /* x22 */ "s6",
+    /* x23 */ "s7",
+    /* x24 */ "s8",
+    /* x25 */ "s9",
+    /* x26 */ "s10",
+    /* x27 */ "s11",
+    /* x28 */ "t3",
+    /* x29 */ "t4",
+    /* x30 */ "t5",
+    /* x31 */ "t6"};
+constexpr char kCRegisterAliases[32][6] = {
+    "czero", "cra", "csp",  "cgp",  "ctp", "ct0", "ct1", "ct2",
+    "cs0",   "cs1", "ca0",  "ca1",  "ca2", "ca3", "ca4", "ca5",
+    "ca6",   "ca7", "cs2",  "cs3",  "cs4", "cs5", "cs6", "cs7",
+    "cs8",   "cs9", "cs10", "cs11", "ct3", "ct4", "ct5", "ct6"};
+constexpr char kFRegisterAliases[32][6] = {
+    "ft0", "ft1", "ft2",  "ft3",  "ft4", "ft5", "ft6",  "ft7",
+    "fs0", "fs1", "fa0",  "fa1",  "fa2", "fa3", "fa4",  "fa5",
+    "fa6", "fa7", "fs2",  "fs3",  "fs4", "fs5", "fs6",  "fs7",
+    "fs8", "fs9", "fs10", "fs11", "ft8", "ft9", "ft10", "ft11"};
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_REGISTER_ALIASES_H_
diff --git a/cheriot/riscv_cheriot_zicsr_instructions.cc b/cheriot/riscv_cheriot_zicsr_instructions.cc
new file mode 100644
index 0000000..16649b9
--- /dev/null
+++ b/cheriot/riscv_cheriot_zicsr_instructions.cc
@@ -0,0 +1,271 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_zicsr_instructions.h"
+
+#include <any>
+#include <cstdint>
+
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_csr_enum.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv//riscv_csr.h"
+#include "riscv//riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+using ::mpact::sim::riscv::PrivilegeMode;
+using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
+using RV_EC = ::mpact::sim::riscv::ExceptionCode;
+using CH_EC = ::mpact::sim::cheriot::ExceptionCode;
+using CapReg = ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::RegisterBase;
+
+// Helper to get capability destination registers.
+static inline CapReg *GetCapDest(const Instruction *instruction, int i) {
+  return static_cast<CapReg *>(
+      std::any_cast<RegisterBase *>(instruction->Destination(i)->GetObject()));
+}
+
+// Writing an integer result requires invalidating the capability and setting
+// it to null.
+template <typename Result>
+static inline void WriteCapIntResult(const Instruction *instruction, int i,
+                                     Result value) {
+  auto *cap_reg = GetCapDest(instruction, i);
+  cap_reg->data_buffer()->Set<Result>(0, value);
+  cap_reg->Invalidate();
+  cap_reg->set_is_null();
+}
+
+template <typename T>
+inline T ReadCsr(RiscVCsrInterface *) {}
+
+template <>
+inline uint32_t ReadCsr<uint32_t>(RiscVCsrInterface *csr) {
+  return csr->AsUint32();
+}
+template <>
+inline uint64_t ReadCsr<uint64_t>(RiscVCsrInterface *csr) {
+  return csr->AsUint64();
+}
+
+// Helper function to check that the CSR permission is valid. If not, throw
+// an illegal instruction exception.
+bool CheckCsrPermission(int csr_index, Instruction *instruction,
+                        bool is_write) {
+  auto required_mode = (csr_index >> 8) & 0x3;
+  auto *state = static_cast<CheriotState *>(instruction->state());
+  auto current_mode = PrivilegeMode::kMachine;
+  // If the register isn't available in CHERIoT, throw an exception.
+  if (required_mode == *PrivilegeMode::kSupervisor) {
+    state->Trap(/*is_interrupt*/ false, 0, *RV_EC::kIllegalInstruction,
+                instruction->address(), instruction);
+    return false;
+  }
+  // If the privilege mode is too low, throw an exception.
+  if (*current_mode < required_mode) {
+    state->Trap(/*is_interrupt*/ false, 0, *RV_EC::kIllegalInstruction,
+                instruction->address(), instruction);
+    return false;
+  }
+  // Accesses to fflags, frm, and fcsr are all ok.
+  if ((csr_index >= *RiscVCheriotCsrEnum::kFFlags) &&
+      (csr_index <= *RiscVCheriotCsrEnum::kFCsr)) {
+    return true;
+  }
+  // Reads to MCycle, MInstret, MHpmcounterN are all ok.
+  if (!is_write && (((csr_index >= *RiscVCheriotCsrEnum::kMCycle) &&
+                     (csr_index <= *RiscVCheriotCsrEnum::kMHpmcounter31)) ||
+                    ((csr_index >= *RiscVCheriotCsrEnum::kMCycleH) &&
+                     (csr_index <= *RiscVCheriotCsrEnum::kMHpmcounter31H)))) {
+    return true;
+  }
+  // Check pcc capability.
+  if ((required_mode != *PrivilegeMode::kUser) &&
+      (!state->pcc()->HasPermission(PB::kPermitAccessSystemRegisters))) {
+    state->HandleCheriRegException(
+        instruction, instruction->address(),
+        CH_EC::kCapExPermitAccessSystemRegistersViolation, state->pcc());
+    return false;
+  }
+  return true;
+}
+
+// Templated helper functions.
+
+// Read the CSR, write a new value back.
+template <typename T>
+static inline void RVZiCsrrw(Instruction *instruction) {
+  // Get a handle to the state instance.
+  auto state = static_cast<CheriotState *>(instruction->state());
+  // Get the csr index.
+  int csr_index = instruction->Source(1)->AsInt32(0);
+  if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
+  // Read the csr.
+  auto result = state->csr_set()->GetCsr(csr_index);
+  if (!result.ok()) {
+    // Signal error if it failed.
+    LOG(ERROR) << absl::StrCat("Instruction at address 0x",
+                               absl::Hex(instruction->address()),
+                               " failed to read CSR 0x", absl::Hex(csr_index),
+                               ": ", result.status().message());
+    return;
+  }
+  // Get the new value.
+  T new_value = generic::GetInstructionSource<T>(instruction, 0);
+  auto *csr = result.value();
+  // Update the register.
+  auto csr_val = ReadCsr<T>(csr);
+  WriteCapIntResult(instruction, 0, csr_val);
+  // Write the new value to the csr.
+  csr->Write(new_value);
+}
+
+// Read the CSR, set the bits specified by the new value and write back.
+template <typename T>
+static inline void RVZiCsrrs(Instruction *instruction) {
+  // Get a handle to the state instance.
+  auto state = static_cast<CheriotState *>(instruction->state());
+  // Get the csr index.
+  int csr_index = instruction->Source(1)->AsInt32(0);
+  if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
+  // Read the csr.
+  auto result = state->csr_set()->GetCsr(csr_index);
+  if (!result.ok()) {
+    // Signal error if it failed.
+    LOG(ERROR) << absl::StrCat("Instruction at address 0x",
+                               absl::Hex(instruction->address()),
+                               " failed to read CSR 0x", absl::Hex(csr_index),
+                               ": ", result.status().message());
+    return;
+  }
+  // Get the new value.
+  T new_value = generic::GetInstructionSource<T>(instruction, 0);
+  auto *csr = result.value();
+  // Update the register.
+  auto csr_val = ReadCsr<T>(csr);
+  WriteCapIntResult(instruction, 0, csr_val);
+  // Write the new value to the csr.
+  csr->SetBits(new_value);
+}
+
+// Read the CSR, clear the bits specified by the new value and write back.
+template <typename T>
+static inline void RVZiCsrrc(Instruction *instruction) {
+  // Get a handle to the state instance.
+  auto state = static_cast<CheriotState *>(instruction->state());
+  // Get the csr index.
+  int csr_index = instruction->Source(1)->AsInt32(0);
+  if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
+  // Read the csr.
+  auto result = state->csr_set()->GetCsr(csr_index);
+  if (!result.ok()) {
+    // Signal error if it failed.
+    LOG(ERROR) << absl::StrCat("Instruction at address 0x",
+                               absl::Hex(instruction->address()),
+                               " failed to read CSR 0x", absl::Hex(csr_index),
+                               ": ", result.status().message());
+    return;
+  }
+  // Get the new value.
+  T new_value = generic::GetInstructionSource<T>(instruction, 0);
+  auto *csr = result.value();
+  // Write the current value of the CSR to the destination register.
+  auto csr_val = ReadCsr<T>(csr);
+  WriteCapIntResult(instruction, 0, csr_val);
+  // Write the new value to the csr.
+  csr->ClearBits(new_value);
+}
+
+// Do not read the CSR, just write the new value back.
+template <typename T>
+static inline void RVZiCsrrwNr(Instruction *instruction) {
+  // Get a handle to the state instance.
+  auto state = static_cast<CheriotState *>(instruction->state());
+  // Get the csr index.
+  int csr_index = instruction->Source(1)->AsInt32(0);
+  if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/true)) return;
+  // Read the csr.
+  auto result = state->csr_set()->GetCsr(csr_index);
+  if (!result.ok()) {
+    LOG(ERROR) << absl::StrCat("Instruction at address 0x",
+                               absl::Hex(instruction->address()),
+                               " failed to write CSR 0x", absl::Hex(csr_index),
+                               ": ", result.status().message());
+    return;
+  }
+  auto *csr = result.value();
+  // Write the new value to the csr.
+  T new_value = generic::GetInstructionSource<T>(instruction, 0);
+  csr->Write(new_value);
+}
+
+// Do not write a value back to the CSR, just read it.
+template <typename T>
+static inline void RVZiCsrrNw(Instruction *instruction) {
+  // Get a handle to the state instance.
+  auto state = static_cast<CheriotState *>(instruction->state());
+  // Get the csr index.
+  int csr_index = instruction->Source(0)->AsInt32(0);
+  if (!CheckCsrPermission(csr_index, instruction, /*is_write=*/false)) return;
+  // Read the csr.
+  auto result = state->csr_set()->GetCsr(csr_index);
+  if (!result.ok()) {
+    LOG(ERROR) << absl::StrCat("Instruction at address 0x",
+                               absl::Hex(instruction->address()),
+                               " failed to read CSR 0x", absl::Hex(csr_index),
+                               ": ", result.status().message());
+    return;
+  }
+  // Get the CSR object.
+  auto *csr = result.value();
+  auto csr_val = ReadCsr<T>(csr);
+  WriteCapIntResult(instruction, 0, csr_val);
+}
+
+using RegisterType = CheriotRegister;
+using UintReg = RegisterType::ValueType;
+
+// Read the CSR, write a new value back.
+void RiscVZiCsrrw(Instruction *instruction) { RVZiCsrrw<UintReg>(instruction); }
+
+// Read the CSR, set the bits specified by the new value and write back.
+void RiscVZiCsrrs(Instruction *instruction) { RVZiCsrrs<UintReg>(instruction); }
+
+// Read the CSR, clear the bits specified by the new value and write back.
+void RiscVZiCsrrc(Instruction *instruction) { RVZiCsrrc<UintReg>(instruction); }
+
+// Do not read the CSR, just write the new value back.
+void RiscVZiCsrrwNr(Instruction *instruction) {
+  RVZiCsrrwNr<UintReg>(instruction);
+}
+
+// Do not write a value back to the CSR, just read it.
+void RiscVZiCsrrNw(Instruction *instruction) {
+  RVZiCsrrNw<UintReg>(instruction);
+}
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
diff --git a/cheriot/riscv_cheriot_zicsr_instructions.h b/cheriot/riscv_cheriot_zicsr_instructions.h
new file mode 100644
index 0000000..763785c
--- /dev/null
+++ b/cheriot/riscv_cheriot_zicsr_instructions.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__RISCV_CHERIOT_ZICSR_INSTRUCTIONS_H_
+#define MPACT_CHERIOT__RISCV_CHERIOT_ZICSR_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::Instruction;
+
+void RiscVZiCsrrw(Instruction *instruction);
+void RiscVZiCsrrs(Instruction *instruction);
+void RiscVZiCsrrc(Instruction *instruction);
+void RiscVZiCsrrwNr(Instruction *instruction);
+void RiscVZiCsrrNw(Instruction *instruction);
+
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT__RISCV_CHERIOT_ZICSR_INSTRUCTIONS_H_
diff --git a/cheriot/test/BUILD b/cheriot/test/BUILD
new file mode 100644
index 0000000..a65c979
--- /dev/null
+++ b/cheriot/test/BUILD
@@ -0,0 +1,236 @@
+# Copyright 2024 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Package for tests of CHERI RiscV simulator code.
+
+package(default_applicable_licenses = ["//:license"])
+
+cc_library(
+    name = "riscv_cheriot_fp_test_base",
+    testonly = True,
+    hdrs = ["riscv_cheriot_fp_test_base.h"],
+    copts = [
+        "-ffp-model=strict",
+        "-fprotect-parens",
+    ],
+    tags = ["not_run:arm"],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:span",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_test(
+    name = "cheriot_register_test",
+    size = "small",
+    srcs = ["cheriot_register_test.cc"],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:arch_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+    ],
+)
+
+cc_test(
+    name = "cheriot_state_test",
+    size = "small",
+    srcs = ["cheriot_state_test.cc"],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_instructions_test.cc",
+    ],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/types:span",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_i_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_i_instructions_test.cc",
+    ],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/types:span",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_m_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_m_instructions_test.cc",
+    ],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/random",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_f_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_f_instructions_test.cc",
+    ],
+    copts = [
+        "-ffp-model=strict",
+        "-fprotect-parens",
+    ],
+    tags = ["not_run:arm"],
+    deps = [
+        ":riscv_cheriot_fp_test_base",
+        "//cheriot:riscv_cheriot",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_zicsr_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_zicsr_instructions_test.cc",
+    ],
+    tags = ["not_run:arm"],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:span",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-riscv//riscv:riscv_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_encoding_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_encoding_test.cc",
+    ],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "//cheriot:riscv_cheriot_decoder",
+        "//cheriot:riscv_cheriot_isa",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
+cc_test(
+    name = "riscv_cheriot_a_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_cheriot_a_instructions_test.cc",
+    ],
+    deps = [
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings:string_view",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_test(
+    name = "cheriot_load_filter_test",
+    size = "small",
+    srcs = [
+        "cheriot_load_filter_test.cc",
+    ],
+    deps = [
+        "//cheriot:cheriot_load_filter",
+        "//cheriot:riscv_cheriot",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:counters",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_test(
+    name = "cheriot_test_rig_test",
+    size = "small",
+    srcs = [
+        "cheriot_test_rig_test.cc",
+    ],
+    deps = [
+        "//cheriot:cheriot_test_rig_lib",
+        "@com_google_absl//absl/log:check",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "memory_use_profiler_test",
+    size = "small",
+    srcs = ["memory_use_profiler_test.cc"],
+    deps = [
+        "//cheriot:instrumentation",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+    ],
+)
diff --git a/cheriot/test/cheriot_load_filter_test.cc b/cheriot/test/cheriot_load_filter_test.cc
new file mode 100644
index 0000000..f9f6009
--- /dev/null
+++ b/cheriot/test/cheriot_load_filter_test.cc
@@ -0,0 +1,260 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_load_filter.h"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "absl/strings/str_cat.h"
+#include "cheriot/cheriot_register.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/counters.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_watcher.h"
+
+// This file contains the unit tests for CheriotLoadFilter, which traverses
+// memory and checks for capabilities that should be revoked.
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotLoadFilter;
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::DataBufferFactory;
+using ::mpact::sim::generic::SimpleCounter;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+using ::mpact::sim::util::TaggedMemoryInterface;
+using ::mpact::sim::util::TaggedMemoryWatcher;
+
+constexpr int kCapSize = CheriotRegister::kCapabilitySizeInBytes;
+constexpr int kNumCaps = 8;
+// Where the capabilities are located.
+constexpr uint64_t kBase = 0x8000'0000;
+constexpr uint64_t kTop = 0x8000'0000 + kNumCaps * kCapSize;
+// Base of memory addressed by capabilities.
+constexpr uint64_t kMemBase = 0x0;
+// Base of memory area storing revocation bits.
+constexpr uint64_t kRevocationBase = 0x9000'0000;
+// Size of increment in the base address of the created capabilities. Each
+// capability is given a base address such that cap[i].base == i * kCapBase.
+constexpr uint64_t kCapBase = 0x0000'1000;
+// Size of each capability, i.e., length().
+constexpr int kCapRegionSize = 0x1000;
+
+// Test fixture to provide convenience methods and objects for the test.
+class CheriotLoadFilterTest : public ::testing::Test {
+ public:
+  CheriotLoadFilterTest() : cap_reg_(nullptr, "dummy") {
+    db_ = db_factory_.Allocate<uint32_t>(1);
+    cap_reg_.SetDataBuffer(db_);
+    cap_reg_.ResetNull();
+    db_->DecRef();
+    memory_ = std::make_unique<TaggedFlatDemandMemory>(
+        CheriotRegister::kCapabilitySizeInBytes);
+    // Intercept memory accesses for capabilities and revocation memory.
+    watcher_ = std::make_unique<TaggedMemoryWatcher>(memory_.get());
+    CHECK_OK(watcher_->SetLoadWatchCallback(
+        {kBase, kTop},
+        [this](uint64_t address, int size) { cap_loads_.push_back(address); }));
+    CHECK_OK(watcher_->SetStoreWatchCallback(
+        {kBase, kTop}, [this](uint64_t address, int size) {
+          cap_stores_.push_back(address);
+        }));
+    CHECK_OK(watcher_->SetLoadWatchCallback(
+        {kRevocationBase, kRevocationBase + 0x1000ULL},
+        [this](uint64_t address, int size) {
+          revoke_loads_.push_back(address);
+        }));
+    db_ = db_factory_.Allocate<uint32_t>(2);
+    tag_db_ = db_factory_.Allocate<uint8_t>(1);
+    counter_.SetIsEnabled(/*is_enabled=*/true);
+  }
+
+  ~CheriotLoadFilterTest() override {
+    db_->DecRef();
+    tag_db_->DecRef();
+  }
+
+  // Create a set of capabilities in memory.  A 1 in the mask indicates a valid
+  // capability, a 0 means that the capability is invalidated (the tag cleared).
+  // The capabilities are set to 0x1000 size region starting at 0x0.
+  void CreateMemoryCaps(uint32_t cap_mask) {
+    uint64_t address = kBase;
+    for (int i = 0; i < kNumCaps; i++) {
+      cap_reg_.ResetMemoryRoot();
+      cap_reg_.set_address(i * kCapBase);
+      cap_reg_.SetBounds(i * kCapBase, kCapRegionSize);
+      if (!(cap_mask & (1 << i))) {
+        cap_reg_.Invalidate();
+      }
+      db_->Set<uint32_t>(0, cap_reg_.address());
+      db_->Set<uint32_t>(1, cap_reg_.Compress());
+      tag_db_->Set<uint8_t>(0, cap_reg_.tag());
+      memory_->Store(address, db_, tag_db_);
+      address += kCapSize;
+    }
+  }
+
+  // Revoke the capability with the given base address.
+  void Revoke(uint64_t address) {
+    uint64_t revoke_address = address - kMemBase;
+    uint64_t byte_offset = revoke_address >> 6;
+    uint64_t bit_offset = (revoke_address >> 3) & 0b111;
+    // Using the tag data buffer only because its size is a byte. It is not
+    // loading or storing a tag per se.
+    memory_->Load(kRevocationBase + byte_offset, tag_db_, nullptr, nullptr);
+    tag_db_->Set<uint8_t>(0, tag_db_->Get<uint8_t>(0) | (1 << bit_offset));
+    memory_->Store(kRevocationBase + byte_offset, tag_db_);
+  }
+
+  // Check if the capability at the given address is valid or not.
+  bool IsValid(uint64_t address) {
+    uint64_t cap_address = address & ~0x7ULL;
+    memory_->Load(cap_address, nullptr, tag_db_, nullptr, nullptr);
+    return tag_db_->Get<uint8_t>(0);
+  }
+
+ protected:
+  DataBufferFactory db_factory_;
+  DataBuffer *db_;
+  DataBuffer *tag_db_;
+  SimpleCounter<uint64_t> counter_;
+  CheriotRegister cap_reg_;
+  std::unique_ptr<TaggedMemoryInterface> memory_;
+  std::unique_ptr<TaggedMemoryWatcher> watcher_;
+  std::vector<uint64_t> cap_loads_;
+  std::vector<uint64_t> cap_stores_;
+  std::vector<uint64_t> revoke_loads_;
+};
+
+// Test the load filter by setting period to 1 and count to 1.
+TEST_F(CheriotLoadFilterTest, MemoryLoads_1_1) {
+  // Create 8 valid capabilities.
+  CreateMemoryCaps(0xff);
+  auto load_filter = std::make_unique<CheriotLoadFilter>(
+      watcher_.get(), /*period=*/1, /*count=*/1, kBase, kTop, /*cap_base=*/0,
+      kRevocationBase);
+  counter_.AddListener(load_filter.get());
+  // Increment the counter 8 times. This should lead to 8 loads and 8 revocation
+  // loads.
+  for (int i = 1; i <= kNumCaps; i++) {
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), i);
+    EXPECT_EQ(cap_stores_.size(), 0);
+    EXPECT_EQ(revoke_loads_.size(), i);
+  }
+  cap_loads_.clear();
+  cap_stores_.clear();
+  revoke_loads_.clear();
+  // Create 4 valid caps interleaved with 4 invalid caps.
+  CreateMemoryCaps(0b1010'1010);
+  // Increment the counter 8 times. This should lead to 8 loads, but only 4
+  // revocation loads, since every other capability is invalid.
+  for (int i = 1; i <= kNumCaps; i++) {
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), i);
+    EXPECT_EQ(cap_stores_.size(), 0);
+    EXPECT_EQ(revoke_loads_.size(), i / 2);
+  }
+}
+
+// Tests the load filter by setting period to 2 and count to 1. This means
+// that 1 capability is checked every two increments.
+TEST_F(CheriotLoadFilterTest, MemoryLoads_2_1) {
+  // Create 8 valid capabilities.
+  CreateMemoryCaps(0xff);
+  auto load_filter = std::make_unique<CheriotLoadFilter>(
+      watcher_.get(), /*period=*/2, /*count=*/1, kBase, kTop, /*cap_base=*/0,
+      kRevocationBase);
+  counter_.AddListener(load_filter.get());
+  // Iterate 16 times, since the period is twice as long.
+  for (int i = 1; i <= kNumCaps * 2; i++) {
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), i / 2) << i;
+    EXPECT_EQ(cap_stores_.size(), 0);
+    EXPECT_EQ(revoke_loads_.size(), i / 2) << i;
+  }
+  cap_loads_.clear();
+  cap_stores_.clear();
+  revoke_loads_.clear();
+  // Create 4 valid caps interleaved with 4 invalid caps.
+  CreateMemoryCaps(0b1010'1010);
+  for (int i = 1; i <= kNumCaps * 2; i++) {
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), i / 2) << i;
+    EXPECT_EQ(cap_stores_.size(), 0);
+    EXPECT_EQ(revoke_loads_.size(), i / 4) << i;
+  }
+}
+
+// Test the load filter by setting period to 1 and count to 2. This means that
+// 2 caps are checked every increment.
+TEST_F(CheriotLoadFilterTest, MemoryLoads_1_2) {
+  // Create 8 valid capabilities.
+  CreateMemoryCaps(0xff);
+  auto load_filter = std::make_unique<CheriotLoadFilter>(
+      watcher_.get(), /*period=*/1, /*count=*/2, kBase, kTop, /*cap_base=*/0,
+      kRevocationBase);
+  counter_.AddListener(load_filter.get());
+  // Iterate 4 times, since 2 caps are processed in each period.
+  for (int i = 1; i <= kNumCaps / 2; i++) {
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), 2 * i) << i;
+    EXPECT_EQ(cap_stores_.size(), 0);
+    EXPECT_EQ(revoke_loads_.size(), 2 * i) << i;
+  }
+  cap_loads_.clear();
+  cap_stores_.clear();
+  revoke_loads_.clear();
+  // Create 4 valid caps interleaved with 4 invalid caps.
+  CreateMemoryCaps(0b1010'1010);
+  for (int i = 1; i <= kNumCaps / 2; i++) {
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), 2 * i) << i;
+    EXPECT_EQ(cap_stores_.size(), 0);
+    EXPECT_EQ(revoke_loads_.size(), i) << i;
+  }
+}
+
+TEST_F(CheriotLoadFilterTest, FilterTest) {
+  // Create 8 valid capabilities.
+  CreateMemoryCaps(0xff);
+  // The load filter is set to filter all 8 capabilities every increment.
+  auto load_filter = std::make_unique<CheriotLoadFilter>(
+      watcher_.get(), /*period=*/1, /*count=*/8, kBase, kTop, /*cap_base=*/0,
+      kRevocationBase);
+  counter_.AddListener(load_filter.get());
+  // In this loop, revoke one capability per iteration. Then filter all the
+  // capabilities. Only one should be made invalid after each iteration.
+  for (int i = 0; i < kNumCaps; i++) {
+    Revoke(i * kCapBase);
+    counter_.Increment(1);
+    EXPECT_EQ(cap_loads_.size(), 8 * (i + 1)) << i;
+    EXPECT_EQ(cap_stores_.size(), i + 1) << i;
+    EXPECT_EQ(revoke_loads_.size(), 8 - i) << i;
+    revoke_loads_.clear();
+    // Check validity of the capabilities. Only one capability should be
+    // invalidated for every increment.
+    for (int j = 0; j < kNumCaps; j++) {
+      EXPECT_EQ(IsValid(kBase + j * kCapSize), j > i) << absl::StrCat("j: ", j);
+    }
+  }
+}
+
+}  // namespace
diff --git a/cheriot/test/cheriot_register_test.cc b/cheriot/test/cheriot_register_test.cc
new file mode 100644
index 0000000..d698060
--- /dev/null
+++ b/cheriot/test/cheriot_register_test.cc
@@ -0,0 +1,541 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_register.h"
+
+#include <cstdint>
+#include <limits>
+#include <memory>
+
+#include "absl/log/check.h"
+#include "absl/random/random.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/operand_interface.h"
+
+// This file contains tests for the 32 bit RiscVCapabilityRegisters (CHERIoT).
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::generic::ArchState;
+using ::mpact::sim::generic::SourceOperandInterface;
+
+using ObjectType = CheriotRegister::ObjectType;
+using PermissionBits = CheriotRegister::PermissionBits;
+
+static constexpr uint64_t kBase = 0x1'0011;
+
+class MockArchState : public ArchState {
+ public:
+  MockArchState(absl::string_view id, SourceOperandInterface* pc_op)
+      : ArchState(id, pc_op) {}
+  explicit MockArchState(absl::string_view id) : MockArchState(id, nullptr) {}
+};
+
+// Test fixture.
+class CheriotRegisterTest : public ::testing::Test {
+ protected:
+  CheriotRegisterTest() {
+    arch_state_ = new MockArchState("test");
+    cap_reg_ = new CheriotRegister(arch_state_, "test");
+  }
+
+  ~CheriotRegisterTest() override {
+    delete cap_reg_;
+    delete arch_state_;
+  }
+
+  CheriotRegister* cap_reg() { return cap_reg_; }
+
+  absl::BitGen bitgen_;
+  MockArchState* arch_state_;
+  CheriotRegister* cap_reg_;
+};
+
+// Verify Reset().
+TEST_F(CheriotRegisterTest, Reset) {
+  // Register value should be 0 on reset.
+  EXPECT_EQ(cap_reg()->data_buffer()->Get<uint32_t>(0), 0);
+  // The capability should be the null capability.
+  EXPECT_FALSE(cap_reg()->tag());
+  EXPECT_EQ(cap_reg()->base(), 0);
+  EXPECT_EQ(cap_reg()->length(), 0);
+  EXPECT_EQ(cap_reg()->object_type(), ObjectType::kUnsealed);
+  EXPECT_EQ(cap_reg()->permissions(), 0);
+  EXPECT_FALSE(cap_reg()->IsValid());
+  EXPECT_FALSE(cap_reg()->IsUnsealed());
+  EXPECT_FALSE(cap_reg()->IsSealed());
+  // Update values, then reset, then re-verify.
+  cap_reg()->ResetMemoryRoot();
+  (void)cap_reg()->SetBounds(0xabcd'0000, 0x10'0000);
+  cap_reg()->set_object_type(ObjectType::kUnsealed);
+  cap_reg()->ResetNull();
+  // The capability should be the null capability.
+  EXPECT_FALSE(cap_reg()->tag());
+  EXPECT_EQ(cap_reg()->base(), 0);
+  EXPECT_EQ(cap_reg()->length(), 0);
+  EXPECT_EQ(cap_reg()->object_type(), ObjectType::kUnsealed);
+  EXPECT_EQ(cap_reg()->permissions(), 0);
+  EXPECT_FALSE(cap_reg()->IsValid());
+  EXPECT_FALSE(cap_reg()->IsUnsealed());
+  EXPECT_FALSE(cap_reg()->IsSealed());
+}
+
+// Verify ResetRoot to see that the capability becomes a memory root capability.
+TEST_F(CheriotRegisterTest, ResetMemoryRoot) {
+  // The capability is null at first.
+  cap_reg()->ResetMemoryRoot();
+  // Verify that it is a root capability.
+  EXPECT_TRUE(cap_reg()->tag());
+  EXPECT_EQ(cap_reg()->base(), 0);
+  EXPECT_EQ(cap_reg()->length(), 0x1'0000'0000ULL);
+  EXPECT_EQ(cap_reg()->object_type(), ObjectType::kUnsealed);
+  EXPECT_EQ(
+      cap_reg()->permissions(),
+      (PermissionBits::kPermitGlobal | PermissionBits::kPermitLoad |
+       PermissionBits::kPermitStore |
+       PermissionBits::kPermitLoadStoreCapability |
+       PermissionBits::kPermitStoreLocalCapability |
+       PermissionBits::kPermitLoadGlobal | PermissionBits::kPermitLoadMutable));
+  EXPECT_EQ(cap_reg()->data_buffer()->Get<uint32_t>(0), 0);
+  EXPECT_TRUE(cap_reg()->IsValid());
+  EXPECT_TRUE(cap_reg()->IsUnsealed());
+  EXPECT_FALSE(cap_reg()->IsSealed());
+}
+
+// Verify ResetRoot to see that the capability becomes a memory root capability.
+TEST_F(CheriotRegisterTest, ResetExecuteRoot) {
+  // The capability is null at first.
+  cap_reg()->ResetExecuteRoot();
+  // Verify that it is a root capability.
+  EXPECT_TRUE(cap_reg()->tag());
+  EXPECT_EQ(cap_reg()->base(), 0);
+  EXPECT_EQ(cap_reg()->length(), 0x1'0000'0000ULL);
+  EXPECT_EQ(cap_reg()->object_type(), ObjectType::kUnsealed);
+  EXPECT_EQ(
+      cap_reg()->permissions(),
+      (PermissionBits::kPermitGlobal | PermissionBits::kPermitExecute |
+       PermissionBits::kPermitLoad |
+       PermissionBits::kPermitLoadStoreCapability |
+       PermissionBits::kPermitLoadGlobal | PermissionBits::kPermitLoadMutable |
+       PermissionBits::kPermitAccessSystemRegisters));
+  EXPECT_EQ(cap_reg()->data_buffer()->Get<uint32_t>(0), 0);
+  EXPECT_TRUE(cap_reg()->IsValid());
+  EXPECT_TRUE(cap_reg()->IsUnsealed());
+  EXPECT_FALSE(cap_reg()->IsSealed());
+}
+
+// Verify ResetRoot to see that the capability becomes a memory root capability.
+TEST_F(CheriotRegisterTest, ResetSealingRoot) {
+  // The capability is null at first.
+  cap_reg()->ResetSealingRoot();
+  // Verify that it is a root capability.
+  EXPECT_TRUE(cap_reg()->tag());
+  EXPECT_EQ(cap_reg()->base(), 0);
+  EXPECT_EQ(cap_reg()->length(), 0x1'0000'0000ULL);
+  EXPECT_EQ(cap_reg()->object_type(), ObjectType::kUnsealed);
+  EXPECT_EQ(cap_reg()->permissions(),
+            (PermissionBits::kPermitGlobal | PermissionBits::kPermitSeal |
+             PermissionBits::kPermitUnseal | PermissionBits::kUserPerm0));
+  EXPECT_EQ(cap_reg()->data_buffer()->Get<uint32_t>(0), 0);
+  EXPECT_TRUE(cap_reg()->IsValid());
+  EXPECT_TRUE(cap_reg()->IsUnsealed());
+}
+
+TEST_F(CheriotRegisterTest, CompressNull) {
+  // The initial value of the capability is null. Verify that it matches the
+  // compressed, and memory compressed values.
+  EXPECT_EQ(cap_reg()->Compress(), CheriotRegister::kNullCapability);
+}
+
+TEST_F(CheriotRegisterTest, SetBounds) {
+  for (uint32_t length_exp = 0; length_exp <= 32; length_exp++) {
+    cap_reg()->ResetMemoryRoot();
+    uint64_t length = 1ULL << length_exp;
+    uint64_t top = kBase + length;
+    if (top > 0x1'0000'0000ULL) {
+      length = 0x1'0000'0000ULL - kBase;
+    }
+    bool is_exact = cap_reg()->SetBounds(kBase, length);
+    // The bounds are exact if the length exponent is < 10 for the given base.
+    EXPECT_EQ(is_exact, length_exp < 9) << length_exp;
+    if (is_exact) {
+      EXPECT_EQ(length, cap_reg()->length());
+      EXPECT_EQ(kBase, cap_reg()->base());
+    } else {
+      EXPECT_LE(length, cap_reg()->length());
+      EXPECT_GE(kBase, cap_reg()->base());
+    }
+  }
+}
+
+TEST_F(CheriotRegisterTest, ClearPermissions) {
+  // Memory root permissions.
+  cap_reg()->ResetMemoryRoot();
+  // Remove permission bits in order according to state transition diagram
+  // in section 7.13 of the CherIoT documentation.
+  for (uint32_t i :
+       {PermissionBits::kPermitGlobal,
+        PermissionBits::kPermitStoreLocalCapability,
+        PermissionBits::kPermitLoadMutable, PermissionBits::kPermitLoadGlobal,
+        PermissionBits::kPermitLoadStoreCapability, PermissionBits::kPermitLoad,
+        PermissionBits::kPermitStore}) {
+    auto permissions = cap_reg()->permissions();
+    cap_reg()->ClearPermissions(i);
+    auto new_permissions = cap_reg()->permissions();
+    auto diff = new_permissions ^ permissions;
+    if (permissions & i) {
+      EXPECT_EQ(diff, i);
+    } else {
+      EXPECT_EQ(diff, 0);
+    }
+  }
+  // Execute root permissions.
+  cap_reg()->ResetExecuteRoot();
+  for (uint32_t i : {
+           PermissionBits::kPermitGlobal,
+           PermissionBits::kPermitAccessSystemRegisters,
+           PermissionBits::kPermitLoadGlobal,
+           PermissionBits::kPermitLoadMutable,
+           PermissionBits::kPermitExecute,
+           PermissionBits::kPermitLoadStoreCapability,
+           PermissionBits::kPermitLoad,
+       }) {
+    auto permissions = cap_reg()->permissions();
+    cap_reg()->ClearPermissions(i);
+    auto new_permissions = cap_reg()->permissions();
+    auto diff = new_permissions ^ permissions;
+    if (permissions & i) {
+      EXPECT_EQ(diff, i);
+    } else {
+      EXPECT_EQ(diff, 0);
+    }
+  }
+  // Sealing root permissions.
+  cap_reg()->ResetSealingRoot();
+  for (uint32_t i = PermissionBits::kPermitGlobal;
+       i <= PermissionBits::kUserPerm0; i <<= 1) {
+    auto permissions = cap_reg()->permissions();
+    cap_reg()->ClearPermissions(i);
+    auto new_permissions = cap_reg()->permissions();
+    auto diff = new_permissions ^ permissions;
+    if (permissions & i) {
+      EXPECT_EQ(diff, i);
+    } else {
+      EXPECT_EQ(diff, 0);
+    }
+  }
+}
+
+TEST_F(CheriotRegisterTest, Invalidate) {
+  cap_reg()->ResetNull();
+  EXPECT_FALSE(cap_reg()->IsValid());
+  cap_reg()->ResetExecuteRoot();
+  EXPECT_TRUE(cap_reg()->IsValid());
+  cap_reg()->Invalidate();
+  EXPECT_FALSE(cap_reg()->IsValid());
+  cap_reg()->ResetMemoryRoot();
+  EXPECT_TRUE(cap_reg()->IsValid());
+  cap_reg()->Invalidate();
+  EXPECT_FALSE(cap_reg()->IsValid());
+  cap_reg()->ResetSealingRoot();
+  EXPECT_TRUE(cap_reg()->IsValid());
+  cap_reg()->Invalidate();
+  EXPECT_FALSE(cap_reg()->IsValid());
+}
+
+TEST_F(CheriotRegisterTest, SealDataCapabilities) {
+  // Create a sealing capability.
+  auto seal_cap_reg = std::make_unique<CheriotRegister>(arch_state_, "seal");
+  seal_cap_reg->ResetSealingRoot();
+  // Try sealing with different object types.
+  for (uint32_t i = ObjectType::kUnsealed; i <= 16; i++) {
+    // Set cap_reg top be a memory root capability.
+    cap_reg()->ResetMemoryRoot();
+    auto status = cap_reg()->Seal(*seal_cap_reg, i);
+    // Check to see if i is one of the correct object types, and check the
+    // status accordingly.
+    if ((i >= 9) && (i <= 15)) {
+      EXPECT_TRUE(status.ok()) << status.message();
+      EXPECT_TRUE(cap_reg()->IsSealed())
+          << i << ": " << cap_reg()->tag() << " " << cap_reg()->object_type();
+      EXPECT_FALSE(cap_reg()->IsUnsealed())
+          << i << ": " << cap_reg()->tag() << " " << cap_reg()->object_type();
+    } else {
+      EXPECT_FALSE(cap_reg()->IsSealed())
+          << i << ": " << cap_reg()->tag() << " " << cap_reg()->object_type();
+      EXPECT_TRUE(!cap_reg()->tag() || cap_reg()->IsUnsealed())
+          << i << ": " << cap_reg()->tag() << " " << cap_reg()->object_type();
+    }
+  }
+  // Change bounds of sealing capability to > than valid object types.
+  (void)seal_cap_reg->SetBounds(0x100, 0x100);
+  cap_reg()->ResetMemoryRoot();
+  auto status = cap_reg()->Seal(*seal_cap_reg, 9);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(), testing::HasSubstr("out of range"))
+      << status.message();
+  // Try with a sealing root with cleared tag.
+  seal_cap_reg->ResetSealingRoot();
+  seal_cap_reg->Invalidate();
+  EXPECT_FALSE(cap_reg()->Seal(*seal_cap_reg, 9).ok());
+
+  cap_reg()->ResetMemoryRoot();
+  seal_cap_reg->ResetSealingRoot();
+  // Seal the sealing capability. This should succeed.
+  CHECK_OK(seal_cap_reg->Seal(*seal_cap_reg, 10));
+  // Now try to seal using the sealed sealing capability. That should fail.
+  status = cap_reg()->Seal(*seal_cap_reg, 10);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Cannot seal using a sealed capability"))
+      << status.message();
+  // Try to use a capability without sealing permission.
+  seal_cap_reg->ResetSealingRoot();
+  seal_cap_reg->ClearPermissions(PermissionBits::kPermitSeal);
+  cap_reg()->ResetMemoryRoot();
+  status = cap_reg()->Seal(*seal_cap_reg, 10);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kPermissionDenied);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Missing sealing permission"))
+      << status.message();
+  // Try sealing a null capability.
+  seal_cap_reg->ResetSealingRoot();
+  cap_reg()->ResetNull();
+  status = cap_reg()->Seal(*seal_cap_reg, 10);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Target is not a valid capability"));
+  // Try sealing twice.
+  cap_reg()->ResetMemoryRoot();
+  CHECK_OK(cap_reg()->Seal(*seal_cap_reg, 10));
+  status = cap_reg()->Seal(*seal_cap_reg, 10);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Cannot seal already sealed capability"))
+      << status.message();
+}
+
+TEST_F(CheriotRegisterTest, SealExecuteCapability) {
+  // Create a sealing capability.
+  auto seal_cap_reg = std::make_unique<CheriotRegister>(arch_state_, "seal");
+  seal_cap_reg->ResetSealingRoot();
+  // Try sealing with different object types.
+  for (uint32_t i = ObjectType::kUnsealed; i <= 16; i++) {
+    // Set cap_reg top be a memory root capability.
+    cap_reg()->ResetExecuteRoot();
+    auto status = cap_reg()->Seal(*seal_cap_reg, i);
+    // If the object type is out of range for data, expect failure.
+    if ((i == ObjectType::kSentry) ||
+        (i == ObjectType::kInterruptDisablingSentry) ||
+        (i == ObjectType::kInterruptEnablingSentry) ||
+        (i == ObjectType::kSealedExecutable6) ||
+        (i == ObjectType::kSealedExecutable7)) {
+      EXPECT_TRUE(status.ok()) << status.message();
+      EXPECT_TRUE(cap_reg()->IsSealed());
+      EXPECT_FALSE(cap_reg()->IsUnsealed());
+    } else {
+      EXPECT_FALSE(status.ok());
+      EXPECT_FALSE(cap_reg()->IsSealed());
+    }
+  }
+  // Change bounds of sealing capability to > than valid object types.
+  (void)seal_cap_reg->SetBounds(0x100, 0x1000);
+  cap_reg()->ResetExecuteRoot();
+  auto status = cap_reg()->Seal(*seal_cap_reg, ObjectType::kSealedExecutable6);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(
+      status.message(),
+      testing::HasSubstr("Sealing capability is not a valid capability"));
+  // Try with a sealing root with cleared tag.
+  seal_cap_reg->ResetSealingRoot();
+  seal_cap_reg->Invalidate();
+  EXPECT_FALSE(cap_reg()->Seal(*seal_cap_reg, ObjectType::kSentry).ok());
+  cap_reg()->ResetExecuteRoot();
+  seal_cap_reg->ResetSealingRoot();
+  // Seal the sealing capability. This should succeed.
+  CHECK_OK(seal_cap_reg->Seal(*seal_cap_reg, 10));
+  // Now try to seal using the sealed sealing capability. That should fail.
+  status = cap_reg()->Seal(*seal_cap_reg, ObjectType::kSealedExecutable6);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Cannot seal using a sealed capability"));
+  // Try to use a capability without sealing permission.
+  seal_cap_reg->ResetSealingRoot();
+  seal_cap_reg->ClearPermissions(PermissionBits::kPermitSeal);
+  cap_reg()->ResetExecuteRoot();
+  status = cap_reg()->Seal(*seal_cap_reg, ObjectType::kSentry);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kPermissionDenied);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Missing sealing permission"));
+  // Try sealing a null capability.
+  seal_cap_reg->ResetSealingRoot();
+  cap_reg()->ResetNull();
+  status = cap_reg()->Seal(*seal_cap_reg, ObjectType::kSentry);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Target is not a valid capability"));
+  // Try sealing twice.
+  cap_reg()->ResetExecuteRoot();
+  CHECK_OK(cap_reg()->Seal(*seal_cap_reg, ObjectType::kSentry));
+  status = cap_reg()->Seal(*seal_cap_reg, ObjectType::kSentry);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
+  EXPECT_THAT(status.message(),
+              testing::HasSubstr("Cannot seal already sealed capability"));
+}
+
+TEST_F(CheriotRegisterTest, CopyFrom) {
+  constexpr uint32_t kAddress = 0xdeadbeef;
+  auto cap_reg_copy = std::make_unique<CheriotRegister>(arch_state_, "copy");
+  // Copy from null capability.
+  cap_reg()->ResetNull();
+  cap_reg()->data_buffer()->Set<uint32_t>(0, kAddress);
+  cap_reg()->set_object_type(ObjectType::kReserved4);
+  cap_reg()->set_reserved(1);
+  cap_reg_copy->CopyFrom(*cap_reg());
+  EXPECT_FALSE(cap_reg_copy->IsValid());
+  EXPECT_EQ(cap_reg_copy->data_buffer()->Get<uint32_t>(0), kAddress);
+  EXPECT_EQ(cap_reg_copy->tag(), cap_reg()->tag());
+  EXPECT_EQ(cap_reg_copy->top(), cap_reg()->top());
+  EXPECT_EQ(cap_reg_copy->base(), cap_reg()->base());
+  EXPECT_EQ(cap_reg_copy->length(), cap_reg()->length());
+  EXPECT_EQ(cap_reg_copy->permissions(), cap_reg()->permissions());
+  EXPECT_EQ(cap_reg_copy->object_type(), cap_reg()->object_type());
+  EXPECT_EQ(cap_reg_copy->reserved(), cap_reg()->reserved());
+  // Copy from memory root capability.
+  cap_reg()->ResetMemoryRoot();
+  cap_reg()->data_buffer()->Set<uint32_t>(0, kAddress);
+  cap_reg()->set_object_type(ObjectType::kReserved4);
+  cap_reg()->set_reserved(1);
+  (void)cap_reg()->SetBounds(kBase, kAddress + 1);
+  cap_reg_copy->CopyFrom(*cap_reg());
+  EXPECT_TRUE(cap_reg_copy->IsValid());
+  EXPECT_EQ(cap_reg_copy->data_buffer()->Get<uint32_t>(0), kAddress);
+  EXPECT_EQ(cap_reg_copy->tag(), cap_reg()->tag());
+  EXPECT_EQ(cap_reg_copy->top(), cap_reg()->top());
+  EXPECT_EQ(cap_reg_copy->base(), cap_reg()->base());
+  EXPECT_EQ(cap_reg_copy->length(), cap_reg()->length());
+  EXPECT_EQ(cap_reg_copy->permissions(), cap_reg()->permissions());
+  EXPECT_EQ(cap_reg_copy->object_type(), cap_reg()->object_type());
+  // Copy from execute root capability.
+  cap_reg()->ResetExecuteRoot();
+  cap_reg()->data_buffer()->Set<uint32_t>(0, kAddress);
+  cap_reg()->set_object_type(ObjectType::kReserved4);
+  cap_reg()->set_reserved(1);
+  (void)cap_reg()->SetBounds(kBase, kAddress + 1);
+  cap_reg_copy->CopyFrom(*cap_reg());
+  EXPECT_TRUE(cap_reg_copy->IsValid());
+  EXPECT_EQ(cap_reg_copy->data_buffer()->Get<uint32_t>(0), kAddress);
+  EXPECT_EQ(cap_reg_copy->tag(), cap_reg()->tag());
+  EXPECT_EQ(cap_reg_copy->top(), cap_reg()->top());
+  EXPECT_EQ(cap_reg_copy->base(), cap_reg()->base());
+  EXPECT_EQ(cap_reg_copy->length(), cap_reg()->length());
+  EXPECT_EQ(cap_reg_copy->permissions(), cap_reg()->permissions());
+  EXPECT_EQ(cap_reg_copy->object_type(), cap_reg()->object_type());
+  // Copy from sealing root capability.
+  cap_reg()->ResetSealingRoot();
+  cap_reg()->data_buffer()->Set<uint32_t>(0, kAddress);
+  cap_reg()->set_object_type(ObjectType::kReserved4);
+  cap_reg()->set_reserved(1);
+  (void)cap_reg()->SetBounds(kBase, kAddress + 1);
+  cap_reg_copy->CopyFrom(*cap_reg());
+  EXPECT_TRUE(cap_reg_copy->IsValid());
+  EXPECT_EQ(cap_reg_copy->data_buffer()->Get<uint32_t>(0), kAddress);
+  EXPECT_EQ(cap_reg_copy->tag(), cap_reg()->tag());
+  EXPECT_EQ(cap_reg_copy->top(), cap_reg()->top());
+  EXPECT_EQ(cap_reg_copy->base(), cap_reg()->base());
+  EXPECT_EQ(cap_reg_copy->length(), cap_reg()->length());
+  EXPECT_EQ(cap_reg_copy->permissions(), cap_reg()->permissions());
+  EXPECT_EQ(cap_reg_copy->object_type(), cap_reg()->object_type());
+}
+
+// Test compress/expand of bounds.
+TEST_F(CheriotRegisterTest, CompressExpand) {
+  // First some random combinations.
+  for (int i = 0; i < 1000; i++) {
+    cap_reg()->ResetMemoryRoot();
+    // Generate random address and compressed capability.
+    uint32_t address = absl::Uniform(absl::IntervalClosed, bitgen_, 0ULL,
+                                     std::numeric_limits<uint32_t>::max());
+    uint32_t compressed = absl::Uniform(absl::IntervalClosed, bitgen_, 0ULL,
+                                        std::numeric_limits<uint32_t>::max());
+    // Expand the capability, get the base and top, then compress it again.
+    cap_reg()->Expand(address, compressed, true);
+    uint32_t base = cap_reg()->base();
+    uint64_t top = cap_reg()->top();
+    uint32_t re_compressed = cap_reg()->Compress();
+    // The starting compressed value should be the same as the re-compressed.
+    EXPECT_EQ(re_compressed, compressed) << absl::StrCat(
+        "address: ", absl::Hex(address, absl::kZeroPad8),
+        " compressed: ", absl::Hex(compressed, absl::kZeroPad8),
+        " re-compressed: ", absl::Hex(re_compressed, absl::kZeroPad8));
+    cap_reg()->ResetMemoryRoot();
+    // Expand the re-compressed capability. The base and top should be the same.
+    cap_reg()->Expand(address, re_compressed, true);
+    EXPECT_EQ(base, cap_reg()->base()) << absl::StrCat(
+        "address: ", absl::Hex(address, absl::kZeroPad8),
+        " compressed: ", absl::Hex(compressed, absl::kZeroPad8),
+        " re-compressed: ", absl::Hex(re_compressed, absl::kZeroPad8));
+    EXPECT_EQ(top, cap_reg()->top()) << absl::StrCat(
+        "address: ", absl::Hex(address, absl::kZeroPad8),
+        " compressed: ", absl::Hex(compressed, absl::kZeroPad8),
+        " re-compressed: ", absl::Hex(re_compressed, absl::kZeroPad8));
+  }
+  for (uint32_t length_exp = 0; length_exp <= 32; length_exp++) {
+    cap_reg()->ResetMemoryRoot();
+    uint64_t length = 1ULL << length_exp;
+    cap_reg()->data_buffer()->Set<uint32_t>(0, kBase);
+    uint64_t top = kBase + length;
+    if (top > 0x1'0000'0000) {
+      length = 0x1'0000'0000 - kBase;
+    }
+    // Set bounds.
+    (void)cap_reg()->SetBounds(kBase, length);
+    // Get the base, top, and length.
+    uint32_t cap_base = cap_reg()->base();
+    uint64_t cap_top = cap_reg()->top();
+    uint64_t cap_length = cap_reg()->length();
+    // Compress the capability.
+    uint32_t compressed = cap_reg()->Compress();
+    // Expand the capability. Make sure the base, top, and length are the same.
+    cap_reg()->Expand(kBase, compressed, true);
+    EXPECT_TRUE(cap_reg()->IsValid())
+        << absl::StrCat("length_exp: ", length_exp);
+    EXPECT_EQ(cap_reg()->base(), cap_base)
+        << absl::StrCat(length_exp, " base: ", absl::Hex(cap_reg()->base()),
+                        " cap_base: ", absl::Hex(cap_base));
+    EXPECT_EQ(cap_reg()->top(), cap_top)
+        << absl::StrCat(length_exp, " top: ", absl::Hex(cap_reg()->top()),
+                        " cap_top: ", absl::Hex(cap_top));
+    EXPECT_EQ(cap_reg()->length(), cap_length)
+        << absl::StrCat(length_exp, " length: ", absl::Hex(cap_reg()->length()),
+                        " cap_length: ", absl::Hex(cap_length));
+  }
+}
+
+}  // namespace
diff --git a/cheriot/test/cheriot_state_test.cc b/cheriot/test/cheriot_state_test.cc
new file mode 100644
index 0000000..28c0c04
--- /dev/null
+++ b/cheriot/test/cheriot_state_test.cc
@@ -0,0 +1,121 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_state.h"
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+#include "absl/log/check.h"
+#include "absl/strings/str_format.h"
+#include "cheriot/cheriot_register.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "riscv//riscv_state.h"
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::cheriot::CheriotState;
+using RVEC = ::mpact::sim::riscv::ExceptionCode;
+
+constexpr int kPcValue = 0x1000;
+constexpr int kMemAddr = 0x1200;
+constexpr uint32_t kMemValue = 0xdeadbeef;
+
+// Only limited testing of the CheriotState class for now as it has limited
+// additional functionality over the ArchState class.
+
+TEST(CheriotStateTest, Basic) {
+  auto *state = new CheriotState("test");
+  // Make sure pc has been created.
+  auto iter = state->registers()->find("pcc");
+  auto *ptr = (iter != state->registers()->end()) ? iter->second : nullptr;
+  CHECK_NE(ptr, nullptr);
+  auto *pcc = static_cast<CheriotRegister *>(ptr);
+  // Make pcc an executable root.
+  pcc->CopyFrom(*state->executable_root());
+  // Set pc to 0x1000, then read value back through pc operand.
+  pcc->data_buffer()->Set<uint32_t>(0, kPcValue);
+  auto *pc_op = state->pc_operand();
+  EXPECT_EQ(pc_op->AsUint32(0), kPcValue);
+  delete state;
+}
+
+TEST(CheriotStateTest, Memory) {
+  auto *state = new CheriotState("test");
+  auto *db = state->db_factory()->Allocate<uint32_t>(1);
+  state->LoadMemory(nullptr, kMemAddr, db, nullptr, nullptr);
+  EXPECT_EQ(db->Get<uint32_t>(0), 0);
+  db->Set<uint32_t>(0, kMemValue);
+  state->StoreMemory(nullptr, kMemAddr, db);
+  db->Set<uint32_t>(0, 0);
+  state->LoadMemory(nullptr, kMemAddr, db, nullptr, nullptr);
+  EXPECT_EQ(db->Get<uint32_t>(0), kMemValue);
+  db->DecRef();
+  delete state;
+}
+
+TEST(CheriotStateTest, OutOfBoundLoad) {
+  auto *state = new CheriotState("test");
+  state->set_max_physical_address(kMemAddr - 4);
+  state->set_on_trap([](bool is_interrupt, uint64_t trap_value,
+                        uint64_t exception_code, uint64_t epc,
+                        const mpact::sim::riscv::Instruction *inst) -> bool {
+    if (exception_code ==
+        static_cast<uint64_t>(
+            mpact::sim::riscv::ExceptionCode::kLoadAccessFault)) {
+      std::cerr << "Load Access Fault" << std::endl;
+      return true;
+    }
+    return false;
+  });
+  auto *db = state->db_factory()->Allocate<uint32_t>(1);
+  // Create a dummy instruction so trap can dereference the address.
+  auto *dummy_inst = new mpact::sim::generic::Instruction(0x0, nullptr);
+  dummy_inst->set_size(4);
+  testing::internal::CaptureStderr();
+  state->LoadMemory(dummy_inst, kMemAddr, db, nullptr, nullptr);
+  const std::string stderr = testing::internal::GetCapturedStderr();
+  EXPECT_THAT(stderr, testing::HasSubstr("Load Access Fault"));
+  db->DecRef();
+  dummy_inst->DecRef();
+  delete state;
+}
+
+// Verify that the mshwm register decrements by 16.
+TEST(CheriotStateTest, Mshwm) {
+  auto *mem = new mpact::sim::util::TaggedFlatDemandMemory(8);
+  auto *state = new CheriotState("test", mem);
+  auto *byte_db = state->db_factory()->Allocate<uint8_t>(1);
+  auto mshwmb_res = state->csr_set()->GetCsr("mshwmb");
+  CHECK_OK(mshwmb_res);
+  auto *mshwmb = mshwmb_res.value();
+  auto mshwm_res = state->csr_set()->GetCsr("mshwm");
+  CHECK_OK(mshwm_res);
+  auto *mshwm = mshwm_res.value();
+  mshwmb->Write(0x0U);
+  mshwm->Write(0x80000000);
+  state->StoreMemory(nullptr, 0x7fff'ffff, byte_db);
+  EXPECT_EQ(mshwm->AsUint32(), 0x7fff'fff0) << absl::StrFormat(
+      "mshwm == 0x%08x != 0x%08x", mshwm->AsUint32(), 0x7fff'fff0);
+  EXPECT_EQ(mshwmb->AsUint32(), 0x0);
+  byte_db->DecRef();
+  delete state;
+  delete mem;
+}
+
+}  // namespace
diff --git a/cheriot/test/cheriot_test_rig_test.cc b/cheriot/test/cheriot_test_rig_test.cc
new file mode 100644
index 0000000..cc6e667
--- /dev/null
+++ b/cheriot/test/cheriot_test_rig_test.cc
@@ -0,0 +1,777 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_test_rig.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cstdint>
+
+#include "absl/log/check.h"
+#include "cheriot/test_rig_packets.h"
+#include "googlemock/include/gmock/gmock.h"
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotTestRig;
+using ::mpact::sim::cheriot::test_rig::ExecutionPacket;
+using ::mpact::sim::cheriot::test_rig::ExecutionPacketExtInteger;
+using ::mpact::sim::cheriot::test_rig::ExecutionPacketExtMemAccess;
+using ::mpact::sim::cheriot::test_rig::ExecutionPacketV2;
+using ::mpact::sim::cheriot::test_rig::InstructionPacket;
+using ::mpact::sim::cheriot::test_rig::kInstruction;
+using ::mpact::sim::cheriot::test_rig::kIntegerData;
+using ::mpact::sim::cheriot::test_rig::kMachineMode;
+using ::mpact::sim::cheriot::test_rig::kMemoryAccess;
+using ::mpact::sim::cheriot::test_rig::kXL32;
+
+// Test instructions encodings.
+constexpr uint32_t kAddi = 0b000000000000'00000'000'00000'0010011;
+constexpr uint32_t kBeq = 0b0000000'00000'00000'000'00000'1100011;
+constexpr uint32_t kLui = 0b00000000000000000000'00000'0110111;
+constexpr uint32_t kLbu = 0b000000000000'00000'100'00000'0000011;
+constexpr uint32_t kLhu = 0b000000000000'00000'101'00000'0000011;
+constexpr uint32_t kLw = 0b000000000000'00000'010'00000'0000011;
+constexpr uint32_t kSb = 0b0000000'00000'00000'000'00000'0100011;
+constexpr uint32_t kSh = 0b0000000'00000'00000'001'00000'0100011;
+constexpr uint32_t kSw = 0b0000000'00000'00000'010'00000'0100011;
+constexpr uint32_t kCSpecialRW = 0b0000001'00000'00000'000'00000'1011011;
+constexpr uint32_t kCSetAddr = 0b0010000'00000'00000'000'00000'1011011;
+
+constexpr uint32_t kMemAddr = 0x8000'2468;
+constexpr uint32_t kMtdc = 29;
+
+// Set register operands in 32 bit format instructions.
+static uint32_t SetRd(uint32_t iword, uint32_t rdval) {
+  return (iword | ((rdval & 0x1f) << 7));
+}
+
+static uint32_t SetRs1(uint32_t iword, uint32_t rsval) {
+  return (iword | ((rsval & 0x1f) << 15));
+}
+
+static uint32_t SetRs2(uint32_t iword, uint32_t rsval) {
+  return (iword | ((rsval & 0x1f) << 20));
+}
+
+// Set immediate operand for I type.
+static uint32_t SetITypeImm(uint32_t iword, uint32_t val) {
+  return (iword | ((val & 0xfff) << 20));
+}
+
+static uint32_t SetUTypeImm(uint32_t iword, uint32_t val) {
+  // Use the upper 20 bits of val.
+  return (iword | (val & 0xffff'f000));
+}
+
+static uint32_t SetSTypeImm(uint32_t iword, uint32_t val) {
+  uint32_t low5 = val & 0x1f;
+  uint32_t high7 = (val >> 5) & 0x7f;
+  return (iword | (high7 << 25) | (low5 << 7));
+}
+
+static uint32_t SetBTypeImm(uint32_t iword, uint32_t val) {
+  // signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
+  uint32_t imm5 = val & 0x1e;
+  imm5 |= (val >> (1 + 4 + 6)) & 0x1;
+  uint32_t imm7 = (val >> (4 + 1)) & 0x3f;
+  imm7 |= (val >> (1 + 6 + 4 + 1)) & 0x1;
+  return (iword | (imm7 << 25) | (imm5 << 7));
+}
+
+static uint32_t SetRTypeInstruction(uint32_t op, uint32_t rd, uint32_t rs1,
+                                    uint32_t rs2) {
+  return SetRd(SetRs1(SetRs2(op, rs2), rs1), rd);
+}
+
+static uint32_t SetSTypeInstruction(uint32_t op, uint32_t rs1, uint32_t rs2,
+                                    uint32_t imm) {
+  return SetRs1(SetRs2(SetSTypeImm(op, imm), rs2), rs1);
+}
+
+static uint32_t SetITypeInstruction(uint32_t op, uint32_t rd, uint32_t rs1,
+                                    uint32_t imm) {
+  return SetRd(SetRs1(SetITypeImm(op, imm), rs1), rd);
+}
+
+static uint32_t SetUTypeInstruction(uint32_t op, uint32_t rd, uint32_t imm) {
+  return SetRd(SetUTypeImm(op, imm), rd);
+}
+
+static uint32_t SetBTypeInstruction(uint32_t op, uint32_t rs1, uint32_t rs2,
+                                    uint32_t imm) {
+  return SetRs1(SetRs2(SetBTypeImm(op, imm), rs2), rs1);
+}
+
+class CheriotTestRigTest : public ::testing::Test {
+ protected:
+  CheriotTestRigTest() {
+    int pipe_fds[2];
+    CHECK_EQ(pipe2(pipe_fds, O_CLOEXEC), 0);
+    read_fd_ = pipe_fds[0];
+    write_fd_ = pipe_fds[1];
+  }
+
+  ~CheriotTestRigTest() override {
+    CHECK_EQ(close(read_fd_), 0);
+    CHECK_EQ(close(write_fd_), 0);
+  }
+
+  CheriotTestRig test_rig_;
+  int read_fd_;
+  int write_fd_;
+};
+
+TEST_F(CheriotTestRigTest, LinearInstructionSequence) {  // NOLINT
+  // Load immediate 0x80002468 into x12
+  // Load immediate 0xdeadbeef into x11
+  // Move data root capability to x10.
+  // Set x10 address to x12
+  // Store x11 as byte, half and word to memory.
+  // Load x12 as byte, half and word from memory.
+  CHECK_OK(test_rig_.SetVersion(1));
+  InstructionPacket i_packet;
+  ExecutionPacket e_packet;
+  uint16_t time = 0;
+  uint64_t inst_count = 0;
+  // Initial pc value;
+  uint64_t pc = 0x8000'0000;
+  // lui x10, 0x80002
+  uint32_t insn = SetUTypeInstruction(kLui, /*rd=*/12, /*imm=*/kMemAddr);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  int res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_intr, 0);
+  EXPECT_EQ(e_packet.rvfi_halt, 0);
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 12);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, kMemAddr & 0xffff'f000);
+  EXPECT_EQ(e_packet.rvfi_insn, insn);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // addi x12, x12, 0x468
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kAddi, /*rd=*/12, /*rs1=*/12, /*imm=*/kMemAddr);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_intr, 0);
+  EXPECT_EQ(e_packet.rvfi_halt, 0);
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 12);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, kMemAddr);
+  EXPECT_EQ(e_packet.rvfi_insn, insn);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // lui x11, 0xdeadb
+  pc += sizeof(uint32_t);
+  insn = SetUTypeInstruction(kLui, /*rd=*/11, /*imm=*/0xdead'ceef);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 11);
+  // Notice, since the following addi becomes a negative number when sign
+  // extended, we load 0xdeadc in the upper 20 bits, so that then addi
+  // 'subtracts' eef, we get the right result.
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0xdeadc000);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // Addi x11, 0xeef
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kAddi, /*rd=*/11, /*rs1=*/11, /*imm=*/0xdeadbeef);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 11);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0xdeadbeef);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // Move data root capability to x10.
+  // CSpecialRW c10, mtdc, c0
+  pc += sizeof(uint32_t);
+  insn = SetRTypeInstruction(kCSpecialRW, /*rd=*/10, /*rs1=*/0, /*rs2=*/kMtdc);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 10);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // Set the address of c10.
+  // CSetAddr c10, c10, x12
+  pc += sizeof(uint32_t);
+  insn = SetRTypeInstruction(kCSetAddr, /*rd=*/10, /*rs1=*/10, /*rs2=*/12);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 10);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, kMemAddr);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // Store values to memory.
+  // sb x11, 8(x10)  (stores to 0x8000'2470)
+  pc += sizeof(uint32_t);
+  insn = SetSTypeInstruction(kSb, /*rs1=*/10, /*rs2=*/11, /*imm=*/8);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0x1);
+  EXPECT_EQ(e_packet.rvfi_mem_wdata, 0xdeadbeef & 0xff);
+  EXPECT_EQ(e_packet.rvfi_mem_addr, kMemAddr + 8);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+  // sh x11, 12(x10) (stores to 0x8000'2474)
+  pc += sizeof(uint32_t);
+  insn = SetSTypeInstruction(kSh, /*rs1=*/10, /*rs2=*/11, /*imm=*/12);
+  i_packet = {insn, time++, kInstruction, /*padding=*/0};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0x3);
+  EXPECT_EQ(e_packet.rvfi_mem_wdata, 0xdeadbeef & 0xffff);
+  EXPECT_EQ(e_packet.rvfi_mem_addr, kMemAddr + 12);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+  // sw x11, 0(x10) (stores to 0x8000'2468)
+  pc += sizeof(uint32_t);
+  insn = SetSTypeInstruction(kSw, /*rs1=*/10, /*rs2=*/11, /*imm=*/0);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0xf);
+  EXPECT_EQ(e_packet.rvfi_mem_wdata, 0xdeadbeef);
+  EXPECT_EQ(e_packet.rvfi_mem_addr, kMemAddr);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+
+  // Now load the values from memory and verify.
+  // lw x13, 0(x10)
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kLw, /*rd=*/13, /*rs1=*/10, /*imm=*/0);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 13);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0xf);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rdata, 0xdeadbeef);
+  EXPECT_EQ(e_packet.rvfi_mem_addr, kMemAddr);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0xdeadbeef);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+  // lh x14, 12(x10)
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kLhu, /*rd=*/14, /*rs1=*/10, /*imm=*/12);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 14);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0x3);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rdata, 0xdeadbeef & 0xffff);
+  EXPECT_EQ(e_packet.rvfi_mem_addr, kMemAddr + 12);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0xdeadbeef & 0xffff);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+  // lb x15, 8(x10)
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kLbu, /*rd=*/15, /*rs1=*/10, /*imm=*/8);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 15);
+  EXPECT_EQ(e_packet.rvfi_mem_rmask, 0x1);
+  EXPECT_EQ(e_packet.rvfi_mem_wmask, 0);
+  EXPECT_EQ(e_packet.rvfi_mem_rdata, 0xdeadbeef & 0xff);
+  EXPECT_EQ(e_packet.rvfi_mem_addr, kMemAddr + 8);
+  EXPECT_EQ(e_packet.rvfi_rd_wdata, 0xdeadbeef & 0xff);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+}
+
+// Test that a branch instruction returns the correct pc_wdata value.
+TEST_F(CheriotTestRigTest, Branch) {
+  CHECK_OK(test_rig_.SetVersion(1));
+  CheriotTestRig test_rig;
+  InstructionPacket i_packet;
+  ExecutionPacket e_packet;
+  uint16_t time = 0;
+  uint64_t inst_count = 0;
+  // Initial pc value;
+  uint64_t pc = 0x8000'0000;
+  uint32_t insn =
+      SetBTypeInstruction(kBeq, /*rs1=*/1, /*rs2=*/2, /*imm*/ 0x124);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  auto res = read(read_fd_, &e_packet, sizeof(e_packet));
+  EXPECT_EQ(res, sizeof(e_packet));
+  EXPECT_EQ(e_packet.rvfi_trap, 0);
+  EXPECT_EQ(e_packet.rvfi_rd_addr, 0);
+  EXPECT_EQ(e_packet.rvfi_pc_wdata, pc + 0x124);
+  EXPECT_EQ(e_packet.rvfi_pc_rdata, pc);
+  EXPECT_EQ(e_packet.rvfi_order, inst_count);
+}
+
+TEST_F(CheriotTestRigTest, LinearInstructionSequenceV2) {  // NOLINT
+  // Load immediate 0x80002468 into x12
+  // Load immediate 0xdeadbeef into x11
+  // Move data root capability to x10.
+  // Set x10 address to x12
+  // Store x11 as byte, half and word to memory.
+  // Load x12 as byte, half and word from memory.
+  CHECK_OK(test_rig_.SetVersion(2));
+  InstructionPacket i_packet;
+  ExecutionPacketExtInteger ep_ext_integer;
+  ExecutionPacketExtMemAccess ep_ext_mem_access;
+  ExecutionPacketV2 ep_v2_packet;
+  uint16_t time = 0;
+  uint64_t inst_count = 0;
+  // Initial pc value;
+  uint64_t pc = 0x8000'0000;
+  // lui x10, 0x80002
+  uint32_t insn = SetUTypeInstruction(kLui, /*rd=*/12, /*imm=*/kMemAddr);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  int res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 12);
+
+  // addi x12, x12, 0x468
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kAddi, /*rd=*/12, /*rs1=*/12, /*imm=*/kMemAddr);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 12);
+
+  // lui x11, 0xdeadb
+  pc += sizeof(uint32_t);
+  insn = SetUTypeInstruction(kLui, /*rd=*/11, /*imm=*/0xdead'ceef);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 11);
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_wdata, 0xdeadc000);
+
+  // Addi x11, 0xeef
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kAddi, /*rd=*/11, /*rs1=*/11, /*imm=*/0xdeadbeef);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 11);
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_wdata, 0xdeadbeef);
+
+  // Move data root capability to x10.
+  // CSpecialRW c10, mtdc, c0
+  pc += sizeof(uint32_t);
+  insn = SetRTypeInstruction(kCSpecialRW, /*rd=*/10, /*rs1=*/0, /*rs2=*/kMtdc);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 10);
+
+  // Set the address of c10.
+  // CSetAddr c10, c10, x12
+  pc += sizeof(uint32_t);
+  insn = SetRTypeInstruction(kCSetAddr, /*rd=*/10, /*rs1=*/10, /*rs2=*/12);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 10);
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_wdata, kMemAddr);
+
+  // Store values to memory.
+  // sb x11, 8(x10)  (stores to 0x8000'2470)
+  pc += sizeof(uint32_t);
+  insn = SetSTypeInstruction(kSb, /*rs1=*/10, /*rs2=*/11, /*imm=*/8);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, 0);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, kMemoryAccess);
+  res = read(read_fd_, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(res, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rmask, 0);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wmask, 0x1);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wdata[0], 0xdeadbeef & 0xff);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_addr, kMemAddr + 8);
+
+  // sh x11, 12(x10) (stores to 0x8000'2474)
+  pc += sizeof(uint32_t);
+  insn = SetSTypeInstruction(kSh, /*rs1=*/10, /*rs2=*/11, /*imm=*/12);
+  i_packet = {insn, time++, kInstruction, /*padding=*/0};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, 0);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, kMemoryAccess);
+  res = read(read_fd_, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(res, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rmask, 0);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wmask, 0x3);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wdata[0], 0xdeadbeef & 0xffff);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_addr, kMemAddr + 12);
+
+  // sw x11, 0(x10) (stores to 0x8000'2468)
+  pc += sizeof(uint32_t);
+  insn = SetSTypeInstruction(kSw, /*rs1=*/10, /*rs2=*/11, /*imm=*/0);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, 0);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, kMemoryAccess);
+  res = read(read_fd_, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(res, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rmask, 0);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wmask, 0xf);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wdata[0], 0xdeadbeef);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_addr, kMemAddr);
+
+  // Now load the values from memory and verify.
+  // lw x13, 0(x10)
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kLw, /*rd=*/13, /*rs1=*/10, /*imm=*/0);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data and memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, kMemoryAccess);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 13);
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_wdata, 0xdeadbeef);
+  res = read(read_fd_, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(res, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rmask, 0xf);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wmask, 0);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rdata[0], 0xdeadbeef);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_addr, kMemAddr);
+
+  // lh x14, 12(x10)
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kLhu, /*rd=*/14, /*rs1=*/10, /*imm=*/12);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data and memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, kMemoryAccess);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 14);
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_wdata, 0xdeadbeef & 0xffff);
+  res = read(read_fd_, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(res, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rmask, 0x3);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wmask, 0);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rdata[0], 0xdeadbeef & 0xffff);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_addr, kMemAddr + 12);
+
+  // lb x15, 8(x10)
+  pc += sizeof(uint32_t);
+  insn = SetITypeInstruction(kLbu, /*rd=*/15, /*rs1=*/10, /*imm=*/8);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + sizeof(uint32_t));
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data and memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, kIntegerData);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, kMemoryAccess);
+  res = read(read_fd_, &ep_ext_integer, sizeof(ep_ext_integer));
+  EXPECT_EQ(res, sizeof(ep_ext_integer));
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_addr, 15);
+  EXPECT_EQ(ep_ext_integer.rvfi_rd_wdata, 0xdeadbeef & 0xff);
+  res = read(read_fd_, &ep_ext_mem_access, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(res, sizeof(ep_ext_mem_access));
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rmask, 0x1);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_wmask, 0);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_rdata[0], 0xdeadbeef & 0xff);
+  EXPECT_EQ(ep_ext_mem_access.rvfi_mem_addr, kMemAddr + 8);
+}
+
+// Test that a branch instruction returns the correct pc_wdata value.
+TEST_F(CheriotTestRigTest, BranchV2) {
+  CHECK_OK(test_rig_.SetVersion(2));
+  CheriotTestRig test_rig;
+  InstructionPacket i_packet;
+  ExecutionPacketV2 ep_v2_packet;
+  uint16_t time = 0;
+  uint64_t inst_count = 0;
+  // Initial pc value;
+  uint64_t pc = 0x8000'0000;
+  uint32_t insn =
+      SetBTypeInstruction(kBeq, /*rs1=*/1, /*rs2=*/2, /*imm*/ 0x124);
+  i_packet = {insn, time++, kInstruction, /*padding=*/'\0'};
+  CHECK_OK(test_rig_.Execute(i_packet, write_fd_));
+  inst_count++;
+  auto res = read(read_fd_, &ep_v2_packet, sizeof(ep_v2_packet));
+  EXPECT_EQ(res, sizeof(ep_v2_packet));
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_intr, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_halt, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_trap, 0);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_insn, insn);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_mode, kMachineMode);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_ixl, kXL32);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_valid, 1);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_wdata, pc + 0x124);
+  EXPECT_EQ(ep_v2_packet.pc_data.rvfi_pc_rdata, pc);
+  EXPECT_EQ(ep_v2_packet.basic_data.rvfi_order, inst_count);
+  // This packet should have integer data, but not memory access data.
+  EXPECT_EQ(ep_v2_packet.available_fields & kIntegerData, 0);
+  EXPECT_EQ(ep_v2_packet.available_fields & kMemoryAccess, 0);
+}
+
+}  // namespace
diff --git a/cheriot/test/memory_use_profiler_test.cc b/cheriot/test/memory_use_profiler_test.cc
new file mode 100644
index 0000000..abb32c5
--- /dev/null
+++ b/cheriot/test/memory_use_profiler_test.cc
@@ -0,0 +1,180 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/memory_use_profiler.h"
+
+#include <cstdint>
+#include <iostream>
+
+#include "absl/strings/str_format.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
+
+namespace {
+
+using ::mpact::sim::cheriot::MemoryUseProfiler;
+using ::mpact::sim::cheriot::internal::MemoryUseTracker;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::DataBufferFactory;
+
+constexpr uint64_t kMemoryBase = 0x1234'5678;
+
+class MemoryUseProfilerTest : public ::testing::Test {
+ public:
+  MemoryUseProfilerTest() {
+    db1_ = db_factory_.Allocate<uint8_t>(1);
+    db2_ = db_factory_.Allocate<uint16_t>(1);
+    db4_ = db_factory_.Allocate<uint32_t>(1);
+    db8_ = db_factory_.Allocate<uint64_t>(1);
+    profiler_.set_is_enabled(true);
+  }
+
+  ~MemoryUseProfilerTest() override {
+    db1_->DecRef();
+    db2_->DecRef();
+    db4_->DecRef();
+    db8_->DecRef();
+  }
+
+  MemoryUseProfiler profiler_;
+  DataBufferFactory db_factory_;
+  DataBuffer *db1_;
+  DataBuffer *db2_;
+  DataBuffer *db4_;
+  DataBuffer *db8_;
+};
+
+// If no references are captured, then there shouldn't be any output.
+TEST_F(MemoryUseProfilerTest, NoReferences) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_EQ(output, "");
+}
+
+// Test single memory references.
+TEST_F(MemoryUseProfilerTest, SingleByteLoad) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Load(kMemoryBase, db1_, nullptr, nullptr);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleHalfLoad) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Load(kMemoryBase, db2_, nullptr, nullptr);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleWordLoad) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Load(kMemoryBase, db4_, nullptr, nullptr);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleDoubleLoad) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Load(kMemoryBase, db8_, nullptr, nullptr);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase + 4)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleByteStore) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Store(kMemoryBase, db1_);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleHalfStore) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Store(kMemoryBase, db2_);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleWordStore) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Store(kMemoryBase, db4_);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SingleDoubleStore) {
+  testing::internal::CaptureStdout();
+  profiler_.WriteProfile(std::cout);
+  profiler_.Store(kMemoryBase, db8_);
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase + 4)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SpanInSingleRange) {
+  testing::internal::CaptureStdout();
+  for (int i = 0; i < 0x64; i += 4) {
+    profiler_.Load(kMemoryBase + i, db4_, nullptr, nullptr);
+  }
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output, testing::HasSubstr(absl::StrFormat(
+                          "0x%llx,0x%llx", kMemoryBase, kMemoryBase + 0x60)))
+      << output;
+}
+
+TEST_F(MemoryUseProfilerTest, SpanInMultipleRanges) {
+  testing::internal::CaptureStdout();
+  auto seg_size = MemoryUseTracker::kSegmentSize;
+  for (int i = 0; i < seg_size; i += 4) {
+    profiler_.Load(kMemoryBase + i, db4_, nullptr, nullptr);
+  }
+  profiler_.WriteProfile(std::cout);
+  auto output = testing::internal::GetCapturedStdout();
+  EXPECT_THAT(output,
+              testing::HasSubstr(absl::StrFormat("0x%llx,0x%llx", kMemoryBase,
+                                                 kMemoryBase + seg_size - 4)))
+      << output;
+}
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_a_instructions_test.cc b/cheriot/test/riscv_cheriot_a_instructions_test.cc
new file mode 100644
index 0000000..27388d9
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_a_instructions_test.cc
@@ -0,0 +1,365 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_a_instructions.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <ios>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "absl/strings/string_view.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_i_instructions.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/atomic_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::cheriot::CheriotState;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::util::AtomicMemory;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+
+using Operation = ::mpact::sim::util::AtomicMemoryOpInterface::Operation;
+
+// Instruction semantic functions to test.
+using ::mpact::sim::cheriot::AAmoaddw;
+using ::mpact::sim::cheriot::AAmoandw;
+using ::mpact::sim::cheriot::AAmomaxuw;
+using ::mpact::sim::cheriot::AAmomaxw;
+using ::mpact::sim::cheriot::AAmominuw;
+using ::mpact::sim::cheriot::AAmominw;
+using ::mpact::sim::cheriot::AAmoorw;
+using ::mpact::sim::cheriot::AAmoswapw;
+using ::mpact::sim::cheriot::AAmoxorw;
+using ::mpact::sim::cheriot::ALrw;
+using ::mpact::sim::cheriot::AScw;
+
+// The load data write back semantic functions.
+using ::mpact::sim::cheriot::RiscVILwChild;
+
+// Register names.
+constexpr char kX1[] = "x1";
+constexpr char kX2[] = "x2";
+constexpr char kX3[] = "x3";
+constexpr char kX4[] = "x4";
+constexpr char kX5[] = "x5";
+
+// Common values used in the test.
+constexpr uint32_t kInstAddress = 0x2468;
+constexpr uint32_t kWMemAddress = 0x1000;
+constexpr uint32_t kWMemContent = 0xDEADBEEF;
+constexpr uint64_t kWRegContent = 0xDEADBEEF;
+constexpr uint32_t kWA5 = 0xA5A5'5A5A;
+
+class RiscVAInstructionsTest : public ::testing::Test {
+ protected:
+  RiscVAInstructionsTest() {
+    // Create memory objects.
+    memory_ = new TaggedFlatDemandMemory(8);
+    atomic_memory_ = new AtomicMemory(memory_);
+    // Create and initialize state and instruction objects.
+    state_ = new CheriotState("test", memory_, atomic_memory_);
+    child_instruction_ = new Instruction(state_);
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    instruction_->AppendChild(child_instruction_);
+    child_instruction_->DecRef();
+    // Set the memory locations to known values.
+    db_w_ = state_->db_factory()->Allocate<uint32_t>(1);
+    db_w_->Set<uint32_t>(0, kWMemContent);
+    memory_->Store(kWMemAddress, db_w_);
+  }
+
+  ~RiscVAInstructionsTest() override {
+    db_w_->DecRef();
+    instruction_->DecRef();
+    delete state_;
+    delete atomic_memory_;
+    delete memory_;
+  }
+
+  // Appends the source and destination operands for the register names
+  // given in the two vectors.
+  void AppendRegisterOperands(Instruction *inst,
+                              const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  void AppendRegisterOperands(const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    AppendRegisterOperands(instruction_, sources, destinations);
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      auto *db = state_->db_factory()->Allocate<CheriotRegister::ValueType>(1);
+      db->Set<T>(0, value);
+      reg->SetDataBuffer(db);
+      db->DecRef();
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+  // Initializes the semantic function of the instruction object.
+  void SetChildSemanticFunction(Instruction::SemanticFunction fcn) {
+    child_instruction_->set_semantic_function(fcn);
+  }
+
+  // Returns the value of the named register.
+  template <typename T>
+  T GetRegisterValue(absl::string_view reg_name) {
+    auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+    if (std::is_signed<T>::value) {
+      return static_cast<T>(reg->data_buffer()->Get<int32_t>(0));
+    } else {
+      return static_cast<T>(reg->data_buffer()->Get<uint32_t>(0));
+    }
+  }
+
+  DataBuffer *db_w_;
+  TaggedFlatDemandMemory *memory_;
+  AtomicMemory *atomic_memory_;
+  CheriotState *state_;
+  Instruction *instruction_;
+  Instruction *child_instruction_;
+};
+
+TEST_F(RiscVAInstructionsTest, ALrw) {
+  AppendRegisterOperands({kX1, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX4, 0}, {kX5, 0}, {kX3, 0}});
+  SetSemanticFunction(&ALrw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+}
+
+TEST_F(RiscVAInstructionsTest, AScw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, 1}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AScw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The Store conditional fails without a prior Load linked.
+  EXPECT_NE(GetRegisterValue<uint32_t>(kX3), 0)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the old value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), kWMemContent)
+      << std::hex << db_w_->Get<uint32_t>(0);
+
+  // Perform a load linked first to the address.
+  CHECK_OK(atomic_memory_->PerformMemoryOp(kWMemAddress, Operation::kLoadLinked,
+                                           db_w_, nullptr, nullptr));
+  // Now run the instruction again.
+  instruction_->Execute();
+  // The Store conditional succeeds.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), 0)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the old value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), 1) << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmoswapw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, 1}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmoswapw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the swap value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), 1) << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAamoaddw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, 1}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmoaddw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), kWMemContent + 1)
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAamoandw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmoandw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), kWMemContent & kWA5)
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmodorw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmoorw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), kWMemContent | kWA5)
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmoxorw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmoxorw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0), kWMemContent ^ kWA5)
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmomaxw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmomaxw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<int32_t>(0), std::max(static_cast<int32_t>(kWMemContent),
+                                             static_cast<int32_t>(kWA5)))
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmomaxuw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmomaxuw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0),
+            std::max(static_cast<uint32_t>(kWMemContent),
+                     static_cast<uint32_t>(kWA5)))
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmominw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmominw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<int32_t>(0), std::min(static_cast<int32_t>(kWMemContent),
+                                             static_cast<int32_t>(kWA5)))
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+TEST_F(RiscVAInstructionsTest, AAmominuw) {
+  AppendRegisterOperands({kX1, kX2, kX4, kX5}, {});
+  AppendRegisterOperands(child_instruction_, {}, {kX3});
+  SetRegisterValues<uint32_t>(
+      {{kX1, kWMemAddress}, {kX2, kWA5}, {kX3, 0}, {kX4, 0}, {kX5, 0}});
+  SetSemanticFunction(&AAmominuw);
+  SetChildSemanticFunction(&RiscVILwChild);
+  instruction_->Execute();
+  // The memory value should now be in the register.
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kWRegContent)
+      << std::hex << GetRegisterValue<uint32_t>(kX3);
+  // The memory location will have the new value.
+  memory_->Load(kWMemAddress, db_w_, nullptr, nullptr);
+  EXPECT_EQ(db_w_->Get<uint32_t>(0),
+            std::min(static_cast<uint32_t>(kWMemContent),
+                     static_cast<uint32_t>(kWA5)))
+      << std::hex << db_w_->Get<uint32_t>(0);
+}
+
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_encoding_test.cc b/cheriot/test/riscv_cheriot_encoding_test.cc
new file mode 100644
index 0000000..04fc563
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_encoding_test.cc
@@ -0,0 +1,711 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_encoding.h"
+
+#include <cstdint>
+
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_enums.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotState;
+using ::mpact::sim::cheriot::isa32::kOpcodeNames;
+using ::mpact::sim::cheriot::isa32::RiscVCheriotEncoding;
+using SlotEnum = mpact::sim::cheriot::isa32::SlotEnum;
+using OpcodeEnum = mpact::sim::cheriot::isa32::OpcodeEnum;
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+// Constexpr for opcodes for RV32 CHERIoT instructions grouped by isa group.
+
+// RV32I
+constexpr uint32_t kLui = 0b0000000000000000000000000'0110111;
+constexpr uint32_t kJal = 0b00000000000000000000'00000'1101111;
+constexpr uint32_t kJalr = 0b00000000000'00000'000'00000'1100111;
+constexpr uint32_t kBeq = 0b0000000'00000'00000'000'00000'1100011;
+constexpr uint32_t kBne = 0b0000000'00000'00000'001'00000'1100011;
+constexpr uint32_t kBlt = 0b0000000'00000'00000'100'00000'1100011;
+constexpr uint32_t kBge = 0b0000000'00000'00000'101'00000'1100011;
+constexpr uint32_t kBltu = 0b0000000'00000'00000'110'00000'1100011;
+constexpr uint32_t kBgeu = 0b0000000'00000'00000'111'00000'1100011;
+constexpr uint32_t kLb = 0b000000000000'00000'000'00000'0000011;
+constexpr uint32_t kLh = 0b000000000000'00000'001'00000'0000011;
+constexpr uint32_t kLw = 0b000000000000'00000'010'00000'0000011;
+constexpr uint32_t kLbu = 0b000000000000'00000'100'00000'0000011;
+constexpr uint32_t kLhu = 0b000000000000'00000'101'00000'0000011;
+constexpr uint32_t kSb = 0b0000000'00000'00000'000'00000'0100011;
+constexpr uint32_t kSh = 0b0000000'00000'00000'001'00000'0100011;
+constexpr uint32_t kSw = 0b0000000'00000'00000'010'00000'0100011;
+constexpr uint32_t kAddi = 0b000000000000'00000'000'00000'0010011;
+constexpr uint32_t kSlti = 0b000000000000'00000'010'00000'0010011;
+constexpr uint32_t kSltiu = 0b000000000000'00000'011'00000'0010011;
+constexpr uint32_t kXori = 0b000000000000'00000'100'00000'0010011;
+constexpr uint32_t kOri = 0b000000000000'00000'110'00000'0010011;
+constexpr uint32_t kAndi = 0b000000000000'00000'111'00000'0010011;
+constexpr uint32_t kSlli = 0b0000000'00000'00000'001'00000'0010011;
+constexpr uint32_t kSrli = 0b0000000'00000'00000'101'00000'0010011;
+constexpr uint32_t kSrai = 0b0100000'00000'00000'101'00000'0010011;
+constexpr uint32_t kAdd = 0b0000000'00000'00000'000'00000'0110011;
+constexpr uint32_t kSub = 0b0100000'00000'00000'000'00000'0110011;
+constexpr uint32_t kSll = 0b0000000'00000'00000'001'00000'0110011;
+constexpr uint32_t kSlt = 0b0000000'00000'00000'010'00000'0110011;
+constexpr uint32_t kSltu = 0b0000000'00000'00000'011'00000'0110011;
+constexpr uint32_t kXor = 0b0000000'00000'00000'100'00000'0110011;
+constexpr uint32_t kSrl = 0b0000000'00000'00000'101'00000'0110011;
+constexpr uint32_t kSra = 0b0100000'00000'00000'101'00000'0110011;
+constexpr uint32_t kOr = 0b0000000'00000'00000'110'00000'0110011;
+constexpr uint32_t kAnd = 0b0000000'00000'00000'111'00000'0110011;
+// constexpr uint32_t kFence = 0b000000000000'00000'000'00000'0001111;
+constexpr uint32_t kEcall = 0b000000000000'00000'000'00000'1110011;
+constexpr uint32_t kEbreak = 0b000000000001'00000'000'00000'1110011;
+// RV32 CHERIoT
+constexpr uint32_t kCheriotAuicgp = 0b000000000000'00000'000'00000'1111011;
+constexpr uint32_t kCheriotAuipcc = 0b000000000000'00000'000'00000'0010111;
+constexpr uint32_t kCheriotAndperm = 0b0001101'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotCleartag = 0b1111111'01011'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGetaddr = 0b1111111'01111'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGetbase = 0b1111111'00010'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGethigh = 0b1111111'10111'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGetlen = 0b1111111'00011'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGetperm = 0b1111111'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGettag = 0b1111111'00100'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGettop = 0b1111111'11000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotGettype = 0b1111111'00001'00000'000'00000'1011011;
+constexpr uint32_t kCheriotIncaddr = 0b0010001'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotIncaddrimm = 0b000000000000'00000'001'00000'1011011;
+constexpr uint32_t kCheriotJal = 0b00000000000000000000'00000'1101111;
+constexpr uint32_t kCheriotJalr = 0b00000000000'00000'000'00000'1100111;
+constexpr uint32_t kCheriotLc = 0b000000000000'00000'011'00000'0000011;
+constexpr uint32_t kCheriotMove = 0b1111111'01010'00000'000'00000'1011011;
+constexpr uint32_t kCheriotRepresentableAlignmentMask =
+    0b1111111'01001'00000'000'00000'1011011;
+constexpr uint32_t kCheriotRoundRepresentableLength =
+    0b1111111'01000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSc = 0b0000000'00000'00000'011'00000'0100011;
+constexpr uint32_t kCheriotSeal = 0b0001011'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSetaddr = 0b0010000'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSetbounds = 0b0001000'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSetboundsexact =
+    0b0001001'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSetboundsimm =
+    0b000000000000'00000'010'00000'1011011;
+constexpr uint32_t kCheriotSetequalexact =
+    0b0100001'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSethigh = 0b0010110'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSpecialrw = 0b0000001'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotSub = 0b0010100'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotTestsubset = 0b0100000'00000'00000'000'00000'1011011;
+constexpr uint32_t kCheriotUnseal = 0b0001100'00000'00000'000'00000'1011011;
+// RV32 Zifencei
+// constexpr uint32_t kFencei = 0b000000000000'00000'001'00000'0001111;
+// RV32 Zicsr
+constexpr uint32_t kCsrw = 0b000000000000'00000'001'00000'1110011;
+constexpr uint32_t kCsrs = 0b000000000000'00000'010'00000'1110011;
+constexpr uint32_t kCsrc = 0b000000000000'00000'011'00000'1110011;
+constexpr uint32_t kCsrwi = 0b000000000000'00000'101'00000'1110011;
+constexpr uint32_t kCsrsi = 0b000000000000'00000'110'00000'1110011;
+constexpr uint32_t kCsrci = 0b000000000000'00000'111'00000'1110011;
+// RV32M
+constexpr uint32_t kMul = 0b0000001'00000'00000'000'00000'0110011;
+constexpr uint32_t kMulh = 0b0000001'00000'00000'001'00000'0110011;
+constexpr uint32_t kMulhsu = 0b0000001'00000'00000'010'00000'0110011;
+constexpr uint32_t kMulhu = 0b0000001'00000'00000'011'00000'0110011;
+constexpr uint32_t kDiv = 0b0000001'00000'00000'100'00000'0110011;
+constexpr uint32_t kDivu = 0b0000001'00000'00000'101'00000'0110011;
+constexpr uint32_t kRem = 0b0000001'00000'00000'110'00000'0110011;
+constexpr uint32_t kRemu = 0b0000001'00000'00000'111'00000'0110011;
+// RV32A
+// constexpr uint32_t kLrw = 0b00010'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kScw = 0b00011'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmoswapw = 0b00001'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmoaddw = 0b00000'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmoxorw = 0b00100'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmoandw = 0b01100'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmoorw = 0b01000'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmominw = 0b10000'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmomaxw = 0b10100'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmominuw = 0b11000'0'0'00000'00000'010'00000'0101111;
+// constexpr uint32_t kAmomaxuw = 0b11100'0'0'00000'00000'010'00000'0101111;
+// RV32F
+constexpr uint32_t kFlw = 0b000000000000'00000'010'00000'0000111;
+constexpr uint32_t kFsw = 0b0000000'00000'00000'010'00000'0100111;
+constexpr uint32_t kFmadds = 0b00000'00'00000'00000'000'00000'1000011;
+constexpr uint32_t kFmsubs = 0b00000'00'00000'00000'000'00000'1000111;
+constexpr uint32_t kFnmsubs = 0b00000'00'00000'00000'000'00000'1001011;
+constexpr uint32_t kFnmadds = 0b00000'00'00000'00000'000'00000'1001111;
+constexpr uint32_t kFadds = 0b0000000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFsubs = 0b0000100'00000'00000'000'00000'1010011;
+constexpr uint32_t kFmuls = 0b0001000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFdivs = 0b0001100'00000'00000'000'00000'1010011;
+constexpr uint32_t kFsqrts = 0b0101100'00000'00000'000'00000'1010011;
+constexpr uint32_t kFsgnjs = 0b0010000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFsgnjns = 0b0010000'00000'00000'001'00000'1010011;
+constexpr uint32_t kFsgnjxs = 0b0010000'00000'00000'010'00000'1010011;
+constexpr uint32_t kFmins = 0b0010100'00000'00000'000'00000'1010011;
+constexpr uint32_t kFmaxs = 0b0010100'00000'00000'001'00000'1010011;
+constexpr uint32_t kFcvtws = 0b1100000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFcvtwus = 0b1100000'00001'00000'000'00000'1010011;
+constexpr uint32_t kFmvxw = 0b1110000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFeqs = 0b1010000'00000'00000'010'00000'1010011;
+constexpr uint32_t kFlts = 0b1010000'00000'00000'001'00000'1010011;
+constexpr uint32_t kFles = 0b1010000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFclasss = 0b1110000'00000'00000'001'00000'1010011;
+constexpr uint32_t kFcvtsw = 0b1101000'00000'00000'000'00000'1010011;
+constexpr uint32_t kFcvtswu = 0b1101000'00001'00000'000'00000'1010011;
+constexpr uint32_t kFmvwx = 0b1111000'00000'00000'000'00000'1010011;
+// RV32C
+constexpr uint32_t kClwsp = 0b010'0'00000'00000'10;
+constexpr uint32_t kCldsp = 0b011'0'00000'00000'10;
+// constexpr uint32_t kCdldsp = 0b001'0'00000'00000'10;
+constexpr uint32_t kCswsp = 0b110'000000'00000'10;
+constexpr uint32_t kCsdsp = 0b111'000000'00000'10;
+// constexpr uint32_t kCdsdsp = 0b101'000000'00000'10;
+constexpr uint32_t kClw = 0b010'000'000'00'000'00;
+constexpr uint32_t kCld = 0b011'000'000'00'000'00;
+// constexpr uint32_t kCdld = 0b001'000'000'00'000'00;
+constexpr uint32_t kCsw = 0b110'000'000'00'000'00;
+constexpr uint32_t kCsd = 0b111'000'000'00'000'00;
+// constexpr uint32_t kCdsd = 0b101'000'000'00'000'00;
+constexpr uint32_t kCheriotCj = 0b101'00000000000'01;
+constexpr uint32_t kCheriotCjal = 0b001'00000000000'01;
+constexpr uint32_t kCheriotCjr = 0b100'0'00000'00000'10;
+constexpr uint32_t kCheriotCjalr = 0b100'1'00000'00000'10;
+constexpr uint32_t kCbeqz = 0b110'000'000'00000'01;
+constexpr uint32_t kCbnez = 0b111'000'000'00000'01;
+constexpr uint32_t kCli = 0b010'0'00000'00000'01;
+constexpr uint32_t kClui = 0b011'0'00000'00000'01;
+constexpr uint32_t kCaddi = 0b000'0'00000'00000'01;
+constexpr uint32_t kCaddi16sp = 0b011'0'00010'00000'01;
+constexpr uint32_t kCaddi4spn = 0b000'00000000'000'00;
+constexpr uint32_t kCslli = 0b000'0'00000'00000'10;
+constexpr uint32_t kCsrli = 0b100'0'00'000'00000'01;
+constexpr uint32_t kCsrai = 0b100'0'01'000'00000'01;
+constexpr uint32_t kCandi = 0b100'0'10'000'00000'01;
+constexpr uint32_t kCmv = 0b100'0'00000'00000'10;
+constexpr uint32_t kCadd = 0b100'1'00000'00000'10;
+constexpr uint32_t kCand = 0b100'0'11'000'11'000'01;
+constexpr uint32_t kCor = 0b100'0'11'000'10'000'01;
+constexpr uint32_t kCxor = 0b100'0'11'000'01'000'01;
+constexpr uint32_t kCSub = 0b100'0'11'000'00'000'01;
+constexpr uint32_t kCnop = 0b000'0'00000'00000'01;
+constexpr uint32_t kCebreak = 0b100'1'00000'00000'10;
+// constexpr uint32_t kMret = 0b001'1000'00010'00000'000'00000'111'0011;
+// constexpr uint32_t kWfi = 0b000'1000'00101'00000'000'00000'111'0011;
+// constexpr uint32_t kSfenceVmaZz = 0b000'1001'00000'00000'000'00000'111'0011;
+// constexpr uint32_t kSfenceVmaZn = 0b000'1001'00001'00000'000'00000'111'0011;
+// constexpr uint32_t kSfenceVmaNz = 0b000'1001'00000'00001'000'00000'111'0011;
+// constexpr uint32_t kSfenceVmaNn = 0b000'1001'00001'00001'000'00000'111'0011;
+
+class RiscVCheriotEncodingTest : public testing::Test {
+ protected:
+  RiscVCheriotEncodingTest() {
+    state_ = new CheriotState("test");
+    enc_ = new RiscVCheriotEncoding(state_);
+  }
+  ~RiscVCheriotEncodingTest() override {
+    delete enc_;
+    delete state_;
+  }
+
+  CheriotState *state_;
+  RiscVCheriotEncoding *enc_;
+};
+
+constexpr int kRdValue = 1;
+constexpr int kSuccValue = 0xf;
+constexpr int kPredValue = 0xf;
+
+static uint32_t SetRd(uint32_t iword, uint32_t rdval) {
+  return (iword | ((rdval & 0x1f) << 7));
+}
+
+static uint32_t SetRs1(uint32_t iword, uint32_t rsval) {
+  return (iword | ((rsval & 0x1f) << 15));
+}
+
+static uint32_t SetRs2(uint32_t iword, uint32_t rsval) {
+  return (iword | ((rsval & 0x1f) << 20));
+}
+
+static uint32_t SetPred(uint32_t iword, uint32_t pred) {
+  return (iword | ((pred & 0xf) << 24));
+}
+
+static uint32_t SetSucc(uint32_t iword, uint32_t succ) {
+  return (iword | ((succ & 0xf) << 20));
+}
+
+static uint32_t Set16Rd(uint32_t iword, uint32_t val) {
+  return (iword | ((val & 0x1f) << 7));
+}
+
+static uint32_t Set16Rs2(uint32_t iword, uint32_t val) {
+  return (iword | ((val & 0x1f) << 2));
+}
+
+TEST_F(RiscVCheriotEncodingTest, RV32IOpcodes) {
+  enc_->ParseInstruction(SetRd(kLui, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLui);
+  enc_->ParseInstruction(SetRd(kCheriotJal, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotJal);
+  enc_->ParseInstruction(SetRd(kCheriotJalr, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotJalr);
+  enc_->ParseInstruction(kBeq);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kBeq);
+  enc_->ParseInstruction(kBne);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kBne);
+  enc_->ParseInstruction(kBlt);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kBlt);
+  enc_->ParseInstruction(kBge);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kBge);
+  enc_->ParseInstruction(kBltu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kBltu);
+  enc_->ParseInstruction(kBgeu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kBgeu);
+  enc_->ParseInstruction(SetRd(kLb, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLb);
+  enc_->ParseInstruction(SetRd(kLh, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLh);
+  enc_->ParseInstruction(SetRd(kLw, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLw);
+  enc_->ParseInstruction(SetRd(kLbu, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLbu);
+  enc_->ParseInstruction(SetRd(kLhu, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLhu);
+  enc_->ParseInstruction(SetRd(kSb, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSb);
+  enc_->ParseInstruction(SetRd(kSh, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSh);
+  enc_->ParseInstruction(SetRd(kSw, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSw);
+  enc_->ParseInstruction(SetRd(kAddi, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kAddi);
+  enc_->ParseInstruction(SetRd(kSlti, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSlti);
+  enc_->ParseInstruction(SetRd(kSltiu, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSltiu);
+  enc_->ParseInstruction(SetRd(kXori, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kXori);
+  enc_->ParseInstruction(SetRd(kOri, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kOri);
+  enc_->ParseInstruction(SetRd(kAndi, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kAndi);
+  enc_->ParseInstruction(SetRd(kSlli, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSlli);
+  enc_->ParseInstruction(SetRd(kSrli, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSrli);
+  enc_->ParseInstruction(SetRd(kSrai, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSrai);
+  enc_->ParseInstruction(SetRd(kAdd, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kAdd);
+  enc_->ParseInstruction(SetRd(kSub, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSub);
+  enc_->ParseInstruction(SetRd(kSll, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSll);
+  enc_->ParseInstruction(SetRd(kSlt, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSlt);
+  enc_->ParseInstruction(SetRd(kSltu, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSltu);
+  enc_->ParseInstruction(SetRd(kXor, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kXor);
+  enc_->ParseInstruction(SetRd(kSrl, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSrl);
+  enc_->ParseInstruction(SetRd(kSra, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kSra);
+  enc_->ParseInstruction(SetRd(kOr, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kOr);
+  enc_->ParseInstruction(SetRd(kAnd, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kAnd);
+  // enc_->ParseInstruction(SetSucc(SetPred(kFence, kPredValue), kSuccValue));
+  // EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+  // OpcodeEnum::kFence);
+  enc_->ParseInstruction(kEcall);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kEcall);
+  enc_->ParseInstruction(kEbreak);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kEbreak);
+}
+
+// TEST_F(RiscVCheriotEncodingTest, ZifenceiOpcodes) {
+// RV32 Zifencei
+//   enc_->ParseInstruction(kFencei);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kFencei);
+// }
+
+TEST_F(RiscVCheriotEncodingTest, ZicsrOpcodes) {
+  // RV32 Zicsr
+  enc_->ParseInstruction(SetRd(kCsrw, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrrw);
+  enc_->ParseInstruction(SetRd(SetRs1(kCsrs, kRdValue), kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrrs);
+  enc_->ParseInstruction(SetRd(SetRs1(kCsrc, kRdValue), kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrrc);
+  enc_->ParseInstruction(kCsrw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCsrrwNr);
+  enc_->ParseInstruction(kCsrs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCsrrsNw);
+  enc_->ParseInstruction(kCsrc);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCsrrcNw);
+  enc_->ParseInstruction(SetRd(kCsrwi, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrrwi);
+  enc_->ParseInstruction(SetRd(SetRs1(kCsrsi, kRdValue), kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrrsi);
+  enc_->ParseInstruction(SetRd(SetRs1(kCsrci, kRdValue), kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrrci);
+  enc_->ParseInstruction(kCsrwi);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCsrrwiNr);
+  enc_->ParseInstruction(kCsrsi);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCsrrsiNw);
+  enc_->ParseInstruction(kCsrci);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCsrrciNw);
+}
+
+TEST_F(RiscVCheriotEncodingTest, RV32MOpcodes) {
+  // RV32M
+  enc_->ParseInstruction(kMul);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kMul);
+  enc_->ParseInstruction(kMulh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kMulh);
+  enc_->ParseInstruction(kMulhsu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kMulhsu);
+  enc_->ParseInstruction(kMulhu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kMulhu);
+  enc_->ParseInstruction(kDiv);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kDiv);
+  enc_->ParseInstruction(kDivu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kDivu);
+  enc_->ParseInstruction(kRem);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kRem);
+  enc_->ParseInstruction(kRemu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kRemu);
+}
+
+// TEST_F(RiscVCheriotEncodingTest, RV32AOpcodes) {
+// RV32A
+//  enc_->ParseInstruction(kLrw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kLrw);
+//  enc_->ParseInstruction(kScw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kScw);
+//  enc_->ParseInstruction(kAmoswapw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmoswapw); enc_->ParseInstruction(kAmoaddw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmoaddw); enc_->ParseInstruction(kAmoxorw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmoxorw); enc_->ParseInstruction(kAmoandw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmoandw); enc_->ParseInstruction(kAmoorw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmoorw); enc_->ParseInstruction(kAmominw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmominw); enc_->ParseInstruction(kAmomaxw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmomaxw); enc_->ParseInstruction(kAmominuw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmominuw); enc_->ParseInstruction(kAmomaxuw);
+//  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//  OpcodeEnum::kAmomaxuw);
+// }
+
+TEST_F(RiscVCheriotEncodingTest, RV32FOpcodes) {
+  // RV32F
+  enc_->ParseInstruction(kFlw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFlw);
+  enc_->ParseInstruction(kFsw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFsw);
+  enc_->ParseInstruction(kFmadds);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFmaddS);
+  enc_->ParseInstruction(kFmsubs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFmsubS);
+  enc_->ParseInstruction(kFnmsubs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFnmsubS);
+  enc_->ParseInstruction(kFnmadds);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFnmaddS);
+  enc_->ParseInstruction(kFadds);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFaddS);
+  enc_->ParseInstruction(kFsubs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFsubS);
+  enc_->ParseInstruction(kFmuls);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFmulS);
+  enc_->ParseInstruction(kFdivs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFdivS);
+  enc_->ParseInstruction(kFsqrts);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFsqrtS);
+  enc_->ParseInstruction(kFsgnjs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFsgnjS);
+  enc_->ParseInstruction(kFsgnjns);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFsgnjnS);
+  enc_->ParseInstruction(kFsgnjxs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFsgnjxS);
+  enc_->ParseInstruction(kFmins);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFminS);
+  enc_->ParseInstruction(kFmaxs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFmaxS);
+  enc_->ParseInstruction(kFcvtws);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFcvtWs);
+  enc_->ParseInstruction(kFcvtwus);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFcvtWus);
+  enc_->ParseInstruction(kFmvxw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFmvXw);
+  enc_->ParseInstruction(kFeqs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFcmpeqS);
+  enc_->ParseInstruction(kFlts);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFcmpltS);
+  enc_->ParseInstruction(kFles);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFcmpleS);
+  enc_->ParseInstruction(kFclasss);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFclassS);
+  enc_->ParseInstruction(kFcvtsw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFcvtSw);
+  enc_->ParseInstruction(kFcvtswu);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kFcvtSwu);
+  enc_->ParseInstruction(kFmvwx);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kFmvWx);
+}
+
+// Test for decoding compact opcodes.
+TEST_F(RiscVCheriotEncodingTest, RV32COpcodes) {
+  enc_->ParseInstruction(Set16Rd(kClwsp, 1));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kClwsp);
+  enc_->ParseInstruction(Set16Rd(kCldsp, 1));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCldsp);
+  //  enc_->ParseInstruction(kCdldsp);
+  //  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+  //  OpcodeEnum::kCfldsp);
+  enc_->ParseInstruction(kCswsp);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCswsp);
+  enc_->ParseInstruction(kCsdsp);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsdsp);
+  //  enc_->ParseInstruction(kCdsdsp);
+  //  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+  //  OpcodeEnum::kCfsdsp);
+  enc_->ParseInstruction(kClw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kClw);
+  enc_->ParseInstruction(kCld);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCld);
+  enc_->ParseInstruction(kCsw);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsw);
+  enc_->ParseInstruction(kCsd);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsd);
+  //  enc_->ParseInstruction(kCdsd);
+  //  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+  //  OpcodeEnum::kCdsd);
+  enc_->ParseInstruction(kCheriotCj);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotCj);
+  enc_->ParseInstruction(kCheriotCjal);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotCjal);
+  enc_->ParseInstruction(Set16Rd(kCheriotCjr, 1));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotCjr);
+  enc_->ParseInstruction(Set16Rd(kCheriotCjalr, 1));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotCjalr);
+  enc_->ParseInstruction(kCbeqz);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCbeqz);
+  enc_->ParseInstruction(kCbnez);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCbnez);
+  enc_->ParseInstruction(Set16Rd(kCli, 1));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCli);
+  enc_->ParseInstruction(Set16Rs2(Set16Rd(kClui, 1), 5));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kClui);
+  enc_->ParseInstruction(Set16Rs2(Set16Rd(kCaddi, 1), 5));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCaddi);
+  enc_->ParseInstruction(Set16Rs2(kCaddi16sp, 5));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCaddi16sp);
+  enc_->ParseInstruction(kCaddi4spn | 0b000'01010101'000'00);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCaddi4spn);
+  enc_->ParseInstruction(Set16Rs2(Set16Rd(kCslli, 1), 5));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCslli);
+  enc_->ParseInstruction(Set16Rs2(kCsrli, 5));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrli);
+  enc_->ParseInstruction(Set16Rs2(kCsrai, 5));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsrai);
+  enc_->ParseInstruction(kCandi);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCandi);
+  enc_->ParseInstruction(Set16Rs2(Set16Rd(kCmv, 1), 2));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCmv);
+  enc_->ParseInstruction(Set16Rs2(Set16Rd(kCadd, 1), 2));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCadd);
+  enc_->ParseInstruction(kCand);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCand);
+  enc_->ParseInstruction(kCor);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCor);
+  enc_->ParseInstruction(kCxor);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCxor);
+  enc_->ParseInstruction(kCSub);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCsub);
+  enc_->ParseInstruction(kCnop);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kCnop);
+  enc_->ParseInstruction(kCebreak);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCebreak);
+}
+
+TEST_F(RiscVCheriotEncodingTest, RiscVCheriotOpcodes) {
+  enc_->ParseInstruction(kCheriotAndperm);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotAndperm)
+      << kOpcodeNames[*enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0)];
+  enc_->ParseInstruction(kCheriotAuicgp);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotAuicgp);
+  enc_->ParseInstruction(kCheriotAuipcc);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotAuipcc);
+  enc_->ParseInstruction(kCheriotCleartag);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotCleartag);
+  enc_->ParseInstruction(kCheriotGetaddr);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGetaddr);
+  enc_->ParseInstruction(kCheriotGetbase);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGetbase);
+  enc_->ParseInstruction(kCheriotGethigh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGethigh);
+  enc_->ParseInstruction(kCheriotGetlen);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGetlen);
+  enc_->ParseInstruction(kCheriotGetperm);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGetperm);
+  enc_->ParseInstruction(kCheriotGettag);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGettag);
+  enc_->ParseInstruction(kCheriotGettop);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGettop);
+  enc_->ParseInstruction(kCheriotGettype);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotGettype);
+  enc_->ParseInstruction(kCheriotIncaddr);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotIncaddr);
+  enc_->ParseInstruction(kCheriotIncaddrimm);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotIncaddrimm);
+  enc_->ParseInstruction(SetRd(kCheriotJal, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotJal);
+  enc_->ParseInstruction(SetRd(kCheriotJalr, kRdValue));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotJalr);
+  enc_->ParseInstruction(kCheriotJal);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotJ);
+  enc_->ParseInstruction(kCheriotJalr);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotJr);
+  enc_->ParseInstruction(kCheriotLc);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotLc);
+  enc_->ParseInstruction(kCheriotMove);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotMove);
+  enc_->ParseInstruction(kCheriotRepresentableAlignmentMask);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotRepresentablealignmentmask);
+  enc_->ParseInstruction(kCheriotRoundRepresentableLength);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotRoundrepresentablelength);
+  enc_->ParseInstruction(kCheriotSc);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSc);
+  enc_->ParseInstruction(kCheriotSeal);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSeal);
+  enc_->ParseInstruction(kCheriotSetaddr);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSetaddr);
+  enc_->ParseInstruction(kCheriotSetbounds);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSetbounds);
+  enc_->ParseInstruction(kCheriotSetboundsexact);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSetboundsexact);
+  enc_->ParseInstruction(kCheriotSetboundsimm);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSetboundsimm);
+  enc_->ParseInstruction(kCheriotSetequalexact);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSetequalexact);
+  enc_->ParseInstruction(kCheriotSethigh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSethigh);
+  enc_->ParseInstruction(SetRs2(kCheriotSpecialrw, 28));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSpecialr)
+      << kOpcodeNames[*enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0)];
+  enc_->ParseInstruction(SetRs2(SetRs1(kCheriotSpecialrw, kRdValue), 28));
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSpecialrw)
+      << kOpcodeNames[*enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0)];
+  enc_->ParseInstruction(kCheriotSub);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotSub);
+  enc_->ParseInstruction(kCheriotTestsubset);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotTestsubset);
+  enc_->ParseInstruction(kCheriotUnseal);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+            OpcodeEnum::kCheriotUnseal);
+}
+
+// TEST_F(RiscVCheriotEncodingTest, RV32PrivilegedOpcodes) {
+//   enc_->ParseInstruction(kUret);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kUret); enc_->ParseInstruction(kSret);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kSret); enc_->ParseInstruction(kMret);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kMret); enc_->ParseInstruction(kWfi);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0), OpcodeEnum::kWfi);
+//   enc_->ParseInstruction(kSfenceVmaZz);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kSfenceVmaZz); enc_->ParseInstruction(kSfenceVmaZn);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kSfenceVmaZn); enc_->ParseInstruction(kSfenceVmaNz);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kSfenceVmaNz); enc_->ParseInstruction(kSfenceVmaNn);
+//   EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32Cheriot, 0),
+//   OpcodeEnum::kSfenceVmaNn);
+// }
+
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_f_instructions_test.cc b/cheriot/test/riscv_cheriot_f_instructions_test.cc
new file mode 100644
index 0000000..4fb1e27
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_f_instructions_test.cc
@@ -0,0 +1,347 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_f_instructions.h"
+
+#include <cmath>
+#include <cstdint>
+#include <tuple>
+
+#include "cheriot/test/riscv_cheriot_fp_test_base.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/instruction.h"
+#include "riscv//riscv_register.h"
+
+namespace {
+
+using ::mpact::sim::cheriot::test::FPTypeInfo;
+using ::mpact::sim::cheriot::test::OptimizationBarrier;
+using ::mpact::sim::cheriot::test::RiscVFPInstructionTestBase;
+using ::mpact::sim::riscv::FPExceptions;
+using ::mpact::sim::riscv::RVFpRegister;
+
+using ::mpact::sim::cheriot::RiscVFAdd;
+using ::mpact::sim::cheriot::RiscVFClass;
+using ::mpact::sim::cheriot::RiscVFCmpeq;
+using ::mpact::sim::cheriot::RiscVFCmple;
+using ::mpact::sim::cheriot::RiscVFCmplt;
+using ::mpact::sim::cheriot::RiscVFCvtSw;
+using ::mpact::sim::cheriot::RiscVFCvtSwu;
+using ::mpact::sim::cheriot::RiscVFCvtWs;
+using ::mpact::sim::cheriot::RiscVFCvtWus;
+using ::mpact::sim::cheriot::RiscVFDiv;
+using ::mpact::sim::cheriot::RiscVFMadd;
+using ::mpact::sim::cheriot::RiscVFMax;
+using ::mpact::sim::cheriot::RiscVFMin;
+using ::mpact::sim::cheriot::RiscVFMsub;
+using ::mpact::sim::cheriot::RiscVFMul;
+using ::mpact::sim::cheriot::RiscVFNmadd;
+using ::mpact::sim::cheriot::RiscVFNmsub;
+using ::mpact::sim::cheriot::RiscVFSgnj;
+using ::mpact::sim::cheriot::RiscVFSgnjn;
+using ::mpact::sim::cheriot::RiscVFSgnjx;
+using ::mpact::sim::cheriot::RiscVFSqrt;
+using ::mpact::sim::cheriot::RiscVFSub;
+
+class RVCheriot32FInstructionTest : public RiscVFPInstructionTestBase {};
+
+static bool is_snan(float a) {
+  if (!std::isnan(a)) return false;
+  uint32_t ua = *reinterpret_cast<uint32_t *>(&a);
+  if ((ua & (1 << (FPTypeInfo<float>::kSigSize - 1))) == 0) return true;
+  return false;
+}
+
+// Test basic arithmetic instructions.
+TEST_F(RVCheriot32FInstructionTest, RiscVFadd) {
+  SetSemanticFunction(&RiscVFAdd);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fadd", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float { return lhs + rhs; });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFsub) {
+  SetSemanticFunction(&RiscVFSub);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fsub", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float { return lhs - rhs; });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFmul) {
+  SetSemanticFunction(&RiscVFMul);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fmul", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float { return lhs * rhs; });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFdiv) {
+  SetSemanticFunction(&RiscVFDiv);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fdiv", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float { return lhs / rhs; });
+}
+
+// Test square root.
+TEST_F(RVCheriot32FInstructionTest, RiscVFsqrt) {
+  SetSemanticFunction(&RiscVFSqrt);
+}
+
+// Test Min/Max.
+TEST_F(RVCheriot32FInstructionTest, RiscVFmin) {
+  SetSemanticFunction(&RiscVFMin);
+  BinaryOpWithFflagsFPTestHelper<float, float, float>(
+      "fmin", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> std::tuple<float, uint32_t> {
+        uint32_t flag = 0;
+        if (is_snan(lhs) || is_snan(rhs)) {
+          flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
+        }
+        if (std::isnan(lhs) && std::isnan(rhs)) {
+          uint32_t val = FPTypeInfo<float>::kCanonicalNaN;
+          return std::tie(*reinterpret_cast<const float *>(&val), flag);
+        }
+        if (std::isnan(lhs)) return std::tie(rhs, flag);
+        if (std::isnan(rhs)) return std::tie(lhs, flag);
+        if ((lhs == 0.0) && (rhs == 0.0)) {
+          return std::tie(std::signbit(lhs) ? lhs : rhs, flag);
+        }
+        return std::tie(lhs > rhs ? rhs : lhs, flag);
+      });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFmax) {
+  SetSemanticFunction(&RiscVFMax);
+  BinaryOpWithFflagsFPTestHelper<float, float, float>(
+      "fmax", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> std::tuple<float, uint32_t> {
+        uint32_t flag = 0;
+        if (is_snan(lhs) || is_snan(rhs)) {
+          flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
+        }
+        if (std::isnan(lhs) && std::isnan(rhs)) {
+          uint32_t val = FPTypeInfo<float>::kCanonicalNaN;
+          return std::tie(*reinterpret_cast<const float *>(&val), flag);
+        }
+        if (std::isnan(lhs)) return std::tie(rhs, flag);
+        if (std::isnan(rhs)) return std::tie(lhs, flag);
+        if ((lhs == 0.0) && (rhs == 0.0)) {
+          return std::tie(std::signbit(lhs) ? rhs : lhs, flag);
+        }
+        return std::tie(lhs < rhs ? rhs : lhs, flag);
+      });
+}
+
+// Test MAC versions.
+TEST_F(RVCheriot32FInstructionTest, RiscVFMadd) {
+  SetSemanticFunction(&RiscVFMadd);
+  TernaryOpFPTestHelper<float, float, float, float>(
+      "fmadd", instruction_, {"f", "f", "f", "f"}, 32,
+      [](float lhs, float mhs, float rhs) -> float {
+        return OptimizationBarrier(lhs * mhs) + rhs;
+      });
+}
+TEST_F(RVCheriot32FInstructionTest, RiscVFMsub) {
+  SetSemanticFunction(&RiscVFMsub);
+  TernaryOpFPTestHelper<float, float, float, float>(
+      "fmsub", instruction_, {"f", "f", "f", "f"}, 32,
+      [](float lhs, float mhs, float rhs) -> float {
+        return OptimizationBarrier(lhs * mhs) - rhs;
+      });
+}
+TEST_F(RVCheriot32FInstructionTest, RiscVFNmadd) {
+  SetSemanticFunction(&RiscVFNmadd);
+  TernaryOpFPTestHelper<float, float, float, float>(
+      "fnmadd", instruction_, {"f", "f", "f", "f"}, 32,
+      [](float lhs, float mhs, float rhs) -> float {
+        return -OptimizationBarrier(lhs * mhs) - rhs;
+      });
+}
+TEST_F(RVCheriot32FInstructionTest, RiscVFNmsub) {
+  SetSemanticFunction(&RiscVFNmsub);
+  TernaryOpFPTestHelper<float, float, float, float>(
+      "fnmsub", instruction_, {"f", "f", "f", "f"}, 32,
+      [](float lhs, float mhs, float rhs) -> float {
+        return -OptimizationBarrier(lhs * mhs) + rhs;
+      });
+}
+
+// Test conversion instructions.
+// Double to signed 32 bit integer.
+TEST_F(RVCheriot32FInstructionTest, RiscVFCvtWs) {
+  SetSemanticFunction(&RiscVFCvtWs);
+  UnaryOpWithFflagsFPTestHelper<int32_t, float>(
+      "fcvt.w.s", instruction_, {"f", "x"}, 32,
+      [&](float lhs) -> std::tuple<int32_t, uint32_t> {
+        if (std::isnan(lhs))
+          return std::make_tuple(
+              0x7fff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp));
+        if (std::isinf(lhs))
+          return std::make_tuple(
+              lhs < 0 ? 0x8000'0000 : 0x7fff'ffff,
+              static_cast<uint32_t>(FPExceptions::kInvalidOp));
+        if (abs(lhs) > static_cast<float>(0x7fff'ffff))
+          return std::make_tuple(
+              lhs < 0 ? 0x8000'0000 : 0x7fff'ffff,
+              static_cast<uint32_t>(FPExceptions::kInvalidOp));
+        uint32_t flag = 0;
+        if (ceil(lhs) != lhs) {
+          flag |= static_cast<uint32_t>(FPExceptions::kInexact);
+          lhs = RoundToInteger(lhs);
+        }
+        return std::make_tuple(static_cast<int32_t>(lhs), flag);
+      });
+}
+
+// Signed 32 bit integer to float.
+TEST_F(RVCheriot32FInstructionTest, RiscVFCvtSw) {
+  SetSemanticFunction(&RiscVFCvtSw);
+  UnaryOpFPTestHelper<float, int32_t>(
+      "fcvt.s.w", instruction_, {"x", "f"}, 32,
+      [](int32_t lhs) -> float { return static_cast<float>(lhs); });
+}
+
+// Double to unsigned 32 bit integer.
+TEST_F(RVCheriot32FInstructionTest, RiscVFCvtWus) {
+  SetSemanticFunction(&RiscVFCvtWus);
+  UnaryOpWithFflagsFPTestHelper<uint32_t, float>(
+      "fcvt.wu.s", instruction_, {"f", "x"}, 32,
+      [&](float lhs) -> std::tuple<uint32_t, uint32_t> {
+        if (std::isnan(lhs))
+          return std::make_tuple(
+              0xffff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp));
+        if (lhs < 0) {
+          if ((lhs > -1.0) && (static_cast<int32_t>(lhs) == 0.0)) {
+            return std::make_tuple(
+                0, static_cast<uint32_t>(FPExceptions::kInexact));
+          }
+          return std::make_tuple(
+              0, static_cast<uint32_t>(FPExceptions::kInvalidOp));
+        }
+        if (std::isinf(lhs) || lhs > static_cast<float>(0xffff'ffffUL)) {
+          return std::make_tuple(
+              0xffff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp));
+        }
+        uint32_t flag = 0;
+        if (ceil(lhs) != lhs) {
+          flag |= static_cast<uint32_t>(FPExceptions::kInexact);
+          lhs = RoundToInteger(lhs);
+        }
+        return std::make_tuple(static_cast<uint32_t>(lhs), flag);
+      });
+}
+
+// Unsigned 32 bit integer to float.
+TEST_F(RVCheriot32FInstructionTest, RiscVFCvtSwu) {
+  SetSemanticFunction(&RiscVFCvtSwu);
+  UnaryOpFPTestHelper<float, uint32_t>(
+      "fcvt.s.w", instruction_, {"x", "f"}, 32,
+      [](uint32_t lhs) -> float { return static_cast<float>(lhs); });
+}
+
+// Test sign manipulation instructions.
+TEST_F(RVCheriot32FInstructionTest, RiscVFSgnj) {
+  SetSemanticFunction(&RiscVFSgnj);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fsgnj", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float { return copysign(abs(lhs), rhs); });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFSgnjn) {
+  SetSemanticFunction(&RiscVFSgnjn);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fsgnjn", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float { return copysign(abs(lhs), -rhs); });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFSgnjx) {
+  SetSemanticFunction(&RiscVFSgnjx);
+  BinaryOpFPTestHelper<float, float, float>(
+      "fsgnjn", instruction_, {"f", "f", "f"}, 32,
+      [](float lhs, float rhs) -> float {
+        auto lhs_u = *reinterpret_cast<uint32_t *>(&lhs);
+        auto rhs_u = *reinterpret_cast<uint32_t *>(&rhs);
+        auto res_u = (lhs_u ^ rhs_u) & 0x8000'0000;
+        auto res = *reinterpret_cast<float *>(&res_u);
+        return copysign(abs(lhs), res);
+      });
+}
+
+// Test compare instructions.
+TEST_F(RVCheriot32FInstructionTest, RiscVFCmpeq) {
+  SetSemanticFunction(&RiscVFCmpeq);
+  BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>(
+      "fcmpeq", instruction_, {"f", "f", "x"}, 32,
+      [](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> {
+        uint32_t flag = 0;
+        if (is_snan(lhs) || is_snan(rhs)) {
+          flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
+        }
+        return std::make_tuple(lhs == rhs, flag);
+      });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFCmplt) {
+  SetSemanticFunction(&RiscVFCmplt);
+  BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>(
+      "fcmplt", instruction_, {"f", "f", "x"}, 32,
+      [](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> {
+        uint32_t flag = 0;
+        if (std::isnan(lhs) || std::isnan(rhs)) {
+          flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
+        }
+        return std::make_tuple(lhs < rhs, flag);
+      });
+}
+
+TEST_F(RVCheriot32FInstructionTest, RiscVFCmple) {
+  SetSemanticFunction(&RiscVFCmple);
+  BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>(
+      "fcmple", instruction_, {"f", "f", "x"}, 32,
+      [](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> {
+        uint32_t flag = 0;
+        if (std::isnan(lhs) || std::isnan(rhs)) {
+          flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
+        }
+        return std::make_tuple(lhs <= rhs, flag);
+      });
+}
+
+// Test class instruction.
+TEST_F(RVCheriot32FInstructionTest, RiscVFClass) {
+  SetSemanticFunction(&RiscVFClass);
+  UnaryOpFPTestHelper<uint32_t, float>(
+      "fclass.d", instruction_, {"f", "x"}, 32, [](float lhs) -> uint32_t {
+        auto fp_class = std::fpclassify(lhs);
+        switch (fp_class) {
+          case FP_INFINITE:
+            return std::signbit(lhs) ? 1 : 1 << 7;
+          case FP_NAN: {
+            auto uint_val =
+                *reinterpret_cast<typename FPTypeInfo<float>::IntType *>(&lhs);
+            bool quiet_nan =
+                (uint_val >> (FPTypeInfo<float>::kSigSize - 1)) & 1;
+            return quiet_nan ? 1 << 9 : 1 << 8;
+          }
+          case FP_ZERO:
+            return std::signbit(lhs) ? 1 << 3 : 1 << 4;
+          case FP_SUBNORMAL:
+            return std::signbit(lhs) ? 1 << 2 : 1 << 5;
+          case FP_NORMAL:
+            return std::signbit(lhs) ? 1 << 1 : 1 << 6;
+        }
+        return 0;
+      });
+}
+
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_fp_test_base.h b/cheriot/test/riscv_cheriot_fp_test_base.h
new file mode 100644
index 0000000..b47923d
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_fp_test_base.h
@@ -0,0 +1,781 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT___TEST_RISCV_CHERIOT_FP_TEST_BASE_H_
+#define MPACT_CHERIOT___TEST_RISCV_CHERIOT_FP_TEST_BASE_H_
+
+#include <cmath>
+#include <cstdint>
+#include <functional>
+#include <ios>
+#include <limits>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+#include "absl/random/random.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_fp_state.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "riscv//riscv_fp_host.h"
+#include "riscv//riscv_fp_info.h"
+#include "riscv//riscv_register.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+namespace test {
+
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::cheriot::CheriotState;
+using ::mpact::sim::cheriot::RiscVCheriotFPState;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::riscv::FPRoundingMode;
+using ::mpact::sim::riscv::RV64Register;
+using ::mpact::sim::riscv::RVFpRegister;
+using ::mpact::sim::riscv::ScopedFPRoundingMode;
+using ::mpact::sim::riscv::ScopedFPStatus;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+
+constexpr int kTestValueLength = 256;
+
+constexpr uint32_t kInstAddress = 0x1000;
+constexpr uint32_t kDataLoadAddress = 0x1'0000;
+constexpr uint32_t kDataStoreAddress = 0x2'0000;
+
+// Templated helper structs to provide information about floating point types.
+template <typename T>
+struct FPTypeInfo {
+  using IntType = T;
+  static const int kBitSize = 8 * sizeof(T);
+  static const int kExpSize = 0;
+  static const int kSigSize = 0;
+  static bool IsNaN(T value) { return false; }
+  static constexpr IntType kQNaN = 0;
+  static constexpr IntType kSNaN = 0;
+  static constexpr IntType kPosInf = std::numeric_limits<T>::max();
+  static constexpr IntType kNegInf = std::numeric_limits<T>::min();
+  static constexpr IntType kPosZero = 0;
+  static constexpr IntType kNegZero = 0;
+  static constexpr IntType kPosDenorm = 0;
+  static constexpr IntType kNegDenorm = 0;
+};
+
+template <>
+struct FPTypeInfo<float> {
+  using T = float;
+  using IntType = uint32_t;
+  static const int kExpBias = 127;
+  static const int kBitSize = sizeof(float) << 3;
+  static const int kExpSize = 8;
+  static const int kSigSize = kBitSize - kExpSize - 1;
+  static const IntType kExpMask = ((1ULL << kExpSize) - 1) << kSigSize;
+  static const IntType kSigMask = (1ULL << kSigSize) - 1;
+  static const IntType kQNaN = kExpMask | (1ULL << (kSigSize - 1)) | 1;
+  static const IntType kSNaN = kExpMask | 1;
+  static const IntType kPosInf = kExpMask;
+  static const IntType kNegInf = kExpMask | (1ULL << (kBitSize - 1));
+  static const IntType kPosZero = 0;
+  static const IntType kNegZero = 1ULL << (kBitSize - 1);
+  static const IntType kPosDenorm = 1ULL << (kSigSize - 2);
+  static const IntType kNegDenorm =
+      (1ULL << (kBitSize - 1)) | (1ULL << (kSigSize - 2));
+  static const IntType kCanonicalNaN = 0x7fc0'0000ULL;
+  static bool IsNaN(T value) { return std::isnan(value); }
+};
+
+template <>
+struct FPTypeInfo<double> {
+  using T = double;
+  using IntType = uint64_t;
+  static const int kExpBias = 1023;
+  static const int kBitSize = sizeof(double) << 3;
+  static const int kExpSize = 11;
+  static const int kSigSize = kBitSize - kExpSize - 1;
+  static const IntType kExpMask = ((1ULL << kExpSize) - 1) << kSigSize;
+  static const IntType kSigMask = (1ULL << kSigSize) - 1;
+  static const IntType kQNaN = kExpMask | (1ULL << (kSigSize - 1)) | 1;
+  static const IntType kSNaN = kExpMask | 1;
+  static const IntType kPosInf = kExpMask;
+  static const IntType kNegInf = kExpMask | (1ULL << (kBitSize - 1));
+  static const IntType kPosZero = 0;
+  static const IntType kNegZero = 1ULL << (kBitSize - 1);
+  static const IntType kPosDenorm = 1ULL << (kSigSize - 2);
+  static const IntType kNegDenorm =
+      (1ULL << (kBitSize - 1)) | (1ULL << (kSigSize - 2));
+  static const IntType kCanonicalNaN = 0x7ff8'0000'0000'0000ULL;
+  static bool IsNaN(T value) { return std::isnan(value); }
+};
+
+// Templated helper function for classifying fp numbers.
+template <typename T>
+typename FPTypeInfo<T>::IntType VfclassVHelper(T val) {
+  auto fp_class = fpclassify(val);
+  switch (fp_class) {
+    case FP_INFINITE:
+      return std::signbit(val) ? 1 : 1 << 7;
+    case FP_NAN: {
+      auto uint_val =
+          *reinterpret_cast<typename FPTypeInfo<T>::IntType *>(&val);
+      bool quiet_nan = (uint_val >> (FPTypeInfo<T>::kSigSize - 1)) & 1;
+      return quiet_nan ? 1 << 9 : 1 << 8;
+    }
+    case FP_ZERO:
+      return std::signbit(val) ? 1 << 3 : 1 << 4;
+    case FP_SUBNORMAL:
+      return std::signbit(val) ? 1 << 2 : 1 << 5;
+    case FP_NORMAL:
+      return std::signbit(val) ? 1 << 1 : 1 << 6;
+  }
+  return 0;
+}
+
+// These templated functions allow for comparison of values with a tolerance
+// given for floating point types. The tolerance is stated as the bit position
+// in the mantissa of the op, with 0 being the msb of the mantissa. If the
+// bit position is beyond the mantissa, a comparison of equal is performed.
+template <typename T>
+inline void FPCompare(T op, T reg, int, absl::string_view str) {
+  EXPECT_EQ(reg, op) << str;
+}
+
+template <>
+inline void FPCompare<float>(float op, float reg, int delta_position,
+                             absl::string_view str) {
+  using T = float;
+  using UInt = typename FPTypeInfo<T>::IntType;
+  UInt u_op = *reinterpret_cast<UInt *>(&op);
+  UInt u_reg = *reinterpret_cast<UInt *>(&reg);
+  if (!std::isnan(op) && !std::isinf(op) &&
+      delta_position < FPTypeInfo<T>::kSigSize) {
+    T delta;
+    UInt exp = FPTypeInfo<T>::kExpMask >> FPTypeInfo<T>::kSigSize;
+    if (exp > delta_position) {
+      exp -= delta_position;
+      UInt udelta = exp << FPTypeInfo<T>::kSigSize;
+      delta = *reinterpret_cast<T *>(&udelta);
+    } else {
+      // Becomes a denormal
+      int diff = delta_position - exp;
+      UInt udelta = 1ULL << (FPTypeInfo<T>::kSigSize - 1 - diff);
+      delta = *reinterpret_cast<T *>(&udelta);
+    }
+    EXPECT_THAT(reg, testing::NanSensitiveFloatNear(op, delta))
+        << str << "  op: " << std::hex << u_op << "  reg: " << std::hex
+        << u_reg;
+  } else {
+    EXPECT_THAT(reg, testing::NanSensitiveFloatEq(op))
+        << str << "  op: " << std::hex << u_op << "  reg: " << std::hex
+        << u_reg;
+  }
+}
+
+template <>
+inline void FPCompare<double>(double op, double reg, int delta_position,
+                              absl::string_view str) {
+  using T = double;
+  using UInt = typename FPTypeInfo<T>::IntType;
+  UInt u_op = *reinterpret_cast<UInt *>(&op);
+  UInt u_reg = *reinterpret_cast<UInt *>(&reg);
+  if (!std::isnan(op) && !std::isinf(op) &&
+      delta_position < FPTypeInfo<T>::kSigSize) {
+    T delta;
+    UInt exp = FPTypeInfo<T>::kExpMask >> FPTypeInfo<T>::kSigSize;
+    if (exp > delta_position) {
+      exp -= delta_position;
+      UInt udelta = exp << FPTypeInfo<T>::kSigSize;
+      delta = *reinterpret_cast<T *>(&udelta);
+    } else {
+      // Becomes a denormal
+      int diff = delta_position - exp;
+      UInt udelta = 1ULL << (FPTypeInfo<T>::kSigSize - 1 - diff);
+      delta = *reinterpret_cast<T *>(&udelta);
+    }
+    EXPECT_THAT(reg, testing::NanSensitiveDoubleNear(op, delta))
+        << str << "  op: " << std::hex << u_op << "  reg: " << std::hex
+        << u_reg;
+  } else {
+    EXPECT_THAT(reg, testing::NanSensitiveDoubleEq(op))
+        << str << "  op: " << std::hex << u_op << "  reg: " << std::hex
+        << u_reg;
+  }
+}
+
+template <typename FP>
+FP OptimizationBarrier(FP op) {
+  asm volatile("" : "+X"(op));
+  return op;
+}
+
+namespace internal {
+
+// These are predicates used in the following NaNBox function definitions, as
+// part of the enable_if construct.
+template <typename S, typename D>
+struct EqualSize {
+  static const bool value = sizeof(S) == sizeof(D) &&
+                            std::is_floating_point<S>::value &&
+                            std::is_integral<D>::value;
+};
+
+template <typename S, typename D>
+struct GreaterSize {
+  static const bool value =
+      sizeof(S) > sizeof(D) &&
+      std::is_floating_point<S>::value &&std::is_integral<D>::value;
+};
+
+template <typename S, typename D>
+struct LessSize {
+  static const bool value = sizeof(S) < sizeof(D) &&
+                            std::is_floating_point<S>::value &&
+                            std::is_integral<D>::value;
+};
+
+}  // namespace internal
+
+// Template functions to NaN box a floating point value when being assigned
+// to a wider register. The first version places a smaller floating point value
+// in a NaN box (all upper bits in the word are set to 1).
+
+// Enable_if is used to select the proper implementation for different S and D
+// type combinations. It uses the SFINAE (substitution failure is not an error)
+// "feature" of C++ to hide the implementation that don't match the predicate
+// from being resolved.
+
+template <typename S, typename D>
+inline typename std::enable_if<internal::LessSize<S, D>::value, D>::type NaNBox(
+    S value) {
+  using SInt = typename FPTypeInfo<S>::IntType;
+  SInt sval = *reinterpret_cast<SInt *>(&value);
+  D dval = (~static_cast<D>(0) << (sizeof(S) * 8)) | sval;
+  return *reinterpret_cast<D *>(&dval);
+}
+
+// This version does a straight copy - as the data types are the same size.
+template <typename S, typename D>
+inline typename std::enable_if<internal::EqualSize<S, D>::value, D>::type
+NaNBox(S value) {
+  return *reinterpret_cast<D *>(&value);
+}
+
+// Signal error if the register is smaller than the floating point value.
+template <typename S, typename D>
+inline typename std::enable_if<internal::GreaterSize<S, D>::value, D>::type
+NaNBox(S value) {
+  // No return statement, so error will be reported.
+}
+
+class RiscVFPInstructionTestBase : public testing::Test {
+ public:
+  RiscVFPInstructionTestBase() {
+    memory_ = new TaggedFlatDemandMemory(8);
+    state_ = new CheriotState("test", memory_);
+    rv_fp_ = new RiscVCheriotFPState(state_);
+    state_->set_rv_fp(rv_fp_);
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    child_instruction_ = new Instruction(kInstAddress, state_);
+    child_instruction_->set_size(4);
+    // Initialize a portion of memory with a known pattern.
+    auto *db = state_->db_factory()->Allocate(8192);
+    auto span = db->Get<uint8_t>();
+    for (int i = 0; i < 8192; i++) {
+      span[i] = i & 0xff;
+    }
+    memory_->Store(kDataLoadAddress - 4096, db);
+    db->DecRef();
+    for (int i = 1; i < 32; i++) {
+      creg_[i] =
+          state_->GetRegister<CheriotRegister>(absl::StrCat("c", i)).first;
+    }
+    for (int i = 1; i < 32; i++) {
+      freg_[i] = state_->GetRegister<RVFpRegister>(absl::StrCat("f", i)).first;
+    }
+    for (int i = 1; i < 32; i++) {
+      dreg_[i] = state_->GetRegister<RVFpRegister>(absl::StrCat("d", i)).first;
+    }
+  }
+  ~RiscVFPInstructionTestBase() override {
+    delete rv_fp_;
+    rv_fp_ = nullptr;
+    state_->set_rv_fp(nullptr);
+    delete state_;
+    state_ = nullptr;
+    if (instruction_ != nullptr) instruction_->DecRef();
+    instruction_ = nullptr;
+    if (child_instruction_ != nullptr) child_instruction_->DecRef();
+    child_instruction_ = nullptr;
+    delete memory_;
+    memory_ = nullptr;
+  }
+
+  // Clear the instruction instance and allocate a new one.
+  void ResetInstruction() {
+    instruction_->DecRef();
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+  }
+
+  // Creates source and destination scalar register operands for the registers
+  // named in the two vectors and append them to the given instruction.
+  void AppendRegisterOperands(Instruction *inst,
+                              const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  // Creates source and destination scalar register operands for the registers
+  // named in the two vectors and append them to the default instruction.
+  void AppendRegisterOperands(const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    AppendRegisterOperands(instruction_, sources, destinations);
+  }
+
+  // named register and sets it to the corresponding value.
+  template <typename T, typename RegisterType = CheriotRegister>
+  void SetRegisterValues(
+      const std::vector<std::tuple<std::string, const T>> &values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<RegisterType>(reg_name).first;
+      auto *db =
+          state_->db_factory()->Allocate<typename RegisterType::ValueType>(1);
+      db->template Set<T>(0, value);
+      reg->SetDataBuffer(db);
+      db->DecRef();
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction *inst,
+                           Instruction::SemanticFunction fcn) {
+    inst->set_semantic_function(fcn);
+  }
+
+  // Initializes the semantic function for the default instruction.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+
+  // Sets the default child instruction as the child of the default instruction.
+  void SetChildInstruction() { instruction_->AppendChild(child_instruction_); }
+
+  // Initializes the semantic function for the default child instruction.
+  void SetChildSemanticFunction(Instruction::SemanticFunction fcn) {
+    child_instruction_->set_semantic_function(fcn);
+  }
+
+  // Construct a random FP value by separately generating integer values for
+  // sign, exponent and mantissa.
+  template <typename T>
+  T RandomFPValue() {
+    using UInt = typename FPTypeInfo<T>::IntType;
+    UInt sign = absl::Uniform(absl::IntervalClosed, bitgen_, 0ULL, 1ULL);
+    UInt exp = absl::Uniform(absl::IntervalClosedOpen, bitgen_, 0ULL,
+                             1ULL << FPTypeInfo<T>::kExpSize);
+    UInt sig = absl::Uniform(absl::IntervalClosedOpen, bitgen_, 0ULL,
+                             1ULL << FPTypeInfo<T>::kSigSize);
+    UInt value = (sign & 1) << (FPTypeInfo<T>::kBitSize - 1) |
+                 (exp << FPTypeInfo<T>::kSigSize) | sig;
+    T val = *reinterpret_cast<T *>(&value);
+    return val;
+  }
+
+  // This method uses random values for each field in the fp number.
+  template <typename T>
+  void FillArrayWithRandomFPValues(absl::Span<T> span) {
+    for (auto &val : span) {
+      val = RandomFPValue<T>();
+    }
+  }
+
+  template <typename R, typename LHS>
+  void UnaryOpFPTestHelper(absl::string_view name, Instruction *inst,
+                           absl::Span<const absl::string_view> reg_prefixes,
+                           int delta_position,
+                           std::function<R(LHS)> operation) {
+    using LhsRegisterType = RVFpRegister;
+    using DestRegisterType = RVFpRegister;
+    LHS lhs_values[kTestValueLength];
+    auto lhs_span = absl::Span<LHS>(lhs_values);
+    const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1);
+    const std::string kRdName = absl::StrCat(reg_prefixes[1], 5);
+    // This is used for the rounding mode operand.
+    const std::string kRmName = absl::StrCat("x", 10);
+    AppendRegisterOperands({kR1Name, kRmName}, {kRdName});
+    FillArrayWithRandomFPValues<LHS>(lhs_span);
+    using LhsInt = typename FPTypeInfo<LHS>::IntType;
+    *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
+    *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
+    for (int i = 0; i < kTestValueLength; i++) {
+      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+
+      for (int rm : {0, 1, 2, 3, 4}) {
+        rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
+        SetRegisterValues<int, CheriotRegister>({{kRmName, rm}});
+        SetRegisterValues<R, DestRegisterType>({{kRdName, 0}});
+
+        inst->Execute(nullptr);
+
+        R op_val;
+        {
+          ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface());
+          op_val = operation(lhs_span[i]);
+        }
+        auto reg_val = state_->GetRegister<DestRegisterType>(kRdName)
+                           .first->data_buffer()
+                           ->template Get<R>(0);
+        FPCompare<R>(op_val, reg_val, delta_position,
+                     absl::StrCat(name, "  ", i, ": ", lhs_span[i]));
+      }
+    }
+  }
+
+  // Tester for unary instructions that produce an exception flag value.
+  template <typename R, typename LHS>
+  void UnaryOpWithFflagsFPTestHelper(
+      absl::string_view name, Instruction *inst,
+      absl::Span<const absl::string_view> reg_prefixes, int delta_position,
+      std::function<std::tuple<R, uint32_t>(LHS)> operation) {
+    using LhsRegisterType = RVFpRegister;
+    using DestRegisterType = RVFpRegister;
+    using LhsInt = typename FPTypeInfo<LHS>::IntType;
+    using RInt = typename FPTypeInfo<R>::IntType;
+    LHS lhs_values[kTestValueLength];
+    auto lhs_span = absl::Span<LHS>(lhs_values);
+    const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1);
+    const std::string kRdName = absl::StrCat(reg_prefixes[1], 5);
+    // This is used for the rounding mode operand.
+    const std::string kRmName = absl::StrCat("x", 10);
+    AppendRegisterOperands({kR1Name, kRmName}, {kRdName});
+    auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+    instruction_->AppendDestination(flag_op);
+    FillArrayWithRandomFPValues<LHS>(lhs_span);
+    *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
+    *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
+    for (int i = 0; i < kTestValueLength; i++) {
+      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+
+      for (int rm : {0, 1, 2, 3, 4}) {
+        rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
+        rv_fp_->fflags()->Write(static_cast<uint32_t>(0));
+        SetRegisterValues<int, CheriotRegister>({{kRmName, rm}, {}});
+        SetRegisterValues<R, DestRegisterType>({{kRdName, 0}});
+
+        inst->Execute(nullptr);
+        auto fflags = rv_fp_->fflags()->GetUint32();
+
+        R op_val;
+        uint32_t flag;
+        {
+          ScopedFPRoundingMode scoped_rm(rv_fp_->host_fp_interface(), rm);
+          std::tie(op_val, flag) = operation(lhs_span[i]);
+        }
+
+        auto reg_val = state_->GetRegister<DestRegisterType>(kRdName)
+                           .first->data_buffer()
+                           ->template Get<R>(0);
+        FPCompare<R>(
+            op_val, reg_val, delta_position,
+            absl::StrCat(name, "  ", i, ": ", lhs_span[i], " rm: ", rm));
+        auto lhs_uint = *reinterpret_cast<LhsInt *>(&lhs_span[i]);
+        auto op_val_uint = *reinterpret_cast<RInt *>(&op_val);
+        EXPECT_EQ(flag, fflags)
+            << name << "(" << lhs_span[i] << ")  " << std::hex << name << "(0x"
+            << lhs_uint << ") == " << op_val << std::hex << "  0x"
+            << op_val_uint << " rm: " << rm;
+      }
+    }
+  }
+
+  // Test helper for binary fp instructions.
+  template <typename R, typename LHS, typename RHS>
+  void BinaryOpFPTestHelper(absl::string_view name, Instruction *inst,
+                            absl::Span<const absl::string_view> reg_prefixes,
+                            int delta_position,
+                            std::function<R(LHS, RHS)> operation) {
+    using LhsRegisterType = RVFpRegister;
+    using RhsRegisterType = RVFpRegister;
+    using DestRegisterType = RVFpRegister;
+    LHS lhs_values[kTestValueLength];
+    RHS rhs_values[kTestValueLength];
+    auto lhs_span = absl::Span<LHS>(lhs_values);
+    auto rhs_span = absl::Span<RHS>(rhs_values);
+    const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1);
+    const std::string kR2Name = absl::StrCat(reg_prefixes[1], 2);
+    const std::string kRdName = absl::StrCat(reg_prefixes[2], 5);
+    // This is used for the rounding mode operand.
+    const std::string kRmName = absl::StrCat("x", 10);
+    AppendRegisterOperands({kR1Name, kR2Name, kRmName}, {kRdName});
+    auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+    instruction_->AppendDestination(flag_op);
+    FillArrayWithRandomFPValues<LHS>(lhs_span);
+    FillArrayWithRandomFPValues<RHS>(rhs_span);
+    using LhsInt = typename FPTypeInfo<LHS>::IntType;
+    *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
+    *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
+    for (int i = 0; i < kTestValueLength; i++) {
+      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}});
+
+      for (int rm : {0, 1, 2, 3, 4}) {
+        rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
+        SetRegisterValues<int, CheriotRegister>({{kRmName, rm}});
+        SetRegisterValues<R, DestRegisterType>({{kRdName, 0}});
+
+        inst->Execute(nullptr);
+
+        R op_val;
+        {
+          ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface());
+          op_val = operation(lhs_span[i], rhs_span[i]);
+        }
+        auto reg_val = state_->GetRegister<DestRegisterType>(kRdName)
+                           .first->data_buffer()
+                           ->template Get<R>(0);
+        FPCompare<R>(op_val, reg_val, delta_position,
+                     absl::StrCat(name, "  ", i, ": ", lhs_span[i], "  ",
+                                  rhs_span[i], " rm: ", rm));
+      }
+      if (HasFailure()) return;
+    }
+  }
+
+  // Test helper for binary instructions that also produce an exception flag
+  // value.
+  template <typename R, typename LHS, typename RHS>
+  void BinaryOpWithFflagsFPTestHelper(
+      absl::string_view name, Instruction *inst,
+      absl::Span<const absl::string_view> reg_prefixes, int delta_position,
+      std::function<std::tuple<R, uint32_t>(LHS, RHS)> operation) {
+    using LhsRegisterType = RVFpRegister;
+    using RhsRegisterType = RVFpRegister;
+    using DestRegisterType = RVFpRegister;
+    using LhsUInt = typename FPTypeInfo<LHS>::IntType;
+    using RhsUInt = typename FPTypeInfo<RHS>::IntType;
+    LHS lhs_values[kTestValueLength];
+    RHS rhs_values[kTestValueLength];
+    auto lhs_span = absl::Span<LHS>(lhs_values);
+    auto rhs_span = absl::Span<RHS>(rhs_values);
+    const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1);
+    const std::string kR2Name = absl::StrCat(reg_prefixes[1], 2);
+    const std::string kRdName = absl::StrCat(reg_prefixes[2], 5);
+    // This is used for the rounding mode operand.
+    const std::string kRmName = absl::StrCat("x", 10);
+    AppendRegisterOperands({kR1Name, kR2Name, kRmName}, {kRdName});
+    auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+    instruction_->AppendDestination(flag_op);
+    FillArrayWithRandomFPValues<LHS>(lhs_span);
+    FillArrayWithRandomFPValues<RHS>(rhs_span);
+    using LhsInt = typename FPTypeInfo<LHS>::IntType;
+    *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
+    *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
+    for (int i = 0; i < kTestValueLength; i++) {
+      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}});
+
+      for (int rm : {0, 1, 2, 3, 4}) {
+        rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
+        rv_fp_->fflags()->Write(static_cast<uint32_t>(0));
+        SetRegisterValues<int, CheriotRegister>({{kRmName, rm}, {}});
+        SetRegisterValues<R, DestRegisterType>({{kRdName, 0}});
+
+        inst->Execute(nullptr);
+
+        R op_val;
+        uint32_t flag;
+        {
+          ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface());
+          std::tie(op_val, flag) = operation(lhs_span[i], rhs_span[i]);
+        }
+        auto reg_val = state_->GetRegister<DestRegisterType>(kRdName)
+                           .first->data_buffer()
+                           ->template Get<R>(0);
+        FPCompare<R>(
+            op_val, reg_val, delta_position,
+            absl::StrCat(name, "  ", i, ": ", lhs_span[i], "  ", rhs_span[i]));
+        auto lhs_uint = *reinterpret_cast<LhsUInt *>(&lhs_span[i]);
+        auto rhs_uint = *reinterpret_cast<RhsUInt *>(&rhs_span[i]);
+        auto fflags = rv_fp_->fflags()->GetUint32();
+        EXPECT_EQ(flag, fflags)
+            << std::hex << name << "(" << lhs_uint << ", " << rhs_uint << ")";
+      }
+    }
+  }
+
+  template <typename R, typename LHS, typename MHS, typename RHS>
+  void TernaryOpFPTestHelper(absl::string_view name, Instruction *inst,
+                             absl::Span<const absl::string_view> reg_prefixes,
+                             int delta_position,
+                             std::function<R(LHS, MHS, RHS)> operation) {
+    using LhsRegisterType = RVFpRegister;
+    using MhsRegisterType = RVFpRegister;
+    using RhsRegisterType = RVFpRegister;
+    using DestRegisterType = RVFpRegister;
+    LHS lhs_values[kTestValueLength];
+    MHS mhs_values[kTestValueLength];
+    RHS rhs_values[kTestValueLength];
+    auto lhs_span = absl::Span<LHS>(lhs_values);
+    auto mhs_span = absl::Span<MHS>(mhs_values);
+    auto rhs_span = absl::Span<RHS>(rhs_values);
+    const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1);
+    const std::string kR2Name = absl::StrCat(reg_prefixes[1], 2);
+    const std::string kR3Name = absl::StrCat(reg_prefixes[2], 3);
+    const std::string kRdName = absl::StrCat(reg_prefixes[3], 5);
+    // This is used for the rounding mode operand.
+    const std::string kRmName = absl::StrCat("x", 10);
+    AppendRegisterOperands({kR1Name, kR2Name, kR3Name, kRmName}, {kRdName});
+    FillArrayWithRandomFPValues<LHS>(lhs_span);
+    FillArrayWithRandomFPValues<MHS>(mhs_span);
+    FillArrayWithRandomFPValues<RHS>(rhs_span);
+    using LhsInt = typename FPTypeInfo<LHS>::IntType;
+    *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN;
+    *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf;
+    *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero;
+    *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
+    *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
+    for (int i = 0; i < kTestValueLength; i++) {
+      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}});
+      SetRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}});
+
+      for (int rm : {0, 1, 2, 3, 4}) {
+        rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
+        SetRegisterValues<int, CheriotRegister>({{kRmName, rm}});
+        SetRegisterValues<R, DestRegisterType>({{kRdName, 0}});
+
+        inst->Execute(nullptr);
+
+        R op_val;
+        {
+          ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface());
+          op_val = operation(lhs_span[i], mhs_span[i], rhs_span[i]);
+        }
+        auto reg_val = state_->GetRegister<DestRegisterType>(kRdName)
+                           .first->data_buffer()
+                           ->template Get<R>(0);
+        FPCompare<R>(
+            op_val, reg_val, delta_position,
+            absl::StrCat(name, "  ", i, ": ", lhs_span[i], "  ", rhs_span[i]));
+      }
+    }
+  }
+
+  absl::Span<CheriotRegister *> creg() {
+    return absl::Span<CheriotRegister *>(creg_);
+  }
+
+  absl::Span<RVFpRegister *> freg() {
+    return absl::Span<RVFpRegister *>(freg_);
+  }
+
+  absl::Span<RV64Register *> dreg() {
+    return absl::Span<RV64Register *>(dreg_);
+  }
+  absl::BitGen &bitgen() { return bitgen_; }
+  Instruction *instruction() { return instruction_; }
+
+  template <typename T>
+  T RoundToInteger(T val) {
+    using FromUint = typename FPTypeInfo<T>::IntType;
+    auto constexpr kBias = FPTypeInfo<T>::kExpBias;
+    auto constexpr kExpMask = FPTypeInfo<T>::kExpMask;
+    auto constexpr kSigSize = FPTypeInfo<T>::kSigSize;
+    auto constexpr kSigMask = FPTypeInfo<T>::kSigMask;
+    FromUint val_u = *reinterpret_cast<FromUint *>(&val);
+    FromUint exp = kExpMask & val_u;
+    FromUint sign = val_u & (1ULL << (FPTypeInfo<T>::kBitSize - 1));
+    int exp_value = exp >> kSigSize;
+    FromUint sig = kSigMask & val_u;
+    // Turn the value into a denormal.
+    constexpr FromUint hidden = 1ULL << (kSigSize - 1);
+    FromUint tmp_u = sign | ((exp != 0) ? hidden : 0ULL) | (sig >> 1);
+    T tmp = *reinterpret_cast<T *>(&tmp_u);
+    if ((exp_value >= kBias) && (exp_value - kBias + 1 < kSigSize)) {
+      // Divide so that only the bits we care about are left in the significand.
+      int shift = kBias + kSigSize - exp_value - 1;
+      FromUint div_exp = shift + kBias;
+      FromUint div_u = div_exp << kSigSize;
+      T div = *reinterpret_cast<T *>(&div_u);
+      tmp /= div;
+      // Convert back to normalized number, by using the original sign
+      // and exponent, and the normalized and significand from the division.
+      tmp_u = *reinterpret_cast<FromUint *>(&tmp);
+      val_u = sign | exp | ((tmp_u << (shift + 1)) & kSigMask);
+      val = *reinterpret_cast<T *>(&val_u);
+    }
+    return val;
+  }
+
+ protected:
+  CheriotRegister *creg_[32];
+  RV64Register *dreg_[32];
+  RVFpRegister *freg_[32];
+  CheriotState *state_;
+  Instruction *instruction_;
+  Instruction *child_instruction_;
+  TaggedFlatDemandMemory *memory_;
+  RiscVCheriotFPState *rv_fp_;
+  absl::BitGen bitgen_;
+};
+
+}  // namespace test
+}  // namespace cheriot
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_CHERIOT___TEST_RISCV_CHERIOT_FP_TEST_BASE_H_
diff --git a/cheriot/test/riscv_cheriot_i_instructions_test.cc b/cheriot/test/riscv_cheriot_i_instructions_test.cc
new file mode 100644
index 0000000..93dbd41
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_i_instructions_test.cc
@@ -0,0 +1,912 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_i_instructions.h"
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+#include "absl/types/span.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+// This file contains tests for individual RiscV32I instructions.
+
+namespace {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::cheriot::CheriotState;
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::Instruction;
+using CH_EC = ::mpact::sim::cheriot::ExceptionCode;
+using PB = CheriotRegister::PermissionBits;
+
+constexpr char kC1[] = "c1";
+constexpr char kC2[] = "c2";
+constexpr char kC3[] = "c3";
+
+constexpr int kC1Num = 1;
+
+constexpr uint32_t kInstAddress = 0x2468;
+constexpr int32_t kVal1 = 0x1234;
+constexpr int32_t kVal2 = -0x5678;
+constexpr uint32_t kOffset = 0x248;
+constexpr uint32_t kBranchTarget = kInstAddress + kOffset;
+constexpr uint32_t kMemAddress = 0x1000;
+constexpr uint32_t kMemValue = 0x81'92'a3'b4;
+constexpr uint32_t kShift = 6;
+
+// The test fixture allocates a machine state object and an instruction object.
+// It also contains convenience methods for interacting with the instruction
+// object in a more short hand form.
+class RVCheriotIInstructionTest : public testing::Test {
+ public:
+  RVCheriotIInstructionTest() {
+    state_ = new CheriotState("test");
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    creg_1_ = state_->GetRegister<CheriotRegister>(kC1).first;
+    creg_2_ = state_->GetRegister<CheriotRegister>(kC2).first;
+    creg_3_ = state_->GetRegister<CheriotRegister>(kC3).first;
+    state_->set_on_trap([this](bool is_interrupt, uint64_t trap_value,
+                               uint64_t exception_code, uint64_t epc,
+                               const Instruction *inst) {
+      return TrapHandler(is_interrupt, trap_value, exception_code, epc, inst);
+    });
+  }
+
+  ~RVCheriotIInstructionTest() override {
+    delete state_;
+    delete instruction_;
+  }
+
+  // Appends the source and destination operands for the register names
+  // given in the two vectors.
+  void AppendRegisterOperands(Instruction *inst,
+                              absl::Span<const std::string> sources,
+                              absl::Span<const std::string> destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  void AppendRegisterOperands(const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    AppendRegisterOperands(instruction_, sources, destinations);
+  }
+
+  // Appends immediate source operands with the given values.
+  template <typename T>
+  void AppendImmediateOperands(const std::vector<T> &values) {
+    for (auto value : values) {
+      auto *src = new ImmediateOperand<T>(value);
+      instruction_->AppendSource(src);
+    }
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      reg->set_address(value);
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+
+  // Returns the value of the named register.
+  template <typename T>
+  T GetRegisterValue(std::string_view reg_name) {
+    auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+    return reg->address();
+  }
+  // Gets called on a trap.
+  bool TrapHandler(bool is_interrupt, uint64_t trap_value,
+                   uint64_t exception_code, uint64_t epc,
+                   const Instruction *inst);
+
+  CheriotState *state_;
+  Instruction *instruction_;
+  CheriotRegister *creg_1_;
+  CheriotRegister *creg_2_;
+  CheriotRegister *creg_3_;
+  bool trap_taken_ = false;
+  bool trap_is_interrupt_ = false;
+  uint64_t trap_value_ = 0;
+  uint64_t trap_exception_code_ = 0;
+  uint64_t trap_epc_ = 0;
+  const Instruction *trap_inst_ = nullptr;
+};
+
+bool RVCheriotIInstructionTest::TrapHandler(bool is_interrupt,
+                                            uint64_t trap_value,
+                                            uint64_t exception_code,
+                                            uint64_t epc,
+                                            const Instruction *inst) {
+  trap_taken_ = true;
+  trap_is_interrupt_ = is_interrupt;
+  trap_value_ = trap_value;
+  trap_exception_code_ = exception_code;
+  trap_epc_ = epc;
+  trap_inst_ = inst;
+  return true;
+}
+// Almost all the tests below follow the same pattern. There are two phases.
+// In the first register and or immediate operands are added to the instruction,
+// and the instruction semantic function under test is bound to the instruction.
+// In the second phase, the values of register operands are assigned, the
+// instruction is executed, and the value(s) of the output register(s) is (are)
+// compared against the expected value. The second phase may be repeated for
+// different combinations of register operand values.
+
+TEST_F(RVCheriotIInstructionTest, RV32IAdd) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIAdd);
+
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal1 + kVal2);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISub) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISub);
+
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal1 - kVal2);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISlt) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISlt);
+
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal1 < kVal2);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), 0);
+  EXPECT_EQ(creg_3_->base(), 0);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+
+  SetRegisterValues<int32_t>({{kC1, kVal2}, {kC2, kVal1}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal2 < kVal1);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), 0);
+  EXPECT_EQ(creg_3_->base(), 0);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISltu) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISltu);
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3),
+            static_cast<uint32_t>(kVal1) < static_cast<uint32_t>(kVal2));
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), 0);
+  EXPECT_EQ(creg_3_->base(), 0);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+
+  SetRegisterValues<int32_t>({{kC1, kVal2}, {kC2, kVal1}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3),
+            static_cast<uint32_t>(kVal2) < static_cast<uint32_t>(kVal1));
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), 0);
+  EXPECT_EQ(creg_3_->base(), 0);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IAnd) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIAnd);
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal1 & kVal2);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), 0);
+  EXPECT_EQ(creg_3_->base(), 0);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IOr) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIOr);
+
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal1 | kVal2);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IXor) {
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIXor);
+  SetRegisterValues<int32_t>({{kC1, kVal1}, {kC2, kVal2}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kVal1 ^ kVal2);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISll) {
+  AppendRegisterOperands({kC1}, {kC3});
+  AppendImmediateOperands<uint32_t>({kShift});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISll);
+
+  SetRegisterValues<uint32_t>({{kC1, kMemValue}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kMemValue << kShift);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISrl) {
+  AppendRegisterOperands({kC1}, {kC3});
+  AppendImmediateOperands<uint32_t>({kShift});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISrl);
+
+  SetRegisterValues<uint32_t>({{kC1, kMemValue}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kMemValue >> kShift);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISra) {
+  AppendRegisterOperands({kC1}, {kC3});
+  AppendImmediateOperands<uint32_t>({kShift});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISra);
+  SetRegisterValues<uint32_t>({{kC1, kMemValue}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3),
+            static_cast<int32_t>(kMemValue) >> kShift);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILui) {
+  AppendRegisterOperands({}, {kC3});
+  AppendImmediateOperands<uint32_t>({kMemValue});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILui);
+
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), kMemValue & ~0xfff);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32INop) {
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVINop);
+  // Verify that the semantic functions executes without any operands.
+  instruction_->Execute(nullptr);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IBeq) {
+  AppendRegisterOperands({kC1, kC2}, {});
+  AppendImmediateOperands<int32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIBeq);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(state_->pcc()->address(), kBranchTarget);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal2}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(state_->pcc()->address(), kInstAddress);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IBne) {
+  AppendRegisterOperands({kC1, kC2}, {});
+  AppendImmediateOperands<int32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIBne);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal2}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(state_->pcc()->address(), kBranchTarget);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(state_->pcc()->address(), kInstAddress);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IBlt) {
+  AppendRegisterOperands({kC1, kC2}, {});
+  AppendImmediateOperands<int32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIBlt);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kInstAddress);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal2}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kInstAddress);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal2}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kBranchTarget);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IBltu) {
+  AppendRegisterOperands({kC1, kC2}, {});
+  AppendImmediateOperands<int32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIBltu);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kInstAddress);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal2}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kBranchTarget);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal2}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kInstAddress);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IBge) {
+  AppendRegisterOperands({kC1, kC2}, {});
+  AppendImmediateOperands<int32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIBge);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kBranchTarget);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal2}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kBranchTarget);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal2}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kInstAddress);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IBgeu) {
+  AppendRegisterOperands({kC1, kC2}, {});
+  AppendImmediateOperands<int32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVIBgeu);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kBranchTarget);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal1}, {kC2, kVal2}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kInstAddress);
+
+  SetRegisterValues<int32_t>(
+      {{kC1, kVal2}, {kC2, kVal1}, {CheriotState::kPcName, kInstAddress}});
+  instruction_->Execute(nullptr);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(CheriotState::kPcName), kBranchTarget);
+}
+
+// Load instructions require additional setup. First the memory locations have
+// to be initialized. Second, all load instructions use a child instruction for
+// the value writeback to the destination register.
+
+TEST_F(RVCheriotIInstructionTest, RV32ILw) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILw);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILwChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kC3), kMemValue);
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILwTagViolation) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILw);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILwChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->Invalidate();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_NE(GetRegisterValue<uint32_t>(kC3), kMemValue);
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExTagViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILwSealViolation) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILw);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILwChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  (void)creg_1_->Seal(*state_->sealing_root(), 9);
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_NE(GetRegisterValue<uint32_t>(kC3), kMemValue);
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExSealViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILwPermitLoadViolation) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILw);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILwChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  creg_1_->ClearPermissions(PB::kPermitLoad);
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_NE(GetRegisterValue<uint32_t>(kC3), kMemValue);
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExPermitLoadViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILwBoundsViolation) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILw);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILwChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_1_->SetBounds(kMemAddress, kOffset - 0x100);
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_NE(GetRegisterValue<uint32_t>(kC3), kMemValue);
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExBoundsViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILh) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILh);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILhChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), static_cast<int16_t>(kMemValue));
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILhu) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILhu);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILhuChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kC3), static_cast<uint16_t>(kMemValue));
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILb) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILb);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILbChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+  EXPECT_EQ(GetRegisterValue<int32_t>(kC3), static_cast<int8_t>(kMemValue));
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ILbu) {
+  // Initialize memory.
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  db->Set<uint32_t>(0, kMemValue);
+  state_->StoreMemory(instruction_, kMemAddress + kOffset, db);
+  db->DecRef();
+
+  // Initialize instruction.
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVILbu);
+  auto *child = new Instruction(state_);
+  child->set_semantic_function(&::mpact::sim::cheriot::RiscVILbuChild);
+  AppendRegisterOperands(child, {}, {kC3});
+  instruction_->AppendChild(child);
+  child->DecRef();
+
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, 0}});
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kC3), static_cast<uint8_t>(kMemValue));
+  EXPECT_FALSE(creg_3_->tag());
+  EXPECT_EQ(creg_3_->top(), 0);
+  EXPECT_EQ(creg_3_->base(), 0);
+  EXPECT_EQ(creg_3_->permissions(), 0);
+  EXPECT_EQ(creg_3_->object_type(), 0);
+}
+
+// Store instructions are similar to the ALU instructions, except that
+// additional code is added after executing the instruction to fetch the value
+// stored to memory.
+TEST_F(RVCheriotIInstructionTest, RV32ISw) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISw);
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_EQ(db->Get<uint32_t>(0), kMemValue);
+  db->DecRef();
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISwTagViolation) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISw);
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  creg_1_->Invalidate();
+  instruction_->Execute(nullptr);
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_NE(db->Get<uint32_t>(0), kMemValue);
+  db->DecRef();
+
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExTagViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISwSealViolation) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISw);
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  (void)creg_1_->Seal(*state_->sealing_root(), 9);
+  instruction_->Execute(nullptr);
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_NE(db->Get<uint32_t>(0), kMemValue);
+  db->DecRef();
+
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExSealViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISwPermitLoadViolation) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISw);
+  creg_1_->ResetMemoryRoot();
+  creg_1_->ClearPermissions(PB::kPermitStore);
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  instruction_->Execute(nullptr);
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_NE(db->Get<uint32_t>(0), kMemValue);
+  db->DecRef();
+
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExPermitStoreViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISwBoundsViolation) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISw);
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  creg_1_->SetBounds(kMemAddress, kOffset - 0x100);
+  creg_3_->ResetMemoryRoot();
+  instruction_->Execute(nullptr);
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_NE(db->Get<uint32_t>(0), kMemValue);
+  db->DecRef();
+
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_value_, (kC1Num << 5) | *CH_EC::kCapExBoundsViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_inst_, instruction_);
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISh) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISh);
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_EQ(db->Get<uint32_t>(0), static_cast<uint16_t>(kMemValue));
+  db->DecRef();
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32ISb) {
+  AppendRegisterOperands({kC1}, {});
+  AppendImmediateOperands<uint32_t>({kOffset});
+  AppendRegisterOperands({kC3}, {});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVISb);
+  creg_1_->ResetMemoryRoot();
+  SetRegisterValues<uint32_t>({{kC1, kMemAddress}, {kC3, kMemValue}});
+  instruction_->Execute(nullptr);
+  EXPECT_FALSE(trap_taken_);
+
+  auto *db = state_->db_factory()->Allocate<uint32_t>(1);
+  state_->LoadMemory(instruction_, kMemAddress + kOffset, db, nullptr, nullptr);
+  EXPECT_EQ(db->Get<uint32_t>(0), static_cast<uint8_t>(kMemValue));
+  db->DecRef();
+}
+
+// The following instructions aren't tested yet, as the RV32I state doesn't
+// implement these instructions beyond interface stubs.
+
+TEST_F(RVCheriotIInstructionTest, RV32IFence) {
+  // TODO: implement test once the RiscVState handles the call.
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IEcall) {
+  // TODO: implement test once the RiscVState handles the call.
+}
+
+TEST_F(RVCheriotIInstructionTest, RV32IEbreak) {
+  // TODO: implement test once the RiscVState handles the call.
+}
+
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_instructions_test.cc b/cheriot/test/riscv_cheriot_instructions_test.cc
new file mode 100644
index 0000000..e43d550
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_instructions_test.cc
@@ -0,0 +1,1620 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_instructions.h"
+
+#include <array>
+#include <cstdint>
+#include <ios>
+#include <limits>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "absl/random/random.h"
+#include "absl/strings/str_format.h"
+#include "absl/types/span.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "riscv//riscv_state.h"
+
+// This file contains the unit tests for the RiscV CHERIoT capability specific
+// instructions.
+
+namespace {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
+using ::mpact::sim::cheriot::CheriotRegister;
+using PB = CheriotRegister::PermissionBits;
+using OT = CheriotRegister::ObjectType;
+using ::mpact::sim::cheriot::CheriotState;
+using CH_EC = ::mpact::sim::cheriot::ExceptionCode;
+using RV_EC = ::mpact::sim::riscv::ExceptionCode;
+using ISA = ::mpact::sim::riscv::IsaExtension;
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+// Instruction semantic functions.
+using ::mpact::sim::cheriot::CheriotAuicap;
+using ::mpact::sim::cheriot::CheriotCAndPerm;
+using ::mpact::sim::cheriot::CheriotCClearTag;
+using ::mpact::sim::cheriot::CheriotCGetAddr;
+using ::mpact::sim::cheriot::CheriotCGetBase;
+using ::mpact::sim::cheriot::CheriotCGetHigh;
+using ::mpact::sim::cheriot::CheriotCGetLen;
+using ::mpact::sim::cheriot::CheriotCGetPerm;
+using ::mpact::sim::cheriot::CheriotCGetTag;
+using ::mpact::sim::cheriot::CheriotCGetType;
+using ::mpact::sim::cheriot::CheriotCIncAddr;
+using ::mpact::sim::cheriot::CheriotCJal;
+using ::mpact::sim::cheriot::CheriotCJalr;
+using ::mpact::sim::cheriot::CheriotCLc;
+using ::mpact::sim::cheriot::CheriotCLcChild;
+using ::mpact::sim::cheriot::CheriotCMove;
+using ::mpact::sim::cheriot::CheriotCRepresentableAlignmentMask;
+using ::mpact::sim::cheriot::CheriotCRoundRepresentableLength;
+using ::mpact::sim::cheriot::CheriotCSc;
+using ::mpact::sim::cheriot::CheriotCSeal;
+using ::mpact::sim::cheriot::CheriotCSetAddr;
+using ::mpact::sim::cheriot::CheriotCSetBounds;
+using ::mpact::sim::cheriot::CheriotCSetBoundsExact;
+using ::mpact::sim::cheriot::CheriotCSetEqualExact;
+using ::mpact::sim::cheriot::CheriotCSetHigh;
+using ::mpact::sim::cheriot::CheriotCSpecialR;
+using ::mpact::sim::cheriot::CheriotCSpecialRW;
+using ::mpact::sim::cheriot::CheriotCSub;
+using ::mpact::sim::cheriot::CheriotCTestSubset;
+using ::mpact::sim::cheriot::CheriotCUnseal;
+// Register name definitions.
+constexpr char kC1[] = "c1";
+constexpr char kC2[] = "c2";
+constexpr char kC3[] = "c3";
+constexpr char kC4[] = "c4";
+// Register number definitions.
+constexpr int kC1Num = 1;
+constexpr int kPccNum = 0b1'00000;
+
+constexpr uint32_t kInstAddress = 0x2468;
+constexpr uint32_t kMemAddress = 0x1000;
+
+constexpr int kDataSeal10 = 10;
+constexpr int kInstSizeNormal = 4;
+
+// Test fixture.
+class RiscVCheriotInstructionsTest : public ::testing::Test {
+ protected:
+  static constexpr int kCapabilityGranule = 8;
+
+  RiscVCheriotInstructionsTest() {
+    memory_ = new TaggedFlatDemandMemory(kCapabilityGranule);
+    state_ = new CheriotState("test_state", memory_);
+    ResetInstruction(kInstSizeNormal);
+    for (auto &[reg_name, cap_reg_ptr] :
+         std::vector<std::tuple<std::string, CheriotRegister **>>{
+             {kC1, &c1_reg_},
+             {kC2, &c2_reg_},
+             {kC3, &c3_reg_},
+             {kC4, &c4_reg_}}) {
+      *cap_reg_ptr = state_->GetRegister<CheriotRegister>(reg_name).first;
+    }
+    state_->set_on_trap([this](bool is_interrupt, uint64_t trap_value,
+                               uint64_t exception_code, uint64_t epc,
+                               const Instruction *inst) {
+      return TrapHandler(is_interrupt, trap_value, exception_code, epc, inst);
+    });
+  }
+
+  ~RiscVCheriotInstructionsTest() override {
+    inst_->DecRef();
+    delete state_;
+    delete memory_;
+  }
+
+  // Handler for any traps that are raised. It is used to capture the trap
+  // information for checks.
+  bool TrapHandler(bool is_interrupt, uint64_t trap_value,
+                   uint64_t exception_code, uint64_t epc,
+                   const Instruction *inst);
+  // Clear the captured trap values.
+  void ResetTrapHandler();
+
+  void AppendCapabilityOperands(Instruction *inst,
+                                absl::Span<const std::string> sources,
+                                absl::Span<const std::string> dests) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand(reg_name));
+    }
+    for (auto &reg_name : dests) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0, reg_name));
+    }
+  }
+
+  template <typename T>
+  void AppendImmediateOperand(Instruction *inst, T value) {
+    auto *src = new ImmediateOperand<T>(value);
+    inst_->AppendSource(src);
+  }
+
+  template <typename T>
+  void AppendImmediateOperands(const std::vector<T> &values) {
+    for (auto &value : values) {
+      AppendImmediateOperand<T>(inst_, value);
+    }
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      reg->set_address(value);
+    }
+  }
+
+  void ResetInstruction(int size) {
+    if (inst_ != nullptr) inst_->DecRef();
+    inst_ = new Instruction(kInstAddress, state_);
+    inst_->set_size(size);
+  }
+
+  // Return true if the capability is null (except for address field).
+  bool IsNullCapability(CheriotRegister *cap) {
+    return cap->is_null() ||
+           (cap->top() == 0 && cap->base() == 0 && cap->permissions() == 0 &&
+            cap->tag() == 0 && cap->object_type() == 0 && cap->reserved() == 0);
+  }
+
+  void SetUpForLoadCapabilityTest(uint32_t address,
+                                  const CheriotRegister *cap) {
+    ResetInstruction(kInstSizeNormal);
+    inst()->set_semantic_function(&CheriotCLc);
+    // Add the child instruction.
+    auto *child = new Instruction(kInstAddress, state());
+    child->set_semantic_function(&CheriotCLcChild);
+    inst()->AppendChild(child);
+    child->DecRef();
+    // Store a capability to memory.
+    auto cap_db = state()->db_factory()->Allocate<uint32_t>(2);
+    cap_db->Set<uint32_t>(0, 0xdeadbeef);
+    cap_db->Set<uint32_t>(1, cap->Compress());
+    auto tag_db = state()->db_factory()->Allocate<uint8_t>(1);
+    tag_db->Set<uint8_t>(0, 1);
+    state()->StoreCapability(inst(), address, cap_db, tag_db);
+    cap_db->DecRef();
+    tag_db->DecRef();
+  }
+
+  // Accessors.
+  Instruction *inst() { return inst_; }
+  TaggedFlatDemandMemory *memory() { return memory_; }
+  CheriotState *state() { return state_; }
+  // Capability register pointers.
+  CheriotRegister *c1_reg() { return c1_reg_; }
+  CheriotRegister *c2_reg() { return c2_reg_; }
+  CheriotRegister *c3_reg() { return c3_reg_; }
+  CheriotRegister *c4_reg() { return c4_reg_; }
+  absl::BitGen &bitgen() { return bitgen_; }
+  bool trap_taken() { return trap_taken_; }
+  bool trap_is_interrupt() { return trap_is_interrupt_; }
+  uint64_t trap_value() { return trap_value_; }
+  uint64_t trap_exception_code() { return trap_exception_code_; }
+  uint64_t trap_epc() { return trap_epc_; }
+  const Instruction *trap_inst() { return trap_inst_; }
+
+ private:
+  Instruction *inst_ = nullptr;
+  TaggedFlatDemandMemory *memory_;
+  CheriotState *state_;
+  CheriotRegister *c1_reg_;
+  CheriotRegister *c2_reg_;
+  CheriotRegister *c3_reg_;
+  CheriotRegister *c4_reg_;
+  absl::BitGen bitgen_;
+  bool trap_taken_ = false;
+  bool trap_is_interrupt_ = false;
+  uint64_t trap_value_ = 0;
+  uint64_t trap_exception_code_ = 0;
+  uint64_t trap_epc_ = 0;
+  const Instruction *trap_inst_ = nullptr;
+};
+
+bool RiscVCheriotInstructionsTest::TrapHandler(bool is_interrupt,
+                                               uint64_t trap_value,
+                                               uint64_t exception_code,
+                                               uint64_t epc,
+                                               const Instruction *inst) {
+  trap_taken_ = true;
+  trap_is_interrupt_ = is_interrupt;
+  trap_value_ = trap_value;
+  trap_exception_code_ = exception_code;
+  trap_epc_ = epc;
+  trap_inst_ = inst;
+  return true;
+}
+
+void RiscVCheriotInstructionsTest::ResetTrapHandler() {
+  trap_taken_ = false;
+  trap_is_interrupt_ = false;
+  trap_value_ = 0;
+  trap_exception_code_ = 0;
+  trap_epc_ = 0;
+  trap_inst_ = nullptr;
+}
+
+TEST_F(RiscVCheriotInstructionsTest, Auicap) {
+  inst()->set_semantic_function(&CheriotAuicap);
+  // Make the source register the memory root.
+  c1_reg()->ResetMemoryRoot();
+  // Try different offsets.
+  for (int32_t offset : {16, 1024, 0, -16 - 1024}) {
+    AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+    c1_reg()->set_address(kMemAddress);
+    c2_reg()->set_address(offset);
+    inst()->Execute(nullptr);
+    // Verify that the source didn't change, and that the value of the
+    // destination capability is as expected.
+    EXPECT_EQ(c1_reg()->address(), kMemAddress);
+    EXPECT_EQ(c3_reg()->address(), kMemAddress + offset);
+    EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+    EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+    EXPECT_TRUE(c3_reg()->tag());
+  }
+  // Try with c1 being sealed.
+  CHECK_OK(c1_reg()->Seal(*state()->sealing_root(), kDataSeal10));
+  uint32_t offset = 0x1000;
+  c2_reg()->set_address(offset);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress + offset);
+  EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+  EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+  EXPECT_FALSE(c3_reg()->tag());
+  // Now limit c1 to a smaller range and set the offset outside that.
+  c1_reg()->ResetMemoryRoot();
+  offset = 0x210;
+  c1_reg()->SetBounds(kMemAddress, offset - 16);
+  c1_reg()->set_address(kMemAddress);
+  EXPECT_TRUE(c1_reg()->tag());
+  // Set the offset to be just outside.
+  offset = (1 << (c1_reg()->exponent() + 9)) + 1;
+  c2_reg()->set_address(offset);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c1_reg()->address(), kMemAddress);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress + offset);
+  EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+  EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+  EXPECT_FALSE(c3_reg()->tag());
+}
+
+// Verify that permission removal using CAndPerm works.
+TEST_F(RiscVCheriotInstructionsTest, CAndPerm) {
+  uint32_t mask = PB::kPermitGlobal | PB::kPermitLoadGlobal | PB::kPermitStore |
+                  PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
+                  PB::kPermitLoad | PB::kPermitLoadStoreCapability;
+  inst()->set_semantic_function(&CheriotCAndPerm);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC1});
+  c1_reg()->ResetMemoryRoot();  // Full memory permissions.
+  uint32_t and_mask = mask;
+  uint32_t expected = PB::kPermitGlobal | PB::kPermitLoadGlobal |
+                      PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
+                      PB::kPermitLoadStoreCapability | PB::kPermitStore |
+                      PB::kPermitLoad;
+  EXPECT_EQ(c1_reg()->permissions(), expected);
+  for (uint32_t i :
+       {PB::kPermitGlobal, PB::kPermitLoadGlobal, PB::kPermitLoadMutable,
+        PB::kPermitStoreLocalCapability, PB::kPermitLoadStoreCapability,
+        PB::kPermitStore, PB::kPermitLoad}) {
+    and_mask = mask & ~i;
+    expected &= and_mask;
+    c2_reg()->set_address(and_mask);
+    inst()->Execute(nullptr);
+    EXPECT_EQ(c1_reg()->permissions(), expected) << absl::StrFormat(
+        "p: %08x expected: %08x\n", c1_reg()->permissions(), expected);
+    EXPECT_TRUE(c1_reg()->tag());
+  }
+  // A sealed capability should clear the tag.
+  c1_reg()->ResetMemoryRoot();
+  CHECK_OK(c1_reg()->Seal(*state()->sealing_root(), kDataSeal10));
+  expected = PB::kPermitGlobal | PB::kPermitLoadGlobal |
+             PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
+             PB::kPermitLoadStoreCapability | PB::kPermitStore |
+             PB::kPermitLoad;
+  and_mask = mask & ~PB::kPermitGlobal;
+  expected &= and_mask;
+  c2_reg()->set_address(and_mask);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c1_reg()->permissions(), expected) << absl::StrFormat(
+      "p: %08x expected: %08x\n", c1_reg()->permissions(), expected);
+  EXPECT_FALSE(c1_reg()->tag());
+}
+
+// Verify that CClearTag clears the tag properly.
+TEST_F(RiscVCheriotInstructionsTest, CClearTag) {
+  inst()->set_semantic_function(&CheriotCClearTag);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  // Make c3 reg tag true.
+  c3_reg()->ResetMemoryRoot();
+  EXPECT_TRUE(c3_reg()->tag());
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(c3_reg()->tag());
+}
+
+// Verify that the correct address is returned.
+TEST_F(RiscVCheriotInstructionsTest, CGetAddr) {
+  inst()->set_semantic_function(&CheriotCGetAddr);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  c1_reg()->set_address(kMemAddress);
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+}
+
+// Verify that the correct base is returned.
+TEST_F(RiscVCheriotInstructionsTest, CGetBase) {
+  inst()->set_semantic_function(&CheriotCGetBase);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), 0);
+  EXPECT_TRUE(c1_reg()->SetBounds(kMemAddress, 0x200));
+  c1_reg()->set_address(kMemAddress);
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+}
+
+// Verify that the correct 'compressed' value is returned.
+TEST_F(RiscVCheriotInstructionsTest, CGetHigh) {
+  inst()->set_semantic_function(&CheriotCGetHigh);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), c1_reg()->Compress());
+  EXPECT_TRUE(c1_reg()->SetBounds(kMemAddress, 0x200));
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), c1_reg()->Compress());
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+  c1_reg()->ResetNull();
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+}
+
+// Verify that the correct length is returned.
+TEST_F(RiscVCheriotInstructionsTest, CGetLen) {
+  inst()->set_semantic_function(&CheriotCGetLen);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0xffff'ffff);
+  c1_reg()->SetBounds(kMemAddress, 0x200);
+  c3_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0x200);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+}
+
+// Verify that the correct permission bits are returned.
+TEST_F(RiscVCheriotInstructionsTest, CGetPerm) {
+  inst()->set_semantic_function(&CheriotCGetPerm);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetNull();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+  c1_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(),
+            PB::kPermitGlobal | PB::kPermitLoadGlobal | PB::kPermitStore |
+                PB::kPermitLoadMutable | PB::kPermitStoreLocalCapability |
+                PB::kPermitLoad | PB::kPermitLoadStoreCapability);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+  c1_reg()->ResetExecuteRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(),
+            PB::kPermitGlobal | PB::kPermitExecute | PB::kPermitLoad |
+                PB::kPermitLoadStoreCapability | PB::kPermitLoadGlobal |
+                PB::kPermitLoadMutable | PB::kPermitAccessSystemRegisters);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+  c1_reg()->ResetSealingRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), PB::kPermitGlobal | PB::kPermitSeal |
+                                     PB::kPermitUnseal | PB::kUserPerm0);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+}
+
+// Verify that CGetTag gets the correct tag value.
+TEST_F(RiscVCheriotInstructionsTest, CGetTag) {
+  inst()->set_semantic_function(&CheriotCGetTag);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  inst()->Execute(nullptr);
+  // Initial value of a capability register is null, so the tag should be false.
+  EXPECT_EQ(c3_reg()->address(), 0);
+  // Make c1 a valid capability, now the tag should be true.
+  c1_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+}
+
+// Checking that CGetType gets the correct object type.
+TEST_F(RiscVCheriotInstructionsTest, CGetType) {
+  inst()->set_semantic_function(&CheriotCGetType);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  inst()->Execute(nullptr);
+  c1_reg()->ResetExecuteRoot();
+  EXPECT_EQ(c3_reg()->address(), 0);
+  EXPECT_TRUE(IsNullCapability(c3_reg()));
+  for (int i = 0; i < 8; i++) {
+    c1_reg()->set_object_type(i);
+    inst()->Execute(nullptr);
+    EXPECT_EQ(c3_reg()->address(), i & 0x7);
+    EXPECT_TRUE(IsNullCapability(c3_reg()));
+  }
+  c1_reg()->ResetMemoryRoot();
+  for (int i = 0; i < 8; i++) {
+    c1_reg()->set_object_type(i);
+    inst()->Execute(nullptr);
+    if (i == 0) {
+      EXPECT_EQ(c3_reg()->address(), 0);
+    } else {
+      EXPECT_EQ(c3_reg()->address(), 0x8 | i);
+    }
+    EXPECT_TRUE(IsNullCapability(c3_reg()));
+  }
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CIncAddr) {
+  inst()->set_semantic_function(&CheriotCIncAddr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  // Set up c1 as a valid capability, base kMemAddress, length 200.
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->SetBounds(kMemAddress, 0x80);
+  c1_reg()->set_address(kMemAddress);
+  // Set the value of c2 to 0x10.
+  c2_reg()->set_address(0x10);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress + 0x10);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+  EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+  // Change increment to 0x200.
+  c2_reg()->set_address(0x20);
+  // Increment again.
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress + 0x20);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+  EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+  // Change increment to 1 << (exponent + 9) + 1;
+  c2_reg()->set_address((0x1 << (c1_reg()->exponent() + 9)) + 1);
+  // Increment again. This time the tag will be cleared.
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(),
+            kMemAddress + (0x1 << (c1_reg()->exponent() + 9)) + 1);
+  EXPECT_FALSE(c3_reg()->tag())
+      << absl::StrFormat("b: 0x%08x a: 0x%08x e:%d", c3_reg()->base(),
+                         c3_reg()->address(), c3_reg()->exponent());
+  EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+  EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+  // Change increment back to 0x100.
+  c2_reg()->set_address(0x100);
+  // Seal the source capability. That will make the tag false.
+  EXPECT_TRUE(c1_reg()->Seal(*state()->sealing_root(), kDataSeal10).ok());
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress + 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->top(), c1_reg()->top());
+  EXPECT_EQ(c3_reg()->base(), c1_reg()->base());
+}
+
+// Jump and link - no traps.
+TEST_F(RiscVCheriotInstructionsTest, CJal) {
+  inst()->set_semantic_function(&CheriotCJal);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  c1_reg()->set_address(0x200);
+  // Set interrupt enable to true.
+  state()->mstatus()->set_mie(1);
+  state()->mstatus()->Submit();
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                             << " value: " << trap_value();
+  EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_TRUE(c3_reg()->IsSentry());
+  EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptEnablingSentry);
+  EXPECT_TRUE(state()->pcc()->tag());
+  // Set interrupt enable to false.
+  state()->mstatus()->set_mie(0);
+  state()->mstatus()->Submit();
+  state()->pcc()->set_address(inst()->address());
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                             << " value: " << trap_value();
+  EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_TRUE(c3_reg()->IsSentry());
+  EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptDisablingSentry);
+  EXPECT_TRUE(state()->pcc()->tag());
+}
+
+// Jump and link - out of bounds error.
+TEST_F(RiscVCheriotInstructionsTest, CJalOutOfBounds) {
+  inst()->set_semantic_function(&CheriotCJal);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  c1_reg()->set_address(0x200);
+  // Set interrupt enable to true.
+  state()->mstatus()->set_mie(1);
+  state()->mstatus()->Submit();
+  // Restrict the bounds of pcc.
+  EXPECT_TRUE(state()->pcc()->SetBounds(kInstAddress, 0x100));
+  inst()->Execute(nullptr);
+}
+
+// Jump and link - misaligned (jumping to 2 byte aligned address with no
+// compact instructions).
+TEST_F(RiscVCheriotInstructionsTest, CJalMisaligned) {
+  inst()->set_semantic_function(&CheriotCJal);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  state()->misa()->Set(state()->misa()->AsUint64() & ~*ISA::kCompressed);
+  c1_reg()->set_address(0x202);
+  // Set interrupt enable to true.
+  state()->mstatus()->set_mie(1);
+  state()->mstatus()->Submit();
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kInstAddress + 0x202);
+  EXPECT_EQ(trap_exception_code(), *RV_EC::kInstructionAddressMisaligned);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Jump and link register (capability) indirect - no traps, unsealed source.
+TEST_F(RiscVCheriotInstructionsTest, CJalr) {
+  inst()->set_semantic_function(&CheriotCJalr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->set_address(kInstAddress + 0x100);
+  c1_reg()->SetBounds(kInstAddress, 0x400);
+  // Set offset.
+  c2_reg()->set_address(0x100);
+  // Set interrupt enable to true.
+  state()->mstatus()->set_mie(1);
+  state()->mstatus()->Submit();
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                             << " value: " << trap_value();
+  EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_TRUE(c3_reg()->IsSentry());
+  EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptEnablingSentry);
+  EXPECT_TRUE(state()->pcc()->tag());
+  // Set interrupt enable to false.
+  state()->mstatus()->set_mie(0);
+  state()->mstatus()->Submit();
+  state()->pcc()->set_address(inst()->address());
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                             << " value: " << trap_value();
+  EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_TRUE(c3_reg()->IsSentry());
+  EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptDisablingSentry);
+  EXPECT_TRUE(state()->pcc()->tag());
+}
+
+// Jump and link register (capability) indirect - no traps, sentry.
+TEST_F(RiscVCheriotInstructionsTest, CJalrSentry) {
+  inst()->set_semantic_function(&CheriotCJalr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->set_address(kInstAddress + 0x200);
+  c1_reg()->SetBounds(kInstAddress, 0x400);
+  (void)c1_reg()->Seal(*(state()->sealing_root()),
+                       OT::kInterruptEnablingSentry);
+  // Set offset to zero (because c1_reg is sealed).
+  c2_reg()->set_address(0);
+  // Set interrupt enable to false.
+  state()->mstatus()->set_mie(0);
+  state()->mstatus()->Submit();
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(state()->mstatus()->mie());
+  EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                             << " value: " << trap_value();
+  EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_TRUE(c3_reg()->IsSentry());
+  EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptDisablingSentry);
+  EXPECT_TRUE(state()->pcc()->tag());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->SetBounds(kInstAddress, 0x400);
+  c1_reg()->set_address(kInstAddress + 0x200);
+  (void)c1_reg()->Seal(*(state()->sealing_root()),
+                       OT::kInterruptDisablingSentry);
+  // Set interrupt enable to true.
+  state()->mstatus()->set_mie(1);
+  state()->mstatus()->Submit();
+  state()->pcc()->set_address(inst()->address());
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(state()->mstatus()->mie());
+  EXPECT_FALSE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                             << " value: " << trap_value();
+  EXPECT_EQ(state()->pcc()->address(), inst()->address() + 0x200);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_TRUE(c3_reg()->IsSentry());
+  EXPECT_EQ(c3_reg()->object_type(), OT::kInterruptEnablingSentry);
+  EXPECT_TRUE(state()->pcc()->tag());
+}
+
+// Verify an unset tag generates a tag violation exception.
+TEST_F(RiscVCheriotInstructionsTest, CJalrTagViolation) {
+  inst()->set_semantic_function(&CheriotCJalr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->SetBounds(kInstAddress, 0x400);
+  c1_reg()->set_address(kInstAddress + 0x200);
+  // Clear c1_reg tag.
+  c1_reg()->Invalidate();
+  // Set offset to non-zero.
+  c2_reg()->set_address(0);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(state()->pcc()->address(), inst()->address());
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_FALSE(c3_reg()->IsSentry());
+  EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                            << " value: " << trap_value();
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kC1Num << 5 | *CH_EC::kCapExTagViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// For a jalr with a sentry, the immediate has to be zero or it will cause
+// an exception. Make sure the exception happens.
+TEST_F(RiscVCheriotInstructionsTest, CJalrSentryNonZeroImmediate) {
+  inst()->set_semantic_function(&CheriotCJalr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->set_address(kInstAddress + 0x200);
+  c1_reg()->SetBounds(kInstAddress, 0x400);
+  (void)c1_reg()->Seal(*(state()->sealing_root()),
+                       OT::kInterruptEnablingSentry);
+  // Set offset to non-zero - should cause an exception.
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(state()->pcc()->address(), inst()->address());
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_FALSE(c3_reg()->IsSentry());
+  EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                            << " value: " << trap_value();
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kC1Num << 5 | *CH_EC::kCapExSealViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// If the source capability does not have execute permission, there should
+// be an exception.
+TEST_F(RiscVCheriotInstructionsTest, CJalrExecuteViolation) {
+  inst()->set_semantic_function(&CheriotCJalr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->SetBounds(kInstAddress, 0x400);
+  c1_reg()->set_address(kInstAddress + 0x200);
+  c1_reg()->ClearPermissions(PB::kPermitExecute);
+  // Clear c1_reg tag.
+  c1_reg()->Invalidate();
+  // Set offset to non-zero.
+  c2_reg()->set_address(0);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(state()->pcc()->address(), inst()->address());
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_FALSE(c3_reg()->IsSentry());
+  EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                            << " value: " << trap_value();
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kC1Num << 5 | *CH_EC::kCapExTagViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// If the architecture does not have compact instructions, then misaligned
+// access on two byte boundary should cause an exception.
+TEST_F(RiscVCheriotInstructionsTest, CJalrAlignmentViolation) {
+  inst()->set_semantic_function(&CheriotCJalr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  state()->pcc()->set_address(inst()->address());
+  // Set up the destination capability.
+  c1_reg()->ResetExecuteRoot();
+  c1_reg()->set_address(kInstAddress + 0x200);
+  // Set offset to non-zero.
+  c2_reg()->set_address(2);
+  // Clear the compressed isa bit.
+  state()->misa()->Set(state()->misa()->AsUint64() & ~*ISA::kCompressed);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken()) << "ec: " << std::hex << trap_exception_code()
+                            << " value: " << trap_value();
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kInstAddress + 0x202);
+  EXPECT_EQ(trap_exception_code(), *RV_EC::kInstructionAddressMisaligned);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check load capability - no traps.
+TEST_F(RiscVCheriotInstructionsTest, CLc) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  EXPECT_TRUE(*c3_reg() == *state()->memory_root());
+}
+
+// Load without global flag should clear global flag of loaded capability.
+TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadGlobal) {
+  c4_reg()->ResetMemoryRoot();
+  c4_reg()->ClearPermissions(PB::kPermitGlobal | PB::kPermitLoadGlobal);
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->ClearPermissions(PB::kPermitLoadGlobal);
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  EXPECT_FALSE(*c3_reg() == *state()->memory_root());
+  EXPECT_TRUE(*c3_reg() == *c4_reg());
+}
+
+// Load without mutable flag should clear mutable and store permissions of
+// unsealed capability.
+TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadMutableUnsealed) {
+  c4_reg()->ResetMemoryRoot();
+  c4_reg()->ClearPermissions(PB::kPermitLoadMutable | PB::kPermitStore);
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->ClearPermissions(PB::kPermitLoadMutable);
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  EXPECT_FALSE(*c3_reg() == *state()->memory_root());
+  EXPECT_TRUE(*c3_reg() == *c4_reg());
+}
+
+// Load without mutable flag should not clear mutable and store permissions of
+// sealed capability.
+TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadMutableSealed) {
+  c4_reg()->ResetMemoryRoot();
+  (void)c4_reg()->Seal(*(state()->sealing_root()), kDataSeal10);
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, c4_reg());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->ClearPermissions(PB::kPermitLoadMutable);
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  EXPECT_TRUE(*c3_reg() == *c4_reg());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CLcNoLoadStoreCapability) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->ClearPermissions(PB::kPermitLoadStoreCapability);
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  // Result should be equal to memory root, but without the valid tag and some
+  // permissions removed.
+  c4_reg()->ResetMemoryRoot();
+  c4_reg()->set_address(0xdeadbeef);
+  c4_reg()->ClearPermissions(
+      PB::kPermitGlobal | PB::kPermitLoadGlobal | PB::kPermitLoadMutable |
+      PB::kPermitStoreLocalCapability | PB::kPermitStore);
+  c4_reg()->Invalidate();
+  EXPECT_TRUE(*c3_reg() == *c4_reg()) << "c3_reg(): " << c3_reg()->AsString()
+                                      << "\nc4_reg(): " << c4_reg()->AsString();
+}
+
+// Check load capability with invalid capability.
+TEST_F(RiscVCheriotInstructionsTest, CLcTagViolation) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->Invalidate();
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExTagViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check load capability with sealed capability.
+TEST_F(RiscVCheriotInstructionsTest, CLcSealViolation) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  (void)c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExSealViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check load capability with no load permission.
+TEST_F(RiscVCheriotInstructionsTest, CLcPermitLoadViolation) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->ClearPermissions(PB::kPermitLoad);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExPermitLoadViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check load capability with bounds violation.
+TEST_F(RiscVCheriotInstructionsTest, CLcPermitBoundsViolation) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->SetBounds(0, kMemAddress + 0x100);
+  c2_reg()->set_address(0x200);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExBoundsViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check load capability with unaligned address.
+TEST_F(RiscVCheriotInstructionsTest, CLcUnaligned) {
+  SetUpForLoadCapabilityTest(kMemAddress + 0x200, state()->memory_root());
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {});
+  AppendCapabilityOperands(inst()->child(), {}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x201);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kMemAddress + 0x201);
+  EXPECT_EQ(trap_exception_code(), *RV_EC::kLoadAddressMisaligned);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Verify that copy works.
+TEST_F(RiscVCheriotInstructionsTest, CMove) {
+  inst()->set_semantic_function(&CheriotCMove);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(*c1_reg() == *c3_reg());
+  c1_reg()->ResetExecuteRoot();
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(*c3_reg() == *c1_reg());
+  c1_reg()->ResetSealingRoot();
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(*c3_reg() == *c1_reg());
+  c1_reg()->ResetNull();
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(*c3_reg() == *c1_reg());
+}
+
+// Verify that CRepresentableAlignmentMask return the correct mask.
+TEST_F(RiscVCheriotInstructionsTest, CRepresentableAlignmentMask) {
+  inst()->set_semantic_function(&CheriotCRepresentableAlignmentMask);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  std::array fixed_values{5039028};
+  for (int i = 0; i < 1000; i++) {
+    uint32_t len;
+    if (i < fixed_values.size()) {
+      len = fixed_values[i];
+    } else {
+      len = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
+                          std::numeric_limits<uint32_t>::max());
+    }
+    c1_reg()->set_address(len);
+    inst()->Execute(nullptr);
+    uint32_t alignment;
+    if (len <= 511)
+      alignment = 1;
+    else if (len <= 1022)
+      alignment = 2;
+    else if (len <= 2044)
+      alignment = 4;
+    else if (len <= 4088)
+      alignment = 8;
+    else if (len <= 8176)
+      alignment = 16;
+    else if (len <= 16352)
+      alignment = 32;
+    else if (len <= 32704)
+      alignment = 64;
+    else if (len <= 65408)
+      alignment = 128;
+    else if (len <= 130816)
+      alignment = 256;
+    else if (len <= 261632)
+      alignment = 512;
+    else if (len <= 523264)
+      alignment = 1024;
+    else if (len <= 1046528)
+      alignment = 2048;
+    else if (len <= 2093056)
+      alignment = 4096;
+    else if (len <= 4186112)
+      alignment = 8192;
+    else if (len <= 8372224)
+      alignment = 16384;
+    else
+      alignment = 16777216;
+    uint32_t mask = ~(alignment - 1);
+    EXPECT_EQ(mask, c3_reg()->address())
+        << "len: " << len << " exp alignment: " << alignment
+        << " alignment: " << (~(c3_reg()->address()) + 1) << std::hex
+        << " mask: " << mask << " c3_reg: " << c3_reg()->address();
+  }
+}
+
+// Verify that round to representable length works properly. The key here is
+// that the result of this instruction should be the minimum length >= the
+// given length that can be used for exact bounds assuming a suitably aligned
+// base address.
+TEST_F(RiscVCheriotInstructionsTest, CRoundRepresentableLength) {
+  inst()->set_semantic_function(&CheriotCRoundRepresentableLength);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  for (int i = 0; i < 1000; i++) {
+    uint32_t len = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
+                                 std::numeric_limits<uint32_t>::max());
+    c1_reg()->set_address(len);
+    inst()->Execute(nullptr);
+    uint32_t alignment;
+    if (len <= 511)
+      alignment = 1;
+    else if (len <= 1022)
+      alignment = 2;
+    else if (len <= 2044)
+      alignment = 4;
+    else if (len <= 4088)
+      alignment = 8;
+    else if (len <= 8176)
+      alignment = 16;
+    else if (len <= 16352)
+      alignment = 32;
+    else if (len <= 32704)
+      alignment = 64;
+    else if (len <= 65408)
+      alignment = 128;
+    else if (len <= 130816)
+      alignment = 256;
+    else if (len <= 261632)
+      alignment = 512;
+    else if (len <= 523264)
+      alignment = 1024;
+    else if (len <= 1046528)
+      alignment = 2048;
+    else if (len <= 2093056)
+      alignment = 4096;
+    else if (len <= 4186112)
+      alignment = 8192;
+    else if (len <= 8372224)
+      alignment = 16384;
+    else
+      alignment = 16777216;
+    uint32_t length = alignment * ((len + alignment - 1) / alignment);
+    EXPECT_EQ(length, c3_reg()->address());
+  }
+}
+
+// Check store capability - no traps.
+TEST_F(RiscVCheriotInstructionsTest, CSc) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  auto *db = state()->db_factory()->Allocate<uint32_t>(2);
+  memory()->Load(kMemAddress + 0x200, db, nullptr, nullptr);
+  EXPECT_EQ(db->Get<uint32_t>(0), c3_reg()->address());
+  EXPECT_EQ(db->Get<uint32_t>(1), c3_reg()->Compress());
+  db->DecRef();
+}
+
+// Check store capability with invalid capability.
+TEST_F(RiscVCheriotInstructionsTest, CScTagViolation) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->Invalidate();
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExTagViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check store capability with sealed capability.
+TEST_F(RiscVCheriotInstructionsTest, CScSealViolation) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  (void)c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10);
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExSealViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check store capability with no load permission.
+TEST_F(RiscVCheriotInstructionsTest, CScPermitStoreViolation) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->ClearPermissions(PB::kPermitStore);
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExPermitStoreViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check store capability with no load store capability permission.
+TEST_F(RiscVCheriotInstructionsTest, CScPermitStoreCapViolation) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->ClearPermissions(PB::kPermitLoadStoreCapability);
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(),
+            (kC1Num << 5) | *CH_EC::kCapExPermitStoreCapabilityViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check for proper generation of store local cap violation.
+TEST_F(RiscVCheriotInstructionsTest, CScStoreLocalCapViolation) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->ClearPermissions(PB::kPermitStoreLocalCapability);
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetMemoryRoot();
+  c3_reg()->ClearPermissions(PB::kPermitGlobal);
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  auto *db = state()->db_factory()->Allocate<uint32_t>(2);
+  memory()->Load(kMemAddress + 0x200, db, nullptr, nullptr);
+  // Invalidate c3 - the stored tag should be the same as c3, but with the tag
+  // cleared.
+  c3_reg()->Invalidate();
+  EXPECT_EQ(db->Get<uint32_t>(0), c3_reg()->address());
+  EXPECT_EQ(db->Get<uint32_t>(1), c3_reg()->Compress());
+  db->DecRef();
+}
+
+// Check store capability with bounds violation.
+TEST_F(RiscVCheriotInstructionsTest, CScPermitBoundsViolation) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->SetBounds(0, kMemAddress + 0x100);
+  c2_reg()->set_address(0x200);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), (kC1Num << 5) | *CH_EC::kCapExBoundsViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+// Check store capability with unaligned address.
+TEST_F(RiscVCheriotInstructionsTest, CScUnaligned) {
+  inst()->set_semantic_function(&CheriotCSc);
+  AppendCapabilityOperands(inst(), {kC1, kC2, kC3}, {});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x201);
+  c3_reg()->ResetSealingRoot();
+  c3_reg()->set_address(kDataSeal10);
+  inst()->Execute(nullptr);
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(), kMemAddress + 0x201);
+  EXPECT_EQ(trap_exception_code(), *RV_EC::kStoreAddressMisaligned);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSeal) {
+  inst()->set_semantic_function(&CheriotCSeal);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c2_reg()->ResetSealingRoot();
+  // Memory sealing.
+  c1_reg()->ResetMemoryRoot();
+  for (uint32_t o_type = 0; o_type < 18; o_type++) {
+    c2_reg()->set_address(o_type);
+    inst()->Execute(nullptr);
+    EXPECT_EQ(c3_reg()->object_type(), o_type & 0b111);
+    // For executable or illegal object types the tag will be false.
+    if ((o_type <= 8) || (o_type > 15)) {
+      EXPECT_FALSE(c3_reg()->tag());
+    } else {
+      EXPECT_TRUE(c3_reg()->tag());
+    }
+  }
+  // Executable sealing.
+  c1_reg()->ResetExecuteRoot();
+  for (uint32_t o_type = 0; o_type < 18; o_type++) {
+    c2_reg()->set_address(o_type);
+    inst()->Execute(nullptr);
+    EXPECT_EQ(c3_reg()->object_type(), o_type & 0b111);
+    // For executable object types the tag should be true.
+    if ((o_type == OT::kSentry) || (o_type == OT::kInterruptDisablingSentry) ||
+        (o_type == OT::kInterruptEnablingSentry) ||
+        (o_type == OT::kSealedExecutable6) ||
+        (o_type == OT::kSealedExecutable7)) {
+      EXPECT_TRUE(c3_reg()->tag());
+    } else {
+      EXPECT_FALSE(c3_reg()->tag());
+    }
+  }
+  // Sealing type outside range.
+  EXPECT_TRUE(c2_reg()->SetBounds(0, 12));
+  c2_reg()->set_address(14);
+  c1_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), 14 & 0b111);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Attempt sealing using a sealed capability.
+  c2_reg()->ResetSealingRoot();
+  c2_reg()->set_address(kDataSeal10);
+  EXPECT_TRUE(c2_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), 10 & 0b111);
+  EXPECT_FALSE(c3_reg()->tag());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSetAddr) {
+  inst()->set_semantic_function(&CheriotCSetAddr);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  EXPECT_EQ(c1_reg()->address(), 0);
+  c2_reg()->set_address(kMemAddress);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress);
+  EXPECT_TRUE(c3_reg()->tag());
+  // If c1 is sealed, the tag is cleared.
+  EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), 9).ok());
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress);
+  EXPECT_FALSE(c3_reg()->tag());
+  // If address is out of range, the tag is cleared.
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->SetBounds(0, 200);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), kMemAddress);
+  EXPECT_FALSE(c3_reg()->tag());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSetBounds) {
+  inst()->set_semantic_function(&CheriotCSetBounds);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  // Set the requested new base.
+  c1_reg()->set_address(kMemAddress);
+  for (uint32_t len = 1; len < 0x8000'0000; len <<= 1) {
+    // Set the requested new length.
+    c2_reg()->set_address(len);
+    inst()->Execute(nullptr);
+    // The bounds will be no smaller than requested.
+    EXPECT_LE(c3_reg()->base(), kMemAddress);
+    EXPECT_GE(c3_reg()->length(), len);
+    EXPECT_TRUE(c3_reg()->tag());
+  }
+  // Request bounds outside the capability - first base below.
+  c1_reg()->SetBounds(kMemAddress, 0x200);
+  c1_reg()->set_address(0);
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), 0);
+  EXPECT_EQ(c3_reg()->length(), 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Next, length too long.
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x300);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), kMemAddress);
+  EXPECT_EQ(c3_reg()->length(), 0x300);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Base too high.
+  c1_reg()->set_address(kMemAddress * 2);
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), kMemAddress * 2);
+  EXPECT_EQ(c3_reg()->length(), 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Sealed capability.
+  c1_reg()->ResetMemoryRoot();
+  EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), kMemAddress);
+  EXPECT_EQ(c3_reg()->length(), 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSetBoundsExact) {
+  inst()->set_semantic_function(&CheriotCSetBoundsExact);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  // Set the requested new base.
+  c1_reg()->set_address(kMemAddress);
+  for (uint32_t len = 1; len < 0x8000'0000; len <<= 1) {
+    // Set the requested new length.
+    c2_reg()->set_address(len);
+    inst()->Execute(nullptr);
+    // The bounds will be no smaller than requested.
+    EXPECT_LE(c3_reg()->base(), kMemAddress);
+    EXPECT_GE(c3_reg()->length(), len);
+    // If they are not exactly what were requested, the tag will be false.
+    if ((c3_reg()->length() != len) || (c3_reg()->base() != kMemAddress)) {
+      EXPECT_FALSE(c3_reg()->tag());
+    } else {
+      EXPECT_TRUE(c3_reg()->tag());
+    }
+  }
+  // Request bounds outside the capability - first base below.
+  c1_reg()->SetBounds(kMemAddress, 0x200);
+  c1_reg()->set_address(0);
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), 0);
+  EXPECT_EQ(c3_reg()->length(), 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Next, length too long.
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x300);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), kMemAddress);
+  EXPECT_EQ(c3_reg()->length(), 0x300);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Base too high.
+  c1_reg()->set_address(kMemAddress * 2);
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), kMemAddress * 2);
+  EXPECT_EQ(c3_reg()->length(), 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+  // Sealed capability.
+  c1_reg()->ResetMemoryRoot();
+  EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
+  c1_reg()->set_address(kMemAddress);
+  c2_reg()->set_address(0x100);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->base(), kMemAddress);
+  EXPECT_EQ(c3_reg()->length(), 0x100);
+  EXPECT_FALSE(c3_reg()->tag());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSetEqualExact) {
+  inst()->set_semantic_function(&CheriotCSetEqualExact);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c2_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+  // Change c1.
+  c1_reg()->ResetExecuteRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  // Change c2 too.
+  c2_reg()->ResetExecuteRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+  // Change c1 to sealing root.
+  c1_reg()->ResetSealingRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  // Change c2 too.
+  c2_reg()->ResetSealingRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSetHigh) {
+  inst()->set_semantic_function(&CheriotCSetHigh);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c1_reg()->set_address(kMemAddress + 10);
+  // Initialize another capability register.
+  c4_reg()->ResetMemoryRoot();
+  c4_reg()->SetBounds(kMemAddress, 200);
+  uint32_t high = c4_reg()->Compress();
+  c2_reg()->set_address(high);
+  inst()->Execute(nullptr);
+  // Tag should be cleared.
+  EXPECT_FALSE(c3_reg()->tag());
+  // Other fields should be the same.
+  EXPECT_EQ(c3_reg()->address(), c1_reg()->address());
+  EXPECT_EQ(c3_reg()->base(), c4_reg()->base());
+  EXPECT_EQ(c3_reg()->length(), c4_reg()->length());
+  EXPECT_EQ(c3_reg()->permissions(), c4_reg()->permissions());
+  EXPECT_EQ(c3_reg()->object_type(), c4_reg()->object_type());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSpecialR) {
+  inst()->set_semantic_function(&CheriotCSpecialR);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  EXPECT_TRUE(*c1_reg() == *c3_reg());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSpecialRException) {
+  inst()->set_semantic_function(&CheriotCSpecialR);
+  AppendCapabilityOperands(inst(), {kC1}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  // Remove system registers access permission.
+  state()->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters);
+  inst()->Execute(nullptr);
+  // C3 should be null capability, just like c2 is.
+  EXPECT_TRUE(*c3_reg() == *c4_reg());
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(),
+            kPccNum << 5 | *CH_EC::kCapExPermitAccessSystemRegistersViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSpecialRW) {
+  inst()->set_semantic_function(&CheriotCSpecialRW);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c2_reg()->ResetSealingRoot();
+  inst()->Execute(nullptr);
+  EXPECT_FALSE(trap_taken());
+  EXPECT_TRUE(*state()->sealing_root() == *c3_reg());
+  EXPECT_TRUE(*state()->memory_root() == *c2_reg());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSpecialRWException) {
+  inst()->set_semantic_function(&CheriotCSpecialRW);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c2_reg()->ResetSealingRoot();
+  // Remove system registers access permission.
+  state()->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters);
+  inst()->Execute(nullptr);
+  // C3 is unmodified.
+  EXPECT_TRUE(*c3_reg() == *c4_reg());
+  EXPECT_TRUE(trap_taken());
+  EXPECT_FALSE(trap_is_interrupt());
+  EXPECT_EQ(trap_epc(), kInstAddress);
+  EXPECT_EQ(trap_value(),
+            kPccNum << 5 | *CH_EC::kCapExPermitAccessSystemRegistersViolation);
+  EXPECT_EQ(trap_exception_code(), CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(inst(), trap_inst());
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CSub) {
+  inst()->set_semantic_function(&CheriotCSub);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  for (int i = 0; i < 100; i++) {
+    c3_reg()->ResetMemoryRoot();
+    // Generate random address and compressed capability.
+    uint32_t val0 = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
+                                  std::numeric_limits<uint32_t>::max());
+    uint32_t val1 = absl::Uniform(absl::IntervalClosed, bitgen(), 0ULL,
+                                  std::numeric_limits<uint32_t>::max());
+    c1_reg()->set_address(val0);
+    c2_reg()->set_address(val1);
+    inst()->Execute(nullptr);
+    EXPECT_EQ(c3_reg()->address(), val0 - val1);
+    EXPECT_FALSE(c3_reg()->tag());
+  }
+}
+
+// Tests if cs2 is a subset of cs1.
+TEST_F(RiscVCheriotInstructionsTest, CTestSubset) {
+  inst()->set_semantic_function(&CheriotCTestSubset);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c1_reg()->ResetMemoryRoot();
+  c2_reg()->ResetMemoryRoot();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+  // Narrow c1 bounds, now the result should be 0.
+  c1_reg()->set_address(kMemAddress);
+  c1_reg()->SetBounds(kMemAddress, 0x400);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  // Make c2 bounds narrower than c1, result should be 1.
+  c2_reg()->set_address(kMemAddress + 0x100);
+  c2_reg()->SetBounds(kMemAddress + 0x100, 0x200);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+  // Remove a permission bit from c1, result should be 0.
+  c1_reg()->ClearPermissions(PB::kPermitGlobal);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  // Remove that and another bit from c2 result should be 1.
+  c2_reg()->ClearPermissions(PB::kPermitGlobal | PB::kPermitLoadGlobal);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+  // If c1 is invalidated, then the result is 0.
+  c1_reg()->Invalidate();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 0);
+  // If c2 is also invalidated, the result is 1.
+  c2_reg()->Invalidate();
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->address(), 1);
+}
+
+TEST_F(RiscVCheriotInstructionsTest, CUnseal) {
+  inst()->set_semantic_function(&CheriotCUnseal);
+  AppendCapabilityOperands(inst(), {kC1, kC2}, {kC3});
+  c2_reg()->ResetSealingRoot();
+  // Set unsealing cap address to 10.
+  c2_reg()->set_address(kDataSeal10);
+  // If c1 is unsealed, it fails.
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
+  // Seal c1.
+  c1_reg()->ResetMemoryRoot();
+  // Seal c1 with otype 10.
+  EXPECT_TRUE(c1_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
+  // Remove global permission from c2. The resulting capability will have it
+  // removed too.
+  c2_reg()->ClearPermissions(PB::kPermitGlobal);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
+  EXPECT_TRUE(c3_reg()->tag());
+  EXPECT_NE(c3_reg()->permissions(), c1_reg()->permissions());
+  EXPECT_EQ(c3_reg()->permissions() | PB::kPermitGlobal,
+            c1_reg()->permissions());
+  // Set the wrong unsealing value.
+  c2_reg()->ResetSealingRoot();
+  c2_reg()->set_address(11);
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
+  // If c2 is sealed it fails.
+  c2_reg()->set_address(kDataSeal10);
+  EXPECT_TRUE(c2_reg()->Seal(*(state()->sealing_root()), kDataSeal10).ok());
+  inst()->Execute(nullptr);
+  EXPECT_EQ(c3_reg()->object_type(), OT::kUnsealed);
+  EXPECT_FALSE(c3_reg()->tag());
+  EXPECT_EQ(c3_reg()->permissions(), c1_reg()->permissions());
+}
+
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_m_instructions_test.cc b/cheriot/test/riscv_cheriot_m_instructions_test.cc
new file mode 100644
index 0000000..4874485
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_m_instructions_test.cc
@@ -0,0 +1,355 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_m_instructions.h"
+
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+#include "absl/random/random.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+// This file contains tests for individual CHERIoT RiscV32M instructions.
+
+namespace {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: used below (clang error).
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::cheriot::CheriotState;
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::Instruction;
+using CH_EC = ::mpact::sim::cheriot::ExceptionCode;
+using PB = CheriotRegister::PermissionBits;
+
+using ::mpact::sim::cheriot::MDiv;
+using ::mpact::sim::cheriot::MDivu;
+using ::mpact::sim::cheriot::MMul;
+using ::mpact::sim::cheriot::MMulh;
+using ::mpact::sim::cheriot::MMulhsu;
+using ::mpact::sim::cheriot::MMulhu;
+using ::mpact::sim::cheriot::MRem;
+using ::mpact::sim::cheriot::MRemu;
+
+constexpr char kC1[] = "c1";
+constexpr char kC2[] = "c2";
+constexpr char kC3[] = "c3";
+
+constexpr int kNumTests = 100;
+
+constexpr uint32_t kInstAddress = 0x2468;
+
+// The test fixture allocates a machine state object and an instruction object.
+// It also contains convenience methods for interacting with the instruction
+// object in a more short hand form.
+class RVCheriotMInstructionTest : public testing::Test {
+ public:
+  RVCheriotMInstructionTest() {
+    state_ = new CheriotState("test");
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    creg_1_ = state_->GetRegister<CheriotRegister>(kC1).first;
+    creg_2_ = state_->GetRegister<CheriotRegister>(kC2).first;
+    creg_3_ = state_->GetRegister<CheriotRegister>(kC3).first;
+  }
+
+  ~RVCheriotMInstructionTest() override {
+    delete state_;
+    delete instruction_;
+  }
+
+  // Appends the source and destination operands for the register names
+  // given in the two vectors.
+  void AppendRegisterOperands(Instruction *inst,
+                              const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  void AppendRegisterOperands(const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    AppendRegisterOperands(instruction_, sources, destinations);
+  }
+
+  // Appends immediate source operands with the given values.
+  template <typename T>
+  void AppendImmediateOperands(const std::vector<T> &values) {
+    for (auto value : values) {
+      auto *src = new ImmediateOperand<T>(value);
+      instruction_->AppendSource(src);
+    }
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      reg->set_address(value);
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+
+  // Returns the value of the named register.
+  template <typename T>
+  T GetRegisterValue(std::string_view reg_name) {
+    auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+    return reg->address();
+  }
+
+  CheriotState *state_;
+  Instruction *instruction_;
+  CheriotRegister *creg_1_;
+  CheriotRegister *creg_2_;
+  CheriotRegister *creg_3_;
+  absl::BitGen bitgen_;
+};
+
+TEST_F(RVCheriotMInstructionTest, MMul) {
+  instruction_->set_semantic_function(&MMul);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+    int32_t result =
+        static_cast<int32_t>(static_cast<int64_t>(a) * static_cast<int64_t>(b));
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MMulh) {
+  instruction_->set_semantic_function(&MMulh);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+    int32_t result = static_cast<int32_t>(
+        (static_cast<int64_t>(a) * static_cast<int64_t>(b)) >> 32);
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MMulhu) {
+  instruction_->set_semantic_function(&MMulhu);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    uint32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+    uint32_t result = static_cast<uint32_t>(
+        (static_cast<uint64_t>(a) * static_cast<uint64_t>(b)) >> 32);
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MMulhsu) {
+  instruction_->set_semantic_function(&MMulhsu);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+    int32_t result = static_cast<int32_t>(
+        (static_cast<int64_t>(a) * static_cast<uint64_t>(b)) >> 32);
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MDiv) {
+  instruction_->set_semantic_function(&MDiv);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+
+    int32_t result;
+    if (b == 0) {
+      result = -1;
+    } else if ((b == -1) && (a == std::numeric_limits<int32_t>::min())) {
+      result = std::numeric_limits<int32_t>::min();
+    } else {
+      result = a / b;
+    }
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MDivu) {
+  instruction_->set_semantic_function(&MDivu);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    uint32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+
+    uint32_t result;
+    if (b == 0) {
+      result = std::numeric_limits<uint32_t>::max();
+    } else {
+      result = a / b;
+    }
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MRem) {
+  instruction_->set_semantic_function(&MRem);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    int32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    int32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                              std::numeric_limits<int32_t>::min(),
+                              std::numeric_limits<int32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+
+    int32_t result;
+    if (b == 0) {
+      result = a;
+    } else if ((b == -1) && (a == std::numeric_limits<int32_t>::min())) {
+      result = 0;
+    } else {
+      result = a % b;
+    }
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+TEST_F(RVCheriotMInstructionTest, MRemu) {
+  instruction_->set_semantic_function(&MRemu);
+  AppendRegisterOperands({kC1, kC2}, {kC3});
+  for (int i = 0; i < kNumTests; i++) {
+    uint32_t a = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    uint32_t b = absl::Uniform(absl::IntervalClosed, bitgen_,
+                               std::numeric_limits<uint32_t>::min(),
+                               std::numeric_limits<uint32_t>::max());
+    SetRegisterValues<int32_t>({{kC1, a}, {kC2, b}});
+    creg_3_->ResetMemoryRoot();
+    instruction_->Execute(nullptr);
+
+    uint32_t result;
+    if (b == 0) {
+      result = a;
+    } else {
+      result = a % b;
+    }
+    EXPECT_EQ(creg_3_->address(), result);
+    EXPECT_FALSE(creg_3_->tag());
+    EXPECT_EQ(creg_3_->top(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->base(), creg_3_->address() & ~0x1ff);
+    EXPECT_EQ(creg_3_->permissions(), 0);
+    EXPECT_EQ(creg_3_->object_type(), 0);
+  }
+}
+
+}  // namespace
diff --git a/cheriot/test/riscv_cheriot_zicsr_instructions_test.cc b/cheriot/test/riscv_cheriot_zicsr_instructions_test.cc
new file mode 100644
index 0000000..a1792af
--- /dev/null
+++ b/cheriot/test/riscv_cheriot_zicsr_instructions_test.cc
@@ -0,0 +1,294 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/riscv_cheriot_zicsr_instructions.h"
+
+#include <cstdint>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "cheriot/cheriot_register.h"
+#include "cheriot/cheriot_state.h"
+#include "cheriot/riscv_cheriot_csr_enum.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/instruction.h"
+#include "riscv//riscv_csr.h"
+
+// This file contains tests for individual Zicsr instructions.
+
+namespace {
+
+using EC = ::mpact::sim::cheriot::ExceptionCode;
+using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::cheriot::CheriotState;
+using ::mpact::sim::cheriot::RiscVCheriotCsrEnum;
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::riscv::RiscV32SimpleCsr;
+
+constexpr uint32_t kInstAddress = 0x2468;
+
+constexpr char kX1[] = "x1";
+constexpr char kX3[] = "x3";
+
+constexpr uint32_t kMScratchValue =
+    static_cast<uint32_t>(RiscVCheriotCsrEnum::kMScratch);
+constexpr uint32_t kCycleValue =
+    static_cast<uint32_t>(RiscVCheriotCsrEnum::kCycle);
+
+// The test fixture allocates a machine state object and an instruction object.
+// It also contains convenience methods for interacting with the instruction
+// object in a more short hand form.
+class ZicsrInstructionsTest : public testing::Test {
+ protected:
+  ZicsrInstructionsTest() {
+    state_ = new CheriotState("test");
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    state_->set_on_trap([this](bool is_interrupt, uint64_t trap_value,
+                               uint64_t exception_code, uint64_t epc,
+                               const Instruction *inst) {
+      return TrapHandler(is_interrupt, trap_value, exception_code, epc, inst);
+    });
+  }
+
+  ~ZicsrInstructionsTest() override {
+    delete instruction_;
+    delete state_;
+  }
+
+  // Appends the source and destination operands for the register names
+  // given in the two vectors.
+  void AppendRegisterOperands(Instruction *inst,
+                              absl::Span<const std::string> sources,
+                              absl::Span<const std::string> destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  // Appends immediate source operands with the given values.
+  template <typename T>
+  void AppendImmediateOperands(Instruction *inst,
+                               const std::vector<T> &values) {
+    for (auto value : values) {
+      auto *src = new ImmediateOperand<T>(value);
+      inst->AppendSource(src);
+    }
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+      auto *db = state_->db_factory()->Allocate<CheriotRegister::ValueType>(1);
+      db->Set<T>(0, value);
+      reg->SetDataBuffer(db);
+      db->DecRef();
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+
+  // Returns the value of the named register.
+  template <typename T>
+  T GetRegisterValue(absl::string_view reg_name) {
+    auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+    return reg->data_buffer()->Get<T>(0);
+  }
+
+  // Handler for any traps that are raised. It is used to capture the trap
+  // information for checks.
+  bool TrapHandler(bool is_interrupt, uint64_t trap_value,
+                   uint64_t exception_code, uint64_t epc,
+                   const Instruction *inst);
+
+  RiscV32SimpleCsr *csr_;
+  CheriotState *state_;
+  Instruction *instruction_;
+  bool trap_taken_ = false;
+  bool trap_is_interrupt_ = false;
+  uint64_t trap_value_ = 0;
+  uint64_t trap_exception_code_ = 0;
+  uint64_t trap_epc_ = 0;
+  const Instruction *trap_inst_ = nullptr;
+};
+
+bool ZicsrInstructionsTest::TrapHandler(bool is_interrupt, uint64_t trap_value,
+                                        uint64_t exception_code, uint64_t epc,
+                                        const Instruction *inst) {
+  trap_taken_ = true;
+  trap_is_interrupt_ = is_interrupt;
+  trap_value_ = trap_value;
+  trap_exception_code_ = exception_code;
+  trap_epc_ = epc;
+  trap_inst_ = inst;
+  return true;
+}
+
+constexpr uint32_t kCsrValue1 = 0xaaaa5555;
+constexpr uint32_t kCsrValue2 = 0xa5a5a5a5;
+
+// The following tests all follow the same pattern. First the CSR and any
+// registers that are used are initialized with known values. Then the
+// instruction is initialized with the proper operands. The instruction is
+// executed, before checking the values of registers and CSR for correctness.
+
+// Tests the plain Csrrw/Csrrwi function.
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrw) {
+  auto result = state_->csr_set()->GetCsr(kMScratchValue);
+  CHECK_OK(result);
+  auto *csr = result.value();
+  CHECK_NE(csr, nullptr);
+  csr->Set(kCsrValue1);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendRegisterOperands(instruction_, {kX1}, {kX3});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrw);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1);
+
+  EXPECT_EQ(csr->AsUint32(), kCsrValue2);
+}
+
+// Tests the plain Csrrs/Csrrsi function.
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrs) {
+  auto result = state_->csr_set()->GetCsr(kMScratchValue);
+  CHECK_OK(result);
+  auto *csr = result.value();
+  CHECK_NE(csr, nullptr);
+  csr->Set(kCsrValue1);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendRegisterOperands(instruction_, {kX1}, {kX3});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrs);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1);
+  EXPECT_EQ(csr->AsUint32(), kCsrValue1 | kCsrValue2);
+}
+
+// Tests the plain Cssrrc/Csrrci function.
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrc) {
+  auto result = state_->csr_set()->GetCsr(kMScratchValue);
+  CHECK_OK(result);
+  auto *csr = result.value();
+  CHECK_NE(csr, nullptr);
+  csr->Set(kCsrValue1);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendRegisterOperands(instruction_, {kX1}, {kX3});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrc);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1);
+  EXPECT_EQ(csr->AsUint32(), kCsrValue1 & ~kCsrValue2);
+}
+
+// Tests Cssrw when the CSR register isn't read (register source is x0).
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrwNr) {
+  auto result = state_->csr_set()->GetCsr(kMScratchValue);
+  CHECK_OK(result);
+  auto *csr = result.value();
+  CHECK_NE(csr, nullptr);
+  csr->Set(kCsrValue1);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendRegisterOperands(instruction_, {kX1}, {kX3});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrwNr);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), 0);
+  EXPECT_EQ(csr->AsUint32(), kCsrValue2);
+}
+
+// Tests Cssr[wcs]i when the CSR register isn't written (immediate is 0).
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrNw) {
+  auto result = state_->csr_set()->GetCsr(kMScratchValue);
+  CHECK_OK(result);
+  auto *csr = result.value();
+  CHECK_NE(csr, nullptr);
+  csr->Set(kCsrValue1);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue});
+  AppendRegisterOperands(instruction_, {}, {kX3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrNw);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1);
+  EXPECT_EQ(csr->AsUint32(), kCsrValue1);
+}
+
+// Test for trap if accessing without required permission in pcc.
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrNwTrap) {
+  state_->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue});
+  AppendRegisterOperands(instruction_, {kX1}, {kX3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrNw);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_TRUE(trap_taken_);
+  EXPECT_FALSE(trap_is_interrupt_);
+  EXPECT_EQ(trap_value_,
+            (0b1'00000 << 5) | *EC::kCapExPermitAccessSystemRegistersViolation);
+  EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode);
+  EXPECT_EQ(trap_epc_, instruction_->address());
+  EXPECT_EQ(trap_inst_, trap_inst_);
+}
+
+// Test for no trap if accessing 'cycle' without required permission in pcc, as
+// a small subset of CSRs are user mode accessible, and thus does not require
+// pcc permission bit.
+TEST_F(ZicsrInstructionsTest, RiscVZiCsrrNwNoTrap) {
+  state_->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters);
+  SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}});
+  AppendImmediateOperands(instruction_, std::vector<uint32_t>{kCycleValue});
+  AppendRegisterOperands(instruction_, {kX1}, {kX3});
+  SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrNw);
+
+  instruction_->Execute(nullptr);
+
+  EXPECT_FALSE(trap_taken_);
+}
+
+}  // namespace
diff --git a/cheriot/test_rig_packets.h b/cheriot/test_rig_packets.h
new file mode 100644
index 0000000..53a5bc2
--- /dev/null
+++ b/cheriot/test_rig_packets.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MPACT_CHERIOT__TEST_RIG_PACKETS_H_
+#define MPACT_CHERIOT__TEST_RIG_PACKETS_H_
+
+#include <cstdint>
+
+namespace mpact::sim::cheriot {
+
+// Keep these structs and enum in a separate namespace.
+namespace test_rig {
+
+enum TraceCommand : uint8_t {
+  kEndOfTrace = 0,
+  kInstruction = 1,
+  kSetVersion = 0x76,
+};
+
+struct VersionPacket {
+  char version_text[8];
+  uint64_t version;
+  VersionPacket() : version_text{'v', 'e', 'r', 's', 'i', 'o', 'n', '='} {}
+};
+
+struct InstructionPacket {
+  // Instruction word. Sixteen bit instructions are stored in the lower half.
+  uint32_t rvfi_insn;
+  // Timestamp.
+  uint16_t rvfi_time;
+  // Trace command. Currently 0 = EndOfTrace, 1 = Instruction.
+  TraceCommand rvfi_cmd;
+  uint8_t padding;
+};
+
+struct ExecutionPacket {
+  // Instruction number: minstret value after completion.
+  uint64_t rvfi_order;
+  // Pc for current instruction.
+  uint64_t rvfi_pc_rdata;
+  // Pc after instruction (either PC + 4 or jump/trap target).
+  uint64_t rvfi_pc_wdata;
+  // Instruction word.
+  uint64_t rvfi_insn;
+  // Read register values.
+  uint64_t rvfi_rs1_data;
+  uint64_t rvfi_rs2_data;
+  // Write register value. Must be 0 if rvfi_rd_addr is 0.
+  uint64_t rvfi_rd_wdata;
+  // Memory address. Byte address (aligned if define is set). 0 if unused.
+  uint64_t rvfi_mem_addr;
+  // Read data (from memory).
+  uint64_t rvfi_mem_rdata;
+  // Write data (to memory).
+  uint64_t rvfi_mem_wdata;
+  // Read mask: indicates valid bytes read. 0 if unused.
+  uint8_t rvfi_mem_rmask;
+  // Write mask: indicates valid bytes written. 0 if unused.
+  uint8_t rvfi_mem_wmask;
+  // Rs1 source register id. Arbitrary when not used.
+  uint8_t rvfi_rs1_addr;
+  // Rs2 source register id. Arbitrary when not used.
+  uint8_t rvfi_rs2_addr;
+  // Destination register number - must be 0 if not used.
+  uint8_t rvfi_rd_addr;
+  // Marks an exception: invalid decode, misaligned access, or jump to
+  // misaligned address.
+  uint8_t rvfi_trap;
+  // Marks the last instruction retired before halting execution.
+  uint8_t rvfi_halt;
+  // Trap handler indicator. Set for first instruction in a trap handler.
+  uint8_t rvfi_intr;
+};
+
+// The test rig execution trace version 2 uses the following packets.
+
+enum Mode : uint8_t {
+  kUserMode = 0,
+  kSupervisorMode = 1,
+  kMachineMode = 3,
+};
+
+enum ModeXL : uint8_t {
+  kXL32 = 1,
+  kXL64 = 2,
+};
+
+struct ExecutionPacketMetaData {
+  // Set to the instruction index. No indices can be used twice and there must
+  // be no gaps. Instructions may be retired in a a reordered fashion.
+  uint64_t rvfi_order;
+  // Instruction word for the retired instruction. Upper bits are 0 for
+  // instruction words shorter than 64 bits.
+  uint64_t rvfi_insn;
+  // Must be set for an instruction that cannot be decoded as a legal
+  // instruction. Must also be set for a misaligned memory read or write, or
+  // other memory access violations. Must also be set for a jump instruction
+  // that jumps to a misaligned location.
+  uint8_t rvfi_trap;
+  // Set for the last instruction before halting execution.
+  uint8_t rvfi_halt;
+  // Set for the first instruction in a trap handler.
+  uint8_t rvfi_intr;
+  // Set to the current privilege level 0=U, 1=S, 2=reserved, 3=M.
+  uint8_t rvfi_mode;
+  // Set to the value of MXL/SXL/UXL in the current privilege level: 1=32, 2=64.
+  uint8_t rvfi_ixl;
+  // Should be set to 1.
+  uint8_t rvfi_valid;
+  // Padding to make the size a multiple of 8 bytes.
+  uint8_t rvfi_padding[2];
+};
+
+struct ExecutionPacketPC {
+  // Pc for current instruction.
+  uint64_t rvfi_pc_rdata;
+  // Pc after instruction (either PC + 4 or jump/trap target).
+  uint64_t rvfi_pc_wdata;
+};
+
+struct ExecutionPacketExtInteger {
+  // Magic bytes, must be "int-data".
+  char magic[8];
+  // The value of the x register addressed by rd after execution.
+  uint64_t rvfi_rd_wdata;
+  // The value of the x register addressed by rs1 before execution. Must be zero
+  // when rs1 is zero.
+  uint64_t rvfi_rs1_rdata;
+  // The value of the x register addressed by rs2 before execution. Must be zero
+  // when rs2 is zero.
+  uint64_t rvfi_rs2_rdata;
+  // The decoded rd register address for the instruction. Must be zero if the
+  // instruction does not write to rd.
+  uint8_t rvfi_rd_addr;
+  // The decoded rs1 register address for the instruction. Must be zero if the
+  // instruction does not read rs1.
+  uint8_t rvfi_rs1_addr;
+  // The decoded rs2 register address for the instruction. Must be zero if the
+  // instruction does not read rs2.
+  uint8_t rvfi_rs2_addr;
+  // Padding to make the size a multiple of 8 bytes.
+  uint8_t padding[5];
+  ExecutionPacketExtInteger() : magic{'i', 'n', 't', '-', 'd', 'a', 't', 'a'} {}
+};
+
+struct ExecutionPacketExtMemAccess {
+  // Magic bytes, must be "mem-data".
+  char magic[8];
+  // Data read from memory.
+  uint64_t rvfi_mem_rdata[4];
+  // Data written to memory.
+  uint64_t rvfi_mem_wdata[4];
+  // Bitmask for which bytes in rdata are valid.
+  uint32_t rvfi_mem_rmask;
+  // Bitmask for which bytes in wdata are valid.
+  uint32_t rvfi_mem_wmask;
+  // Address of the accessed memory location (when either rmask or wmask is
+  // non-zero).
+  uint64_t rvfi_mem_addr;
+  ExecutionPacketExtMemAccess()
+      : magic{'m', 'e', 'm', '-', 'd', 'a', 't', 'a'} {}
+};
+
+enum AvailableFieldsEnum : uint64_t {
+  kIntegerData = 0x1,
+  kMemoryAccess = 0x2,
+};
+
+struct ExecutionPacketV2 {
+  // Magic bytes, must be "trace-v2".
+  char magic[8];
+  // Size of the trace packet + extensions.
+  uint64_t trace_size;
+  ExecutionPacketMetaData basic_data;
+  ExecutionPacketPC pc_data;
+  // Bit mask showing which extension fields will follow this packet.
+  uint64_t available_fields;
+  ExecutionPacketV2() : magic{'t', 'r', 'a', 'c', 'e', '-', 'v', '2'} {}
+};
+
+}  // namespace test_rig
+
+}  // namespace mpact::sim::cheriot
+
+#endif  // MPACT_CHERIOT__TEST_RIG_PACKETS_H_
diff --git a/riscv_cheriot.bin_fmt b/riscv_cheriot.bin_fmt
new file mode 100644
index 0000000..7ad4346
--- /dev/null
+++ b/riscv_cheriot.bin_fmt
@@ -0,0 +1,487 @@
+// RiscV 32 bit CHERIoT instruction decoder.
+decoder RiscVCheriot {
+  namespace mpact::sim::cheriot::encoding;
+  opcode_enum = "isa32::OpcodeEnum";
+  includes {
+    #include "cheriot/riscv_cheriot_decoder.h"
+  }
+  RiscVCheriotInst32;
+  RiscVCheriotInst16;
+};
+
+format Inst32Format[32] {
+  fields:
+    unsigned bits[25];
+    unsigned opcode[7];
+};
+
+// 3 register instruction format.
+format RType[32] : Inst32Format {
+  fields:
+    unsigned func7[7];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+// 2 register instruction format.
+format R2Type[32] : Inst32Format {
+  fields:
+    unsigned func7[7];
+    unsigned func5[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned r_uimm5[5] = func5;
+};
+
+// 4 register instruction format.
+format R4Type[32] : Inst32Format {
+  fields:
+    unsigned rs3[5];
+    unsigned func2[2];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+// Immediate instruction format.
+format IType[32] : Inst32Format {
+  fields:
+    signed imm12[12];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned u_imm12[12] = imm12;
+};
+
+// 2 immediate instruction format.
+format I2Type[32] : Inst32Format {
+  fields:
+    signed imm12[12];
+    unsigned i_uimm5[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned u_imm12[12] = imm12;
+};
+
+// 5 bit immediate instruction format.
+format I5Type[32] : Inst32Format {
+  fields:
+    unsigned func7[7];
+    unsigned r_uimm5[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+// Store instruction format.
+format SType[32] : Inst32Format {
+  fields:
+    unsigned imm7[7];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned imm5[5];
+    unsigned opcode[7];
+  overlays:
+    signed s_imm[12] = imm7, imm5;
+};
+
+// Branch instruction format.
+format BType[32] : Inst32Format {
+  fields:
+    unsigned imm7[7];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned imm5[5];
+    unsigned opcode[7];
+  overlays:
+    signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
+};
+
+// Long immediate instruction format.
+format UType[32] : Inst32Format {
+  fields:
+    unsigned imm20[20];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned u_imm[32] = imm20, 0b0000'0000'0000;
+    signed s_imm[31] = imm20, 0b000'0000'0000;
+};
+
+// Jump instruction format.
+format JType[32] : Inst32Format {
+  fields:
+    unsigned imm20[20];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
+};
+
+// Fence instruction format.
+format Fence[32] : Inst32Format {
+  fields:
+    unsigned fm[4];
+    unsigned pred[4];
+    unsigned succ[4];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+// Atomic instruction format.
+format AType[32] : Inst32Format {
+  fields:
+    unsigned func5[5];
+    unsigned aq[1];
+    unsigned rl[1];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+instruction group RiscVCheriotInst32[32] : Inst32Format {
+  lui    : UType  : opcode == 0b011'0111;
+  beq    : BType  : func3 == 0b000, opcode == 0b110'0011;
+  bne    : BType  : func3 == 0b001, opcode == 0b110'0011;
+  blt    : BType  : func3 == 0b100, opcode == 0b110'0011;
+  bge    : BType  : func3 == 0b101, opcode == 0b110'0011;
+  bltu   : BType  : func3 == 0b110, opcode == 0b110'0011;
+  bgeu   : BType  : func3 == 0b111, opcode == 0b110'0011;
+  lb     : IType  : func3 == 0b000, opcode == 0b000'0011;
+  lh     : IType  : func3 == 0b001, opcode == 0b000'0011;
+  lw     : IType  : func3 == 0b010, opcode == 0b000'0011;
+  lbu    : IType  : func3 == 0b100, opcode == 0b000'0011;
+  lhu    : IType  : func3 == 0b101, opcode == 0b000'0011;
+  sb     : SType  : func3 == 0b000, opcode == 0b010'0011;
+  sh     : SType  : func3 == 0b001, opcode == 0b010'0011;
+  sw     : SType  : func3 == 0b010, opcode == 0b010'0011;
+  addi   : IType  : func3 == 0b000, opcode == 0b001'0011;
+  slti   : IType  : func3 == 0b010, opcode == 0b001'0011;
+  sltiu  : IType  : func3 == 0b011, opcode == 0b001'0011;
+  xori   : IType  : func3 == 0b100, opcode == 0b001'0011;
+  ori    : IType  : func3 == 0b110, opcode == 0b001'0011;
+  andi   : IType  : func3 == 0b111, opcode == 0b001'0011;
+  slli   : I5Type : func7 == 0b000'0000, func3==0b001, opcode == 0b001'0011;
+  srli   : I5Type : func7 == 0b000'0000, func3==0b101, opcode == 0b001'0011;
+  srai   : I5Type : func7 == 0b010'0000, func3==0b101, opcode == 0b001'0011;
+  add    : RType  : func7 == 0b000'0000, func3==0b000, opcode == 0b011'0011;
+  sub    : RType  : func7 == 0b010'0000, func3==0b000, opcode == 0b011'0011;
+  sll    : RType  : func7 == 0b000'0000, func3==0b001, opcode == 0b011'0011;
+  slt    : RType  : func7 == 0b000'0000, func3==0b010, opcode == 0b011'0011;
+  sltu   : RType  : func7 == 0b000'0000, func3==0b011, opcode == 0b011'0011;
+  xor    : RType  : func7 == 0b000'0000, func3==0b100, opcode == 0b011'0011;
+  srl    : RType  : func7 == 0b000'0000, func3==0b101, opcode == 0b011'0011;
+  sra    : RType  : func7 == 0b010'0000, func3==0b101, opcode == 0b011'0011;
+  or     : RType  : func7 == 0b000'0000, func3==0b110, opcode == 0b011'0011;
+  and    : RType  : func7 == 0b000'0000, func3==0b111, opcode == 0b011'0011;
+  fence  : Fence  : func3 == 0b000, opcode == 0b000'1111;
+  ecall  : Inst32Format : bits == 0b0000'0000'0000'00000'000'00000, opcode == 0b111'0011;
+  ebreak : Inst32Format : bits == 0b0000'0000'0001'00000'000'00000, opcode == 0b111'0011;
+  // Cheriot instructions.
+  cheriot_auicgp : UType : opcode == 0x7b;
+  cheriot_auipcc : UType : opcode == 0x17;
+  cheriot_andperm : RType  : func7 == 0x0d, func3 == 0, opcode == 0x5b;
+  cheriot_cleartag : R2Type : func7 == 0x7f, func5 == 0x0b, func3 == 0, opcode == 0x5b;
+  cheriot_getaddr  : R2Type : func7 == 0x7f, func5 == 0x0f, func3 == 0, opcode == 0x5b;
+  cheriot_getbase  : R2Type : func7 == 0x7f, func5 == 0x02, func3 == 0, opcode == 0x5b;
+  cheriot_gethigh  : R2Type : func7 == 0x7f, func5 == 0x17, func3 == 0, opcode == 0x5b;
+  cheriot_getlen   : R2Type : func7 == 0x7f, func5 == 0x03, func3 == 0, opcode == 0x5b;
+  cheriot_getperm  : R2Type : func7 == 0x7f, func5 == 0x00, func3 == 0, opcode == 0x5b;
+  cheriot_gettag   : R2Type : func7 == 0x7f, func5 == 0x04, func3 == 0, opcode == 0x5b;
+  cheriot_gettop   : R2Type : func7 == 0x7f, func5 == 0x18, func3 == 0, opcode == 0x5b;
+  cheriot_gettype  : R2Type : func7 == 0x7f, func5 == 0x01, func3 == 0, opcode == 0x5b;
+  cheriot_incaddr  : RType : func7 == 0x11, func3 == 0, opcode == 0x5b;
+  cheriot_incaddrimm : IType : func3 == 0x1, opcode == 0x5b;
+  cheriot_jal  : JType : rd != 0, opcode == 0x6f;
+  cheriot_j    : JType : rd == 0, opcode == 0x6f;
+  cheriot_jalr : IType : func3 == 0x0, rd != 0, opcode == 0x67;
+  cheriot_jr   : IType : func3 == 0x0, rd == 0, opcode == 0x67;
+  cheriot_lc   : IType : func3 == 0x3, opcode == 0x03;
+  cheriot_move : RType : func7 == 0x7f, rs2 == 0xa, func3 == 0, opcode == 0x5b;
+  cheriot_representablealignmentmask : R2Type : func7 == 0x7f, func5 == 0x9, func3 == 0, opcode == 0x5b;
+  cheriot_roundrepresentablelength :   R2Type : func7 == 0x7f, func5 == 0x8, func3 == 0, opcode == 0x5b;
+  cheriot_sc             : SType : func3 == 0x3, opcode == 0x23;
+  cheriot_seal           : RType : func7 == 0x0b, func3 == 0, opcode == 0x5b;
+  cheriot_setaddr        : RType : func7 == 0x10, func3 == 0, opcode == 0x5b;
+  cheriot_setbounds      : RType : func7 == 0x08, func3 == 0, opcode == 0x5b;
+  cheriot_setboundsexact : RType : func7 == 0x09, func3 == 0, opcode == 0x5b;
+  cheriot_setboundsimm   : IType : func3 == 0x02, opcode == 0x5b;
+  cheriot_setequalexact  : RType : func7 == 0x21, func3 == 0, opcode == 0x5b;
+  cheriot_sethigh        : RType : func7 == 0x16, func3 == 0, opcode == 0x5b;
+  cheriot_specialr       : R2Type : func7 == 0x01, func5 >= 28, func5 <= 31, func3 == 0, rs1 == 0, opcode == 0x5b;
+  cheriot_specialrw      : R2Type : func7 == 0x01, func5 >= 28, func5 <= 31, func3 == 0, rs1 != 0, opcode == 0x5b;
+  cheriot_sub            : RType : func7 == 0x14, func3 == 0, opcode == 0x5b;
+  cheriot_testsubset     : RType : func7 == 0x20, func3 == 0, opcode == 0x5b;
+  cheriot_unseal         : RType : func7 == 0x0c, func3 == 0, opcode == 0x5b;
+  // RiscV32 Instruction fence.
+  // fencei : IType  : func3 == 001, opcode == 0b000'1111;
+  // RiscV32 multiply divide.
+  mul    : RType  : func7 == 0b000'0001, func3 == 0b000, opcode == 0b011'0011;
+  mulh   : RType  : func7 == 0b000'0001, func3 == 0b001, opcode == 0b011'0011;
+  mulhsu : RType  : func7 == 0b000'0001, func3 == 0b010, opcode == 0b011'0011;
+  mulhu  : RType  : func7 == 0b000'0001, func3 == 0b011, opcode == 0b011'0011;
+  div    : RType  : func7 == 0b000'0001, func3 == 0b100, opcode == 0b011'0011;
+  divu   : RType  : func7 == 0b000'0001, func3 == 0b101, opcode == 0b011'0011;
+  rem    : RType  : func7 == 0b000'0001, func3 == 0b110, opcode == 0b011'0011;
+  remu   : RType  : func7 == 0b000'0001, func3 == 0b111, opcode == 0b011'0011;
+  // RiscV32 atomic instructions.
+  lrw       : AType  : func5 == 0b0'0010, rs2 == 0, func3 == 0b010, opcode == 0b010'1111;
+  scw       : AType  : func5 == 0b0'0011, func3 == 0b010, opcode == 0b010'1111;
+  amoswapw : AType  : func5 == 0b0'0001,  func3 == 0b010, opcode == 0b010'1111;
+  amoaddw  : AType  : func5 == 0b0'0000, func3 == 0b010, opcode == 0b010'1111;
+  amoxorw  : AType  : func5 == 0b0'0100, func3 == 0b010, opcode == 0b010'1111;
+  amoandw  : AType  : func5 == 0b0'1100, func3 == 0b010, opcode == 0b010'1111;
+  amoorw   : AType  : func5 == 0b0'1000, func3 == 0b010, opcode == 0b010'1111;
+  amominw  : AType  : func5 == 0b1'0000, func3 == 0b010, opcode == 0b010'1111;
+  amomaxw  : AType  : func5 == 0b1'0100, func3 == 0b010, opcode == 0b010'1111;
+  amominuw : AType  : func5 == 0b1'1000, func3 == 0b010, opcode == 0b010'1111;
+  amomaxuw : AType  : func5 == 0b1'1100, func3 == 0b010, opcode == 0b010'1111;
+  // RiscV32 single precision floating point instructions.
+  flw      : IType  : func3 == 0b010, opcode == 0b000'0111;
+  fsw      : SType  : func3 == 0b010, opcode == 0b010'0111;
+  fmadd_s  : R4Type : func2 == 0b00,  opcode == 0b100'0011;
+  fmsub_s  : R4Type : func2 == 0b00,  opcode == 0b100'0111;
+  fnmsub_s : R4Type : func2 == 0b00,  opcode == 0b100'1011;
+  fnmadd_s : R4Type : func2 == 0b00,  opcode == 0b100'1111;
+  fadd_s   : RType  : func7 == 0b000'0000, opcode == 0b101'0011;
+  fsub_s   : RType  : func7 == 0b000'0100, opcode == 0b101'0011;
+  fmul_s   : RType  : func7 == 0b000'1000, opcode == 0b101'0011;
+  fdiv_s   : RType  : func7 == 0b000'1100, opcode == 0b101'0011;
+  fsqrt_s  : R2Type  : func7 == 0b010'1100, func5 == 0, opcode == 0b101'0011;
+  fsgnj_s  : RType  : func7 == 0b001'0000, func3 == 0b000, opcode == 0b101'0011;
+  fsgnjn_s : RType  : func7 == 0b001'0000, func3 == 0b001, opcode == 0b101'0011;
+  fsgnjx_s : RType  : func7 == 0b001'0000, func3 == 0b010, opcode == 0b101'0011;
+  fmin_s   : RType  : func7 == 0b001'0100, func3 == 0b000, opcode == 0b101'0011;
+  fmax_s   : RType  : func7 == 0b001'0100, func3 == 0b001, opcode == 0b101'0011;
+  fcvt_ws  : R2Type  : func7 == 0b110'0000, func5 == 0, opcode == 0b101'0011;
+  fcvt_wus : R2Type  : func7 == 0b110'0000, func5 == 1, opcode == 0b101'0011;
+  fmv_xw   : R2Type  : func7 == 0b111'0000, func5 == 0, func3 == 0b000, opcode == 0b101'0011;
+  fcmpeq_s : RType  : func7 == 0b101'0000, func3 == 0b010, opcode == 0b101'0011;
+  fcmplt_s : RType  : func7 == 0b101'0000, func3 == 0b001, opcode == 0b101'0011;
+  fcmple_s : RType  : func7 == 0b101'0000, func3 == 0b000, opcode == 0b101'0011;
+  fclass_s : R2Type  : func7 == 0b111'0000, func5 == 0, func3 == 0b001, opcode == 0b101'0011;
+  fcvt_sw  : R2Type  : func7 == 0b110'1000, func5 == 0, opcode == 0b101'0011;
+  fcvt_swu : R2Type  : func7 == 0b110'1000, func5 == 1, opcode == 0b101'0011;
+  fmv_wx   : R2Type  : func7 == 0b111'1000, func5 == 0, func3 == 0b000, opcode == 0b101'0011;
+  // RiscV32 CSR manipulation instructions.
+  csrrw    : IType  : func3 == 0b001, rd != 0,  opcode == 0b111'0011;
+  csrrs    : IType  : func3 == 0b010, rs1 != 0, rd != 0, opcode == 0b111'0011;
+  csrrc    : IType  : func3 == 0b011, rs1 != 0, rd != 0, opcode == 0b111'0011;
+  csrrs_nr : IType  : func3 == 0b010, rs1 != 0, rd == 0, opcode == 0b111'0011;
+  csrrc_nr : IType  : func3 == 0b011, rs1 != 0, rd == 0, opcode == 0b111'0011;
+  csrrw_nr : IType  : func3 == 0b001, rd == 0,  opcode == 0b111'0011;
+  csrrs_nw : IType  : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
+  csrrc_nw : IType  : func3 == 0b011, rs1 == 0, opcode == 0b111'0011;
+  csrrwi   : I2Type : func3 == 0b101, rd != 0,  opcode == 0b111'0011;
+  csrrsi   : I2Type : func3 == 0b110, i_uimm5 != 0, rd != 0, opcode == 0b111'0011;
+  csrrci   : I2Type : func3 == 0b111, i_uimm5 != 0, rd != 0, opcode == 0b111'0011;
+  csrrsi_nr: I2Type : func3 == 0b110, i_uimm5 != 0, rd == 0, opcode == 0b111'0011;
+  csrrci_nr: I2Type : func3 == 0b111, i_uimm5 != 0, rd == 0, opcode == 0b111'0011;
+  csrrwi_nr: I2Type : func3 == 0b101, rd == 0,  opcode == 0b111'0011;
+  csrrsi_nw: I2Type : func3 == 0b110, i_uimm5 == 0, opcode == 0b111'0011;
+  csrrci_nw: I2Type : func3 == 0b111, i_uimm5 == 0, opcode == 0b111'0011;
+  // RiscV32 Privileged instructions.
+  mret    : Inst32Format  : bits == 0b001'1000'00010'00000'000'00000, opcode == 0b111'0011;
+  wfi     : Inst32Format  : bits == 0b000'1000'00101'00000'000'00000, opcode == 0b111'0011;
+  // sfence_vma_zz : RType : func7 == 0b000'1001, rs2 == 0, rs1 == 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+  // sfence_vma_zn : RType : func7 == 0b000'1001, rs2 != 0, rs1 == 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+  // sfence_vma_nz : RType : func7 == 0b000'1001, rs2 == 0, rs1 != 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+  // sfence_vma_nn : RType : func7 == 0b000'1001, rs2 != 0, rs1 != 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+};
+
+// Compact instruction formats.
+
+format Inst16Format[16] {
+  fields:
+    unsigned func3[3];
+    unsigned bits[11];
+    unsigned op[2];
+};
+
+format CSS[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm6[6];
+    unsigned rs2[5];
+    unsigned op[2];
+  overlays:
+    unsigned css_imm_w[8] = imm6[1..0], imm6[5..2], 0b00;
+    unsigned css_imm_d[9] = imm6[2..0], imm6[5..3], 0b000;
+};
+
+format CL[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm2[2];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rd[5] = 0b01, rdp;
+    unsigned cl_imm_w[7] = imm2[0], imm3, imm2[1], 0b00;
+    unsigned cl_imm_d[8] = imm2, imm3, 0b000;
+};
+
+format CS[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm2[2];
+    unsigned rs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, rs2p;
+    unsigned cs_imm_w[7] = imm2[0], imm3, imm2[1], 0b00;
+    unsigned cs_imm_d[8] = imm2, imm3, 0b000;
+};
+
+format CJ[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm11[11];
+    unsigned op[2];
+  overlays:
+    signed jimm[12] = imm11[10, 6, 8..7, 4, 5, 0, 9, 3..1], 0b0;
+};
+
+format CR[16] : Inst16Format {
+  fields:
+    unsigned func4[4];
+    unsigned rs1[5];
+    unsigned rs2[5];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = rs1;
+};
+
+// Compact shift immediate instructions.
+format CSH[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm1[1];
+    unsigned op2[2];
+    unsigned rs1p[3];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned uimm6[6] = imm1, imm5;
+    signed imm6[6] = imm1, imm5;
+    unsigned imm3[3] = imm1, op2;
+    unsigned rd[5] = 0b01, rs1p;
+    unsigned rs1[5] = 0b01, rs1p;
+};
+
+// Compact branch instructions.
+format CB[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    signed bimm[9] = imm3[2], imm5[4..3, 0], imm3[1..0], imm5[2..1], 0b0;
+};
+
+format CI[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm1[1];
+    unsigned rs1[5];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = rs1;
+    signed imm6[6] = imm1, imm5;
+    unsigned uimm6[6] = imm1, imm5;
+    signed imm18[18] = imm1, imm5, 0b0000'0000'0000;
+    signed ci_imm10[10] = imm1, imm5[2..1, 3, 0, 4], 0b0000;
+    unsigned ci_imm_w[8] = imm5[1..0], imm1, imm5[4..2], 0b00;
+    unsigned ci_imm_d[9] = imm5[2..0], imm1, imm5[4..3], 0b000;
+};
+
+format CIW[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm8[8];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = 0b01, rdp;
+    unsigned ciw_imm10[10] = imm8[5..2, 7..6, 0, 1], 0b00;
+};
+
+format CA[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func2[2];
+    unsigned fs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, fs2p;
+    unsigned rd[5] = 0b01, rs1p;
+};
+
+// Compact instruction encodings.
+instruction group RiscVCheriotInst16[16] : Inst16Format {
+  caddi4spn : CIW: func3 == 0b000, op == 0b00, imm8 != 0;
+  clw       : CL : func3 == 0b010, op == 0b00;
+  cld       : CL : func3 == 0b011, op == 0b00;
+  csw       : CS : func3 == 0b110, op == 0b00;
+  csd       : CS : func3 == 0b111, op == 0b00;
+  cnop      : CI : func3 == 0b000, imm1 == 0, rs1 == 0, imm5 == 0, op == 0b01;
+  caddi     : CI : func3 == 0b000, imm6 != 0, rd != 0, op == 0b01;
+  cli       : CI : func3 == 0b010, rd != 0, op == 0b01;
+  caddi16sp : CI : func3 == 0b011, ci_imm10 != 0, rd == 2, op == 0b01;
+  clui      : CI : func3 == 0b011, rd != 0, rd != 2, imm18 != 0, op == 0b01;
+  // TODO(torerik): The following two instructions should have imm1 == 0  and 
+  // imm5 != 0 instead of imm6 != 0 in the constraints, but that has been 
+  // temporarily removed so as to make it easier to verify in TestRIG 
+  csrli     : CSH : func3 == 0b100, op2 == 0b00, uimm6 != 0, op == 0b01;
+  csrai     : CSH : func3 == 0b100, op2 == 0b01, uimm6 != 0, op == 0b01;
+  candi     : CSH : func3 == 0b100, op2 == 0b10, op == 0b01;
+  csub      : CA : func6 == 0b100'011, func2 == 0b00, op == 0b01;
+  cxor      : CA : func6 == 0b100'011, func2 == 0b01, op == 0b01;
+  cor       : CA : func6 == 0b100'011, func2 == 0b10, op == 0b01;
+  cand      : CA : func6 == 0b100'011, func2 == 0b11, op == 0b01;
+  cbeqz     : CB : func3 == 0b110, op == 0b01;
+  cbnez     : CB : func3 == 0b111, op == 0b01;
+  cslli     : CI : func3 == 0b000, imm1 == 0, imm5 != 0, rs1 != 0, op == 0b10;
+  clwsp     : CI : func3 == 0b010, rd != 0, op == 0b10;
+  cldsp     : CI : func3 == 0b011, rd != 0, op == 0b10;
+  cmv       : CR : func4 == 0b1000, rs1 != 0, rs2 != 0, op == 0b10;
+  cebreak   : Inst16Format : func3 == 0b100, bits == 0b1'00000'00000, op == 0b10;
+  cadd      : CR : func4 == 0b1001, rs1 != 0, rs2 != 0, op == 0b10;
+  cswsp     : CSS: func3 == 0b110, op == 0b10;
+  csdsp     : CSS: func3 == 0b111, op == 0b10;
+  cheriot_cj    : CJ : func3 == 0b101, op == 0b01;
+  cheriot_cjal  : CJ : func3 == 0b001, op == 0b01;
+  cheriot_cjr   : CR : func4 == 0b1000, rs1 != 0, rs2 == 0, op == 0b10;
+  cheriot_cjalr : CR : func4 == 0b1001, rs1 != 0, rs2 == 0, op == 0b10;
+};
diff --git a/riscv_cheriot.isa b/riscv_cheriot.isa
new file mode 100644
index 0000000..28fec73
--- /dev/null
+++ b/riscv_cheriot.isa
@@ -0,0 +1,667 @@
+// This file contains the ISA description for the RiscV32G architecture.
+
+includes {
+#include "absl/functional/bind_front.h"
+}
+
+// First disasm field is 18 char wide and left justified.
+disasm widths = {-18};
+
+int global_latency = 0;
+
+isa RiscVCheriot {
+  namespace mpact::sim::cheriot::isa32;
+  slots { riscv32_cheriot; }
+}
+
+// Basic integer ALU instructions, part of the RiscV 32i subset.
+slot riscv32i {
+  includes {
+    #include "cheriot/riscv_cheriot_i_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    addi{: rs1, I_imm12 : rd},
+      disasm: "addi", "%rd, %rs1, %I_imm12",
+      semfunc: "&RiscVIAdd";
+    slti{: rs1, I_imm12 : rd},
+      disasm: "slti", "%rd, %rs1, %I_imm12",
+      semfunc: "&RiscVISlt";
+    sltiu{: rs1, I_imm12 : rd},
+      disasm: "sltiu", "%rd, %rs1, %I_imm12",
+      semfunc: "&RiscVISltu";
+    andi{: rs1, I_imm12 : rd},
+      disasm: "andi", "%rd, %rs1, %I_imm12",
+      semfunc: "&RiscVIAnd";
+    ori{: rs1, I_imm12 : rd},
+      disasm: "ori", "%rd, %rs1, %I_imm12",
+      semfunc: "&RiscVIOr";
+    xori{: rs1, I_imm12 : rd},
+      disasm: "xori", "%rd, %rs1, %I_imm12",
+      semfunc: "&RiscVIXor";
+    slli{: rs1, I_uimm5 : rd},
+      disasm: "slli", "%rd, %rs1, 0x%(I_uimm5:x)",
+      semfunc: "&RiscVISll";
+    srli{: rs1, I_uimm5 : rd},
+      disasm: "srli", "%rd  %rs1, 0x%(I_uimm5:x)",
+      semfunc: "&RiscVISrl";
+    srai{: rs1, I_uimm5 : rd},
+      disasm: "srai", "%rd, %rs1, 0x%(I_uimm5:x)",
+      semfunc: "&RiscVISra";
+    lui{: U_imm20 : rd},
+      disasm: "lui", "%rd, 0x%(U_imm20:8x)",
+      semfunc: "&RiscVILui";
+    add{: rs1, rs2 : rd},
+      disasm: "add", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVIAdd";
+    slt{: rs1, rs2 : rd},
+      disasm: "slt", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVISlt";
+    sltu{: rs1, rs2 : rd},
+      disasm: "sltu", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVISltu";
+    and{: rs1, rs2 : rd},
+      disasm: "and", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVIAnd";
+    or{: rs1, rs2 : rd},
+      disasm: "or", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVIOr";
+    xor{: rs1, rs2 : rd},
+      disasm: "xor", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVIXor";
+    sll{: rs1, rs2 : rd},
+      disasm: "sll", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVISll";
+    srl{: rs1, rs2 : rd},
+      disasm: "srl", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVISrl";
+    sub{: rs1, rs2 : rd},
+      disasm: "sub", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVISub";
+    sra{: rs1, rs2 : rd},
+      disasm: "sra", "%rd, %rs1, %rs2",
+      semfunc: "&RiscVISra";
+    nop{},
+      disasm: "nop",
+      semfunc: "&RiscVINop";
+    hint{},
+      disasm: "hint",
+      semfunc: "&RiscVINop";
+    beq{: rs1, rs2, B_imm12 : },
+      disasm: "beq", "%rs1, %rs2, %(@+B_imm12:08x)",
+      semfunc: "&RiscVIBeq";
+    bne{: rs1, rs2, B_imm12 : },
+      disasm: "bne", "%rs1, %rs2, %(@+B_imm12:08x)",
+      semfunc: "&RiscVIBne";
+    blt{: rs1, rs2, B_imm12 : },
+      disasm: "blt", "%rs1, %rs2, %(@+B_imm12:08x)",
+      semfunc: "&RiscVIBlt";
+    bltu{: rs1, rs2, B_imm12 : },
+      disasm: "bltu", "%rs1, %rs2, %(@+B_imm12:08x)",
+      semfunc: "&RiscVIBltu";
+    bge{: rs1, rs2, B_imm12 : },
+      disasm: "bge", "%rs1, %rs2, %(@+B_imm12:08x)",
+      semfunc: "&RiscVIBge";
+    bgeu{: rs1, rs2, B_imm12 : },
+      disasm: "bgeu", "%rs1, %rs2, %(@+B_imm12:08x)",
+      semfunc: "&RiscVIBgeu";
+    lw{(: cs1, I_imm12), (: : rd)},
+      disasm: "clw", "%rd, %I_imm12(%cs1)",
+      semfunc: "&RiscVILw", "&RiscVILwChild";
+    lh{(: cs1, I_imm12 :), (: : rd)},
+      disasm: "clh", "%rd, %I_imm12(%cs1)",
+      semfunc: "&RiscVILh", "&RiscVILhChild";
+    lhu{(: cs1, I_imm12 :), (: : rd)},
+      disasm: "clhu", "%rd, %I_imm12(%cs1)",
+      semfunc: "&RiscVILhu", "&RiscVILhuChild";
+    lb{(: cs1, I_imm12 :), (: : rd)},
+      disasm: "clb", "%rd, %I_imm12(%cs1)",
+      semfunc: "&RiscVILb", "&RiscVILbChild";
+    lbu{(: cs1, I_imm12 :), (: : rd)},
+      disasm: "clbu", "%rd, %I_imm12(%cs1)",
+      semfunc: "&RiscVILbu", "&RiscVILbuChild";
+    sw{: cs1, S_imm12, rs2 : },
+      disasm: "csw", "%rs2, %S_imm12(%cs1)",
+      semfunc: "&RiscVISw";
+    sh{: cs1, S_imm12, rs2 : },
+      disasm: "csh", "%rs2, %S_imm12(%cs1)",
+      semfunc: "&RiscVISh";
+    sb{: cs1, S_imm12, rs2 : },
+      disasm: "csb", "%rs2, %S_imm12(%cs1)",
+      semfunc: "&RiscVISb";
+    fence{: I_imm12 : },
+      disasm: "fence",
+      semfunc: "&RiscVIFence";
+    ecall{},
+      disasm: "ecall",
+      semfunc: "&RiscVIEcall";
+    ebreak{},
+      disasm: "ebreak",
+      semfunc: "&RiscVIEbreak";
+  }
+}
+
+slot cheriot {
+  includes {
+    #include "cheriot/riscv_cheriot_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    cheriot_auicgp{: cgp, S_imm20 : cd},
+      disasm: "auicgp", "%cd, 0x%(S_imm20:08x)",
+      semfunc: "&CheriotAuicap";
+    cheriot_auipcc{: pcc, S_imm20 : cd},
+      disasm: "auipcc", "%cd, 0x%(S_imm20:08x)",
+      semfunc: "&CheriotAuicap";
+    cheriot_andperm{: cs1, rs2 : cd},
+      disasm: "candperm", "%cd, %cs1, %rs2",
+      semfunc: "&CheriotCAndPerm";
+    cheriot_cleartag{: cs1 : cd},
+      disasm: "ccleartag", "%cd, %cs1",
+      semfunc: "&CheriotCClearTag";
+    cheriot_getaddr{: cs1 : rd},
+      disasm: "cgetaddr", "%rd, %cs1",
+      semfunc: "&CheriotCGetAddr";
+    cheriot_getbase{: cs1 : rd},
+      disasm: "cgetbase", "%rd, %cs1",
+      semfunc: "&CheriotCGetBase";
+    cheriot_gethigh{: cs1 : rd},
+      disasm: "cgethigh", "%rd, %cs1",
+      semfunc: "&CheriotCGetHigh";
+    cheriot_getlen{: cs1 : rd},
+      disasm: "cgetlen", "%rd, %cs1",
+      semfunc: "&CheriotCGetLen";
+    cheriot_getperm{: cs1 : rd},
+      disasm: "cgetperm", "%rd, %cs1",
+      semfunc: "&CheriotCGetPerm";
+    cheriot_gettag{: cs1 : rd},
+      disasm: "cgettag", "%rd, %cs1",
+      semfunc: "&CheriotCGetTag";
+    cheriot_gettop{: cs1 : rd},
+      disasm: "cgettop", "%rd, %cs1",
+      semfunc: "&CheriotCGetTop";
+    cheriot_gettype{: cs1 : rd},
+      disasm: "cgettype", "%rd, %cs1",
+      semfunc: "&CheriotCGetType";
+    cheriot_incaddr{: cs1, rs2 : cd},
+      disasm: "cincaddr", "%cd, %cs1, %rs2",
+      semfunc: "&CheriotCIncAddr";
+    cheriot_incaddrimm{: cs1, I_imm12 : cd},
+      disasm: "cincaddrimm", "%cd, %cs1, 0x%(I_imm12:08x)",
+      semfunc: "&CheriotCIncAddr";
+    cheriot_jal{: J_imm20 : cd},
+      disasm: "cjal", "%cd, 0x%(J_imm20:08x)",
+      semfunc: "&CheriotCJal";
+    cheriot_j{: J_imm20 : },
+      disasm: "cj", "0x%(J_imm20:08x)",
+      semfunc: "&CheriotCJ";
+    cheriot_jalr{: cs1, J_imm12 : cd},
+      disasm: "cjalr", "%cd, %cs1, 0x%(J_imm12:08x)",
+      semfunc: "&CheriotCJalr";
+    cheriot_jr{: cs1, J_imm12 : },
+      disasm: "cjalr", "%cs1, 0x%(J_imm12:08x)",
+      semfunc: "&CheriotCJr";
+    cheriot_lc{(: cs1, I_imm12 :),(: : cd)},
+      disasm: "clc", "%cd, %cs1, 0x%(I_imm12:08x)",
+      semfunc: "&CheriotCLc", "&CheriotCLcChild";
+    cheriot_move{: cs1 : cd},
+      disasm: "cmove", "%cd, %cs1",
+      semfunc: "&CheriotCMove";
+    cheriot_representablealignmentmask{: rs1 : rd},
+      disasm: "crepresentablealignmentmask", "%rd, %rs1",
+      semfunc: "&CheriotCRepresentableAlignmentMask";
+    cheriot_roundrepresentablelength{: rs1 : rd},
+      disasm: "croundrepresentablelength", "%rd, %rs1",
+      semfunc: "&CheriotCRoundRepresentableLength";
+    cheriot_sc{: cs1, S_imm12, cs2 :},
+      disasm: "csc", "%cs2, %S_imm12(%cs1)",
+      semfunc: "&CheriotCSc";
+    cheriot_seal{: cs1, cs2 : cd},
+      disasm: "cseal", "%cd, %cs1, %cs2",
+      semfunc: "&CheriotCSeal";
+    cheriot_setaddr{: cs1, rs2 : cd},
+      disasm: "csetaddr", "%cd, %cs1, %rs2",
+      semfunc: "&CheriotCSetAddr";
+    cheriot_setbounds{: cs1, rs2 : cd},
+      disasm: "csetbounds", "%cd, %cs1, %rs2",
+      semfunc: "&CheriotCSetBounds";
+    cheriot_setboundsexact{: cs1, rs2 : cd},
+      disasm: "csetboundsexact", "%cd, %cs1, %rs2",
+      semfunc: "&CheriotCSetBoundsExact";
+    cheriot_setboundsimm{: cs1, I_uimm12 : cd},
+      disasm: "csetboundsimm", "%cd, %cs1, 0x%(I_uimm12:08x)",
+      semfunc: "&CheriotCSetBounds";
+    cheriot_setequalexact{: cs1, cs2 : rd},
+      disasm: "csetequalexact", "%rd, %cs1, %cs2",
+      semfunc: "&CheriotCSetEqualExact";
+    cheriot_sethigh{: cs1, rs2 : cd},
+      disasm: "csethigh", "%cd, %cs1, %rs2",
+      semfunc: "&CheriotCSetHigh";
+    cheriot_specialr{: scr : cd},
+      disasm: "cspecialrw", "%cd, %scr",
+      semfunc: "&CheriotCSpecialR";
+    cheriot_specialrw{: cs1, scr : cd, scr},
+      disasm: "cspecialrw", "%cd, %scr, %cs1",
+      semfunc: "&CheriotCSpecialRW";
+    cheriot_sub{: cs1, cs2 : cd},
+      disasm: "csub", "%cd, %cs1, %cs2",
+      semfunc: "&CheriotCSub";
+    cheriot_testsubset{: cs1, cs2 : rd},
+      disasm: "ctestsubset", "%rd, %cs1, %cs2",
+      semfunc: "&CheriotCTestSubset";
+    cheriot_unseal{: cs1, cs2 : cd},
+      disasm: "cunseal", "%cd, %cs1, %cs2",
+      semfunc: "&CheriotCUnseal";
+  }
+}
+
+// Privileged instructions.
+slot privileged {
+  includes {
+    #include "cheriot/riscv_cheriot_priv_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    mret{: : },
+      disasm: "mret",
+      semfunc: "&RiscVPrivMRet";
+    wfi{},
+      disasm: "wfi",
+      semfunc: "&RiscVPrivWfi";
+  }
+}
+
+// Instruction fence.
+slot zfencei {
+  includes {
+    #include "cheriot/riscv_cheriot_zfencei_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    fencei{: I_imm12 : },
+      disasm: "fence.i",
+      semfunc: "&RiscVZFencei";
+  }
+}
+
+// RiscV32 multiply/divide instructions.
+slot riscv32m {
+  includes {
+    #include "cheriot/riscv_cheriot_m_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    mul{: rs1, rs2 : rd},
+      disasm: "mul", "%rd, %rs1, %rs2",
+      semfunc: "&MMul";
+    mulh{: rs1, rs2 : rd},
+      disasm: "mulh", "%rd, %rs1, %rs2",
+      semfunc: "&MMulh";
+    mulhu{: rs1, rs2: rd},
+      disasm: "mulhu", "%rd, %rs1, %rs2",
+      semfunc: "&MMulhu";
+    mulhsu{: rs1, rs2: rd},
+      disasm: "mulhsu", "%rd, %rs1, %rs2",
+      semfunc: "&MMulhsu";
+    div{: rs1, rs2 : rd},
+      disasm: "div", "%rd, %rs1, %rs2",
+      semfunc: "&MDiv";
+    divu{: rs1, rs2 : rd},
+      disasm: "divu", "%rd, %rs1, %rs2",
+      semfunc: "&MDivu";
+    rem{: rs1, rs2 : rd},
+      disasm: "rem", "%rd, %rs1, %rs2",
+      semfunc: "&MRem";
+    remu{: rs1, rs2 : rd},
+      disasm: "remu", "%rd, %rs1, %rs2",
+      semfunc: "&MRemu";
+  }
+}
+
+// The RiscV architecture allows for different subsets of the AMO instructions
+// to be implemented. The following slot definitions define these subsets.
+
+// RiscV atomic memory instructions subset AMO None.
+slot riscv32_amo_none {
+  includes {
+    #include "cheriot/riscv_cheriot_a_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    lrw{(: rs1, A_aq, A_rl :),(: : rd)},
+            semfunc: "&ALrw", "&RiscVILwChild";
+    scw{(: rs1, rs2, A_aq, A_rl :), (: : rd)},
+            semfunc: "&AScw", "&RiscVILwChild";
+  }
+}
+
+// RiscV atomic memory instructions subset AMO swap.
+slot riscv32_amo_swap : riscv32_amo_none {
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    amoswapw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmoswapw", "&RiscVILwChild";
+  }
+}
+
+// RiscV atomic memory instructions subset AMO logical.
+slot riscv32_amo_logical : riscv32_amo_swap {
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    amoandw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmoandw", "&RiscVILwChild";
+    amoorw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmoorw", "&RiscVILwChild";
+    amoxorw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmoxorw", "&RiscVILwChild";
+  }
+}
+
+// RiscV atomic memory instructions subset AMO arithmetic.
+slot riscv32_amo_arithmetic : riscv32_amo_logical {
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    amoaddw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmoaddw", "&RiscVILwChild";
+    amomaxw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmomaxw", "&RiscVILwChild";
+    amomaxuw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmomaxuw", "&RiscVILwChild";
+    amominw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmominw", "&RiscVILwChild";
+    amominuw{(: rs1, rs2, A_aq, A_rl: ), (: : rd)},
+            semfunc: "&AAmominuw", "&RiscVILwChild";
+  }
+}
+
+// RiscV32 CSR manipulation instructions.
+slot zicsr {
+  includes {
+    #include "cheriot/riscv_cheriot_zicsr_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    csrrw{: rs1, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrw",
+      disasm: "csrrw", "%rd, %csr, %rs1";
+    csrrs{: rs1, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrs",
+      disasm: "csrrs", "%rd, %csr, %rs1";
+    csrrc{: rs1, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrc",
+      disasm: "csrrc", "%rd, %csr, %rs1";
+    csrrs_nr{: rs1, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrs",
+      disasm: "csrrs", "%csr, %rs1";
+    csrrc_nr{: rs1, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrc",
+      disasm: "csrrc", "%csr, %rs1";
+    csrrw_nr{: rs1, csr : csr},
+      semfunc: "&RiscVZiCsrrwNr", // rd == 0 (x0).
+      disasm: "csrrw", "%csr, %rs1";
+    csrrs_nw{: csr : rd},
+      semfunc: "&RiscVZiCsrrNw", // rs1 == 0 (x0).
+      disasm: "csrrs", "%rd, %csr";
+    csrrc_nw{: csr : rd},
+      semfunc: "&RiscVZiCsrrNw", // rs1 == 0 (x0).
+      disasm: "csrrc", "%rd, %csr";
+    csrrwi{: CSR_uimm5, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrw",
+      disasm: "csrrwi", "%rd, %csr, %CSR_uimm5";
+    csrrsi{: CSR_uimm5, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrs",
+      disasm: "csrrsi", "%rd, %csr, %CSR_uimm5";
+    csrrci{: CSR_uimm5, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrc",
+      disasm: "csrrci", "%rd, %csr, %CSR_uimm5";
+    csrrsi_nr{: CSR_uimm5, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrs",
+      disasm: "csrrsi", "%csr, %CSR_uimm5";
+    csrrci_nr{: CSR_uimm5, csr : rd, csr},
+      semfunc: "&RiscVZiCsrrc",
+      disasm: "csrrci", "%csr, %CSR_uimm5";
+    csrrwi_nr{: CSR_uimm5, csr : csr},
+      semfunc: "&RiscVZiCsrrwNr",  // rd == 0 (x0).
+      disasm: "csrrwi", "%csr, %CSR_uimm5";
+    csrrsi_nw{: csr : rd},
+      semfunc: "&RiscVZiCsrrNw", // uimm5 == 0.
+      disasm: "csrrsi", "%rd, %csr, 0";
+    csrrci_nw{: csr : rd},
+      semfunc: "&RiscVZiCsrrNw", // uimm5 == 0.
+      disasm: "csrrwi", "%rd, %csr, 0";
+  }
+}
+
+// RiscV32 F (single precision floating point) instructions.
+slot riscv32f {
+  includes {
+    #include "cheriot/riscv_cheriot_f_instructions.h"
+  }
+  default size = 4;
+  default latency = global_latency;
+  opcodes {
+    flw{(: rs1, I_imm12 : ), (: : frd)},
+      semfunc: "&RiscVILw", "&RiscVIFlwChild",
+      disasm: "flw", "%frd, %I_imm12(%rs1)";
+    fsw{: rs1, S_imm12, frs2},
+      semfunc: "&RiscVFSw",
+      disasm: "fsw", "%frs2, %S_imm12(%rs1)";
+    fadd_s{: frs1, frs2, rm : frd},
+            semfunc: "&RiscVFAdd",
+      disasm: "fadd", "%frd, %frs1, %frs2";
+    fsub_s{: frs1, frs2, rm : frd},
+            semfunc: "&RiscVFSub",
+      disasm: "fsub", "%frd, %frs1, %frs2";
+    fmul_s{: frs1, frs2, rm : frd},
+            semfunc: "&RiscVFMul",
+      disasm: "fmul", "%frd, %frs1, %frs2";
+    fdiv_s{: frs1, frs2, rm : frd},
+            semfunc: "&RiscVFDiv",
+      disasm: "fdiv", "%frd, %frs1, %frs2";
+    fsqrt_s{: frs1, rm : frd},
+            semfunc: "&RiscVFSqrt",
+      disasm: "fsqrt", "%frd, %frs1";
+    fmin_s{: frs1, frs2 : frd, fflags},
+            semfunc: "&RiscVFMin",
+      disasm: "fmin", "%frd, %frs1, %frs2";
+    fmax_s{: frs1, frs2 : frd, fflags},
+            semfunc: "&RiscVFMax",
+      disasm: "fmax", "%frd, %frs1, %frs2";
+    fmadd_s{: frs1, frs2, frs3, rm : frd, fflags},
+      semfunc: "&RiscVFMadd",
+      disasm: "fmadd", "%frd, %frs1, %frs2, %frs3";
+    fmsub_s{: frs1, frs2, frs3, rm : frd, fflags},
+      semfunc: "&RiscVFMsub",
+      disasm: "fmsub", "%frd, %frs1, %frs2, %frs3";
+    fnmadd_s{: frs1, frs2, frs3, rm : frd, fflags},
+      semfunc: "&RiscVFNmadd",
+      disasm: "fnmadd", "%frd, %frs1, %frs2, %frs3";
+    fnmsub_s{: frs1, frs2, frs3, rm : frd, fflags},
+      semfunc: "&RiscVFNmsub",
+      disasm: "fnmsub", "%frd, %frs1, %frs2, %frs3";
+    fcvt_ws{: frs1, rm : rd, fflags},
+            semfunc: "&RiscVFCvtWs",
+      disasm: "fcvt.w.s", "%rd, %frs1";
+    fcvt_sw{: rs1, rm : frd},
+            semfunc: "&RiscVFCvtSw",
+      disasm: "fcvt.s.w", "%frd, %rs1";
+    fcvt_wus{: frs1, rm : rd, fflags},
+            semfunc: "&RiscVFCvtWus",
+      disasm: "fcvt.wu.s", "%rd, %frs1";
+    fcvt_swu{: rs1, rm : frd},
+            semfunc: "&RiscVFCvtSwu",
+      disasm: "fcvt.s.wu", "%frd, %rs1";
+    fsgnj_s{: frs1, frs2 : frd},
+            semfunc: "&RiscVFSgnj",
+      disasm: "fsgn.s", "%frd, %frs1, %frs2";
+    fsgnjn_s{: frs1, frs2 : frd},
+            semfunc: "&RiscVFSgnjn",
+      disasm: "fsgnjx.s", "%frd, %frs1, %frs2";
+    fsgnjx_s{: frs1, frs2 : frd},
+            semfunc: "&RiscVFSgnjx",
+      disasm: "fsgnjx.s", "%frd, %frs1, %frs2";
+    fmv_xw{: frs1 : rd},
+      disasm: "mv.x.w", "%rd, %frs1",
+      semfunc: "&RiscVFMvxw";
+    fmv_wx{: rs1 : frd},
+      disasm: "mv.w.x", "%frd, %rs1",
+      semfunc: "&RiscVFMvwx";
+    fcmpeq_s{: frs1, frs2 : rd, fflags},
+      semfunc: "&RiscVFCmpeq",
+      disasm: "fcmpeq", "%rd, %frs1, %frs2";
+    fcmplt_s{: frs1, frs2 : rd, fflags},
+      semfunc: "&RiscVFCmplt",
+      disasm: "fcmplt", "%rd, %frs1, %frs2";
+    fcmple_s{: frs1, frs2 : rd, fflags},
+      semfunc: "&RiscVFCmple",
+      disasm: "fcmple", "%rd, %frs1, %frs2";
+    fclass_s{: frs1 : rd},
+      semfunc: "&RiscVFClass",
+      disasm: "fclass", "%rd, %frs1";
+  }
+}
+
+// RISCV32 C (compact instructions).
+slot riscv32c {
+  default size = 2;
+  default latency = global_latency;
+  opcodes {
+    clwsp{(: c2, I_ci_uimm6x4 : ), (: : rd)},
+      disasm: "c.lw", "%rd, %I_ci_uimm6x4(%c2)",
+      semfunc: "&RiscVILw", "&RiscVILwChild";
+    cflwsp{(: c2, I_ci_uimm6x4 : ), (: : frd)},
+      disasm: "c.flw", "%frd, %I_ci_uimm6x4(%c2)",
+      semfunc: "&RiscVILw", "&RiscVILwChild";
+    // Reused for clc
+    cldsp{(: c2, I_ci_uimm6x8 : ), (: : cd)},
+      disasm: "c.clc", "%cd, %I_ci_uimm6x8(%c2)",
+      semfunc: "&CheriotCLc", "&CheriotCLcChild";
+    // cfldsp{(: x2, I_ci_uimm6x8 : ), (: : drd)},
+    //  disasm: "fld", "%drd, %I_ci_uimm6x8(%x2)",
+    //  semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
+    cswsp{: c2, I_css_uimm6x4, crs2 : },
+      disasm: "c.csw", "%crs2, %I_css_uimm6x4(%c2)",
+      semfunc: "&RiscVISw";
+    cfswsp{: c2, I_css_uimm6x4, cfrs2 : },
+      disasm: ".cfsw", "%cfrs2, %I_css_uimm6x4(%c2)",
+      semfunc: "&RiscVISw";
+    // Reused for csc
+    csdsp{: c2, I_css_uimm6x8, ccs2 : },
+      disasm: "c.csc", "%ccs2, %I_css_uimm6x8(%c2)",
+      semfunc: "&CheriotCSc";
+    // cfsdsp{: x2, I_css_uimm6x8, rdrs2 : },
+    //   disasm: "fsd", "%rdrs2, %I_css_uimm6x8(%x2)",
+    //   semfunc: "&RV64::RiscVISd";
+    clw{(: c3rs1, I_cl_uimm5x4 : ), (: : c3rd)},
+      disasm: ".clw", "%c3rd, %I_cl_uimm5x4(%c3rs1)",
+      semfunc: "&RiscVILw", "&RiscVILwChild";
+    // Reused for clc
+    cld{(: c3cs1, I_cl_uimm5x8 : ), (: : c3cd)},
+      disasm: "c.clc", "%c3cd, %I_cl_uimm5x8(%c3cs1)",
+      semfunc: "&CheriotCLc", "&CheriotCLcChild";
+    // cfld{(: c3rs1, I_cl_uimm5x8 : ), (: : c3drd)},
+    //   disasm: "fld", "%c3drd, %I_cl_uimm5x8(%c3rs1)",
+    //   semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
+    csw{: c3rs1, I_cl_uimm5x4, c3cs2 : },
+      disasm: "c.csw", "%c3cs2, %I_cl_uimm5x4(%c3rs1)",
+      semfunc: "&RiscVISw";
+    // Reused for csc
+    csd{: c3cs1, I_cl_uimm5x8, c3cs2 : },
+      disasm: "c.csc", "%c3cs2, %I_cl_uimm5x8(%c3cs1)",
+      semfunc: "&CheriotCSc";
+    // cfsd{: c3rs1, I_cl_uimm5x8, c3drs2 : },
+    //   disasm: "fsd", "%c3drs2, %I_cl_uimm5x8(%c3rs1)",
+    //   semfunc: "&RiscVDSd";
+    cheriot_cj{: I_cj_imm11, x0 :},
+      disasm: "c.j", "%(@+I_cj_imm11:08x)",
+      semfunc: "&CheriotCJ";
+    cheriot_cjal{: I_cj_imm11, x0 :  x1},
+      disasm: "c.jal", "%(@+I_cj_imm11:08x)",
+      semfunc: "&CheriotCJal";
+    cheriot_cjr{: crs1, x0 :},
+      disasm: "c.jr", "%crs1",
+      semfunc: "&CheriotCJr";
+    cheriot_cjalr{: crs1, x0 :  x1},
+      disasm: "c.jalr", "%crs1",
+      semfunc: "&CheriotCJalr";
+    cbeqz{: c3rs1, x0, I_cb_imm8 : },
+      disasm: "c.beqz", "%c3rs1, %(@+I_cb_imm8:08x)",
+      semfunc: "&RiscVIBeq";
+    cbnez{: c3rs1, x0, I_cb_imm8 : },
+      disasm: "c.bnez", "%c3rs1, %(@+I_cb_imm8:08x)",
+      semfunc: "&RiscVIBne";
+    cli{: x0, I_ci_imm6 : rd},
+      disasm: "c.li", "%rd, %I_ci_imm6",
+      semfunc: "&RiscVIAdd";
+    clui{: I_ci_imm6_12 : rd},
+      disasm: "c.lui", "%rd, 0x%(I_ci_imm6_12:08x)",
+      semfunc: "&RiscVILui";
+    caddi{: rd, I_ci_imm6 : rd},
+      disasm: "c.cincoffset", "%rd, %rd, %I_ci_imm6",
+      semfunc: "&RiscVIAdd";
+    // Reused for compact CIncAddressImmediate
+    caddi16sp{: c2, I_ci_imm6x16 : c2},
+      disasm: "c.cincoffset", "%c2, %c2, %(I_ci_imm6x16:d)",
+      semfunc: "&CheriotCIncAddr";
+    caddi4spn{: x2, I_ciw_uimm8x4 : c3cd},
+      disasm: "c.cincoffset", "%c3cd, %x2, %I_ciw_uimm8x4",
+      semfunc: "&CheriotCIncAddr";
+    cslli{: rd, I_ci_uimm6 : rd},
+      disasm: "c.slli", "%rd, %rd, 0x%(I_ci_uimm6:x)",
+      semfunc: "&RiscVISll";
+    csrli{: c3rs1, I_csh_uimm6 : c3rs1},
+      disasm: "c.srli", "%c3rs1, %c3rs1, 0x%(I_csh_uimm6:x)",
+      semfunc: "&RiscVISrl";
+    csrai{: c3rs1, I_csh_uimm6 : c3rs1},
+      disasm: "c.srai", "%c3rs1, %c3rs1, 0x%(I_csh_uimm6:x)",
+      semfunc: "&RiscVISra";
+    candi{: c3rs1, I_csh_imm6 : c3rs1},
+      disasm: "c.andi", "%c3rs1, %c3rs1, %I_csh_imm6",
+      semfunc: "&RiscVIAnd";
+    cmv{: crs2 , x0: rd},
+      disasm: "c.mv", "%rd, %crs2",
+      semfunc: "&RiscVIAdd";
+    cadd{: crs2, rd: rd},
+      disasm: "c.add", "%rd, %rd, %crs2",
+      semfunc: "&RiscVIAdd";
+    cand{: c3rs1, c3rs2 : c3rs1},
+      disasm: "c.and", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RiscVIAnd";
+    cor{: c3rs1, c3rs2 : c3rs1},
+      disasm: "c.or", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RiscVIOr";
+    cxor{: c3rs1, c3rs2 : c3rs1},
+      disasm: "c.xor", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RiscVIXor";
+    csub{: c3rs1, c3rs2 : c3rs1},
+      disasm: "c.sub", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RiscVISub";
+    cnop{},
+      disasm: "c.nop",
+      semfunc: "&RiscVINop";
+    cebreak{},
+      disasm: "c.ebreak",
+      semfunc: "&RiscVIEbreak";
+  }
+}
+
+// This should be the RiscV32 CHERIoT set.
+slot riscv32_cheriot : riscv32i, cheriot, riscv32_amo_arithmetic, riscv32c, 
+                       riscv32m, riscv32f, zicsr, privileged {
+  default size = 4;
+  default opcode =
+    disasm: "Illegal instruction at 0x%(@:08x)",
+    semfunc: "&RiscVIllegalInstruction";
+}
+
+