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:
- 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"

5
Cargo.lock generated
View file

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

View file

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

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::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>) -> 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>) -> 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 {
Yes,
No,
Yes,
}
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 {
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<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);
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<Inst, DecodeError> {
/// Decode a normal (not compressed) instruction.
pub fn decode_normal(code: u32) -> Result<Inst, DecodeError> {
let code = InstCode(code);
let inst = match code.opcode() {
// LUI

View file

@ -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<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 {
use owo_colors::*;
use std::hash::{Hash, Hasher};

View file

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

View file

@ -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();

View file

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