mirror of
https://github.com/Noratrieb/rustv32i.git
synced 2026-01-14 13:25:01 +01:00
start tests
This commit is contained in:
parent
ca565ddeb3
commit
0af012d43a
10 changed files with 412 additions and 118 deletions
165
Cargo.lock
generated
165
Cargo.lock
generated
|
|
@ -2,6 +2,28 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eyre"
|
name = "eyre"
|
||||||
version = "0.6.12"
|
version = "0.6.12"
|
||||||
|
|
@ -12,6 +34,24 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -24,16 +64,141 @@ version = "0.2.170"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.3"
|
version = "1.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustv32i"
|
name = "rustv32i"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"libc",
|
"libc",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,6 @@ libc = "0.2.170"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 1
|
debug = 1
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.18.0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use eyre::{Result, bail};
|
use eyre::{Result, bail};
|
||||||
|
|
||||||
pub struct Elf {
|
pub struct Elf<'a> {
|
||||||
pub content: Vec<u8>,
|
pub content: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -30,7 +30,7 @@ pub struct Phdr {
|
||||||
pub p_align: u32,
|
pub p_align: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Elf {
|
impl<'a> Elf<'a> {
|
||||||
pub fn header(&self) -> Result<Header> {
|
pub fn header(&self) -> Result<Header> {
|
||||||
let (ident, rest) = self.content.split_bytes(16)?;
|
let (ident, rest) = self.content.split_bytes(16)?;
|
||||||
if ident[..4] != *b"\x7fELF" {
|
if ident[..4] != *b"\x7fELF" {
|
||||||
|
|
|
||||||
45
src/emu.rs
45
src/emu.rs
|
|
@ -10,9 +10,9 @@ pub struct Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
fn check_align(&self, addr: u32, align: u32) -> Result<(), Error> {
|
fn check_align(&self, addr: u32, align: u32) -> Result<(), Status> {
|
||||||
if addr % 2 != 0 {
|
if addr % 2 != 0 {
|
||||||
Err(Error::UnaligneMemoryAccess {
|
Err(Status::UnaligneMemoryAccess {
|
||||||
addr,
|
addr,
|
||||||
required_align: align,
|
required_align: align,
|
||||||
})
|
})
|
||||||
|
|
@ -20,44 +20,44 @@ impl Memory {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn slice(&self, addr: u32, len: u32) -> Result<&[u8], Error> {
|
pub fn slice(&self, addr: u32, len: u32) -> Result<&[u8], Status> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.mem
|
.mem
|
||||||
.get((addr as usize)..)
|
.get((addr as usize)..)
|
||||||
.ok_or(Error::InvalidMemoryAccess(addr))?
|
.ok_or(Status::InvalidMemoryAccess(addr))?
|
||||||
.get(..(len as usize))
|
.get(..(len as usize))
|
||||||
.ok_or(Error::InvalidMemoryAccess(addr))?)
|
.ok_or(Status::InvalidMemoryAccess(addr))?)
|
||||||
}
|
}
|
||||||
pub fn slice_mut(&mut self, addr: u32, len: u32) -> Result<&mut [u8], Error> {
|
pub fn slice_mut(&mut self, addr: u32, len: u32) -> Result<&mut [u8], Status> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.mem
|
.mem
|
||||||
.get_mut((addr as usize)..)
|
.get_mut((addr as usize)..)
|
||||||
.ok_or(Error::InvalidMemoryAccess(addr))?
|
.ok_or(Status::InvalidMemoryAccess(addr))?
|
||||||
.get_mut(..(len as usize))
|
.get_mut(..(len as usize))
|
||||||
.ok_or(Error::InvalidMemoryAccess(addr))?)
|
.ok_or(Status::InvalidMemoryAccess(addr))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_u8(&self, addr: u32) -> Result<u8, Error> {
|
pub fn load_u8(&self, addr: u32) -> Result<u8, Status> {
|
||||||
Ok(u8::from_le_bytes(self.slice(addr, 1)?.try_into().unwrap()))
|
Ok(u8::from_le_bytes(self.slice(addr, 1)?.try_into().unwrap()))
|
||||||
}
|
}
|
||||||
pub fn load_u16(&self, addr: u32) -> Result<u16, Error> {
|
pub fn load_u16(&self, addr: u32) -> Result<u16, Status> {
|
||||||
Ok(u16::from_le_bytes(self.slice(addr, 2)?.try_into().unwrap()))
|
Ok(u16::from_le_bytes(self.slice(addr, 2)?.try_into().unwrap()))
|
||||||
}
|
}
|
||||||
pub fn load_u32(&self, addr: u32) -> Result<u32, Error> {
|
pub fn load_u32(&self, addr: u32) -> Result<u32, Status> {
|
||||||
Ok(u32::from_le_bytes(self.slice(addr, 4)?.try_into().unwrap()))
|
Ok(u32::from_le_bytes(self.slice(addr, 4)?.try_into().unwrap()))
|
||||||
}
|
}
|
||||||
pub fn store_u8(&mut self, addr: u32, value: u8) -> Result<(), Error> {
|
pub fn store_u8(&mut self, addr: u32, value: u8) -> Result<(), Status> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.slice_mut(addr, 1)?
|
.slice_mut(addr, 1)?
|
||||||
.copy_from_slice(&value.to_le_bytes()))
|
.copy_from_slice(&value.to_le_bytes()))
|
||||||
}
|
}
|
||||||
pub fn store_u16(&mut self, addr: u32, value: u16) -> Result<(), Error> {
|
pub fn store_u16(&mut self, addr: u32, value: u16) -> Result<(), Status> {
|
||||||
self.check_align(addr, 2)?;
|
self.check_align(addr, 2)?;
|
||||||
Ok(self
|
Ok(self
|
||||||
.slice_mut(addr, 2)?
|
.slice_mut(addr, 2)?
|
||||||
.copy_from_slice(&value.to_le_bytes()))
|
.copy_from_slice(&value.to_le_bytes()))
|
||||||
}
|
}
|
||||||
pub fn store_u32(&mut self, addr: u32, value: u32) -> Result<(), Error> {
|
pub fn store_u32(&mut self, addr: u32, value: u32) -> Result<(), Status> {
|
||||||
self.check_align(addr, 4)?;
|
self.check_align(addr, 4)?;
|
||||||
Ok(self
|
Ok(self
|
||||||
.slice_mut(addr, 4)?
|
.slice_mut(addr, 4)?
|
||||||
|
|
@ -66,7 +66,7 @@ impl Memory {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Status {
|
||||||
Trap(&'static str),
|
Trap(&'static str),
|
||||||
IllegalInstruction(InstCode, &'static str),
|
IllegalInstruction(InstCode, &'static str),
|
||||||
InvalidMemoryAccess(u32),
|
InvalidMemoryAccess(u32),
|
||||||
|
|
@ -87,8 +87,7 @@ pub struct Emulator {
|
||||||
pub reservation_set: Option<u32>,
|
pub reservation_set: Option<u32>,
|
||||||
|
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
|
pub ecall_handler: Box<dyn FnMut(&mut Memory, &mut [u32; 32]) -> Result<(), Status>>,
|
||||||
pub ecall_handler: Box<dyn FnMut(&mut Memory, &mut [u32; 32]) -> Result<(), Error>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<Reg> for Emulator {
|
impl Index<Reg> for Emulator {
|
||||||
|
|
@ -141,14 +140,14 @@ impl Display for Reg {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Emulator {
|
impl Emulator {
|
||||||
pub fn start_linux(&mut self) -> Error {
|
pub fn start_linux(&mut self) -> Status {
|
||||||
// set top of stack. just some yolo address. with no values there. who needs abi?
|
// set top of stack. just some yolo address. with no values there. who needs abi?
|
||||||
self[Reg::SP] = 4096;
|
self[Reg::SP] = 4096;
|
||||||
|
|
||||||
self.execute()
|
self.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(&mut self) -> Error {
|
fn execute(&mut self) -> Status {
|
||||||
loop {
|
loop {
|
||||||
if let Err(err) = self.step() {
|
if let Err(err) = self.step() {
|
||||||
return err;
|
return err;
|
||||||
|
|
@ -156,15 +155,15 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_pc(&mut self, pc: u32) -> Result<(), Error> {
|
fn set_pc(&mut self, pc: u32) -> Result<(), Status> {
|
||||||
if pc % 4 != 0 {
|
if pc % 4 != 0 {
|
||||||
return Err(Error::UnalignedPc(pc));
|
return Err(Status::UnalignedPc(pc));
|
||||||
}
|
}
|
||||||
self.pc = pc;
|
self.pc = pc;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(&mut self) -> Result<(), Error> {
|
fn step(&mut self) -> Result<(), Status> {
|
||||||
let code = self.mem.load_u32(self.pc)?;
|
let code = self.mem.load_u32(self.pc)?;
|
||||||
let inst = Inst::decode(code)?;
|
let inst = Inst::decode(code)?;
|
||||||
|
|
||||||
|
|
@ -314,7 +313,7 @@ impl Emulator {
|
||||||
Inst::Ecall => {
|
Inst::Ecall => {
|
||||||
(self.ecall_handler)(&mut self.mem, &mut self.xreg)?;
|
(self.ecall_handler)(&mut self.mem, &mut self.xreg)?;
|
||||||
}
|
}
|
||||||
Inst::Ebreak => return Err(Error::Ebreak),
|
Inst::Ebreak => return Err(Status::Ebreak),
|
||||||
Inst::Mul { dest, src1, src2 } => {
|
Inst::Mul { dest, src1, src2 } => {
|
||||||
self[dest] = ((self[src1] as i32).wrapping_mul(self[src2] as i32)) as u32;
|
self[dest] = ((self[src1] as i32).wrapping_mul(self[src2] as i32)) as u32;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
src/inst.rs
38
src/inst.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::emu::{Error, Reg};
|
use crate::emu::{Status, Reg};
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
|
@ -413,7 +413,7 @@ impl Debug for InstCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inst {
|
impl Inst {
|
||||||
pub fn decode(code: u32) -> Result<Inst, Error> {
|
pub fn decode(code: u32) -> Result<Inst, Status> {
|
||||||
let code = InstCode(code);
|
let code = InstCode(code);
|
||||||
let inst = match code.opcode() {
|
let inst = match code.opcode() {
|
||||||
// LUI
|
// LUI
|
||||||
|
|
@ -438,7 +438,7 @@ impl Inst {
|
||||||
base: code.rs1(),
|
base: code.rs1(),
|
||||||
dest: code.rd(),
|
dest: code.rd(),
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
||||||
},
|
},
|
||||||
// BRANCH
|
// BRANCH
|
||||||
0b1100011 => match code.funct3() {
|
0b1100011 => match code.funct3() {
|
||||||
|
|
@ -472,7 +472,7 @@ impl Inst {
|
||||||
src1: code.rs1(),
|
src1: code.rs1(),
|
||||||
src2: code.rs2(),
|
src2: code.rs2(),
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
||||||
},
|
},
|
||||||
// LOAD
|
// LOAD
|
||||||
0b0000011 => match code.funct3() {
|
0b0000011 => match code.funct3() {
|
||||||
|
|
@ -501,7 +501,7 @@ impl Inst {
|
||||||
dest: code.rd(),
|
dest: code.rd(),
|
||||||
base: code.rs1(),
|
base: code.rs1(),
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
||||||
},
|
},
|
||||||
// STORE
|
// STORE
|
||||||
0b0100011 => match code.funct3() {
|
0b0100011 => match code.funct3() {
|
||||||
|
|
@ -520,7 +520,7 @@ impl Inst {
|
||||||
src: code.rs2(),
|
src: code.rs2(),
|
||||||
base: code.rs1(),
|
base: code.rs1(),
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
||||||
},
|
},
|
||||||
// OP-IMM
|
// OP-IMM
|
||||||
0b0010011 => match code.funct3() {
|
0b0010011 => match code.funct3() {
|
||||||
|
|
@ -570,9 +570,9 @@ impl Inst {
|
||||||
dest: code.rd(),
|
dest: code.rd(),
|
||||||
src1: code.rs1(),
|
src1: code.rs1(),
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct7")),
|
_ => return Err(Status::IllegalInstruction(code, "funct7")),
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
||||||
},
|
},
|
||||||
// OP
|
// OP
|
||||||
0b0110011 => {
|
0b0110011 => {
|
||||||
|
|
@ -597,7 +597,7 @@ impl Inst {
|
||||||
(0b101, 0b0000001) => Inst::Divu { dest, src1, src2 },
|
(0b101, 0b0000001) => Inst::Divu { dest, src1, src2 },
|
||||||
(0b110, 0b0000001) => Inst::Rem { dest, src1, src2 },
|
(0b110, 0b0000001) => Inst::Rem { dest, src1, src2 },
|
||||||
(0b111, 0b0000001) => Inst::Remu { dest, src1, src2 },
|
(0b111, 0b0000001) => Inst::Remu { dest, src1, src2 },
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3/funct7")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3/funct7")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// MISC-MEM
|
// MISC-MEM
|
||||||
|
|
@ -626,34 +626,34 @@ impl Inst {
|
||||||
src: code.rs1(),
|
src: code.rs1(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct3")),
|
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SYSTEM
|
// SYSTEM
|
||||||
0b1110011 => {
|
0b1110011 => {
|
||||||
if code.0 == 0b11000000000000000001000001110011 {
|
if code.0 == 0b11000000000000000001000001110011 {
|
||||||
return Err(Error::Trap("unimp instruction"));
|
return Err(Status::Trap("unimp instruction"));
|
||||||
}
|
}
|
||||||
if code.rd().0 != 0 {
|
if code.rd().0 != 0 {
|
||||||
return Err(Error::IllegalInstruction(code, "rd"));
|
return Err(Status::IllegalInstruction(code, "rd"));
|
||||||
}
|
}
|
||||||
if code.funct3() != 0 {
|
if code.funct3() != 0 {
|
||||||
return Err(Error::IllegalInstruction(code, "funct3"));
|
return Err(Status::IllegalInstruction(code, "funct3"));
|
||||||
}
|
}
|
||||||
if code.rs1().0 != 0 {
|
if code.rs1().0 != 0 {
|
||||||
return Err(Error::IllegalInstruction(code, "rs1"));
|
return Err(Status::IllegalInstruction(code, "rs1"));
|
||||||
}
|
}
|
||||||
match code.imm_i() {
|
match code.imm_i() {
|
||||||
0b000000000000 => Inst::Ecall,
|
0b000000000000 => Inst::Ecall,
|
||||||
0b000000000001 => Inst::Ebreak,
|
0b000000000001 => Inst::Ebreak,
|
||||||
_ => return Err(Error::IllegalInstruction(code, "imm")),
|
_ => return Err(Status::IllegalInstruction(code, "imm")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// AMO
|
// AMO
|
||||||
00101111 => {
|
00101111 => {
|
||||||
// width must be W
|
// width must be W
|
||||||
if code.funct3() != 0b010 {
|
if code.funct3() != 0b010 {
|
||||||
return Err(Error::IllegalInstruction(code, "funct3"));
|
return Err(Status::IllegalInstruction(code, "funct3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind = code.extract(27..=31);
|
let kind = code.extract(27..=31);
|
||||||
|
|
@ -666,7 +666,7 @@ impl Inst {
|
||||||
// LR
|
// LR
|
||||||
0b00010 => {
|
0b00010 => {
|
||||||
if code.rs2().0 != 0 {
|
if code.rs2().0 != 0 {
|
||||||
return Err(Error::IllegalInstruction(code, "rs2"));
|
return Err(Status::IllegalInstruction(code, "rs2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst::LrW {
|
Inst::LrW {
|
||||||
|
|
@ -693,7 +693,7 @@ impl Inst {
|
||||||
0b10100 => AmoOp::Max,
|
0b10100 => AmoOp::Max,
|
||||||
0b11000 => AmoOp::Minu,
|
0b11000 => AmoOp::Minu,
|
||||||
0b11100 => AmoOp::Maxu,
|
0b11100 => AmoOp::Maxu,
|
||||||
_ => return Err(Error::IllegalInstruction(code, "funct7")),
|
_ => return Err(Status::IllegalInstruction(code, "funct7")),
|
||||||
};
|
};
|
||||||
Inst::AmoW {
|
Inst::AmoW {
|
||||||
order,
|
order,
|
||||||
|
|
@ -705,7 +705,7 @@ impl Inst {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(Error::IllegalInstruction(code, "opcode")),
|
_ => return Err(Status::IllegalInstruction(code, "opcode")),
|
||||||
};
|
};
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
src/lib.rs
73
src/lib.rs
|
|
@ -1,3 +1,76 @@
|
||||||
|
use eyre::{OptionExt, bail};
|
||||||
|
|
||||||
pub mod elf;
|
pub mod elf;
|
||||||
pub mod emu;
|
pub mod emu;
|
||||||
pub mod inst;
|
pub mod inst;
|
||||||
|
|
||||||
|
// 2 MiB
|
||||||
|
const MEMORY_SIZE: usize = 2 << 21;
|
||||||
|
|
||||||
|
pub fn execute_linux_elf(
|
||||||
|
elf: &[u8],
|
||||||
|
debug: bool,
|
||||||
|
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
|
||||||
|
) -> eyre::Result<emu::Status> {
|
||||||
|
let elf = elf::Elf { content: elf };
|
||||||
|
let header = elf.header()?;
|
||||||
|
|
||||||
|
let segments = elf.segments()?;
|
||||||
|
|
||||||
|
let mut mem = emu::Memory {
|
||||||
|
mem: vec![0; MEMORY_SIZE],
|
||||||
|
};
|
||||||
|
|
||||||
|
for phdr in segments {
|
||||||
|
match phdr.p_type {
|
||||||
|
// PT_NULL
|
||||||
|
0 => {}
|
||||||
|
// PT_LOAD
|
||||||
|
1 => {
|
||||||
|
if phdr.p_filesz > 0 {
|
||||||
|
let contents = &elf
|
||||||
|
.content
|
||||||
|
.get((phdr.p_offset as usize)..)
|
||||||
|
.ok_or_eyre("invalid offset")?
|
||||||
|
.get(..(phdr.p_filesz as usize))
|
||||||
|
.ok_or_eyre("invalid offset")?;
|
||||||
|
|
||||||
|
mem.mem
|
||||||
|
.get_mut((phdr.p_vaddr as usize)..)
|
||||||
|
.ok_or_eyre("invalid offset")?
|
||||||
|
.get_mut(..(phdr.p_filesz as usize))
|
||||||
|
.ok_or_eyre("invalid offset")?
|
||||||
|
.copy_from_slice(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PT_DYNAMIC
|
||||||
|
2 => {}
|
||||||
|
// PT_PHDR
|
||||||
|
6 => {}
|
||||||
|
// PT_GNU_EH_FRAME
|
||||||
|
1685382480 => {}
|
||||||
|
// PT_GNU_STACK
|
||||||
|
1685382481 => {}
|
||||||
|
// PT_GNU_RELRO
|
||||||
|
1685382482 => {}
|
||||||
|
// PT_RISCV_ATTRIBUTES
|
||||||
|
0x70000003 => {}
|
||||||
|
_ => bail!("unknown program header type: {}", phdr.p_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = header.e_entry;
|
||||||
|
|
||||||
|
let mut emu = emu::Emulator {
|
||||||
|
mem,
|
||||||
|
xreg: [0; 32],
|
||||||
|
xreg0_value: 0,
|
||||||
|
pc: start,
|
||||||
|
reservation_set: None,
|
||||||
|
|
||||||
|
debug,
|
||||||
|
ecall_handler,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(emu.start_linux())
|
||||||
|
}
|
||||||
|
|
|
||||||
85
src/main.rs
85
src/main.rs
|
|
@ -1,88 +1,27 @@
|
||||||
use eyre::{OptionExt, bail, eyre};
|
use eyre::eyre;
|
||||||
use rustv32i::{
|
use rustv32i::emu::{self, Memory, Reg};
|
||||||
elf,
|
|
||||||
emu::{self, Memory, Reg},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2 MiB
|
|
||||||
const MEMORY_SIZE: usize = 2 << 21;
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
let elf = elf::Elf { content };
|
let status = rustv32i::execute_linux_elf(
|
||||||
let header = elf.header()?;
|
&content,
|
||||||
|
std::env::args().any(|arg| arg == "--debug"),
|
||||||
|
Box::new(ecall_handler),
|
||||||
|
)?;
|
||||||
|
|
||||||
let segments = elf.segments()?;
|
match status {
|
||||||
|
emu::Status::Exit { code } => {
|
||||||
let mut mem = emu::Memory {
|
|
||||||
mem: vec![0; MEMORY_SIZE],
|
|
||||||
};
|
|
||||||
|
|
||||||
for phdr in segments {
|
|
||||||
match phdr.p_type {
|
|
||||||
// PT_NULL
|
|
||||||
0 => {}
|
|
||||||
// PT_LOAD
|
|
||||||
1 => {
|
|
||||||
if phdr.p_filesz > 0 {
|
|
||||||
let contents = &elf
|
|
||||||
.content
|
|
||||||
.get((phdr.p_offset as usize)..)
|
|
||||||
.ok_or_eyre("invalid offset")?
|
|
||||||
.get(..(phdr.p_filesz as usize))
|
|
||||||
.ok_or_eyre("invalid offset")?;
|
|
||||||
|
|
||||||
mem.mem
|
|
||||||
.get_mut((phdr.p_vaddr as usize)..)
|
|
||||||
.ok_or_eyre("invalid offset")?
|
|
||||||
.get_mut(..(phdr.p_filesz as usize))
|
|
||||||
.ok_or_eyre("invalid offset")?
|
|
||||||
.copy_from_slice(contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// PT_DYNAMIC
|
|
||||||
2 => {}
|
|
||||||
// PT_PHDR
|
|
||||||
6 => {}
|
|
||||||
// PT_GNU_EH_FRAME
|
|
||||||
1685382480 => {}
|
|
||||||
// PT_GNU_STACK
|
|
||||||
1685382481 => {}
|
|
||||||
// PT_GNU_RELRO
|
|
||||||
1685382482 => {}
|
|
||||||
// PT_RISCV_ATTRIBUTES
|
|
||||||
0x70000003 => {}
|
|
||||||
_ => bail!("unknown program header type: {}", phdr.p_type),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = header.e_entry;
|
|
||||||
|
|
||||||
let mut emu = emu::Emulator {
|
|
||||||
mem,
|
|
||||||
xreg: [0; 32],
|
|
||||||
xreg0_value: 0,
|
|
||||||
pc: start,
|
|
||||||
reservation_set: None,
|
|
||||||
|
|
||||||
debug: std::env::args().any(|arg| arg == "--debug"),
|
|
||||||
|
|
||||||
ecall_handler: Box::new(ecall_handler),
|
|
||||||
};
|
|
||||||
|
|
||||||
match emu.start_linux() {
|
|
||||||
emu::Error::Exit { code } => {
|
|
||||||
eprintln!("exited with code {code}");
|
eprintln!("exited with code {code}");
|
||||||
}
|
}
|
||||||
emu::Error::Trap(cause) => eprintln!("program trapped: {cause}"),
|
emu::Status::Trap(cause) => eprintln!("program trapped: {cause}"),
|
||||||
e => return Err(eyre!("error: {e:?}")),
|
e => return Err(eyre!("error: {e:?}")),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Error> {
|
fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Status> {
|
||||||
let nr = xreg[Reg::A7.0 as usize];
|
let nr = xreg[Reg::A7.0 as usize];
|
||||||
|
|
||||||
match nr {
|
match nr {
|
||||||
|
|
@ -122,7 +61,7 @@ fn ecall_handler(mem: &mut Memory, xreg: &mut [u32; 32]) -> Result<(), emu::Erro
|
||||||
}
|
}
|
||||||
// exit
|
// exit
|
||||||
93 => {
|
93 => {
|
||||||
return Err(emu::Error::Exit {
|
return Err(emu::Status::Exit {
|
||||||
code: xreg[Reg::A0.0 as usize] as i32,
|
code: xreg[Reg::A0.0 as usize] as i32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
86
tests/check.rs
Normal file
86
tests/check.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use eyre::{Context, bail};
|
||||||
|
use rustv32i::emu::{Reg, Status};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check() -> eyre::Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir().wrap_err("failed to create tempdir")?;
|
||||||
|
|
||||||
|
let dir =
|
||||||
|
Path::new(&std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set"))
|
||||||
|
.join("tests")
|
||||||
|
.join("check");
|
||||||
|
let files = std::fs::read_dir(&dir).wrap_err(format!("reading {}", dir.display()))?;
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
let file = file.wrap_err(format!("reading file in {}", dir.display()))?;
|
||||||
|
|
||||||
|
let name = file.file_name();
|
||||||
|
let name = name.to_str().unwrap();
|
||||||
|
if !name.ends_with(".s") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(std::io::stdout(), "test {name} ...")?;
|
||||||
|
std::io::stdout().flush()?;
|
||||||
|
|
||||||
|
let output = build(&tmpdir.path(), &file.path()).wrap_err(format!("building {name}"))?;
|
||||||
|
let content =
|
||||||
|
std::fs::read(&output).wrap_err(format!("reading output from {}", output.display()))?;
|
||||||
|
|
||||||
|
let status = rustv32i::execute_linux_elf(
|
||||||
|
&content,
|
||||||
|
matches!(std::env::var("EMULATOR_DEBUG").as_deref(), Ok(v) if v != "0"),
|
||||||
|
Box::new(|_, xreg| {
|
||||||
|
if xreg[Reg::A7.0 as usize] == u32::MAX {
|
||||||
|
if xreg[Reg::A0.0 as usize] == 1 {
|
||||||
|
Err(rustv32i::emu::Status::Exit { code: 0 })
|
||||||
|
} else {
|
||||||
|
Err(rustv32i::emu::Status::Trap("wrong exit code"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(rustv32i::emu::Status::Trap("wrong syscall"))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.wrap_err(format!("{name} failed"))?;
|
||||||
|
|
||||||
|
if let Status::Exit { code: 0 } = status {
|
||||||
|
writeln!(std::io::stdout(), " ✅")?;
|
||||||
|
} else {
|
||||||
|
bail!("{name} returned an error: {status:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(tmpdir: &Path, src: &Path) -> eyre::Result<PathBuf> {
|
||||||
|
let out_path = tmpdir.join(Path::new(src.file_name().unwrap()).with_extension(""));
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new("clang");
|
||||||
|
cmd.args([
|
||||||
|
"-target",
|
||||||
|
"riscv32-unknown-none-elf",
|
||||||
|
"-nostdlib",
|
||||||
|
"-march=rv32ima",
|
||||||
|
]);
|
||||||
|
cmd.arg(src);
|
||||||
|
cmd.arg("-o");
|
||||||
|
cmd.arg(&out_path);
|
||||||
|
|
||||||
|
let output = cmd.output().wrap_err("failed to spawn clang")?;
|
||||||
|
if !output.status.success() {
|
||||||
|
bail!(
|
||||||
|
"failed to compile {}:\n{}",
|
||||||
|
src.display(),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out_path)
|
||||||
|
}
|
||||||
23
tests/check/arith.s
Normal file
23
tests/check/arith.s
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
.section .text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
li t0, 10
|
||||||
|
li t1, 20
|
||||||
|
add t2, t0, t1
|
||||||
|
li t3, 30
|
||||||
|
bne t2, t3, fail
|
||||||
|
|
||||||
|
li t0, 10
|
||||||
|
li t1, -2
|
||||||
|
add t2, t0, t1
|
||||||
|
li t3, 8
|
||||||
|
bne t2, t3, fail
|
||||||
|
|
||||||
|
li a7, -1
|
||||||
|
li a0, 1
|
||||||
|
ecall
|
||||||
|
|
||||||
|
fail:
|
||||||
|
li a7, -1
|
||||||
|
li a0, 0
|
||||||
|
ecall
|
||||||
6
tests/check/smoke.s
Normal file
6
tests/check/smoke.s
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.section .text
|
||||||
|
.globl _start
|
||||||
|
_start:
|
||||||
|
li a7, -1
|
||||||
|
li a0, 1
|
||||||
|
ecall
|
||||||
Loading…
Add table
Add a link
Reference in a new issue