-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Alignof/develop
Ver 0.1.0
- Loading branch information
Showing
16 changed files
with
2,389 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: Rust | ||
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
pull_request: | ||
|
||
env: | ||
CARGO_INCREMENTAL: 0 | ||
|
||
jobs: | ||
rust: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- uses: dtolnay/rust-toolchain@v1 | ||
with: | ||
toolchain: stable #(Rustup toolchain specifier e.g. stable, nightly, 1.42.0, nightly-2022-01-01. ) | ||
#target: (Comma-separated string of additional targets to install e.g. wasm32-unknown-unknown) | ||
components: clippy, rustfmt | ||
|
||
- name: cache dependencies | ||
uses: Swatinem/[email protected] | ||
|
||
- name: reviewdog / clippy | ||
uses: sksat/[email protected] | ||
with: | ||
reporter: github-pr-review | ||
clippy_flags: --all-features | ||
|
||
- name: format check | ||
run: cargo fmt --all -- --check | ||
|
||
- name: unit test | ||
run: cargo test | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[package] | ||
name = "raki" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,28 @@ | ||
# raki | ||
RISC-V instruction decoder. | ||
[![Rust](https://github.com/Alignof/raki/actions/workflows/rust.yml/badge.svg)](https://github.com/Alignof/raki/actions/workflows/rust.yml) | ||
RISC-V instruction decoder written in Rust. | ||
|
||
- Both 32/64bit support. | ||
- Support `rv32/64imac`, `Zicsr`, `Zifencei` extensions. | ||
- Implement Display trait for formatting. | ||
|
||
## Usage | ||
Call the `decode` as u16/u32 method. | ||
```rust | ||
use raki::Isa; | ||
use raki::decode::Decode; | ||
use raki::instruction::Instruction; | ||
|
||
let inst: u32 = 0b1110_1110_1100_0010_1000_0010_1001_0011; | ||
let inst: Instruction = match inst.decode(Isa::Rv32) { | ||
Ok(inst) => inst, | ||
Err(e) => panic!("decoding failed due to {e:?}"), | ||
}; | ||
println!("{inst}"); | ||
// --output-- | ||
// addi t0, t0, -276 | ||
``` | ||
|
||
## License | ||
This crate is licensed under MIT. | ||
See `LICENSE` for details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
//! Implementation of decoder. | ||
mod inst_16; | ||
mod inst_32; | ||
|
||
use crate::instruction::{Extensions, Instruction, OpcodeKind}; | ||
use crate::Isa; | ||
|
||
/// Return Err if given opcode is only available on Rv64. | ||
fn only_rv64(opcode: OpcodeKind, isa: Isa) -> Result<OpcodeKind, DecodingError> { | ||
match isa { | ||
Isa::Rv32 => Err(DecodingError::OnlyRv64Inst), | ||
Isa::Rv64 => Ok(opcode), | ||
} | ||
} | ||
|
||
/// Cause of decoding error. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// use raki::Isa; | ||
/// use raki::decode::{Decode, DecodingError}; | ||
/// use raki::instruction::Instruction; | ||
/// | ||
/// // try to decode illegal instruction. | ||
/// let illegal_inst: u32 = 0b0000_0000_0000_0000_0000_0000_0000_0000; | ||
/// if let Err(error) = illegal_inst.decode(Isa::Rv64) { | ||
/// assert!(matches!(error, DecodingError::IllegalOpcode)); | ||
/// } | ||
/// | ||
/// // try to decode rv64 instruction on rv32 environment. | ||
/// let rv64_inst: u32 = 0b100000000100010011010000100011; | ||
/// if let Err(error) = rv64_inst.decode(Isa::Rv32) { | ||
/// assert!(matches!(error, DecodingError::OnlyRv64Inst)); | ||
/// } | ||
/// ``` | ||
#[derive(Debug)] | ||
pub enum DecodingError { | ||
/// 32bit instructions are expected, but it is compressed instruction. | ||
Not16BitInst, | ||
/// Compressed instructions are expected, but it is 32bit length. | ||
Not32BitInst, | ||
/// It has unexpected Funct3 value. | ||
IllegalFunct3, | ||
/// It has unexpected Funct5 value. | ||
IllegalFunct5, | ||
/// It has unexpected Funct6 value. | ||
IllegalFunct6, | ||
/// It has unexpected Funct7 value. | ||
IllegalFunct7, | ||
/// Has an opcode that cannot be decoded. | ||
IllegalOpcode, | ||
/// This instruction is only for Rv64 but appeared at Rv32. | ||
OnlyRv64Inst, | ||
} | ||
|
||
/// A trait to decode an instruction from u16/u32. | ||
/// | ||
/// # Usage | ||
/// `decode` method is implemented for u16/u32. | ||
/// thus, just call `decode` as method of u16/u32. | ||
/// ``` | ||
/// use raki::Isa; | ||
/// use raki::decode::Decode; | ||
/// | ||
/// let inst: u32 = 0b1110_1110_1100_0010_1000_0010_1001_0011; | ||
/// println!("{:?}", inst.decode(Isa::Rv64)); | ||
/// ``` | ||
pub trait Decode { | ||
/// Decode an instruction from u16/u32. | ||
fn decode(&self, isa: Isa) -> Result<Instruction, DecodingError>; | ||
/// Parse opcode. | ||
fn parse_opcode(self, isa: Isa) -> Result<OpcodeKind, DecodingError>; | ||
/// Parse destination register. | ||
fn parse_rd(self, opkind: &OpcodeKind) -> Result<Option<usize>, DecodingError>; | ||
/// Parse source register 1. | ||
fn parse_rs1(self, opkind: &OpcodeKind) -> Result<Option<usize>, DecodingError>; | ||
/// Parse source register 2. | ||
fn parse_rs2(self, opkind: &OpcodeKind) -> Result<Option<usize>, DecodingError>; | ||
/// Parse immediate. | ||
fn parse_imm(self, opkind: &OpcodeKind, isa: Isa) -> Result<Option<i32>, DecodingError>; | ||
} | ||
|
||
/// A trait to help decoding. | ||
trait DecodeUtil { | ||
/// Obtains bits in a specified range. | ||
/// The range is `[end, start]`. | ||
/// ```ignore | ||
/// use raki::decode::DecodeUtil; | ||
/// let bit = 0b0101_0101_1001; | ||
/// let sliced = bit.slice(5, 2); | ||
/// assert_eq!(sliced, 0b1_0110); | ||
/// ``` | ||
/// # Arguments | ||
/// * `end` - end of range. | ||
/// * `start` - start of range. | ||
fn slice(self, end: u32, start: u32) -> Self; | ||
|
||
/// The values of the bits of Self are set to the array value positions in order from the highest to the lowest. | ||
/// ```ignore | ||
/// use raki::decode::DecodeUtil; | ||
/// let bit: u32 = 0b1010_1101; | ||
/// let sliced = bit.set(&[7, 5, 3, 2, 0, 6, 4, 1]); | ||
/// assert_eq!(sliced, 0b1111_1000); | ||
/// ``` | ||
/// # Arguments | ||
/// * `mask` - It contain the bit order. | ||
fn set(self, mask: &[u32]) -> u32; | ||
|
||
/// Get `Extensions` from a u16/u32 value. | ||
fn extension(self) -> Extensions; | ||
|
||
/// Convert i32 to a sign-extended any size number. | ||
/// # Arguments | ||
/// * `imm32` - The value to be converted. | ||
/// * `bit_size` - Bit width to be converted. | ||
fn to_signed_nbit(&self, imm32: i32, bit_size: u32) -> i32 { | ||
let imm32 = imm32 & (2_i32.pow(bit_size) - 1); | ||
if imm32 >> (bit_size - 1) & 0x1 == 1 { | ||
imm32 - 2_i32.pow(bit_size) | ||
} else { | ||
imm32 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
#[allow(non_snake_case)] | ||
mod c_extension; | ||
|
||
use super::{Decode, DecodeUtil, DecodingError}; | ||
use crate::instruction::{Extensions, InstFormat, Instruction, OpcodeKind}; | ||
use crate::Isa; | ||
|
||
impl Decode for u16 { | ||
fn decode(&self, isa: Isa) -> Result<Instruction, DecodingError> { | ||
let new_opc = self.parse_opcode(isa)?; | ||
let new_rd: Option<usize> = self.parse_rd(&new_opc)?; | ||
let new_rs1: Option<usize> = self.parse_rs1(&new_opc)?; | ||
let new_rs2: Option<usize> = self.parse_rs2(&new_opc)?; | ||
let new_imm: Option<i32> = self.parse_imm(&new_opc, isa)?; | ||
let new_ext: Extensions = new_opc.get_extension(); | ||
let new_fmt: InstFormat = new_opc.get_format(); | ||
|
||
Ok(Instruction { | ||
opc: new_opc, | ||
rd: new_rd, | ||
rs1: new_rs1, | ||
rs2: new_rs2, | ||
imm: new_imm, | ||
extension: new_ext, | ||
inst_format: new_fmt, | ||
}) | ||
} | ||
|
||
fn parse_opcode(self, isa: Isa) -> Result<OpcodeKind, DecodingError> { | ||
match self.extension() { | ||
Extensions::C => c_extension::parse_opcode(self, isa), | ||
_ => Err(DecodingError::Not16BitInst), | ||
} | ||
} | ||
|
||
fn parse_rd(self, opkind: &OpcodeKind) -> Result<Option<usize>, DecodingError> { | ||
match self.extension() { | ||
Extensions::C => c_extension::parse_rd(self, opkind), | ||
_ => Err(DecodingError::Not16BitInst), | ||
} | ||
} | ||
|
||
fn parse_rs1(self, opkind: &OpcodeKind) -> Result<Option<usize>, DecodingError> { | ||
match self.extension() { | ||
Extensions::C => c_extension::parse_rs1(self, opkind), | ||
_ => Err(DecodingError::Not16BitInst), | ||
} | ||
} | ||
|
||
fn parse_rs2(self, opkind: &OpcodeKind) -> Result<Option<usize>, DecodingError> { | ||
match self.extension() { | ||
Extensions::C => c_extension::parse_rs2(self, opkind), | ||
_ => Err(DecodingError::Not16BitInst), | ||
} | ||
} | ||
|
||
fn parse_imm(self, opkind: &OpcodeKind, _isa: Isa) -> Result<Option<i32>, DecodingError> { | ||
match self.extension() { | ||
Extensions::C => c_extension::parse_imm(self, opkind), | ||
_ => Err(DecodingError::Not16BitInst), | ||
} | ||
} | ||
} | ||
|
||
impl DecodeUtil for u16 { | ||
fn slice(self, end: u32, start: u32) -> Self { | ||
(self >> start) & (2_u16.pow(end - start + 1) - 1) | ||
} | ||
|
||
fn set(self, mask: &[u32]) -> u32 { | ||
let mut inst: u32 = 0; | ||
for (i, m) in mask.iter().rev().enumerate() { | ||
inst |= ((u32::from(self) >> i) & 0x1) << m; | ||
} | ||
|
||
inst | ||
} | ||
|
||
fn extension(self) -> Extensions { | ||
Extensions::C | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
#[allow(unused_variables)] | ||
mod decode_16 { | ||
#[test] | ||
fn decoding_16bit_test() { | ||
use super::*; | ||
use OpcodeKind::*; | ||
let test_16 = |inst_16: u16, | ||
op: OpcodeKind, | ||
rd: Option<usize>, | ||
rs1: Option<usize>, | ||
rs2: Option<usize>, | ||
imm: Option<i32>| { | ||
let op_16 = inst_16.parse_opcode(Isa::Rv64).unwrap(); | ||
assert!(matches!(&op_16, op)); | ||
assert_eq!(inst_16.parse_rd(&op_16).unwrap(), rd); | ||
assert_eq!(inst_16.parse_rs1(&op_16).unwrap(), rs1); | ||
assert_eq!(inst_16.parse_rs2(&op_16).unwrap(), rs2); | ||
assert_eq!(inst_16.parse_imm(&op_16, Isa::Rv64).unwrap(), imm); | ||
}; | ||
|
||
test_16(0b0000_0000_0000_0001, C_NOP, None, None, None, Some(0)); | ||
test_16(0b0110_0011_1000_0001, C_LUI, Some(7), None, None, Some(0)); | ||
test_16( | ||
0b1000_0010_1100_0001, | ||
C_SRAI, | ||
Some(13), | ||
Some(13), | ||
None, | ||
Some(16), | ||
); | ||
test_16(0x4521, C_LI, Some(10), None, None, Some(8)); | ||
test_16(0xb5e5, C_J, None, None, None, Some(-280)); | ||
test_16(0x6105, C_ADDI, None, Some(2), None, Some(32)); | ||
test_16(0x8082, C_JR, None, Some(1), None, None); | ||
test_16(0xe29d, C_BNEZ, None, Some(13), None, Some(38)); | ||
test_16(0xc05c, C_SW, None, Some(8), Some(15), Some(4)); | ||
test_16(0x9002, C_EBREAK, None, None, None, None); | ||
test_16(0x880a, C_MV, Some(16), None, Some(2), None); | ||
test_16(0x8585, C_SRAI, Some(11), Some(11), None, Some(1)); | ||
} | ||
} |
Oops, something went wrong.