use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use std::path::Path;
use crate::core::space::{AddressSpace, RamStore, Region, UnassignedPolicy};
use super::csr::Extensions;
use super::elf::Elf;
use super::isa::Xlen;
use super::{Config, Hart};
const RAM_BASE: u64 = 0x8000_0000;
const RAM_SIZE: u64 = 16 << 20;
const STEP_LIMIT: u64 = 2_000_000;
#[derive(Debug, PartialEq, Eq)]
enum Outcome {
Pass,
Failed {
subtest: u64,
mcause: u64,
mepc: u64,
mtval: u64,
},
Timeout { pc: u64, instret: u64 },
Skipped(String),
}
fn run_one(path: &Path) -> Outcome {
let bytes = match std::fs::read(path) {
Ok(b) => b,
Err(e) => return Outcome::Skipped(format!("unreadable: {e}")),
};
let elf = match Elf::parse(&bytes) {
Ok(e) => e,
Err(e) => return Outcome::Skipped(e.to_string()),
};
let Some(tohost) = elf.symbol("tohost") else {
return Outcome::Skipped("no `tohost` symbol".to_string());
};
if !(RAM_BASE..RAM_BASE + RAM_SIZE).contains(&tohost) {
return Outcome::Skipped(format!("`tohost` at {tohost:#x} is outside RAM"));
}
let ram = Arc::new(RamStore::new(RAM_SIZE));
for segment in &elf.segments {
if segment.addr < RAM_BASE || segment.addr + segment.mem_len > RAM_BASE + RAM_SIZE {
return Outcome::Skipped(format!(
"segment at {:#x} does not fit in RAM",
segment.addr
));
}
let at = segment.addr - RAM_BASE;
ram.write_at(at, &segment.bytes).expect("in range");
if segment.mem_len > segment.bytes.len() as u64 {
ram.fill(
at + segment.bytes.len() as u64,
segment.mem_len - segment.bytes.len() as u64,
0,
)
.expect("in range");
}
}
let space = AddressSpace::new("mem", 64).with_unassigned(UnassignedPolicy::FAULT);
space
.topology()
.map(Region::ram("ram", Arc::clone(&ram)), RAM_BASE)
.expect("RAM fits");
let xlen = if elf.is_64 { Xlen::Rv64 } else { Xlen::Rv32 };
let hart = Hart::new(
Config {
xlen,
ext: Extensions::GC,
..Config::rv64gc()
}
.with_reset_vector(elf.entry),
);
hart.attach_space(Arc::new(space));
let tohost_offset = tohost - RAM_BASE;
let read_tohost = || {
let mut v = 0u64;
for k in 0..8 {
v |= u64::from(ram.read_u8(tohost_offset + k).unwrap_or(0)) << (8 * k);
}
v
};
for _ in 0..STEP_LIMIT {
hart.step();
let status = read_tohost();
if status != 0 {
if status == 1 {
return Outcome::Pass;
}
let csrs = hart.csrs();
return Outcome::Failed {
subtest: status >> 1,
mcause: csrs.mcause,
mepc: csrs.mepc,
mtval: csrs.mtval,
};
}
}
Outcome::Timeout {
pc: hart.pc(),
instret: hart.instret(),
}
}
#[test]
fn riscv_tests() {
let Ok(dir) = std::env::var("RSEMU_RISCV_TESTS") else {
println!(
"conformance: set RSEMU_RISCV_TESTS to a directory of built \
riscv-tests ELF binaries to run the suite (see the module docs \
for how to build them without a cross toolchain)"
);
return;
};
let only = std::env::var("RSEMU_RISCV_TESTS_ONLY").ok();
let dir = Path::new(&dir);
let mut entries: Vec<_> = std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("{}: {e}", dir.display()))
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.is_file())
.filter(|p| p.extension().is_none())
.collect();
entries.sort();
let mut passed = 0usize;
let mut skipped = 0usize;
let mut failures: Vec<String> = Vec::new();
for path in entries {
let name = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
if let Some(only) = &only
&& !only.split(',').any(|f| name.contains(f.trim()))
{
continue;
}
match run_one(&path) {
Outcome::Pass => passed += 1,
Outcome::Failed {
subtest,
mcause,
mepc,
mtval,
} => {
failures.push(format!(
"{name}: subtest {subtest} failed \
(mcause {mcause:#x} mepc {mepc:#x} mtval {mtval:#x})"
));
}
Outcome::Timeout { pc, instret } => {
failures.push(format!(
"{name}: no result after {instret} instructions, pc {pc:#x}"
));
}
Outcome::Skipped(why) => {
skipped += 1;
println!("skipped {name}: {why}");
}
}
}
for failure in &failures {
println!("FAIL {failure}");
}
println!(
"conformance: {passed} passed, {} failed, {skipped} skipped",
failures.len()
);
assert!(
passed + failures.len() > 0,
"no test binaries under {}",
dir.display()
);
assert!(failures.is_empty(), "{} failing tests", failures.len());
}