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] 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<()> {