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