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:
nora 2025-03-14 20:58:18 +01:00
parent fdb4968c8b
commit b2c3c9fc80
8 changed files with 290 additions and 64 deletions

View file

@ -1,4 +1,4 @@
use crate::inst::{AmoOp, Inst, InstCode, IsCompressed};
use crate::inst::{AmoOp, DecodeError, Inst, IsCompressed};
use std::{
fmt::{Debug, Display},
io::Write,
@ -66,7 +66,7 @@ impl Memory {
#[derive(Debug)]
pub enum Status {
Trap(&'static str),
IllegalInstruction(InstCode, &'static str),
IllegalInstruction(DecodeError),
InvalidMemoryAccess(u32),
UnalignedPc(u32),
UnaligneMemoryAccess { addr: u32, required_align: u32 },
@ -74,6 +74,12 @@ pub enum Status {
Exit { code: i32 },
}
impl From<DecodeError> for Status {
fn from(value: DecodeError) -> Self {
Status::IllegalInstruction(value)
}
}
pub struct Emulator {
pub mem: Memory,
pub xreg: [u32; 32],
@ -87,6 +93,7 @@ pub struct Emulator {
pub is_breaking: bool,
pub debug: bool,
pub break_pc: u32,
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.
self.mem.store_u32(next_word(), 0)?;
Ok(())
}
@ -238,16 +244,16 @@ impl Emulator {
fn step(&mut self) -> Result<(), Status> {
let code = self.mem.load_u32(self.pc)?;
let (inst, was_compressed) = Inst::decode(code)?;
if self.is_breaking {
self.debug_interactive();
if self.debug {
print!("0x{:x} ", self.pc);
}
let (inst, was_compressed) = Inst::decode(code)?;
if self.debug {
println!(
"0x{:x} {} (sp: {}) {inst:?}",
self.pc,
"{} (sp: {}) {inst:?}",
match was_compressed {
IsCompressed::Yes => "C",
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 {
IsCompressed::Yes => 2,
IsCompressed::No => 4,
@ -515,8 +529,8 @@ impl Emulator {
std::io::stdout().flush().unwrap();
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
match input {
let mut input = input.trim().split_whitespace();
match input.next().unwrap_or_default() {
"c" => {
self.is_breaking = false;
return;
@ -525,7 +539,43 @@ impl Emulator {
return;
}
"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| {
if v == 0 {
format!("{:0>8x}", v.black())
@ -626,7 +676,8 @@ impl Emulator {
_ => println!(
"commands:
- ?: 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
- s: step one instruction
- q: quit"

View file

@ -1,4 +1,4 @@
use crate::emu::{Reg, Status};
use crate::emu::Reg;
use std::fmt::{Debug, Display};
use std::ops::RangeInclusive;
@ -173,6 +173,11 @@ pub enum AmoOp {
Maxu,
}
pub struct DecodeError {
pub instruction: u32,
pub unexpected_field: &'static str,
}
impl Fence {
pub fn is_pause(&self) -> bool {
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 {
let right = u32::BITS - size;
(((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)]
pub struct InstCodeC(u16);
@ -537,8 +557,15 @@ pub enum IsCompressed {
No,
}
fn decode_error(instruction: impl Into<InstCode>, unexpected_field: &'static str) -> DecodeError {
DecodeError {
instruction: instruction.into().0,
unexpected_field,
}
}
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;
if is_compressed {
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);
if code.0 == 0 {
return Err(Status::IllegalInstruction(code.into(), "null instruction"));
return Err(decode_error(code, "null instruction"));
}
let inst = match code.quadrant() {
// C0
@ -573,7 +600,7 @@ impl Inst {
src: code.rs2_short(),
base: code.rs1_short(),
},
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
_ => return Err(decode_error(code, "funct3")),
},
// C1
0b01 => match code.funct3() {
@ -610,7 +637,7 @@ impl Inst {
// C.SRLI -> srli \rd', \rd', \imm
0b00 => {
if bit12 != 0 {
return Err(Status::IllegalInstruction(code.into(), "imm"));
return Err(decode_error(code, "imm"));
}
Inst::Srli {
@ -622,7 +649,7 @@ impl Inst {
// C.SRAI -> srai \rd', \rd', \imm
0b01 => {
if bit12 != 0 {
return Err(Status::IllegalInstruction(code.into(), "imm"));
return Err(decode_error(code, "imm"));
}
Inst::Srai {
@ -639,7 +666,7 @@ impl Inst {
},
0b11 => {
if bit12 != 0 {
return Err(Status::IllegalInstruction(code.into(), "bit 12"));
return Err(decode_error(code, "bit 12"));
}
let funct2 = code.extract(5..=6);
match funct2 {
@ -696,6 +723,7 @@ impl Inst {
(3..=4, 7),
(5..=5, 6),
(6..=6, 4),
(12..=12, 9),
]),
dest: Reg::SP,
src1: Reg::SP,
@ -704,7 +732,7 @@ impl Inst {
_ => {
let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]);
if uimm == 0 {
return Err(Status::IllegalInstruction(code.into(), "imm"));
return Err(decode_error(code, "imm"));
}
Inst::Lui {
uimm,
@ -737,14 +765,14 @@ impl Inst {
src1: code.rs1_short(),
src2: Reg::ZERO,
},
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
_ => return Err(decode_error(code, "funct3")),
},
// C2
0b10 => match code.funct3() {
// C.SLLI -> slli \rd, \rd, \imm
0b000 => {
if code.extract(12..=12) != 0 {
return Err(Status::IllegalInstruction(code.into(), "imm"));
return Err(decode_error(code, "imm"));
}
Inst::Slli {
imm: code.immediate_u(&[(2..=6, 0), (12..=12, 5)]),
@ -756,7 +784,7 @@ impl Inst {
0b010 => {
let dest = code.rd();
if dest.0 == 0 {
return Err(Status::IllegalInstruction(code.into(), "rd"));
return Err(decode_error(code, "rd"));
}
Inst::Lw {
@ -773,7 +801,7 @@ impl Inst {
// C.JR -> jalr zero, 0(\rs1)
(0, _, 0) => {
if rd_rs1.0 == 0 {
return Err(Status::IllegalInstruction(code.into(), "rs1"));
return Err(decode_error(code, "rs1"));
}
Inst::Jalr {
offset: 0,
@ -801,7 +829,7 @@ impl Inst {
src1: rd_rs1,
src2: rs2,
},
_ => return Err(Status::IllegalInstruction(code.into(), "inst")),
_ => return Err(decode_error(code, "inst")),
}
}
// C.SWSP -> sw \reg \offset(sp)
@ -810,14 +838,14 @@ impl Inst {
src: code.rs2(),
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)
}
fn decode_normal(code: u32) -> Result<Inst, Status> {
fn decode_normal(code: u32) -> Result<Inst, DecodeError> {
let code = InstCode(code);
let inst = match code.opcode() {
// LUI
@ -842,7 +870,7 @@ impl Inst {
base: code.rs1(),
dest: code.rd(),
},
_ => return Err(Status::IllegalInstruction(code, "funct3")),
_ => return Err(decode_error(code, "funct3")),
},
// BRANCH
0b1100011 => match code.funct3() {
@ -876,7 +904,7 @@ impl Inst {
src1: code.rs1(),
src2: code.rs2(),
},
_ => return Err(Status::IllegalInstruction(code, "funct3")),
_ => return Err(decode_error(code, "funct3")),
},
// LOAD
0b0000011 => match code.funct3() {
@ -905,7 +933,7 @@ impl Inst {
dest: code.rd(),
base: code.rs1(),
},
_ => return Err(Status::IllegalInstruction(code, "funct3")),
_ => return Err(decode_error(code, "funct3")),
},
// STORE
0b0100011 => match code.funct3() {
@ -924,7 +952,7 @@ impl Inst {
src: code.rs2(),
base: code.rs1(),
},
_ => return Err(Status::IllegalInstruction(code, "funct3")),
_ => return Err(decode_error(code, "funct3")),
},
// OP-IMM
0b0010011 => match code.funct3() {
@ -974,9 +1002,9 @@ impl Inst {
dest: code.rd(),
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
0b0110011 => {
@ -1001,7 +1029,7 @@ impl Inst {
(0b101, 0b0000001) => Inst::Divu { dest, src1, src2 },
(0b110, 0b0000001) => Inst::Rem { 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
@ -1030,34 +1058,34 @@ impl Inst {
src: code.rs1(),
},
},
_ => return Err(Status::IllegalInstruction(code, "funct3")),
_ => return Err(decode_error(code, "funct3")),
}
}
// SYSTEM
0b1110011 => {
if code.0 == 0b11000000000000000001000001110011 {
return Err(Status::Trap("unimp instruction"));
return Err(decode_error(code, "unimp instruction"));
}
if code.rd().0 != 0 {
return Err(Status::IllegalInstruction(code, "rd"));
return Err(decode_error(code, "rd"));
}
if code.funct3() != 0 {
return Err(Status::IllegalInstruction(code, "funct3"));
return Err(decode_error(code, "funct3"));
}
if code.rs1().0 != 0 {
return Err(Status::IllegalInstruction(code, "rs1"));
return Err(decode_error(code, "rs1"));
}
match code.imm_i() {
0b000000000000 => Inst::Ecall,
0b000000000001 => Inst::Ebreak,
_ => return Err(Status::IllegalInstruction(code, "imm")),
_ => return Err(decode_error(code, "imm")),
}
}
// AMO
0b00101111 => {
// width must be W
if code.funct3() != 0b010 {
return Err(Status::IllegalInstruction(code, "funct3"));
return Err(decode_error(code, "funct3"));
}
let kind = code.extract(27..=31);
@ -1070,7 +1098,7 @@ impl Inst {
// LR
0b00010 => {
if code.rs2().0 != 0 {
return Err(Status::IllegalInstruction(code, "rs2"));
return Err(decode_error(code, "rs2"));
}
Inst::LrW {
@ -1097,7 +1125,7 @@ impl Inst {
0b10100 => AmoOp::Max,
0b11000 => AmoOp::Minu,
0b11100 => AmoOp::Maxu,
_ => return Err(Status::IllegalInstruction(code, "funct7")),
_ => return Err(decode_error(code, "funct7")),
};
Inst::AmoW {
order,
@ -1109,7 +1137,7 @@ impl Inst {
}
}
}
_ => return Err(Status::IllegalInstruction(code, "opcode")),
_ => return Err(decode_error(code, "opcode")),
};
Ok(inst)
}

View file

@ -10,6 +10,7 @@ const MEMORY_SIZE: usize = 2 << 21;
pub fn execute_linux_elf(
elf: &[u8],
debug: bool,
break_addr: u32,
ecall_handler: Box<dyn FnMut(&mut emu::Memory, &mut [u32; 32]) -> Result<(), emu::Status>>,
) -> eyre::Result<emu::Status> {
let elf = elf::Elf { content: elf };
@ -67,9 +68,10 @@ pub fn execute_linux_elf(
xreg0_value: 0,
pc: start,
reservation_set: None,
is_breaking: false,
break_pc: break_addr,
debug,
ecall_handler,
};

View file

@ -1,17 +1,36 @@
use std::io::Write;
use eyre::eyre;
use rustv32i::emu::{self, Memory, Reg};
fn main() -> eyre::Result<()> {
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 status = rustv32i::execute_linux_elf(
&content,
std::env::args().any(|arg| arg == "--debug"),
debug,
break_addr,
Box::new(move |mem, xreg| ecall_handler(mem, xreg, &mut syscall_state)),
)?;
std::io::stdout().flush()?;
match status {
emu::Status::Exit { code } => {
eprintln!("exited with code {code}");