prepare rvdc

This commit is contained in:
nora 2025-03-22 20:32:06 +01:00
parent a132b481e6
commit 5447d44290
10 changed files with 143 additions and 60 deletions

View file

@ -15,8 +15,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build - name: Build
run: cargo build --verbose run: cargo build --workspace --verbose
- name: Run tests - name: Run tests
run: cargo test --verbose run: cargo test --workspace --verbose
env: env:
RUSTFLAGS: "-Copt-level=3 --cfg slow_tests" RUSTFLAGS: "-Copt-level=3 --cfg slow_tests"

5
Cargo.lock generated
View file

@ -102,9 +102,14 @@ dependencies = [
"eyre", "eyre",
"libc", "libc",
"owo-colors", "owo-colors",
"rvdc",
"tempfile", "tempfile",
] ]
[[package]]
name = "rvdc"
version = "0.1.0"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.18.0" version = "3.18.0"

View file

@ -1,3 +1,6 @@
[workspace]
members = [".", "rvdc"]
[package] [package]
name = "rustv32i" name = "rustv32i"
version = "0.1.0" version = "0.1.0"
@ -7,6 +10,7 @@ edition = "2024"
eyre = "0.6.12" eyre = "0.6.12"
libc = "0.2.170" libc = "0.2.170"
owo-colors = "4.2.0" owo-colors = "4.2.0"
rvdc = { path = "./rvdc" }
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1
@ -17,9 +21,6 @@ debug = 1
[dev-dependencies] [dev-dependencies]
tempfile = "3.18.0" tempfile = "3.18.0"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(slow_tests)'] }
[lints.clippy] [lints.clippy]
type_complexity = "allow" type_complexity = "allow"
if_same_then_else = "allow" if_same_then_else = "allow"

10
rvdc/Cargo.toml Normal file
View file

@ -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)'] }

14
rvdc/README.md Normal file
View file

@ -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

View file

@ -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::fmt::{Debug, Display};
use std::ops::RangeInclusive; 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] #[rustfmt::skip]
pub enum Inst { pub enum Inst {
/// Load Upper Immediate /// Load Upper Immediate
@ -135,7 +186,7 @@ pub enum Inst {
}, },
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fence { pub struct Fence {
pub fm: u32, pub fm: u32,
pub pred: FenceSet, pub pred: FenceSet,
@ -144,7 +195,7 @@ pub struct Fence {
pub src: Reg, pub src: Reg,
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct FenceSet { pub struct FenceSet {
pub device_input: bool, pub device_input: bool,
pub device_output: bool, pub device_output: bool,
@ -152,29 +203,41 @@ pub struct FenceSet {
pub memory_write: bool, 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 { pub enum AmoOrdering {
/// No bits.
Relaxed, Relaxed,
/// `aq`
Acquire, Acquire,
/// `rl`
Release, Release,
/// `aq`, `rl`
SeqCst, SeqCst,
} }
#[derive(Clone, Copy)] /// An atomic memory operations from the Zaamo extension.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum AmoOp { pub enum AmoOp {
Swap, Swap,
Add, Add,
Xor, Xor,
And, And,
Or, Or,
/// Signed minimum
Min, Min,
/// Signed maximum
Max, Max,
/// Unsigned minimum
Minu, Minu,
/// Unsigned maximum
Maxu, Maxu,
} }
pub struct DecodeError { pub struct DecodeError {
/// The instruction bytes that failed to decode.
pub instruction: u32, pub instruction: u32,
/// Which field of the instruction contained unexpected bits.
pub unexpected_field: &'static str, pub unexpected_field: &'static str,
} }
@ -216,6 +279,7 @@ impl Debug for Inst {
} }
} }
/// Prints the instruction in disassembled form.
impl Display for Inst { impl Display for Inst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self { match *self {
@ -425,7 +489,7 @@ fn sign_extend(value: u32, size: u32) -> u32 {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct InstCode(u32); struct InstCode(u32);
impl InstCode { impl InstCode {
fn extract(self, range: RangeInclusive<u32>) -> u32 { fn extract(self, range: RangeInclusive<u32>) -> u32 {
@ -488,7 +552,7 @@ impl InstCode {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct InstCodeC(u16); struct InstCodeC(u16);
impl InstCodeC { impl InstCodeC {
fn extract(self, range: RangeInclusive<u32>) -> u32 { fn extract(self, range: RangeInclusive<u32>) -> u32 {
@ -552,9 +616,13 @@ impl From<InstCodeC> 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 { pub enum IsCompressed {
Yes,
No, No,
Yes,
} }
fn decode_error(instruction: impl Into<InstCode>, unexpected_field: &'static str) -> DecodeError { fn decode_error(instruction: impl Into<InstCode>, unexpected_field: &'static str) -> DecodeError {
@ -565,6 +633,18 @@ fn decode_error(instruction: impl Into<InstCode>, unexpected_field: &'static str
} }
impl Inst { 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> { pub fn decode(code: u32) -> Result<(Inst, IsCompressed), DecodeError> {
let is_compressed = (code & 0b11) != 0b11; let is_compressed = (code & 0b11) != 0b11;
if is_compressed { if is_compressed {
@ -574,7 +654,18 @@ impl Inst {
} }
} }
fn decode_compressed(code: u16) -> Result<Inst, DecodeError> { /// 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<Inst, DecodeError> {
let code = InstCodeC(code); let code = InstCodeC(code);
if code.0 == 0 { if code.0 == 0 {
return Err(decode_error(code, "null instruction")); return Err(decode_error(code, "null instruction"));
@ -845,7 +936,8 @@ impl Inst {
Ok(inst) Ok(inst)
} }
fn decode_normal(code: u32) -> Result<Inst, DecodeError> { /// Decode a normal (not compressed) instruction.
pub fn decode_normal(code: u32) -> Result<Inst, DecodeError> {
let code = InstCode(code); let code = InstCode(code);
let inst = match code.opcode() { let inst = match code.opcode() {
// LUI // LUI

View file

@ -1,4 +1,4 @@
use crate::inst::{AmoOp, DecodeError, Inst, IsCompressed}; use rvdc::{AmoOp, DecodeError, Inst, IsCompressed, Reg};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
io::Write, io::Write,
@ -114,46 +114,6 @@ impl<XLEN> IndexMut<Reg> for Emulator<XLEN> {
} }
} }
#[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 { fn hash_color(value: usize) -> impl Display {
use owo_colors::*; use owo_colors::*;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};

View file

@ -2,7 +2,6 @@ use eyre::{OptionExt, bail};
pub mod elf; pub mod elf;
pub mod emu; pub mod emu;
pub mod inst;
// 2 MiB // 2 MiB
const MEMORY_SIZE: usize = 2 << 21; const MEMORY_SIZE: usize = 2 << 21;

View file

@ -1,7 +1,8 @@
use std::io::Write; use std::io::Write;
use eyre::eyre; use eyre::eyre;
use rustv32i::emu::{self, Memory, Reg}; use rustv32i::emu::{self, Memory};
use rvdc::Reg;
fn main() -> eyre::Result<()> { fn main() -> eyre::Result<()> {
let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap(); let content = std::fs::read(std::env::args().nth(1).unwrap()).unwrap();

View file

@ -5,7 +5,8 @@ use std::{
}; };
use eyre::{Context, bail}; use eyre::{Context, bail};
use rustv32i::emu::{Reg, Status}; use rustv32i::emu::Status;
use rvdc::Reg;
#[test] #[test]
fn check() -> eyre::Result<()> { fn check() -> eyre::Result<()> {