From 5447d4429031f776345a29e1e9e7d2861a42f16e Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 22 Mar 2025 20:32:06 +0100 Subject: [PATCH 1/5] prepare rvdc --- .github/workflows/ci.yml | 4 +- Cargo.lock | 5 ++ Cargo.toml | 7 +- rvdc/Cargo.toml | 10 +++ rvdc/README.md | 14 ++++ src/inst.rs => rvdc/src/lib.rs | 114 +++++++++++++++++++++++++++++---- src/emu.rs | 42 +----------- src/lib.rs | 1 - src/main.rs | 3 +- tests/check.rs | 3 +- 10 files changed, 143 insertions(+), 60 deletions(-) create mode 100644 rvdc/Cargo.toml create mode 100644 rvdc/README.md rename src/inst.rs => rvdc/src/lib.rs (91%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a1e9f1..6a83f6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,8 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build - run: cargo build --verbose + run: cargo build --workspace --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --workspace --verbose env: RUSTFLAGS: "-Copt-level=3 --cfg slow_tests" diff --git a/Cargo.lock b/Cargo.lock index b9aacfb..237b555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,9 +102,14 @@ dependencies = [ "eyre", "libc", "owo-colors", + "rvdc", "tempfile", ] +[[package]] +name = "rvdc" +version = "0.1.0" + [[package]] name = "tempfile" version = "3.18.0" diff --git a/Cargo.toml b/Cargo.toml index 40009e6..4cc2367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = [".", "rvdc"] + [package] name = "rustv32i" version = "0.1.0" @@ -7,6 +10,7 @@ edition = "2024" eyre = "0.6.12" libc = "0.2.170" owo-colors = "4.2.0" +rvdc = { path = "./rvdc" } [profile.dev] opt-level = 1 @@ -17,9 +21,6 @@ debug = 1 [dev-dependencies] tempfile = "3.18.0" -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(slow_tests)'] } - [lints.clippy] type_complexity = "allow" if_same_then_else = "allow" diff --git a/rvdc/Cargo.toml b/rvdc/Cargo.toml new file mode 100644 index 0000000..2e8200f --- /dev/null +++ b/rvdc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rvdc" +version = "0.1.0" +description = "RISC-V instruction decoder" +edition = "2024" + +[dependencies] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(slow_tests)'] } diff --git a/rvdc/README.md b/rvdc/README.md new file mode 100644 index 0000000..19cbbba --- /dev/null +++ b/rvdc/README.md @@ -0,0 +1,14 @@ +# rvdc + +RISC-V instruction decoder. + +## Supported extensions + +The decoder supports the following instructions: + +- [x] Base RV32I instruction set +- [x] M standard extension +- [x] A standard extension + - [x] Zalrsc standard extension + - [x] Zaamo standard extension +- [x] C standard extension diff --git a/src/inst.rs b/rvdc/src/lib.rs similarity index 91% rename from src/inst.rs rename to rvdc/src/lib.rs index 23ab9f9..dbdc1e4 100644 --- a/src/inst.rs +++ b/rvdc/src/lib.rs @@ -1,8 +1,59 @@ -use crate::emu::Reg; +//! RISC-V instruction decoder. +//! +//! ```rust +//! // Compressed addi sp, sp, -0x20 +//! let x = 0x1101_u32; +//! let expected = rvdc::Inst::Addi { imm: (-0x20_i32) as u32, dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; +//! +//! let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); +//! assert_eq!(inst, expected); +//! assert_eq!(is_compressed, rvdc::IsCompressed::No); +//! ``` + use std::fmt::{Debug, Display}; use std::ops::RangeInclusive; -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Reg(pub u32); + +impl Reg { + pub const ZERO: Reg = Reg(0); + + pub const RA: Reg = Reg(1); + pub const SP: Reg = Reg(2); + // arguments, return values: + pub const A0: Reg = Reg(10); + pub const A1: Reg = Reg(11); + // arguments: + pub const A2: Reg = Reg(12); + pub const A3: Reg = Reg(13); + pub const A4: Reg = Reg(14); + pub const A5: Reg = Reg(15); + pub const A6: Reg = Reg(16); + pub const A7: Reg = Reg(17); +} + +impl Display for Reg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let n = self.0; + match n { + 0 => write!(f, "zero"), + 1 => write!(f, "ra"), + 2 => write!(f, "sp"), + 3 => write!(f, "gp"), + 4 => write!(f, "tp"), + 5..=7 => write!(f, "t{}", n - 5), + 8 => write!(f, "s0"), + 9 => write!(f, "s1"), + 10..=17 => write!(f, "a{}", n - 10), + 18..=27 => write!(f, "s{}", n - 18 + 2), + 28..=31 => write!(f, "t{}", n - 28 + 3), + _ => unreachable!("invalid register"), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[rustfmt::skip] pub enum Inst { /// Load Upper Immediate @@ -135,7 +186,7 @@ pub enum Inst { }, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Fence { pub fm: u32, pub pred: FenceSet, @@ -144,7 +195,7 @@ pub struct Fence { pub src: Reg, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct FenceSet { pub device_input: bool, pub device_output: bool, @@ -152,29 +203,41 @@ pub struct FenceSet { pub memory_write: bool, } -#[derive(Clone, Copy)] +/// An atomic memory ordering for instructions from the A extension. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum AmoOrdering { + /// No bits. Relaxed, + /// `aq` Acquire, + /// `rl` Release, + /// `aq`, `rl` SeqCst, } -#[derive(Clone, Copy)] +/// An atomic memory operations from the Zaamo extension. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum AmoOp { Swap, Add, Xor, And, Or, + /// Signed minimum Min, + /// Signed maximum Max, + /// Unsigned minimum Minu, + /// Unsigned maximum Maxu, } pub struct DecodeError { + /// The instruction bytes that failed to decode. pub instruction: u32, + /// Which field of the instruction contained unexpected bits. pub unexpected_field: &'static str, } @@ -216,6 +279,7 @@ impl Debug for Inst { } } +/// Prints the instruction in disassembled form. impl Display for Inst { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { @@ -425,7 +489,7 @@ fn sign_extend(value: u32, size: u32) -> u32 { } #[derive(Clone, Copy)] -pub struct InstCode(u32); +struct InstCode(u32); impl InstCode { fn extract(self, range: RangeInclusive) -> u32 { @@ -488,7 +552,7 @@ impl InstCode { } #[derive(Clone, Copy)] -pub struct InstCodeC(u16); +struct InstCodeC(u16); impl InstCodeC { fn extract(self, range: RangeInclusive) -> u32 { @@ -552,9 +616,13 @@ impl From for InstCode { } } +/// Whether the decoded instruction was a compressed instruction or not. +/// If it was compressed, only the first two bytes were used. +/// If it was not compressed, all four bytes are consumed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum IsCompressed { - Yes, No, + Yes, } fn decode_error(instruction: impl Into, unexpected_field: &'static str) -> DecodeError { @@ -565,6 +633,18 @@ fn decode_error(instruction: impl Into, unexpected_field: &'static str } impl Inst { + pub fn first_byte_is_compressed(byte: u8) -> bool { + (byte & 0b11) != 0b11 + } + + /// Decode an instruction from four bytes. + /// + /// The instruction may be compressed, in which case only two bytes are consumed. + /// Even in these cases, the full next four bytes must be passed. + /// + /// If the caller wants to avoid reading more bytes than necessary, [`Self::first_byte_is_compressed`] + /// can be used to check, read the required bytes, and then call [`Self::decode_compressed`] or + /// [`Self::decode_normal`] directly. pub fn decode(code: u32) -> Result<(Inst, IsCompressed), DecodeError> { let is_compressed = (code & 0b11) != 0b11; if is_compressed { @@ -574,7 +654,18 @@ impl Inst { } } - fn decode_compressed(code: u16) -> Result { + /// Decode a known compressed instruction from its two bytes. + /// + /// # Example + /// ```rust + /// // Compressed addi sp, sp, -0x20 + /// let x = 0x1101_u16; + /// let expected = rvdc::Inst::Addi { imm: (-0x20_i32) as u32, dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; + /// + /// let inst = rvdc::Inst::decode_compressed(x).unwrap(); + /// assert_eq!(inst, expected); + /// ``` + pub fn decode_compressed(code: u16) -> Result { let code = InstCodeC(code); if code.0 == 0 { return Err(decode_error(code, "null instruction")); @@ -845,7 +936,8 @@ impl Inst { Ok(inst) } - fn decode_normal(code: u32) -> Result { + /// Decode a normal (not compressed) instruction. + pub fn decode_normal(code: u32) -> Result { let code = InstCode(code); let inst = match code.opcode() { // LUI diff --git a/src/emu.rs b/src/emu.rs index d9ffd0e..29c56af 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -1,4 +1,4 @@ -use crate::inst::{AmoOp, DecodeError, Inst, IsCompressed}; +use rvdc::{AmoOp, DecodeError, Inst, IsCompressed, Reg}; use std::{ fmt::{Debug, Display}, io::Write, @@ -114,46 +114,6 @@ impl IndexMut for Emulator { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Reg(pub u32); - -impl Reg { - pub const ZERO: Reg = Reg(0); - - pub const RA: Reg = Reg(1); - pub const SP: Reg = Reg(2); - // arguments, return values: - pub const A0: Reg = Reg(10); - pub const A1: Reg = Reg(11); - // arguments: - pub const A2: Reg = Reg(12); - pub const A3: Reg = Reg(13); - pub const A4: Reg = Reg(14); - pub const A5: Reg = Reg(15); - pub const A6: Reg = Reg(16); - pub const A7: Reg = Reg(17); -} - -impl Display for Reg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let n = self.0; - match n { - 0 => write!(f, "zero"), - 1 => write!(f, "ra"), - 2 => write!(f, "sp"), - 3 => write!(f, "gp"), - 4 => write!(f, "tp"), - 5..=7 => write!(f, "t{}", n - 5), - 8 => write!(f, "s0"), - 9 => write!(f, "s1"), - 10..=17 => write!(f, "a{}", n - 10), - 18..=27 => write!(f, "s{}", n - 18 + 2), - 28..=31 => write!(f, "t{}", n - 28 + 3), - _ => unreachable!("invalid register"), - } - } -} - fn hash_color(value: usize) -> impl Display { use owo_colors::*; use std::hash::{Hash, Hasher}; diff --git a/src/lib.rs b/src/lib.rs index 1a7691d..934cf7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,6 @@ use eyre::{OptionExt, bail}; pub mod elf; pub mod emu; -pub mod inst; // 2 MiB const MEMORY_SIZE: usize = 2 << 21; diff --git a/src/main.rs b/src/main.rs index 3178e89..f70208d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use std::io::Write; use eyre::eyre; -use rustv32i::emu::{self, Memory, Reg}; +use rustv32i::emu::{self, Memory}; +use rvdc::Reg; fn main() -> eyre::Result<()> { let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap(); diff --git a/tests/check.rs b/tests/check.rs index 823ab24..a0310c6 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -5,7 +5,8 @@ use std::{ }; use eyre::{Context, bail}; -use rustv32i::emu::{Reg, Status}; +use rustv32i::emu::Status; +use rvdc::Reg; #[test] fn check() -> eyre::Result<()> { From 4ef323954d8b3bf67239b610acb26d91bc80bfa0 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 22 Mar 2025 21:05:12 +0100 Subject: [PATCH 2/5] rvdc --- Cargo.toml | 1 + LICENSE-APACHE | 176 +++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 25 ++++++ rvdc/CHANGELOG.md | 3 + rvdc/Cargo.toml | 4 + rvdc/LICENSE-APACHE | 1 + rvdc/LICENSE-MIT | 1 + rvdc/src/lib.rs | 189 ++++++++++++++++++++++++++++++++++++++++---- 8 files changed, 384 insertions(+), 16 deletions(-) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 rvdc/CHANGELOG.md create mode 120000 rvdc/LICENSE-APACHE create mode 120000 rvdc/LICENSE-MIT diff --git a/Cargo.toml b/Cargo.toml index 4cc2367..c23a299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [".", "rvdc"] name = "rustv32i" version = "0.1.0" edition = "2024" +license = "MIT OR Apache-2.0" [dependencies] eyre = "0.6.12" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + 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 diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..581dd18 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) Noratrieb + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/rvdc/CHANGELOG.md b/rvdc/CHANGELOG.md new file mode 100644 index 0000000..bd05a0f --- /dev/null +++ b/rvdc/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +Initial release. diff --git a/rvdc/Cargo.toml b/rvdc/Cargo.toml index 2e8200f..beb84ae 100644 --- a/rvdc/Cargo.toml +++ b/rvdc/Cargo.toml @@ -2,7 +2,11 @@ name = "rvdc" version = "0.1.0" description = "RISC-V instruction decoder" +repository = "https://github.com/Noratrieb/rustv32i" edition = "2024" +keywords = ["risc-v", "riscv", "decoder", "instruction", "parser"] +categories = ["Parser implementations"] +license = "MIT OR Apache-2.0" [dependencies] diff --git a/rvdc/LICENSE-APACHE b/rvdc/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/rvdc/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/rvdc/LICENSE-MIT b/rvdc/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/rvdc/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index dbdc1e4..88950f3 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -1,36 +1,150 @@ //! RISC-V instruction decoder. //! +//! The main function is [`Inst::decode`], which will decode an instruction into the [`Inst`] enum. +//! The [`std::fmt::Display`] impl of [`Inst`] provides disassembly functionality +//! (note that the precise output of that implementation is not considered stable). +//! +//! # Register size support +//! +//! This crate currenly only supports RV32 instructions. +//! RV64 instructions that are the same between versions will still be decoded successfully, but the user +//! has to be careful around sign-extended immediates to preserve the correct value when extending them to 64 bits. +//! +//! RV64-specific instructions are not yet implemented, but will be in the future. +//! The immediates will also be switched to `u64` in the future to allow for easier usage of RV64. +//! +//! RV128 is not intended to be supported. +//! +//! # Extension support +//! +//! The decoder currently supports the following instructions: +//! +//! - [x] Base RV32I instruction set +//! - [x] M standard extension +//! - [x] A standard extension +//! - [x] Zalrsc standard extension +//! - [x] Zaamo standard extension +//! - [x] C standard extension +//! +//! More extensions may be implemented in the future. +//! +//! # Examples +//! //! ```rust -//! // Compressed addi sp, sp, -0x20 +//! // addi sp, sp, -0x20 (compressed) //! let x = 0x1101_u32; //! let expected = rvdc::Inst::Addi { imm: (-0x20_i32) as u32, dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; //! //! let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); //! assert_eq!(inst, expected); -//! assert_eq!(is_compressed, rvdc::IsCompressed::No); +//! assert_eq!(is_compressed, rvdc::IsCompressed::Yes); +//! assert_eq!(format!("{inst}"), "addi sp, sp, -32") //! ``` +//! +//! ```rust +//! // auipc t1, 0xa +//! let x = 0x0000a317; +//! let expected = rvdc::Inst::Auipc { uimm: 0xa << 12, dest: rvdc::Reg::T1 }; +//! +//! let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); +//! assert_eq!(inst, expected); +//! assert_eq!(is_compressed, rvdc::IsCompressed::No); +//! assert_eq!(format!("{inst}"), "auipc t1, 10") +//! ``` +//! +//! # Panics +//! +//! [`Inst::decode`] is guaranteed to **never** panic. This is ensured with a 32-bit exhaustive test. +//! +//! # Testing +//! +//! This crate is currently tested as part of an emulator, which tests many different kinds of instructions. +//! In the future, more tests of the decoder specifically may be added. +//! +//! # MSRV +//! +//! This crate targets the latest stable as its MSRV. + +#![deny(missing_docs)] use std::fmt::{Debug, Display}; use std::ops::RangeInclusive; +/// A decoded RISC-V integer register. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Reg(pub u32); +pub struct Reg(pub u8); impl Reg { + /// The zero register `zero` (`x0`) pub const ZERO: Reg = Reg(0); + /// The return address register `ra` (`x1`) pub const RA: Reg = Reg(1); + /// The stack pointer register `sp` (`x2`) pub const SP: Reg = Reg(2); - // arguments, return values: + /// The global pointer register `gp` (`x3`) + pub const GP: Reg = Reg(3); + /// The thread pointer register `tp` (`x4`) + pub const TP: Reg = Reg(4); + + /// Saved register `s0` (`x8`) + pub const S0: Reg = Reg(8); + /// Saved register frame pointer `fp` (`s0`, `x8`) + pub const FP: Reg = Reg(8); + /// Saved register `s1` (`x9`) + pub const S1: Reg = Reg(9); + /// Saved register `s2` (`x18`) + pub const S2: Reg = Reg(18); + /// Saved register `s3` (`x19`) + pub const S3: Reg = Reg(19); + /// Saved register `s4` (`x20`) + pub const S4: Reg = Reg(20); + /// Saved register `s5` (`x21`) + pub const S5: Reg = Reg(21); + /// Saved register `s6` (`x22`) + pub const S6: Reg = Reg(22); + /// Saved register `s7` (`x23`) + pub const S7: Reg = Reg(23); + /// Saved register `s8` (`x24`) + pub const S8: Reg = Reg(24); + /// Saved register `s9` (`x25`) + pub const S9: Reg = Reg(25); + /// Saved register `s10` (`x26`) + pub const S10: Reg = Reg(26); + /// Saved register `s11` (`x27`) + pub const S11: Reg = Reg(27); + + /// Argument/return value register `a0` (`x10`) pub const A0: Reg = Reg(10); + /// Argument/return value register `a1` (`x11`) pub const A1: Reg = Reg(11); - // arguments: + /// Argument register `a2` (`x12`) pub const A2: Reg = Reg(12); + /// Argument register `a3` (`x13`) pub const A3: Reg = Reg(13); + /// Argument register `a4` (`x14`) pub const A4: Reg = Reg(14); + /// Argument register `a5` (`x15`) pub const A5: Reg = Reg(15); + /// Argument register `a6` (`x16`) pub const A6: Reg = Reg(16); + /// Argument register `a7` (`x17`) pub const A7: Reg = Reg(17); + + /// Temporary register `t0` (`x5`) + pub const T0: Reg = Reg(5); + /// Temporary register `t1` (`x6`) + pub const T1: Reg = Reg(6); + /// Temporary register `t2` (`x7`) + pub const T2: Reg = Reg(7); + /// Temporary register `t3` (`x28`) + pub const T3: Reg = Reg(28); + /// Temporary register `t4` (`x29`) + pub const T4: Reg = Reg(29); + /// Temporary register `t5` (`x30`) + pub const T5: Reg = Reg(30); + /// Temporary register `t6` (`x31`) + pub const T6: Reg = Reg(31); } impl Display for Reg { @@ -53,8 +167,16 @@ impl Display for Reg { } } +/// A RISC-V instruction. +/// +/// Every variant is a different instruction, with immediates as `u32`. +/// For instructions that sign-extend immediates, the immediates will have been +/// sign-extended already, so the value can be used as-is. +/// For instructions that have immediates in the upper bits (`lui`, `auipc`), +/// the shift will have been done already, so the value can also be used as-is. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[rustfmt::skip] +#[expect(missing_docs)] // enum variant fields pub enum Inst { /// Load Upper Immediate Lui { uimm: u32, dest: Reg }, @@ -186,16 +308,26 @@ pub enum Inst { }, } +/// The details of a RISC-V `fence` instruction. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Fence { - pub fm: u32, + /// The `fm` field of the instruction. + /// - `0b0000` is a normal fence + /// - `0b1000` with `rw,rw` implies a `fence.tso` + pub fm: u8, + /// The predecessor set. pub pred: FenceSet, + /// The sucessor set. pub succ: FenceSet, + /// The `rd` field of the instruction. Currently always zero. pub dest: Reg, + /// The `rs1` field of the instruction. Currently always zero. pub src: Reg, } +/// The affected parts of a fence. #[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[expect(missing_docs)] pub struct FenceSet { pub device_input: bool, pub device_output: bool, @@ -219,10 +351,15 @@ pub enum AmoOrdering { /// An atomic memory operations from the Zaamo extension. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum AmoOp { + /// Swap Swap, + /// ADD Add, + /// XOR Xor, + /// AND And, + /// OR Or, /// Signed minimum Min, @@ -234,6 +371,9 @@ pub enum AmoOp { Maxu, } +/// The error used for invalid instructions containing information about the instruction and error. +/// +/// Note that this is also returned for the defined illegal instruction of all zero. pub struct DecodeError { /// The instruction bytes that failed to decode. pub instruction: u32, @@ -242,6 +382,7 @@ pub struct DecodeError { } impl Fence { + /// Whether this fence indicates a `pause` assembler pseudoinstruction. pub fn is_pause(&self) -> bool { self.pred == FenceSet { @@ -263,6 +404,7 @@ impl Fence { } impl AmoOrdering { + /// Create a new [`AmoOrdering`] from the two ordering bits. pub fn from_aq_rl(aq: bool, rl: bool) -> Self { match (aq, rl) { (false, false) => Self::Relaxed, @@ -280,6 +422,8 @@ impl Debug for Inst { } /// Prints the instruction in disassembled form. +/// +/// Note that the precise output here is not considered stable. impl Display for Inst { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { @@ -526,13 +670,16 @@ impl InstCode { self.extract(25..=31) } fn rs1(self) -> Reg { - Reg(self.extract(15..=19)) + Reg(self.extract(15..=19) as u8) } fn rs2(self) -> Reg { - Reg(self.extract(20..=24)) + Reg(self.extract(20..=24) as u8) + } + fn rs2_imm(self) -> u32 { + self.extract(20..=24) } fn rd(self) -> Reg { - Reg(self.extract(7..=11)) + Reg(self.extract(7..=11) as u8) } fn imm_i(self) -> u32 { self.immediate_s(&[(20..=31, 0)]) @@ -590,23 +737,23 @@ impl InstCodeC { } /// rd/rs1 (7..=11) fn rd(self) -> Reg { - Reg(self.extract(7..=11)) + Reg(self.extract(7..=11) as u8) } /// rs2 (2..=6) fn rs2(self) -> Reg { - Reg(self.extract(2..=6)) + Reg(self.extract(2..=6) as u8) } /// rs1' (7..=9) fn rs1_short(self) -> Reg { let smol_reg = self.extract(7..=9); // map to x8..=x15 - Reg(smol_reg + 8) + Reg((smol_reg + 8) as u8) } /// rs2' (2..=4) fn rs2_short(self) -> Reg { let smol_reg = self.extract(2..=4); // map to x8..=x15 - Reg(smol_reg + 8) + Reg((smol_reg + 8) as u8) } } @@ -621,7 +768,9 @@ impl From for InstCode { /// If it was not compressed, all four bytes are consumed. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum IsCompressed { + /// Normal 4-byte instruction No, + /// Compressed 2-byte instruction Yes, } @@ -633,6 +782,7 @@ fn decode_error(instruction: impl Into, unexpected_field: &'static str } impl Inst { + /// Whether the first byte of an instruction indicates a compressed or uncompressed instruction. pub fn first_byte_is_compressed(byte: u8) -> bool { (byte & 0b11) != 0b11 } @@ -1085,12 +1235,12 @@ impl Inst { }, 0b101 => match code.funct7() { 0b0000000 => Inst::Srli { - imm: code.rs2().0, + imm: code.rs2_imm(), dest: code.rd(), src1: code.rs1(), }, 0b0100000 => Inst::Srai { - imm: code.rs2().0, + imm: code.rs2_imm(), dest: code.rd(), src1: code.rs1(), }, @@ -1143,7 +1293,7 @@ impl Inst { match code.funct3() { 0b000 => Inst::Fence { fence: Fence { - fm, + fm: fm as u8, pred, succ, dest: code.rd(), @@ -1239,6 +1389,8 @@ impl Inst { mod tests { use std::io::Write; + use crate::Inst; + #[test] #[cfg_attr(not(slow_tests), ignore)] fn exhaustive_decode_no_panic() { @@ -1253,4 +1405,9 @@ mod tests { } let _ = super::Inst::decode(u32::MAX); } + + #[test] + fn size_of_instruction() { + assert!(size_of::() <= 12); + } } From b1282e175ca467d9dcde0a336bb255322447a10c Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 22 Mar 2025 21:07:40 +0100 Subject: [PATCH 3/5] readme --- rvdc/README.md | 64 +++++++++++++++++++++++++++++++++++++++++----- rvdc/src/lib.rs | 67 +------------------------------------------------ 2 files changed, 59 insertions(+), 72 deletions(-) diff --git a/rvdc/README.md b/rvdc/README.md index 19cbbba..43e2e10 100644 --- a/rvdc/README.md +++ b/rvdc/README.md @@ -1,14 +1,66 @@ -# rvdc - RISC-V instruction decoder. -## Supported extensions +The main function is [`Inst::decode`], which will decode an instruction into the [`Inst`] enum. +The [`std::fmt::Display`] impl of [`Inst`] provides disassembly functionality +(note that the precise output of that implementation is not considered stable). -The decoder supports the following instructions: +# Register size support + +This crate currenly only supports RV32 instructions. +RV64 instructions that are the same between versions will still be decoded successfully, but the user +has to be careful around sign-extended immediates to preserve the correct value when extending them to 64 bits. + +RV64-specific instructions are not yet implemented, but will be in the future. +The immediates will also be switched to `u64` in the future to allow for easier usage of RV64. + +RV128 is not intended to be supported. + +# Extension support + +The decoder currently supports the following instructions: - [x] Base RV32I instruction set - [x] M standard extension - [x] A standard extension - - [x] Zalrsc standard extension - - [x] Zaamo standard extension + - [x] Zalrsc standard extension + - [x] Zaamo standard extension - [x] C standard extension + +More extensions may be implemented in the future. + +# Examples + +```rust +// addi sp, sp, -0x20 (compressed) +let x = 0x1101_u32; +let expected = rvdc::Inst::Addi { imm: (-0x20_i32) as u32, dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; + +let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); +assert_eq!(inst, expected); +assert_eq!(is_compressed, rvdc::IsCompressed::Yes); +assert_eq!(format!("{inst}"), "addi sp, sp, -32") +``` + +```rust +// auipc t1, 0xa +let x = 0x0000a317; +let expected = rvdc::Inst::Auipc { uimm: 0xa << 12, dest: rvdc::Reg::T1 }; + +let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); +assert_eq!(inst, expected); +assert_eq!(is_compressed, rvdc::IsCompressed::No); +assert_eq!(format!("{inst}"), "auipc t1, 10") +``` + +# Panics + +[`Inst::decode`] is guaranteed to **never** panic. This is ensured with a 32-bit exhaustive test. + +# Testing + +This crate is currently tested as part of an emulator, which tests many different kinds of instructions. +In the future, more tests of the decoder specifically may be added. + +# MSRV + +This crate targets the latest stable as its MSRV. diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index 88950f3..53470f2 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -1,69 +1,4 @@ -//! RISC-V instruction decoder. -//! -//! The main function is [`Inst::decode`], which will decode an instruction into the [`Inst`] enum. -//! The [`std::fmt::Display`] impl of [`Inst`] provides disassembly functionality -//! (note that the precise output of that implementation is not considered stable). -//! -//! # Register size support -//! -//! This crate currenly only supports RV32 instructions. -//! RV64 instructions that are the same between versions will still be decoded successfully, but the user -//! has to be careful around sign-extended immediates to preserve the correct value when extending them to 64 bits. -//! -//! RV64-specific instructions are not yet implemented, but will be in the future. -//! The immediates will also be switched to `u64` in the future to allow for easier usage of RV64. -//! -//! RV128 is not intended to be supported. -//! -//! # Extension support -//! -//! The decoder currently supports the following instructions: -//! -//! - [x] Base RV32I instruction set -//! - [x] M standard extension -//! - [x] A standard extension -//! - [x] Zalrsc standard extension -//! - [x] Zaamo standard extension -//! - [x] C standard extension -//! -//! More extensions may be implemented in the future. -//! -//! # Examples -//! -//! ```rust -//! // addi sp, sp, -0x20 (compressed) -//! let x = 0x1101_u32; -//! let expected = rvdc::Inst::Addi { imm: (-0x20_i32) as u32, dest: rvdc::Reg::SP, src1: rvdc::Reg::SP }; -//! -//! let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); -//! assert_eq!(inst, expected); -//! assert_eq!(is_compressed, rvdc::IsCompressed::Yes); -//! assert_eq!(format!("{inst}"), "addi sp, sp, -32") -//! ``` -//! -//! ```rust -//! // auipc t1, 0xa -//! let x = 0x0000a317; -//! let expected = rvdc::Inst::Auipc { uimm: 0xa << 12, dest: rvdc::Reg::T1 }; -//! -//! let (inst, is_compressed) = rvdc::Inst::decode(x).unwrap(); -//! assert_eq!(inst, expected); -//! assert_eq!(is_compressed, rvdc::IsCompressed::No); -//! assert_eq!(format!("{inst}"), "auipc t1, 10") -//! ``` -//! -//! # Panics -//! -//! [`Inst::decode`] is guaranteed to **never** panic. This is ensured with a 32-bit exhaustive test. -//! -//! # Testing -//! -//! This crate is currently tested as part of an emulator, which tests many different kinds of instructions. -//! In the future, more tests of the decoder specifically may be added. -//! -//! # MSRV -//! -//! This crate targets the latest stable as its MSRV. +#![doc = include_str!("../README.md")] #![deny(missing_docs)] From 8730313c6818b3f62cbf81fd2e548f3511a5f46a Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 22 Mar 2025 21:09:48 +0100 Subject: [PATCH 4/5] no_std --- rvdc/README.md | 4 ++++ rvdc/src/lib.rs | 29 +++++++++++++++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/rvdc/README.md b/rvdc/README.md index 43e2e10..f7093de 100644 --- a/rvdc/README.md +++ b/rvdc/README.md @@ -52,6 +52,10 @@ assert_eq!(is_compressed, rvdc::IsCompressed::No); assert_eq!(format!("{inst}"), "auipc t1, 10") ``` +# `no_std` + +This crate supports `no_std` without the `alloc` crate. + # Panics [`Inst::decode`] is guaranteed to **never** panic. This is ensured with a 32-bit exhaustive test. diff --git a/rvdc/src/lib.rs b/rvdc/src/lib.rs index 53470f2..2f82845 100644 --- a/rvdc/src/lib.rs +++ b/rvdc/src/lib.rs @@ -1,9 +1,9 @@ #![doc = include_str!("../README.md")] - +#![no_std] #![deny(missing_docs)] -use std::fmt::{Debug, Display}; -use std::ops::RangeInclusive; +use core::fmt::{self, Debug, Display}; +use core::ops::RangeInclusive; /// A decoded RISC-V integer register. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -83,7 +83,7 @@ impl Reg { } impl Display for Reg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let n = self.0; match n { 0 => write!(f, "zero"), @@ -351,7 +351,7 @@ impl AmoOrdering { } impl Debug for Inst { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self, f) } } @@ -360,7 +360,7 @@ impl Debug for Inst { /// /// Note that the precise output here is not considered stable. impl Display for Inst { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Inst::Lui { uimm, dest } => write!(f, "lui {dest}, {}", uimm >> 12), Inst::Auipc { uimm, dest } => write!(f, "auipc {dest}, {}", uimm >> 12), @@ -497,7 +497,7 @@ impl Display for Inst { } impl Display for FenceSet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.device_input { write!(f, "i")?; } @@ -515,7 +515,7 @@ impl Display for FenceSet { } impl Display for AmoOrdering { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AmoOrdering::Relaxed => write!(f, ""), AmoOrdering::Acquire => write!(f, ".aq"), @@ -526,7 +526,7 @@ impl Display for AmoOrdering { } impl Display for AmoOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AmoOp::Swap => write!(f, "swap"), AmoOp::Add => write!(f, "add"), @@ -542,16 +542,16 @@ impl Display for AmoOp { } impl Debug for DecodeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DecodeError") - .field("instruction", &format!("{:0>32b}", self.instruction)) + .field("instruction", &format_args!("{:0>32b}", self.instruction)) .field("unexpected_field", &self.unexpected_field) .finish() } } impl Display for DecodeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "failed to decode instruction '{:0>32b}' because of field '{}'", @@ -560,7 +560,7 @@ impl Display for DecodeError { } } -impl std::error::Error for DecodeError {} +impl core::error::Error for DecodeError {} fn sign_extend(value: u32, size: u32) -> u32 { let right = u32::BITS - size; @@ -1322,6 +1322,7 @@ impl Inst { #[cfg(test)] mod tests { + extern crate std; use std::io::Write; use crate::Inst; @@ -1333,7 +1334,7 @@ mod tests { if (i % (2 << 25)) == 0 { let percent = i as f32 / (u32::MAX as f32); let done = (100.0 * percent) as usize; - print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done)); + std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done)); std::io::stdout().flush().unwrap(); } let _ = super::Inst::decode(i); From fd10c930348f364df460642f302fca02e2586495 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 22 Mar 2025 21:11:00 +0100 Subject: [PATCH 5/5] cat --- rvdc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rvdc/Cargo.toml b/rvdc/Cargo.toml index beb84ae..b3bdb0e 100644 --- a/rvdc/Cargo.toml +++ b/rvdc/Cargo.toml @@ -5,7 +5,7 @@ description = "RISC-V instruction decoder" repository = "https://github.com/Noratrieb/rustv32i" edition = "2024" keywords = ["risc-v", "riscv", "decoder", "instruction", "parser"] -categories = ["Parser implementations"] +categories = ["parser-implementations"] license = "MIT OR Apache-2.0" [dependencies]