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"; +} + +