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::{ 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"

View file

@ -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)
} }

View file

@ -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,
}; };

View file

@ -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}");

View file

@ -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 {

View file

@ -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
View 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

View file

@ -20,7 +20,6 @@
.endm .endm
fail: fail:
ebreak
li a7, -1 li a7, -1
li a0, 0 li a0, 0
ecall ecall