Skip to content

Commit

Permalink
Merge pull request #1 from Alignof/develop
Browse files Browse the repository at this point in the history
Ver 0.1.0
  • Loading branch information
Alignof authored Oct 12, 2023
2 parents 32e207b + 7dbb8a5 commit 70a9e87
Show file tree
Hide file tree
Showing 16 changed files with 2,389 additions and 1 deletion.
39 changes: 39 additions & 0 deletions .github/workflows/rust.yml
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

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb


# Added by cargo

/target
/Cargo.lock
8 changes: 8 additions & 0 deletions Cargo.toml
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]
28 changes: 27 additions & 1 deletion README.md
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.
125 changes: 125 additions & 0 deletions src/decode.rs
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
}
}
}
125 changes: 125 additions & 0 deletions src/decode/inst_16.rs
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));
}
}
Loading

0 comments on commit 70a9e87

Please sign in to comment.