diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a83f6f..4a1e9f1 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 --workspace --verbose + run: cargo build --verbose - name: Run tests - run: cargo test --workspace --verbose + run: cargo test --verbose env: RUSTFLAGS: "-Copt-level=3 --cfg slow_tests" diff --git a/Cargo.lock b/Cargo.lock index 237b555..b9aacfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,14 +102,9 @@ 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 c23a299..40009e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,12 @@ -[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 @@ -22,6 +17,9 @@ 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 deleted file mode 100644 index 1b5ec8b..0000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,176 +0,0 @@ - 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 deleted file mode 100644 index 581dd18..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index bd05a0f..0000000 --- a/rvdc/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.1.0 - -Initial release. diff --git a/rvdc/Cargo.toml b/rvdc/Cargo.toml deleted file mode 100644 index b3bdb0e..0000000 --- a/rvdc/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[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 deleted file mode 120000 index 965b606..0000000 --- a/rvdc/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/rvdc/LICENSE-MIT b/rvdc/LICENSE-MIT deleted file mode 120000 index 76219eb..0000000 --- a/rvdc/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/rvdc/README.md b/rvdc/README.md deleted file mode 100644 index f7093de..0000000 --- a/rvdc/README.md +++ /dev/null @@ -1,70 +0,0 @@ -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/emu.rs b/src/emu.rs index 29c56af..d9ffd0e 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -1,4 +1,4 @@ -use rvdc::{AmoOp, DecodeError, Inst, IsCompressed, Reg}; +use crate::inst::{AmoOp, DecodeError, Inst, IsCompressed}; use std::{ fmt::{Debug, Display}, io::Write, @@ -114,6 +114,46 @@ 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/rvdc/src/lib.rs b/src/inst.rs similarity index 83% rename from rvdc/src/lib.rs rename to src/inst.rs index 2f82845..23ab9f9 100644 --- a/rvdc/src/lib.rs +++ b/src/inst.rs @@ -1,117 +1,9 @@ -#![doc = include_str!("../README.md")] -#![no_std] -#![deny(missing_docs)] +use crate::emu::Reg; +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)] -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)] +#[derive(Clone, Copy)] #[rustfmt::skip] -#[expect(missing_docs)] // enum variant fields pub enum Inst { /// Load Upper Immediate Lui { uimm: u32, dest: Reg }, @@ -243,26 +135,16 @@ pub enum Inst { }, } -/// The details of a RISC-V `fence` instruction. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy)] pub struct Fence { - /// 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 fm: u32, 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)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct FenceSet { pub device_input: bool, pub device_output: bool, @@ -270,54 +152,33 @@ pub struct FenceSet { pub memory_write: bool, } -/// An atomic memory ordering for instructions from the A extension. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy)] pub enum AmoOrdering { - /// No bits. Relaxed, - /// `aq` Acquire, - /// `rl` Release, - /// `aq`, `rl` SeqCst, } -/// An atomic memory operations from the Zaamo extension. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy)] 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 { @@ -339,7 +200,6 @@ 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, @@ -351,16 +211,13 @@ impl AmoOrdering { } impl Debug for Inst { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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 fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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 +354,7 @@ impl Display for Inst { } impl Display for FenceSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.device_input { write!(f, "i")?; } @@ -515,7 +372,7 @@ impl Display for FenceSet { } impl Display for AmoOrdering { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AmoOrdering::Relaxed => write!(f, ""), AmoOrdering::Acquire => write!(f, ".aq"), @@ -526,7 +383,7 @@ impl Display for AmoOrdering { } impl Display for AmoOp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AmoOp::Swap => write!(f, "swap"), AmoOp::Add => write!(f, "add"), @@ -542,16 +399,16 @@ impl Display for AmoOp { } impl Debug for DecodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DecodeError") - .field("instruction", &format_args!("{:0>32b}", self.instruction)) + .field("instruction", &format!("{:0>32b}", self.instruction)) .field("unexpected_field", &self.unexpected_field) .finish() } } impl Display for DecodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "failed to decode instruction '{:0>32b}' because of field '{}'", @@ -560,7 +417,7 @@ impl Display for DecodeError { } } -impl core::error::Error for DecodeError {} +impl std::error::Error for DecodeError {} fn sign_extend(value: u32, size: u32) -> u32 { let right = u32::BITS - size; @@ -568,7 +425,7 @@ fn sign_extend(value: u32, size: u32) -> u32 { } #[derive(Clone, Copy)] -struct InstCode(u32); +pub struct InstCode(u32); impl InstCode { fn extract(self, range: RangeInclusive) -> u32 { @@ -605,16 +462,13 @@ impl InstCode { self.extract(25..=31) } fn rs1(self) -> Reg { - Reg(self.extract(15..=19) as u8) + Reg(self.extract(15..=19)) } fn rs2(self) -> Reg { - Reg(self.extract(20..=24) as u8) - } - fn rs2_imm(self) -> u32 { - self.extract(20..=24) + Reg(self.extract(20..=24)) } fn rd(self) -> Reg { - Reg(self.extract(7..=11) as u8) + Reg(self.extract(7..=11)) } fn imm_i(self) -> u32 { self.immediate_s(&[(20..=31, 0)]) @@ -634,7 +488,7 @@ impl InstCode { } #[derive(Clone, Copy)] -struct InstCodeC(u16); +pub struct InstCodeC(u16); impl InstCodeC { fn extract(self, range: RangeInclusive) -> u32 { @@ -672,23 +526,23 @@ impl InstCodeC { } /// rd/rs1 (7..=11) fn rd(self) -> Reg { - Reg(self.extract(7..=11) as u8) + Reg(self.extract(7..=11)) } /// rs2 (2..=6) fn rs2(self) -> Reg { - Reg(self.extract(2..=6) as u8) + Reg(self.extract(2..=6)) } /// rs1' (7..=9) fn rs1_short(self) -> Reg { let smol_reg = self.extract(7..=9); // map to x8..=x15 - Reg((smol_reg + 8) as u8) + Reg(smol_reg + 8) } /// rs2' (2..=4) fn rs2_short(self) -> Reg { let smol_reg = self.extract(2..=4); // map to x8..=x15 - Reg((smol_reg + 8) as u8) + Reg(smol_reg + 8) } } @@ -698,15 +552,9 @@ 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 { - /// Normal 4-byte instruction - No, - /// Compressed 2-byte instruction Yes, + No, } fn decode_error(instruction: impl Into, unexpected_field: &'static str) -> DecodeError { @@ -717,19 +565,6 @@ 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 { @@ -739,18 +574,7 @@ impl Inst { } } - /// 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 { + fn decode_compressed(code: u16) -> Result { let code = InstCodeC(code); if code.0 == 0 { return Err(decode_error(code, "null instruction")); @@ -1021,8 +845,7 @@ impl Inst { Ok(inst) } - /// Decode a normal (not compressed) instruction. - pub fn decode_normal(code: u32) -> Result { + fn decode_normal(code: u32) -> Result { let code = InstCode(code); let inst = match code.opcode() { // LUI @@ -1170,12 +993,12 @@ impl Inst { }, 0b101 => match code.funct7() { 0b0000000 => Inst::Srli { - imm: code.rs2_imm(), + imm: code.rs2().0, dest: code.rd(), src1: code.rs1(), }, 0b0100000 => Inst::Srai { - imm: code.rs2_imm(), + imm: code.rs2().0, dest: code.rd(), src1: code.rs1(), }, @@ -1228,7 +1051,7 @@ impl Inst { match code.funct3() { 0b000 => Inst::Fence { fence: Fence { - fm: fm as u8, + fm, pred, succ, dest: code.rd(), @@ -1322,11 +1145,8 @@ 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() { @@ -1334,16 +1154,11 @@ mod tests { if (i % (2 << 25)) == 0 { let percent = i as f32 / (u32::MAX as f32); let done = (100.0 * percent) as usize; - std::print!("\r{}{}", "#".repeat(done), "-".repeat(100 - done)); + 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/lib.rs b/src/lib.rs index 934cf7e..1a7691d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ 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 f70208d..3178e89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ use std::io::Write; use eyre::eyre; -use rustv32i::emu::{self, Memory}; -use rvdc::Reg; +use rustv32i::emu::{self, Memory, 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 a0310c6..823ab24 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -5,8 +5,7 @@ use std::{ }; use eyre::{Context, bail}; -use rustv32i::emu::Status; -use rvdc::Reg; +use rustv32i::emu::{Reg, Status}; #[test] fn check() -> eyre::Result<()> {