mirror of
https://github.com/Noratrieb/rustv32i.git
synced 2026-01-14 13:25:01 +01:00
many improvements
- fix a decode bug for C.ADDI16SP - improve test suite (to test that bug) - improve debugging - clean up code
This commit is contained in:
parent
fdb4968c8b
commit
b2c3c9fc80
8 changed files with 290 additions and 64 deletions
75
src/emu.rs
75
src/emu.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::inst::{AmoOp, Inst, InstCode, IsCompressed};
|
use crate::inst::{AmoOp, DecodeError, Inst, IsCompressed};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
io::Write,
|
io::Write,
|
||||||
|
|
@ -66,7 +66,7 @@ impl Memory {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Trap(&'static str),
|
Trap(&'static str),
|
||||||
IllegalInstruction(InstCode, &'static str),
|
IllegalInstruction(DecodeError),
|
||||||
InvalidMemoryAccess(u32),
|
InvalidMemoryAccess(u32),
|
||||||
UnalignedPc(u32),
|
UnalignedPc(u32),
|
||||||
UnaligneMemoryAccess { addr: u32, required_align: u32 },
|
UnaligneMemoryAccess { addr: u32, required_align: u32 },
|
||||||
|
|
@ -74,6 +74,12 @@ pub enum Status {
|
||||||
Exit { code: i32 },
|
Exit { code: i32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<DecodeError> for Status {
|
||||||
|
fn from(value: DecodeError) -> Self {
|
||||||
|
Status::IllegalInstruction(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
pub mem: Memory,
|
pub mem: Memory,
|
||||||
pub xreg: [u32; 32],
|
pub xreg: [u32; 32],
|
||||||
|
|
@ -87,6 +93,7 @@ pub struct Emulator {
|
||||||
pub is_breaking: bool,
|
pub is_breaking: bool,
|
||||||
|
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
|
pub break_pc: u32,
|
||||||
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<(), Status>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,7 +226,6 @@ impl Emulator {
|
||||||
// TODO: add some auxv for the poor process.
|
// TODO: add some auxv for the poor process.
|
||||||
self.mem.store_u32(next_word(), 0)?;
|
self.mem.store_u32(next_word(), 0)?;
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,16 +244,16 @@ impl Emulator {
|
||||||
|
|
||||||
fn step(&mut self) -> Result<(), Status> {
|
fn step(&mut self) -> Result<(), Status> {
|
||||||
let code = self.mem.load_u32(self.pc)?;
|
let code = self.mem.load_u32(self.pc)?;
|
||||||
let (inst, was_compressed) = Inst::decode(code)?;
|
|
||||||
|
|
||||||
if self.is_breaking {
|
if self.debug {
|
||||||
self.debug_interactive();
|
print!("0x{:x} ", self.pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (inst, was_compressed) = Inst::decode(code)?;
|
||||||
|
|
||||||
if self.debug {
|
if self.debug {
|
||||||
println!(
|
println!(
|
||||||
"0x{:x} {} (sp: {}) {inst:?}",
|
"{} (sp: {}) {inst:?}",
|
||||||
self.pc,
|
|
||||||
match was_compressed {
|
match was_compressed {
|
||||||
IsCompressed::Yes => "C",
|
IsCompressed::Yes => "C",
|
||||||
IsCompressed::No => " ",
|
IsCompressed::No => " ",
|
||||||
|
|
@ -256,6 +262,14 @@ impl Emulator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.pc == self.break_pc {
|
||||||
|
self.is_breaking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_breaking {
|
||||||
|
self.debug_interactive();
|
||||||
|
}
|
||||||
|
|
||||||
let next_pc = self.pc.wrapping_add(match was_compressed {
|
let next_pc = self.pc.wrapping_add(match was_compressed {
|
||||||
IsCompressed::Yes => 2,
|
IsCompressed::Yes => 2,
|
||||||
IsCompressed::No => 4,
|
IsCompressed::No => 4,
|
||||||
|
|
@ -515,8 +529,8 @@ impl Emulator {
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap();
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
std::io::stdin().read_line(&mut input).unwrap();
|
std::io::stdin().read_line(&mut input).unwrap();
|
||||||
let input = input.trim();
|
let mut input = input.trim().split_whitespace();
|
||||||
match input {
|
match input.next().unwrap_or_default() {
|
||||||
"c" => {
|
"c" => {
|
||||||
self.is_breaking = false;
|
self.is_breaking = false;
|
||||||
return;
|
return;
|
||||||
|
|
@ -525,7 +539,43 @@ impl Emulator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
"q" => std::process::exit(0),
|
"q" => std::process::exit(0),
|
||||||
"p" => {
|
"m" => {
|
||||||
|
let Some(size) = input.next() else {
|
||||||
|
eprintln!("require b/h/w argument for m command");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(addr) = input.next() else {
|
||||||
|
eprintln!("require address argument for m command");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(addr) = (if let Some(addr) = addr.strip_prefix("0x") {
|
||||||
|
u32::from_str_radix(addr, 16)
|
||||||
|
} else {
|
||||||
|
u32::from_str_radix(&addr, 10)
|
||||||
|
}) else {
|
||||||
|
eprintln!("invalid address");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let value = match size {
|
||||||
|
"w" => self.mem.load_u32(addr),
|
||||||
|
"h" => self.mem.load_u16(addr).map(Into::into),
|
||||||
|
"b" => self.mem.load_u8(addr).map(Into::into),
|
||||||
|
_ => {
|
||||||
|
eprintln!("require b/h/w argument for m command");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let value = match value {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("failed to load value: {e:?}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{value} (0x{value:x})");
|
||||||
|
}
|
||||||
|
"r" => {
|
||||||
let format_value = |v: u32| {
|
let format_value = |v: u32| {
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
format!("{:0>8x}", v.black())
|
format!("{:0>8x}", v.black())
|
||||||
|
|
@ -626,7 +676,8 @@ impl Emulator {
|
||||||
_ => println!(
|
_ => println!(
|
||||||
"commands:
|
"commands:
|
||||||
- ?: help
|
- ?: help
|
||||||
- p: print registers
|
- r: print registers
|
||||||
|
- m <b|h|w> <addr>: read a byte/half/word of memory at an address
|
||||||
- c: continue until next breakpoint
|
- c: continue until next breakpoint
|
||||||
- s: step one instruction
|
- s: step one instruction
|
||||||
- q: quit"
|
- q: quit"
|
||||||
|
|
|
||||||
108
src/inst.rs
108
src/inst.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::emu::{Reg, Status};
|
use crate::emu::Reg;
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
|
@ -173,6 +173,11 @@ pub enum AmoOp {
|
||||||
Maxu,
|
Maxu,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DecodeError {
|
||||||
|
pub instruction: u32,
|
||||||
|
pub unexpected_field: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
impl Fence {
|
impl Fence {
|
||||||
pub fn is_pause(&self) -> bool {
|
pub fn is_pause(&self) -> bool {
|
||||||
self.pred
|
self.pred
|
||||||
|
|
@ -393,6 +398,27 @@ impl Display for AmoOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for DecodeError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("DecodeError")
|
||||||
|
.field("instruction", &format!("{:0>32b}", self.instruction))
|
||||||
|
.field("unexpected_field", &self.unexpected_field)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DecodeError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"failed to decode instruction '{:0>32b}' because of field '{}'",
|
||||||
|
self.instruction, self.unexpected_field
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for DecodeError {}
|
||||||
|
|
||||||
fn sign_extend(value: u32, size: u32) -> u32 {
|
fn sign_extend(value: u32, size: u32) -> u32 {
|
||||||
let right = u32::BITS - size;
|
let right = u32::BITS - size;
|
||||||
(((value << right) as i32) >> right) as u32
|
(((value << right) as i32) >> right) as u32
|
||||||
|
|
@ -461,12 +487,6 @@ impl InstCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for InstCode {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:0>32b}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct InstCodeC(u16);
|
pub struct InstCodeC(u16);
|
||||||
|
|
||||||
|
|
@ -537,8 +557,15 @@ pub enum IsCompressed {
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_error(instruction: impl Into<InstCode>, unexpected_field: &'static str) -> DecodeError {
|
||||||
|
DecodeError {
|
||||||
|
instruction: instruction.into().0,
|
||||||
|
unexpected_field,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Inst {
|
impl Inst {
|
||||||
pub fn decode(code: u32) -> Result<(Inst, IsCompressed), Status> {
|
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 {
|
||||||
Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes))
|
Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes))
|
||||||
|
|
@ -547,10 +574,10 @@ impl Inst {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_compressed(code: u16) -> Result<Inst, Status> {
|
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(Status::IllegalInstruction(code.into(), "null instruction"));
|
return Err(decode_error(code, "null instruction"));
|
||||||
}
|
}
|
||||||
let inst = match code.quadrant() {
|
let inst = match code.quadrant() {
|
||||||
// C0
|
// C0
|
||||||
|
|
@ -573,7 +600,7 @@ impl Inst {
|
||||||
src: code.rs2_short(),
|
src: code.rs2_short(),
|
||||||
base: code.rs1_short(),
|
base: code.rs1_short(),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// C1
|
// C1
|
||||||
0b01 => match code.funct3() {
|
0b01 => match code.funct3() {
|
||||||
|
|
@ -610,7 +637,7 @@ impl Inst {
|
||||||
// C.SRLI -> srli \rd', \rd', \imm
|
// C.SRLI -> srli \rd', \rd', \imm
|
||||||
0b00 => {
|
0b00 => {
|
||||||
if bit12 != 0 {
|
if bit12 != 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "imm"));
|
return Err(decode_error(code, "imm"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst::Srli {
|
Inst::Srli {
|
||||||
|
|
@ -622,7 +649,7 @@ impl Inst {
|
||||||
// C.SRAI -> srai \rd', \rd', \imm
|
// C.SRAI -> srai \rd', \rd', \imm
|
||||||
0b01 => {
|
0b01 => {
|
||||||
if bit12 != 0 {
|
if bit12 != 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "imm"));
|
return Err(decode_error(code, "imm"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst::Srai {
|
Inst::Srai {
|
||||||
|
|
@ -639,7 +666,7 @@ impl Inst {
|
||||||
},
|
},
|
||||||
0b11 => {
|
0b11 => {
|
||||||
if bit12 != 0 {
|
if bit12 != 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "bit 12"));
|
return Err(decode_error(code, "bit 12"));
|
||||||
}
|
}
|
||||||
let funct2 = code.extract(5..=6);
|
let funct2 = code.extract(5..=6);
|
||||||
match funct2 {
|
match funct2 {
|
||||||
|
|
@ -696,6 +723,7 @@ impl Inst {
|
||||||
(3..=4, 7),
|
(3..=4, 7),
|
||||||
(5..=5, 6),
|
(5..=5, 6),
|
||||||
(6..=6, 4),
|
(6..=6, 4),
|
||||||
|
(12..=12, 9),
|
||||||
]),
|
]),
|
||||||
dest: Reg::SP,
|
dest: Reg::SP,
|
||||||
src1: Reg::SP,
|
src1: Reg::SP,
|
||||||
|
|
@ -704,7 +732,7 @@ impl Inst {
|
||||||
_ => {
|
_ => {
|
||||||
let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]);
|
let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]);
|
||||||
if uimm == 0 {
|
if uimm == 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "imm"));
|
return Err(decode_error(code, "imm"));
|
||||||
}
|
}
|
||||||
Inst::Lui {
|
Inst::Lui {
|
||||||
uimm,
|
uimm,
|
||||||
|
|
@ -737,14 +765,14 @@ impl Inst {
|
||||||
src1: code.rs1_short(),
|
src1: code.rs1_short(),
|
||||||
src2: Reg::ZERO,
|
src2: Reg::ZERO,
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// C2
|
// C2
|
||||||
0b10 => match code.funct3() {
|
0b10 => match code.funct3() {
|
||||||
// C.SLLI -> slli \rd, \rd, \imm
|
// C.SLLI -> slli \rd, \rd, \imm
|
||||||
0b000 => {
|
0b000 => {
|
||||||
if code.extract(12..=12) != 0 {
|
if code.extract(12..=12) != 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "imm"));
|
return Err(decode_error(code, "imm"));
|
||||||
}
|
}
|
||||||
Inst::Slli {
|
Inst::Slli {
|
||||||
imm: code.immediate_u(&[(2..=6, 0), (12..=12, 5)]),
|
imm: code.immediate_u(&[(2..=6, 0), (12..=12, 5)]),
|
||||||
|
|
@ -756,7 +784,7 @@ impl Inst {
|
||||||
0b010 => {
|
0b010 => {
|
||||||
let dest = code.rd();
|
let dest = code.rd();
|
||||||
if dest.0 == 0 {
|
if dest.0 == 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "rd"));
|
return Err(decode_error(code, "rd"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst::Lw {
|
Inst::Lw {
|
||||||
|
|
@ -773,7 +801,7 @@ impl Inst {
|
||||||
// C.JR -> jalr zero, 0(\rs1)
|
// C.JR -> jalr zero, 0(\rs1)
|
||||||
(0, _, 0) => {
|
(0, _, 0) => {
|
||||||
if rd_rs1.0 == 0 {
|
if rd_rs1.0 == 0 {
|
||||||
return Err(Status::IllegalInstruction(code.into(), "rs1"));
|
return Err(decode_error(code, "rs1"));
|
||||||
}
|
}
|
||||||
Inst::Jalr {
|
Inst::Jalr {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
|
@ -801,7 +829,7 @@ impl Inst {
|
||||||
src1: rd_rs1,
|
src1: rd_rs1,
|
||||||
src2: rs2,
|
src2: rs2,
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code.into(), "inst")),
|
_ => return Err(decode_error(code, "inst")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// C.SWSP -> sw \reg \offset(sp)
|
// C.SWSP -> sw \reg \offset(sp)
|
||||||
|
|
@ -810,14 +838,14 @@ impl Inst {
|
||||||
src: code.rs2(),
|
src: code.rs2(),
|
||||||
base: Reg::SP,
|
base: Reg::SP,
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code.into(), "op")),
|
_ => return Err(decode_error(code, "op")),
|
||||||
};
|
};
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_normal(code: u32) -> Result<Inst, Status> {
|
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
|
||||||
|
|
@ -842,7 +870,7 @@ impl Inst {
|
||||||
base: code.rs1(),
|
base: code.rs1(),
|
||||||
dest: code.rd(),
|
dest: code.rd(),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// BRANCH
|
// BRANCH
|
||||||
0b1100011 => match code.funct3() {
|
0b1100011 => match code.funct3() {
|
||||||
|
|
@ -876,7 +904,7 @@ impl Inst {
|
||||||
src1: code.rs1(),
|
src1: code.rs1(),
|
||||||
src2: code.rs2(),
|
src2: code.rs2(),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// LOAD
|
// LOAD
|
||||||
0b0000011 => match code.funct3() {
|
0b0000011 => match code.funct3() {
|
||||||
|
|
@ -905,7 +933,7 @@ impl Inst {
|
||||||
dest: code.rd(),
|
dest: code.rd(),
|
||||||
base: code.rs1(),
|
base: code.rs1(),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// STORE
|
// STORE
|
||||||
0b0100011 => match code.funct3() {
|
0b0100011 => match code.funct3() {
|
||||||
|
|
@ -924,7 +952,7 @@ impl Inst {
|
||||||
src: code.rs2(),
|
src: code.rs2(),
|
||||||
base: code.rs1(),
|
base: code.rs1(),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// OP-IMM
|
// OP-IMM
|
||||||
0b0010011 => match code.funct3() {
|
0b0010011 => match code.funct3() {
|
||||||
|
|
@ -974,9 +1002,9 @@ impl Inst {
|
||||||
dest: code.rd(),
|
dest: code.rd(),
|
||||||
src1: code.rs1(),
|
src1: code.rs1(),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct7")),
|
_ => return Err(decode_error(code, "funct7")),
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
},
|
},
|
||||||
// OP
|
// OP
|
||||||
0b0110011 => {
|
0b0110011 => {
|
||||||
|
|
@ -1001,7 +1029,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(Status::IllegalInstruction(code, "funct3/funct7")),
|
_ => return Err(decode_error(code, "funct3/funct7")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// MISC-MEM
|
// MISC-MEM
|
||||||
|
|
@ -1030,34 +1058,34 @@ impl Inst {
|
||||||
src: code.rs1(),
|
src: code.rs1(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct3")),
|
_ => return Err(decode_error(code, "funct3")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SYSTEM
|
// SYSTEM
|
||||||
0b1110011 => {
|
0b1110011 => {
|
||||||
if code.0 == 0b11000000000000000001000001110011 {
|
if code.0 == 0b11000000000000000001000001110011 {
|
||||||
return Err(Status::Trap("unimp instruction"));
|
return Err(decode_error(code, "unimp instruction"));
|
||||||
}
|
}
|
||||||
if code.rd().0 != 0 {
|
if code.rd().0 != 0 {
|
||||||
return Err(Status::IllegalInstruction(code, "rd"));
|
return Err(decode_error(code, "rd"));
|
||||||
}
|
}
|
||||||
if code.funct3() != 0 {
|
if code.funct3() != 0 {
|
||||||
return Err(Status::IllegalInstruction(code, "funct3"));
|
return Err(decode_error(code, "funct3"));
|
||||||
}
|
}
|
||||||
if code.rs1().0 != 0 {
|
if code.rs1().0 != 0 {
|
||||||
return Err(Status::IllegalInstruction(code, "rs1"));
|
return Err(decode_error(code, "rs1"));
|
||||||
}
|
}
|
||||||
match code.imm_i() {
|
match code.imm_i() {
|
||||||
0b000000000000 => Inst::Ecall,
|
0b000000000000 => Inst::Ecall,
|
||||||
0b000000000001 => Inst::Ebreak,
|
0b000000000001 => Inst::Ebreak,
|
||||||
_ => return Err(Status::IllegalInstruction(code, "imm")),
|
_ => return Err(decode_error(code, "imm")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// AMO
|
// AMO
|
||||||
0b00101111 => {
|
0b00101111 => {
|
||||||
// width must be W
|
// width must be W
|
||||||
if code.funct3() != 0b010 {
|
if code.funct3() != 0b010 {
|
||||||
return Err(Status::IllegalInstruction(code, "funct3"));
|
return Err(decode_error(code, "funct3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let kind = code.extract(27..=31);
|
let kind = code.extract(27..=31);
|
||||||
|
|
@ -1070,7 +1098,7 @@ impl Inst {
|
||||||
// LR
|
// LR
|
||||||
0b00010 => {
|
0b00010 => {
|
||||||
if code.rs2().0 != 0 {
|
if code.rs2().0 != 0 {
|
||||||
return Err(Status::IllegalInstruction(code, "rs2"));
|
return Err(decode_error(code, "rs2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst::LrW {
|
Inst::LrW {
|
||||||
|
|
@ -1097,7 +1125,7 @@ impl Inst {
|
||||||
0b10100 => AmoOp::Max,
|
0b10100 => AmoOp::Max,
|
||||||
0b11000 => AmoOp::Minu,
|
0b11000 => AmoOp::Minu,
|
||||||
0b11100 => AmoOp::Maxu,
|
0b11100 => AmoOp::Maxu,
|
||||||
_ => return Err(Status::IllegalInstruction(code, "funct7")),
|
_ => return Err(decode_error(code, "funct7")),
|
||||||
};
|
};
|
||||||
Inst::AmoW {
|
Inst::AmoW {
|
||||||
order,
|
order,
|
||||||
|
|
@ -1109,7 +1137,7 @@ impl Inst {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(Status::IllegalInstruction(code, "opcode")),
|
_ => return Err(decode_error(code, "opcode")),
|
||||||
};
|
};
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const MEMORY_SIZE: usize = 2 << 21;
|
||||||
pub fn execute_linux_elf(
|
pub fn execute_linux_elf(
|
||||||
elf: &[u8],
|
elf: &[u8],
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
break_addr: u32,
|
||||||
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
|
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
|
||||||
) -> eyre::Result<emu::Status> {
|
) -> eyre::Result<emu::Status> {
|
||||||
let elf = elf::Elf { content: elf };
|
let elf = elf::Elf { content: elf };
|
||||||
|
|
@ -67,9 +68,10 @@ pub fn execute_linux_elf(
|
||||||
xreg0_value: 0,
|
xreg0_value: 0,
|
||||||
pc: start,
|
pc: start,
|
||||||
reservation_set: None,
|
reservation_set: None,
|
||||||
|
|
||||||
is_breaking: false,
|
is_breaking: false,
|
||||||
|
|
||||||
|
break_pc: break_addr,
|
||||||
debug,
|
debug,
|
||||||
ecall_handler,
|
ecall_handler,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
21
src/main.rs
21
src/main.rs
|
|
@ -1,17 +1,36 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use eyre::eyre;
|
use eyre::eyre;
|
||||||
use rustv32i::emu::{self, Memory, Reg};
|
use rustv32i::emu::{self, Memory, 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();
|
||||||
|
|
||||||
|
let break_addr = std::env::args()
|
||||||
|
.skip_while(|arg| arg != "--break")
|
||||||
|
.nth(1)
|
||||||
|
.map(|addr| {
|
||||||
|
if let Some(addr) = addr.strip_prefix("0x") {
|
||||||
|
u32::from_str_radix(addr, 16)
|
||||||
|
} else {
|
||||||
|
u32::from_str_radix(&addr, 10)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(0))?;
|
||||||
|
|
||||||
|
let debug = std::env::args().any(|arg| arg == "--debug");
|
||||||
|
|
||||||
let mut syscall_state = SyscallState { set_child_tid: 0 };
|
let mut syscall_state = SyscallState { set_child_tid: 0 };
|
||||||
|
|
||||||
let status = rustv32i::execute_linux_elf(
|
let status = rustv32i::execute_linux_elf(
|
||||||
&content,
|
&content,
|
||||||
std::env::args().any(|arg| arg == "--debug"),
|
debug,
|
||||||
|
break_addr,
|
||||||
Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)),
|
Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
std::io::stdout().flush()?;
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
emu::Status::Exit { code } => {
|
emu::Status::Exit { code } => {
|
||||||
eprintln!("exited with code {code}");
|
eprintln!("exited with code {code}");
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ fn test_case(tmpdir: &Path, file: &DirEntry, name: &str, march: &str) -> eyre::R
|
||||||
let status = rustv32i::execute_linux_elf(
|
let status = rustv32i::execute_linux_elf(
|
||||||
&content,
|
&content,
|
||||||
true,
|
true,
|
||||||
|
0,
|
||||||
Box::new(|_, xreg| {
|
Box::new(|_, xreg| {
|
||||||
if xreg[Reg::A7.0 as usize] == u32::MAX {
|
if xreg[Reg::A7.0 as usize] == u32::MAX {
|
||||||
if xreg[Reg::A0.0 as usize] == 1 {
|
if xreg[Reg::A0.0 as usize] == 1 {
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,75 @@
|
||||||
|
|
||||||
#include "../helper.S"
|
#include "../helper.S"
|
||||||
|
|
||||||
.macro CASER inst a b expected
|
.macro CASER inst:req a:req b:req expected:req
|
||||||
li t0, \a
|
li t0, \a
|
||||||
li t1, \b
|
li t1, \b
|
||||||
\inst t2, t0, t1
|
\inst t2, t0, t1
|
||||||
ASSERT_EQ t2, \expected
|
ASSERT_EQ t2, \expected
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro CASE_IMM inst a b expected
|
.macro CASE_IMM inst:req a:req b:req expected:req
|
||||||
li t0, \a
|
li t0, \a
|
||||||
\inst t2, t0, \b
|
\inst t2, t0, \b
|
||||||
ASSERT_EQ t2, \expected
|
ASSERT_EQ t2, \expected
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro CASE_BOTH inst insti a b expected
|
.macro CASE_BOTH inst:req insti:req a:req b:req expected:req
|
||||||
CASER \inst, \a, \b, \expected
|
CASER \inst, \a, \b, \expected
|
||||||
CASE_IMM \insti, \a, \b, \expected
|
CASE_IMM \insti, \a, \b, \expected
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
.macro CASE inst:req a:req b:req expected:req
|
||||||
.macro CASE inst a b expected
|
|
||||||
CASE_BOTH \inst, \inst\()i, \a, \b, \expected
|
CASE_BOTH \inst, \inst\()i, \a, \b, \expected
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
.macro WITH_SINGLE_TEST_NUMBERS macro
|
||||||
|
\macro a, 0
|
||||||
|
\macro c, 1
|
||||||
|
\macro d, 2
|
||||||
|
\macro u, 3
|
||||||
|
\macro e, 4
|
||||||
|
\macro v, 5
|
||||||
|
\macro f, 8
|
||||||
|
\macro t, 10
|
||||||
|
\macro g, 16
|
||||||
|
\macro h, 32
|
||||||
|
\macro i, 64
|
||||||
|
\macro s, 100
|
||||||
|
\macro j, 128
|
||||||
|
\macro k, 256
|
||||||
|
\macro l, 512
|
||||||
|
\macro w, 1000
|
||||||
|
\macro m, 1024
|
||||||
|
\macro n, 2047
|
||||||
|
\macro b, -1
|
||||||
|
\macro o, -2
|
||||||
|
\macro p, -16
|
||||||
|
\macro q, -1024
|
||||||
|
\macro r, -1000
|
||||||
|
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro WITH_TWO_TEST_NUMBERS macro
|
||||||
|
.macro \macro\()_TMP namea:req a:req
|
||||||
|
.macro \macro\()_TMP_\namea nameb:req b:req
|
||||||
|
\macro \a, \b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_SINGLE_TEST_NUMBERS \macro\()_TMP_\namea
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_SINGLE_TEST_NUMBERS \macro\()_TMP
|
||||||
|
.endm
|
||||||
|
|
||||||
START_TEST
|
START_TEST
|
||||||
# Base instructions
|
# Base instructions
|
||||||
|
|
||||||
CASE add 10, 20, 30
|
.macro CASE_ADD a:req, b:req
|
||||||
CASE add 10, -2, 8
|
CASE add, \a, \b, \a + \b
|
||||||
CASE add 10, 0, 10
|
.endm
|
||||||
CASE add 0, 0, 0
|
|
||||||
|
WITH_TWO_TEST_NUMBERS CASE_ADD
|
||||||
|
|
||||||
CASE slt 10 20 1
|
CASE slt 10 20 1
|
||||||
CASE slt 20 10 0
|
CASE slt 20 10 0
|
||||||
|
|
@ -52,15 +91,33 @@ START_TEST
|
||||||
CASE and, -1, 40, 40
|
CASE and, -1, 40, 40
|
||||||
CASE and, 0b101, 0b100, 0b100
|
CASE and, 0b101, 0b100, 0b100
|
||||||
|
|
||||||
|
.macro CASE_AND a:req, b:req
|
||||||
|
CASE and, \a, \b, \a & \b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_TWO_TEST_NUMBERS CASE_AND
|
||||||
|
|
||||||
CASE or, -1, 0, -1
|
CASE or, -1, 0, -1
|
||||||
CASE or, -1, 40, -1
|
CASE or, -1, 40, -1
|
||||||
CASE or, 0, 0, 0
|
CASE or, 0, 0, 0
|
||||||
CASE or, 0b101, 0b110, 0b111
|
CASE or, 0b101, 0b110, 0b111
|
||||||
|
|
||||||
|
.macro CASE_OR a:req, b:req
|
||||||
|
CASE or, \a, \b, \a | \b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_TWO_TEST_NUMBERS CASE_OR
|
||||||
|
|
||||||
CASE xor, -1, 0, -1
|
CASE xor, -1, 0, -1
|
||||||
CASE xor, -1, -1, 0
|
CASE xor, -1, -1, 0
|
||||||
CASE xor 0b101, 0b100, 0b001
|
CASE xor 0b101, 0b100, 0b001
|
||||||
|
|
||||||
|
.macro CASE_XOR a:req, b:req
|
||||||
|
CASE xor, \a, \b, \a ^ \b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_TWO_TEST_NUMBERS CASE_XOR
|
||||||
|
|
||||||
CASE sll, 2, 1, 4
|
CASE sll, 2, 1, 4
|
||||||
CASE sll, 2, 20, 2097152
|
CASE sll, 2, 20, 2097152
|
||||||
CASE sll, 2, 30, 2147483648
|
CASE sll, 2, 30, 2147483648
|
||||||
|
|
@ -84,6 +141,12 @@ START_TEST
|
||||||
CASER sub, -1, -2, 1
|
CASER sub, -1, -2, 1
|
||||||
CASER sub, 0, 4294967295, 1
|
CASER sub, 0, 4294967295, 1
|
||||||
|
|
||||||
|
.macro CASE_SUB a:req, b:req
|
||||||
|
CASER sub, \a, \b, \a - \b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_TWO_TEST_NUMBERS CASE_SUB
|
||||||
|
|
||||||
CASE sra, 4, 1, 2
|
CASE sra, 4, 1, 2
|
||||||
CASE sra, 0, 10, 0
|
CASE sra, 0, 10, 0
|
||||||
CASE sra, 10, 0, 10
|
CASE sra, 10, 0, 10
|
||||||
|
|
@ -100,6 +163,12 @@ START_TEST
|
||||||
CASER mul, -1, -1, 1
|
CASER mul, -1, -1, 1
|
||||||
CASER mul, 25252566, 5225225, 353909638
|
CASER mul, 25252566, 5225225, 353909638
|
||||||
|
|
||||||
|
.macro CASE_MUL a:req, b:req
|
||||||
|
CASER mul, \a, \b, \a * \b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
WITH_TWO_TEST_NUMBERS CASE_MUL
|
||||||
|
|
||||||
CASER mulh 4, 4, 0
|
CASER mulh 4, 4, 0
|
||||||
CASER mulh, -1, -1, 0
|
CASER mulh, -1, -1, 0
|
||||||
CASER mulh, 25252566, 5225225, 30722
|
CASER mulh, 25252566, 5225225, 30722
|
||||||
|
|
|
||||||
57
tests/check/stack.S
Normal file
57
tests/check/stack.S
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Stack Pointer Addition (special-cased by compressed instructions)
|
||||||
|
# I've had two bugs in this area before...
|
||||||
|
|
||||||
|
#include "../helper.S"
|
||||||
|
|
||||||
|
.macro CASE_FOR_REGISTER reg:req number:req
|
||||||
|
li \reg, 0
|
||||||
|
addi \reg, \reg, \number
|
||||||
|
ASSERT_EQ \reg, \number
|
||||||
|
.endm
|
||||||
|
|
||||||
|
# The goal is sp addition, but why not test some other registers as well while we're at it :)
|
||||||
|
|
||||||
|
.macro CASE number
|
||||||
|
CASE_FOR_REGISTER sp, \number
|
||||||
|
CASE_FOR_REGISTER ra, \number
|
||||||
|
CASE_FOR_REGISTER a0, \number
|
||||||
|
CASE_FOR_REGISTER s0, \number
|
||||||
|
CASE_FOR_REGISTER t0, \number
|
||||||
|
.endm
|
||||||
|
|
||||||
|
START_TEST
|
||||||
|
CASE 0
|
||||||
|
CASE 1
|
||||||
|
CASE 2
|
||||||
|
CASE 4
|
||||||
|
CASE 8
|
||||||
|
CASE 10
|
||||||
|
CASE 16
|
||||||
|
CASE 32
|
||||||
|
CASE 64
|
||||||
|
CASE 100
|
||||||
|
CASE 128
|
||||||
|
CASE 200
|
||||||
|
CASE 256
|
||||||
|
CASE 512
|
||||||
|
CASE 1024
|
||||||
|
CASE 2047
|
||||||
|
|
||||||
|
CASE -1
|
||||||
|
CASE -2
|
||||||
|
CASE -4
|
||||||
|
CASE -8
|
||||||
|
CASE -10
|
||||||
|
CASE -16
|
||||||
|
CASE -32
|
||||||
|
CASE -64
|
||||||
|
CASE -100
|
||||||
|
CASE -128
|
||||||
|
CASE -200
|
||||||
|
CASE -256
|
||||||
|
CASE -512
|
||||||
|
CASE -1024
|
||||||
|
CASE -2047
|
||||||
|
CASE -2048
|
||||||
|
|
||||||
|
PASS
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
ebreak
|
|
||||||
li a7, -1
|
li a7, -1
|
||||||
li a0, 0
|
li a0, 0
|
||||||
ecall
|
ecall
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue