mirror of
https://github.com/Noratrieb/rustv32i.git
synced 2026-01-14 13:25:01 +01:00
prepare rvdc
This commit is contained in:
parent
a132b481e6
commit
5447d44290
10 changed files with 143 additions and 60 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -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
5
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
10
rvdc/Cargo.toml
Normal 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
14
rvdc/README.md
Normal 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
|
||||||
|
|
@ -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
|
||||||
42
src/emu.rs
42
src/emu.rs
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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<()> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue