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..c23a299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,17 @@ +[workspace] +members = [".", "rvdc"] + [package] name = "rustv32i" version = "0.1.0" edition = "2024" +license = "MIT OR Apache-2.0" [dependencies] eyre = "0.6.12" libc = "0.2.170" owo-colors = "4.2.0" +rvdc = { path = "./rvdc" } [profile.dev] opt-level = 1 @@ -17,9 +22,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/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 new file mode 100644 index 0000000..b3bdb0e --- /dev/null +++ b/rvdc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +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] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(slow_tests)'] } 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/README.md b/rvdc/README.md new file mode 100644 index 0000000..f7093de --- /dev/null +++ b/rvdc/README.md @@ -0,0 +1,70 @@ +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") +``` + +# `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. + +# 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/src/inst.rs b/rvdc/src/lib.rs similarity index 83% rename from src/inst.rs rename to rvdc/src/lib.rs index 23ab9f9..2f82845 100644 --- a/src/inst.rs +++ b/rvdc/src/lib.rs @@ -1,9 +1,117 @@ -use crate::emu::Reg; -use std::fmt::{Debug, Display}; -use std::ops::RangeInclusive; +#![doc = include_str!("../README.md")] +#![no_std] +#![deny(missing_docs)] -#[derive(Clone, Copy)] +use core::fmt::{self, Debug, Display}; +use core::ops::RangeInclusive; + +/// A decoded RISC-V integer register. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +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); + /// 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); + /// 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 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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"), + } + } +} + +/// 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 }, @@ -135,16 +243,26 @@ pub enum Inst { }, } -#[derive(Clone, Copy)] +/// 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, } -#[derive(Clone, Copy, PartialEq, Eq)] +/// 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, @@ -152,33 +270,54 @@ 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 Swap, + /// ADD Add, + /// XOR Xor, + /// AND And, + /// OR Or, + /// Signed minimum Min, + /// Signed maximum Max, + /// Unsigned minimum Minu, + /// Unsigned maximum 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, + /// Which field of the instruction contained unexpected bits. pub unexpected_field: &'static str, } impl Fence { + /// Whether this fence indicates a `pause` assembler pseudoinstruction. pub fn is_pause(&self) -> bool { self.pred == FenceSet { @@ -200,6 +339,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, @@ -211,13 +351,16 @@ 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) } } +/// 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 { + 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), @@ -354,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")?; } @@ -372,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"), @@ -383,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"), @@ -399,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 '{}'", @@ -417,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; @@ -425,7 +568,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 { @@ -462,13 +605,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)]) @@ -488,7 +634,7 @@ impl InstCode { } #[derive(Clone, Copy)] -pub struct InstCodeC(u16); +struct InstCodeC(u16); impl InstCodeC { fn extract(self, range: RangeInclusive) -> u32 { @@ -526,23 +672,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) } } @@ -552,9 +698,15 @@ 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, + /// Normal 4-byte instruction No, + /// Compressed 2-byte instruction + Yes, } fn decode_error(instruction: impl Into, unexpected_field: &'static str) -> DecodeError { @@ -565,6 +717,19 @@ 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 + } + + /// 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 +739,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 +1021,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 @@ -993,12 +1170,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(), }, @@ -1051,7 +1228,7 @@ impl Inst { match code.funct3() { 0b000 => Inst::Fence { fence: Fence { - fm, + fm: fm as u8, pred, succ, dest: code.rd(), @@ -1145,8 +1322,11 @@ impl Inst { #[cfg(test)] mod tests { + extern crate std; use std::io::Write; + use crate::Inst; + #[test] #[cfg_attr(not(slow_tests), ignore)] fn exhaustive_decode_no_panic() { @@ -1154,11 +1334,16 @@ 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); } let _ = super::Inst::decode(u32::MAX); } + + #[test] + fn size_of_instruction() { + assert!(size_of::() <= 12); + } } 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<()> {