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 ®ister_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 ®_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 ¤t_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 ®_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 ®_name) const {
+ return capability_registers_.contains(reg_name);
+}
+
+std::string DebugCommandShell::FormatRegister(
+ int core, const std::string ®_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 ®_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 ®_name) const;
+ // Reads and formats a capability register.
+ std::string FormatCapabilityRegister(int core,
+ const std::string ®_name) const;
+ // Reads and formats a register.
+ std::string FormatRegister(int core, const std::string ®_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 *>(¬_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 *>(¬_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 ®_name : sources) {
+ auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+ inst->AppendSource(reg->CreateSourceOperand());
+ }
+ for (auto ®_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 *>(®);
+ 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 *>(®);
+ 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 ®_name : sources) {
+ auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+ inst->AppendSource(reg->CreateSourceOperand());
+ }
+ for (auto ®_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 ®_name : sources) {
+ auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+ inst->AppendSource(reg->CreateSourceOperand());
+ }
+ for (auto ®_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 ®_name : sources) {
+ auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+ inst->AppendSource(reg->CreateSourceOperand(reg_name));
+ }
+ for (auto ®_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 ®_name : sources) {
+ auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+ inst->AppendSource(reg->CreateSourceOperand());
+ }
+ for (auto ®_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 ®_name : sources) {
+ auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
+ inst->AppendSource(reg->CreateSourceOperand());
+ }
+ for (auto ®_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";
+}
+
+