use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use crate::core::device::{Device, ResetKind};
use crate::core::error::Result;
use crate::core::props::Props;
use crate::core::space::{AddressSpace, RamStore, Region};
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use super::csr::{Extensions, Priv, cause, irq, num, status};
use super::isa::Xlen;
use super::{CLASS, Config, Hart, X_NAMES, x_by_name};
const fn r(opcode: u32, funct3: u32, funct7: u32, rd: u32, rs1: u32, rs2: u32) -> u32 {
opcode | (rd << 7) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (funct7 << 25)
}
const fn i(opcode: u32, funct3: u32, rd: u32, rs1: u32, imm: i32) -> u32 {
opcode | (rd << 7) | (funct3 << 12) | (rs1 << 15) | (((imm as u32) & 0xfff) << 20)
}
const fn s(opcode: u32, funct3: u32, rs1: u32, rs2: u32, imm: i32) -> u32 {
let imm = imm as u32;
opcode
| ((imm & 0x1f) << 7)
| (funct3 << 12)
| (rs1 << 15)
| (rs2 << 20)
| (((imm >> 5) & 0x7f) << 25)
}
const fn b(funct3: u32, rs1: u32, rs2: u32, imm: i32) -> u32 {
let imm = imm as u32;
0x63 | (((imm >> 11) & 1) << 7)
| (((imm >> 1) & 0xf) << 8)
| (funct3 << 12)
| (rs1 << 15)
| (rs2 << 20)
| (((imm >> 5) & 0x3f) << 25)
| (((imm >> 12) & 1) << 31)
}
const fn j(rd: u32, imm: i32) -> u32 {
let imm = imm as u32;
0x6f | (rd << 7)
| (((imm >> 12) & 0xff) << 12)
| (((imm >> 11) & 1) << 20)
| (((imm >> 1) & 0x3ff) << 21)
| (((imm >> 20) & 1) << 31)
}
const fn u(opcode: u32, rd: u32, imm: u32) -> u32 {
opcode | (rd << 7) | (imm & 0xffff_f000)
}
const fn addi(rd: u32, rs1: u32, imm: i32) -> u32 {
i(0x13, 0, rd, rs1, imm)
}
const fn add(rd: u32, rs1: u32, rs2: u32) -> u32 {
r(0x33, 0, 0, rd, rs1, rs2)
}
const fn lui(rd: u32, imm: u32) -> u32 {
u(0x37, rd, imm)
}
const fn auipc(rd: u32, imm: u32) -> u32 {
u(0x17, rd, imm)
}
const fn csrrw(rd: u32, csr: u32, rs1: u32) -> u32 {
i(0x73, 1, rd, rs1, csr as i32)
}
const fn csrrs(rd: u32, csr: u32, rs1: u32) -> u32 {
i(0x73, 2, rd, rs1, csr as i32)
}
const ECALL: u32 = 0x0000_0073;
const EBREAK: u32 = 0x0010_0073;
const MRET: u32 = 0x3020_0073;
const SRET: u32 = 0x1020_0073;
const WFI: u32 = 0x1050_0073;
const BASE: u64 = 0x2000_0000;
const RAM_SIZE: u64 = 0x40_0000;
struct Harness {
hart: Hart,
ram: Arc<RamStore>,
}
impl Harness {
fn with(cfg: Config, program: &[u32]) -> Harness {
let ram = Arc::new(RamStore::new(RAM_SIZE));
for (n, word) in program.iter().enumerate() {
for (k, byte) in word.to_le_bytes().iter().enumerate() {
ram.write_u8(n as u64 * 4 + k as u64, *byte).unwrap();
}
}
let space = AddressSpace::new("mem", 64);
space
.topology()
.map(Region::ram("ram", Arc::clone(&ram)), BASE)
.unwrap();
let hart = Hart::new(cfg.with_reset_vector(BASE));
hart.attach_space(Arc::new(space));
Harness { hart, ram }
}
fn rv64i(program: &[u32]) -> Harness {
Harness::with(Config::rv64i(), program)
}
fn rv64gc(program: &[u32]) -> Harness {
let mut cfg = Config::rv64gc();
cfg.pmp_count = 0;
Harness::with(cfg, program)
}
fn put_half(&self, offset: u64, value: u16) {
for (k, byte) in value.to_le_bytes().iter().enumerate() {
self.ram.write_u8(offset + k as u64, *byte).unwrap();
}
}
fn put_u64(&self, addr: u64, value: u64) {
for (k, byte) in value.to_le_bytes().iter().enumerate() {
self.ram.write_u8(addr - BASE + k as u64, *byte).unwrap();
}
}
fn get_u64(&self, addr: u64) -> u64 {
let mut v = 0u64;
for k in 0..8 {
v |= u64::from(self.ram.read_u8(addr - BASE + k).unwrap()) << (8 * k);
}
v
}
fn steps(&self, n: usize) {
for _ in 0..n {
self.hart.step();
}
}
}
#[test]
fn immediates_are_sign_extended() {
let h = Harness::rv64i(&[addi(10, 0, -1), addi(11, 0, 2047), addi(12, 0, -2048)]);
h.steps(3);
assert_eq!(h.hart.x(10), u64::MAX);
assert_eq!(h.hart.x(11), 2047);
assert_eq!(h.hart.x(12), (-2048i64) as u64);
}
#[test]
fn x0_is_hard_wired_to_zero() {
let h = Harness::rv64i(&[addi(0, 0, 42), add(1, 0, 0)]);
h.steps(2);
assert_eq!(h.hart.x(0), 0);
assert_eq!(h.hart.x(1), 0);
h.hart.set_x(0, 99);
assert_eq!(h.hart.x(0), 0);
}
#[test]
fn upper_immediates_are_relative_to_the_right_thing() {
let h = Harness::rv64i(&[lui(10, 0x1234_5000), auipc(11, 0x1000)]);
h.steps(2);
assert_eq!(h.hart.x(10), 0x1234_5000);
assert_eq!(h.hart.x(11), BASE + 4 + 0x1000);
let h = Harness::rv64i(&[lui(10, 0x8000_0000)]);
h.steps(1);
assert_eq!(h.hart.x(10), 0xffff_ffff_8000_0000);
}
#[test]
fn shifts_use_the_configured_width() {
let program = [
addi(11, 0, 1),
i(0x13, 1, 10, 11, 33), ];
let h = Harness::rv64i(&program);
h.steps(2);
assert_eq!(h.hart.x(10), 1 << 33);
let h = Harness::with(Config::rv64i().with_ext(Extensions::I), &program);
let mut cfg = h.hart.config();
cfg.xlen = Xlen::Rv32;
let h = Harness::with(cfg, &program);
h.steps(2);
assert_eq!(
h.hart.csrs().mcause,
cause::ILLEGAL_INSN,
"a 33-bit shift does not exist on RV32"
);
}
#[test]
fn logical_right_shift_respects_rv32() {
let program = [
addi(11, 0, -1),
i(0x13, 5, 10, 11, 1), r(0x13, 5, 0x20, 12, 11, 1),
];
let mut cfg = Config::rv64i();
cfg.xlen = Xlen::Rv32;
let h = Harness::with(cfg, &program);
h.steps(3);
assert_eq!(h.hart.x(10), 0x7fff_ffff);
assert_eq!(h.hart.x(12), u64::MAX, "arithmetic shift keeps the sign");
}
#[test]
fn word_instructions_sign_extend_their_results() {
let h = Harness::rv64i(&[
lui(11, 0x8000_0000),
i(0x1b, 0, 10, 11, 0), r(0x3b, 1, 0, 12, 11, 0), r(0x3b, 5, 0, 13, 11, 0), ]);
h.steps(4);
assert_eq!(h.hart.x(10), 0xffff_ffff_8000_0000);
assert_eq!(h.hart.x(12), 0xffff_ffff_8000_0000);
assert_eq!(h.hart.x(13), 0xffff_ffff_8000_0000);
}
#[test]
fn sltiu_compares_a_sign_extended_immediate_as_unsigned() {
let h = Harness::rv64i(&[
addi(11, 0, 0),
i(0x13, 3, 10, 11, 1), addi(11, 0, 1),
i(0x13, 3, 12, 11, 1),
i(0x13, 3, 13, 0, -1), ]);
h.steps(5);
assert_eq!(h.hart.x(10), 1);
assert_eq!(h.hart.x(12), 0);
assert_eq!(h.hart.x(13), 1);
}
#[test]
fn loads_sign_or_zero_extend_by_their_width() {
let program = [
lui(11, BASE as u32),
i(0x03, 0, 10, 11, 0x100), i(0x03, 4, 12, 11, 0x100), ];
let h = Harness::rv64i(&program);
h.ram.write_u8(0x100, 0xff).unwrap();
h.steps(3);
assert_eq!(h.hart.x(10), u64::MAX);
assert_eq!(h.hart.x(12), 0xff);
}
#[test]
fn stores_and_loads_round_trip_at_every_width() {
let program = [
lui(11, BASE as u32),
addi(10, 0, -2),
s(0x23, 3, 11, 10, 0x200), i(0x03, 3, 12, 11, 0x200), i(0x03, 2, 13, 11, 0x200), i(0x03, 6, 14, 11, 0x200), ];
let h = Harness::rv64i(&program);
h.steps(6);
assert_eq!(h.hart.x(12), (-2i64) as u64);
assert_eq!(h.hart.x(13), (-2i64) as u64);
assert_eq!(h.hart.x(14), 0xffff_fffe);
}
#[test]
fn a_misaligned_access_is_performed_a_byte_at_a_time() {
let program = [
lui(11, BASE as u32),
addi(10, 0, 0x123),
s(0x23, 1, 11, 10, 0x201), i(0x03, 5, 12, 11, 0x201), ];
let h = Harness::rv64i(&program);
h.steps(4);
assert_eq!(h.hart.x(12), 0x123);
assert_eq!(h.hart.csrs().mcause, 0, "no trap was taken");
}
#[test]
fn a_misaligned_access_traps_when_the_hart_says_it_does_not_support_them() {
let mut cfg = Config::rv64i();
cfg.misaligned = false;
let program = [
lui(11, BASE as u32),
i(0x03, 1, 12, 11, 0x201), ];
let h = Harness::with(cfg, &program);
h.steps(2);
assert_eq!(h.hart.csrs().mcause, cause::LOAD_MISALIGNED);
assert_eq!(h.hart.csrs().mtval, BASE + 0x201);
}
#[test]
fn an_access_to_nothing_raises_an_access_fault() {
let h = Harness::rv64i(&[i(0x03, 3, 10, 0, 8)]); h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::LOAD_ACCESS);
assert_eq!(h.hart.csrs().mtval, 8);
assert_eq!(h.hart.bus_faults(), 1);
}
#[test]
fn branches_compare_signed_and_unsigned_differently() {
let program = [
addi(10, 0, -1),
addi(11, 0, 1),
b(4, 10, 11, 8), addi(12, 0, 111), b(6, 10, 11, 8), addi(13, 0, 222), ];
let h = Harness::rv64i(&program);
h.steps(5);
assert_eq!(h.hart.x(12), 0);
assert_eq!(h.hart.x(13), 222);
}
#[test]
fn jal_links_the_following_instruction_and_jalr_clears_the_low_bit() {
let h = Harness::rv64i(&[j(1, 8), addi(10, 0, 1), addi(10, 0, 2)]);
h.steps(2);
assert_eq!(h.hart.x(1), BASE + 4);
assert_eq!(h.hart.x(10), 2, "the middle instruction was skipped");
let h = Harness::rv64i(&[lui(11, BASE as u32), i(0x67, 0, 1, 11, 9)]);
h.steps(2);
assert_eq!(h.hart.pc(), BASE + 8);
}
#[test]
fn a_misaligned_jump_target_traps_without_c() {
let h = Harness::rv64i(&[j(1, 6)]);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::INSN_MISALIGNED);
assert_eq!(h.hart.csrs().mtval, BASE + 6);
let h = Harness::rv64gc(&[j(1, 6)]);
h.steps(1);
assert_eq!(h.hart.pc(), BASE + 6);
}
#[test]
fn multiplication_produces_both_halves() {
let mut cfg = Config::rv64i();
cfg.ext.m = true;
let program = [
addi(10, 0, -1),
addi(11, 0, -1),
r(0x33, 0, 1, 12, 10, 11), r(0x33, 1, 1, 13, 10, 11), r(0x33, 3, 1, 14, 10, 11), r(0x33, 2, 1, 15, 10, 11), ];
let h = Harness::with(cfg, &program);
h.steps(6);
assert_eq!(h.hart.x(12), 1, "(-1) * (-1) is 1");
assert_eq!(h.hart.x(13), 0, "the signed high half is zero");
assert_eq!(h.hart.x(14), 0xffff_ffff_ffff_fffe);
assert_eq!(h.hart.x(15), u64::MAX);
}
#[test]
fn division_by_zero_and_overflow_have_defined_results_and_do_not_trap() {
let mut cfg = Config::rv64i();
cfg.ext.m = true;
let program = [
addi(10, 0, 7),
addi(11, 0, 0),
r(0x33, 4, 1, 12, 10, 11), r(0x33, 6, 1, 13, 10, 11), r(0x33, 5, 1, 14, 10, 11), r(0x33, 7, 1, 15, 10, 11), ];
let h = Harness::with(cfg, &program);
h.steps(6);
assert_eq!(h.hart.x(12), u64::MAX, "division by zero gives all ones");
assert_eq!(h.hart.x(13), 7, "the remainder is the dividend");
assert_eq!(h.hart.x(14), u64::MAX);
assert_eq!(h.hart.x(15), 7);
assert_eq!(h.hart.csrs().mcause, 0, "and nothing traps");
let program = [
addi(10, 0, 1),
i(0x13, 1, 10, 10, 63), addi(11, 0, -1),
r(0x33, 4, 1, 12, 10, 11),
r(0x33, 6, 1, 13, 10, 11),
];
let h = Harness::with(cfg, &program);
h.steps(5);
assert_eq!(h.hart.x(12), 1u64 << 63);
assert_eq!(h.hart.x(13), 0);
}
#[test]
fn the_word_divisions_operate_on_the_low_halves() {
let mut cfg = Config::rv64i();
cfg.ext.m = true;
let program = [
lui(10, 0x8000_0000),
addi(11, 0, -1),
r(0x3b, 4, 1, 12, 10, 11), r(0x3b, 6, 1, 13, 10, 11), ];
let h = Harness::with(cfg, &program);
h.steps(4);
assert_eq!(h.hart.x(12), 0xffff_ffff_8000_0000);
assert_eq!(h.hart.x(13), 0);
}
const fn amo(funct5: u32, funct3: u32, rd: u32, rs1: u32, rs2: u32) -> u32 {
0x2f | (rd << 7) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (funct5 << 27)
}
#[test]
fn store_conditional_succeeds_only_while_the_reservation_holds() {
let mut cfg = Config::rv64i();
cfg.ext.a = true;
let program = [
lui(11, BASE as u32),
addi(11, 11, 0x400),
amo(0b00010, 3, 10, 11, 0), addi(12, 0, 99),
amo(0b00011, 3, 13, 11, 12), amo(0b00011, 3, 14, 11, 12), ];
let h = Harness::with(cfg, &program);
h.steps(6);
assert_eq!(h.hart.x(13), 0, "the first store-conditional succeeds");
assert_eq!(h.hart.x(14), 1, "the second one has no reservation left");
assert_eq!(h.get_u64(BASE + 0x400), 99);
}
#[test]
fn an_intervening_store_breaks_the_reservation() {
let mut cfg = Config::rv64i();
cfg.ext.a = true;
let program = [
lui(11, BASE as u32),
addi(11, 11, 0x400),
amo(0b00010, 3, 10, 11, 0), addi(12, 0, 1),
s(0x23, 3, 11, 12, 0), amo(0b00011, 3, 13, 11, 12), ];
let h = Harness::with(cfg, &program);
h.steps(6);
assert_eq!(h.hart.x(13), 1);
}
#[test]
fn the_amo_family_returns_the_old_value_and_stores_the_new_one() {
let mut cfg = Config::rv64i();
cfg.ext.a = true;
let program = [
lui(11, BASE as u32),
addi(11, 11, 0x400),
addi(12, 0, 5),
amo(0b00000, 3, 10, 11, 12), amo(0b00001, 3, 13, 11, 12), amo(0b10100, 3, 14, 11, 12), ];
let h = Harness::with(cfg, &program);
h.put_u64(BASE + 0x400, 7);
h.steps(6);
assert_eq!(h.hart.x(10), 7, "amoadd returns the old value");
assert_eq!(h.hart.x(13), 12, "and the sum was stored");
assert_eq!(h.hart.x(14), 5);
assert_eq!(h.get_u64(BASE + 0x400), 5, "max(5, 5)");
}
#[test]
fn a_misaligned_atomic_traps() {
let mut cfg = Config::rv64i();
cfg.ext.a = true;
let program = [
lui(11, BASE as u32),
addi(11, 11, 0x401),
amo(0b00000, 3, 10, 11, 0),
];
let h = Harness::with(cfg, &program);
h.steps(3);
assert_eq!(h.hart.csrs().mcause, cause::STORE_MISALIGNED);
}
#[test]
fn compressed_instructions_execute_as_their_expansions() {
let h = Harness::rv64gc(&[]);
h.put_half(0, 0x4515);
h.put_half(2, 0x0505);
h.put_half(4, 0x85aa);
h.steps(3);
assert_eq!(h.hart.x(10), 6);
assert_eq!(h.hart.x(11), 6);
assert_eq!(h.hart.pc(), BASE + 6, "each one advanced the PC by two");
}
#[test]
fn a_compressed_instruction_is_illegal_on_a_core_without_c() {
let h = Harness::rv64i(&[]);
h.put_half(0, 0x4515);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::ILLEGAL_INSN);
assert_eq!(h.hart.csrs().mtval, 0x4515);
}
#[test]
fn the_all_zero_halfword_is_permanently_illegal() {
let h = Harness::rv64gc(&[]);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::ILLEGAL_INSN);
}
#[test]
fn csr_instructions_read_before_they_write() {
let h = Harness::rv64i(&[
addi(10, 0, 0x123),
csrrw(11, num::MSCRATCH, 10), csrrs(12, num::MSCRATCH, 0), ]);
h.steps(3);
assert_eq!(h.hart.x(11), 0);
assert_eq!(h.hart.x(12), 0x123);
}
#[test]
fn a_write_to_a_read_only_csr_is_illegal() {
let h = Harness::rv64i(&[addi(10, 0, 1), csrrw(0, num::MHARTID, 10)]);
h.steps(2);
assert_eq!(h.hart.csrs().mcause, cause::ILLEGAL_INSN);
let h = Harness::rv64i(&[csrrs(10, num::MHARTID, 0)]);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, 0);
}
#[test]
fn a_machine_ecall_traps_to_mtvec_and_mret_comes_back() {
let handler = BASE + 0x40;
let mut program = vec![
lui(10, BASE as u32),
addi(10, 10, 0x40),
csrrw(0, num::MTVEC, 10),
ECALL,
addi(11, 0, 7),
];
program.resize(0x11, 0);
program[0x10] = MRET;
let h = Harness::rv64i(&program);
h.steps(4);
assert_eq!(h.hart.csrs().mcause, cause::ECALL_M);
assert_eq!(h.hart.csrs().mepc, BASE + 12, "mepc is the ecall itself");
assert_eq!(h.hart.pc(), handler);
h.steps(1);
assert_eq!(h.hart.pc(), BASE + 12);
assert_eq!(h.hart.priv_mode(), Priv::Machine);
}
#[test]
fn a_trap_saves_and_restores_the_interrupt_enable() {
let h = Harness::rv64i(&[csrrs(0, num::MSTATUS, 10), ECALL]);
h.hart.set_x(10, status::MIE);
h.steps(2);
let c = h.hart.csrs();
assert_eq!(
c.mstatus & status::MIE,
0,
"interrupts are off in the handler"
);
assert_ne!(c.mstatus & status::MPIE, 0, "and the old value was saved");
assert_eq!(
(c.mstatus & status::MPP) >> status::MPP_SHIFT,
Priv::Machine.bits()
);
}
#[test]
fn ebreak_reports_its_own_address() {
let h = Harness::rv64i(&[EBREAK]);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::BREAKPOINT);
assert_eq!(h.hart.csrs().mtval, BASE);
}
#[test]
fn an_exception_is_delegated_to_supervisor_mode_when_medeleg_says_so() {
let h = Harness::rv64gc(&[
addi(10, 0, 1),
i(0x13, 1, 10, 10, cause::ECALL_U as i32), csrrs(0, num::MEDELEG, 10),
lui(11, BASE as u32),
addi(11, 11, 0x200),
csrrw(0, num::STVEC, 11),
csrrw(0, num::MSTATUS, 0),
lui(12, BASE as u32),
addi(12, 12, 0x40),
csrrw(0, num::MEPC, 12),
MRET,
]);
h.ram.write_at(0x40, &ECALL.to_le_bytes()).unwrap();
h.steps(11);
assert_eq!(h.hart.priv_mode(), Priv::User, "mret dropped to user mode");
h.steps(1);
let c = h.hart.csrs();
assert_eq!(c.priv_mode, Priv::Supervisor, "the trap was delegated");
assert_eq!(c.scause, cause::ECALL_U);
assert_eq!(c.sepc, BASE + 0x40);
assert_eq!(c.mcause, 0, "and the machine registers were not touched");
assert_eq!(h.hart.pc(), BASE + 0x200);
}
#[test]
fn sret_returns_to_the_privilege_spp_names() {
let h = Harness::rv64gc(&[
lui(10, BASE as u32),
addi(10, 10, 0x100),
csrrw(0, num::SEPC, 10),
SRET,
]);
h.steps(4);
assert_eq!(h.hart.pc(), BASE + 0x100);
assert_eq!(h.hart.priv_mode(), Priv::User);
}
#[test]
fn sret_from_supervisor_traps_when_tsr_is_set() {
let h = Harness::rv64gc(&[
addi(10, 0, 1),
i(0x13, 1, 10, 10, 22), csrrs(0, num::MSTATUS, 10),
addi(11, 0, 1),
i(0x13, 1, 11, 11, status::MPP_SHIFT as i32),
csrrs(0, num::MSTATUS, 11),
lui(12, BASE as u32),
addi(12, 12, 0x40),
csrrw(0, num::MEPC, 12),
MRET,
]);
h.ram.write_at(0x40, &SRET.to_le_bytes()).unwrap();
h.steps(10);
assert_eq!(h.hart.priv_mode(), Priv::Supervisor);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::ILLEGAL_INSN);
}
#[test]
fn an_enabled_machine_interrupt_is_taken_between_instructions() {
let h = Harness::rv64i(&[
addi(10, 0, (irq::MTI) as i32),
csrrs(0, num::MIE, 10),
addi(11, 0, status::MIE as i32),
csrrs(0, num::MSTATUS, 11),
addi(12, 0, 1),
]);
h.steps(4);
h.hart.set_interrupt(irq::MTI, true);
h.steps(1);
let c = h.hart.csrs();
assert_ne!(c.mcause >> 63, 0, "the interrupt bit is set");
assert_eq!(c.mcause & 0xff, cause::IRQ_M_TIMER);
assert_eq!(c.mepc, BASE + 16, "the interrupted instruction has not run");
assert_eq!(h.hart.x(12), 0);
}
#[test]
fn a_disabled_interrupt_is_not_taken() {
let h = Harness::rv64i(&[addi(12, 0, 1)]);
h.hart.set_interrupt(irq::MTI, true);
h.steps(1);
assert_eq!(h.hart.x(12), 1, "mie is clear, so nothing happened");
}
#[test]
fn the_priority_order_is_the_specifications_not_the_numeric_one() {
let h = Harness::rv64i(&[
addi(10, 0, -1),
csrrs(0, num::MIE, 10),
addi(11, 0, status::MIE as i32),
csrrs(0, num::MSTATUS, 11),
addi(12, 0, 1),
]);
h.steps(4);
h.hart.set_interrupt(irq::MTI | irq::MEI, true);
h.steps(1);
assert_eq!(h.hart.csrs().mcause & 0xff, cause::IRQ_M_EXT);
}
#[test]
fn wfi_stalls_until_an_interrupt_arrives() {
let h = Harness::rv64i(&[
addi(10, 0, irq::MTI as i32),
csrrs(0, num::MIE, 10),
WFI,
addi(12, 0, 1),
]);
h.steps(3);
assert!(h.hart.is_waiting());
let before = h.hart.instret();
h.steps(5);
assert_eq!(h.hart.instret(), before, "nothing retired while stalled");
assert!(h.hart.cycles() > 0, "but time still passed");
h.hart.set_interrupt(irq::MTI, true);
h.steps(1);
assert!(!h.hart.is_waiting());
}
#[test]
fn an_sv39_walk_translates_a_supervisor_access_end_to_end() {
let h = Harness::rv64gc(&[]);
let v = super::mmu::pte::V;
let x = super::mmu::pte::X;
let rw = super::mmu::pte::R | super::mmu::pte::W;
let ad = super::mmu::pte::A | super::mmu::pte::D;
let root = BASE + 0x1000;
h.put_u64(root, v | rw | x | ad); h.put_u64(root + 8, (((BASE + 0x2000) >> 12) << 10) | v);
h.put_u64(BASE + 0x2000, (((BASE + 0x3000) >> 12) << 10) | v);
h.put_u64(
BASE + 0x3000 + 8,
(((BASE + 0x4000) >> 12) << 10) | v | rw | ad,
);
h.put_u64(BASE + 0x4000, 0xdead_beef);
h.ram
.write_at(0, &i(0x03, 3, 10, 11, 0).to_le_bytes())
.unwrap();
h.hart.set_x(11, 0x4000_1000);
h.steps(1);
assert_eq!(h.hart.x(10), 0);
h.hart.set_pc(BASE);
let mut csrs = h.hart.csrs();
csrs.satp = (8 << 60) | (root >> 12);
csrs.priv_mode = Priv::Supervisor;
h.hart.set_csrs(csrs);
h.steps(1);
assert_eq!(h.hart.x(10), 0xdead_beef);
assert_ne!(h.get_u64(BASE + 0x3000 + 8) & super::mmu::pte::A, 0);
}
#[test]
fn a_supervisor_access_to_an_unmapped_page_raises_a_page_fault() {
let h = Harness::rv64gc(&[i(0x03, 3, 10, 0, 0x40)]);
let mut csrs = h.hart.csrs();
csrs.satp = (8 << 60) | ((BASE + 0x8000) >> 12);
csrs.priv_mode = Priv::Supervisor;
csrs.stvec = BASE + 0x300;
csrs.medeleg = 1 << cause::INSN_PAGE_FAULT;
h.hart.set_csrs(csrs);
h.steps(1);
let c = h.hart.csrs();
assert_eq!(c.scause, cause::INSN_PAGE_FAULT, "the fetch itself faults");
assert_eq!(c.stval, BASE);
}
#[test]
fn the_tlb_absorbs_repeated_translations() {
let h = Harness::rv64gc(&[]);
let v = super::mmu::pte::V;
let perms = super::mmu::pte::R
| super::mmu::pte::W
| super::mmu::pte::X
| super::mmu::pte::A
| super::mmu::pte::D;
h.put_u64(BASE + 0x8000, v | perms);
for n in 0..8u64 {
h.ram
.write_at(n * 4, &addi(10, 10, 1).to_le_bytes())
.unwrap();
}
let mut csrs = h.hart.csrs();
csrs.satp = (8 << 60) | ((BASE + 0x8000) >> 12);
csrs.priv_mode = Priv::Supervisor;
h.hart.set_csrs(csrs);
h.steps(8);
assert_eq!(h.hart.x(10), 8);
let (hits, misses) = h.hart.tlb_stats();
assert!(hits > misses, "{hits} hits against {misses} misses");
}
fn enable_fp(hart: &Hart) {
let mut csrs = hart.csrs();
csrs.mstatus |= 1 << status::FS_SHIFT;
hart.set_csrs(csrs);
}
#[test]
fn floating_point_is_illegal_until_mstatus_says_otherwise() {
let h = Harness::rv64gc(&[r(0x53, 7, 0, 10, 11, 12)]); h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::ILLEGAL_INSN);
let h = Harness::rv64gc(&[r(0x53, 7, 0, 10, 11, 12)]);
enable_fp(&h.hart);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, 0);
}
#[test]
fn single_precision_values_are_nan_boxed() {
let h = Harness::rv64gc(&[
r(0x53, 0, 0x78, 10, 11, 0), r(0x53, 0, 0x70, 12, 10, 0), ]);
enable_fp(&h.hart);
h.hart.set_x(11, 0x4000_0000); h.steps(2);
assert_eq!(h.hart.f(10) >> 32, 0xffff_ffff, "the box is all ones");
assert_eq!(h.hart.x(12), 0x4000_0000);
let h = Harness::rv64gc(&[r(0x53, 1, 0x70, 12, 10, 0)]); enable_fp(&h.hart);
h.hart.set_f(10, 0x0000_0000_4000_0000); h.steps(1);
assert_eq!(h.hart.x(12), 1 << 9, "a quiet NaN, not a normal number");
}
#[test]
fn arithmetic_accumulates_the_sticky_flags() {
let h = Harness::rv64gc(&[
r(0x53, 7, 0x0d, 10, 11, 12), r(0x53, 7, 0x01, 13, 11, 12), ]);
enable_fp(&h.hart);
h.hart.set_f(11, 1.0f64.to_bits());
h.hart.set_f(12, 3.0f64.to_bits());
h.steps(2);
assert_eq!(h.hart.f(10), (1.0f64 / 3.0).to_bits());
assert_eq!(
h.hart.csrs().fcsr & 0x1f,
u64::from(super::float::flags::NX)
);
assert_eq!(h.hart.f(13), 4.0f64.to_bits());
assert_eq!(
h.hart.csrs().fcsr & 0x1f,
u64::from(super::float::flags::NX),
"an exact operation does not clear the sticky flag"
);
}
#[test]
fn the_rounding_mode_comes_from_fcsr_when_the_instruction_is_dynamic() {
let h = Harness::rv64gc(&[
csrrw(0, num::FRM, 10),
r(0x53, 7, 0x01, 12, 11, 13), ]);
enable_fp(&h.hart);
h.hart.set_x(10, 1); h.hart.set_f(11, 1.0f64.to_bits());
h.hart.set_f(13, 0x3ca0_0000_0000_0001); h.steps(2);
assert_eq!(h.hart.f(12), 1.0f64.to_bits());
}
#[test]
fn a_reserved_rounding_mode_is_an_illegal_instruction() {
let h = Harness::rv64gc(&[r(0x53, 5, 0x01, 12, 11, 13)]);
enable_fp(&h.hart);
h.steps(1);
assert_eq!(h.hart.csrs().mcause, cause::ILLEGAL_INSN);
}
#[test]
fn a_float_load_and_store_round_trip_through_memory() {
let h = Harness::rv64gc(&[
lui(11, BASE as u32),
s(0x27, 3, 11, 10, 0x400), i(0x07, 3, 12, 11, 0x400), i(0x07, 2, 13, 11, 0x400), ]);
enable_fp(&h.hart);
h.hart.set_f(10, 0x0123_4567_89ab_cdef);
h.steps(4);
assert_eq!(h.hart.f(12), 0x0123_4567_89ab_cdef);
assert_eq!(h.hart.f(13), 0xffff_ffff_89ab_cdef, "flw NaN-boxes");
}
#[test]
fn fused_multiply_add_writes_the_fused_result() {
let word = 0x43 | (10 << 7) | (7 << 12) | (11 << 15) | (12 << 20) | (13 << 27) | (1 << 25);
let h = Harness::rv64gc(&[word]);
enable_fp(&h.hart);
h.hart.set_f(11, (1.0f64 + f64::from_bits(0)).to_bits() + 1);
h.hart.set_f(12, 1.0f64.to_bits() - 2);
h.hart.set_f(13, (-1.0f64).to_bits());
h.steps(1);
assert_eq!(h.hart.f(10), (1u64 << 63) | (919u64 << 52));
}
#[test]
fn save_and_load_round_trip_to_an_identical_state() -> Result<()> {
let h = Harness::rv64gc(&[addi(10, 0, 1), addi(11, 0, 2), ECALL]);
enable_fp(&h.hart);
h.hart.set_f(3, 0x1234);
h.hart.set_interrupt(irq::MTI, true);
h.steps(3);
let mut shape = MachineShape::new();
shape.add_device("hart", CLASS.name)?;
let mut w = StateWriter::new(shape);
{
let mut chunk = w.chunk("hart", CLASS.name, CLASS.version)?;
h.hart.save(&mut chunk)?;
}
let bytes = w.to_vec()?;
let restored = Hart::new(h.hart.config());
let reader = StateReader::new(&bytes)?;
let chunk = reader.load("hart", CLASS.name, CLASS.version, &Migrations::new())?;
let mut cr = chunk.reader();
restored.load(&mut cr)?;
cr.end()?;
assert_eq!(restored.pc(), h.hart.pc());
assert_eq!(restored.x(10), h.hart.x(10));
assert_eq!(restored.f(3), h.hart.f(3));
assert_eq!(restored.csrs().mcause, h.hart.csrs().mcause);
assert_eq!(restored.interrupts(), h.hart.interrupts());
let mut shape2 = MachineShape::new();
shape2.add_device("hart", CLASS.name)?;
let mut w2 = StateWriter::new(shape2);
{
let mut chunk = w2.chunk("hart", CLASS.name, CLASS.version)?;
restored.save(&mut chunk)?;
}
assert_eq!(w2.to_vec()?, bytes, "a round trip must be a fixed point");
Ok(())
}
#[test]
fn a_reset_returns_the_hart_to_its_reset_vector() {
let h = Harness::rv64i(&[addi(10, 0, 1), addi(10, 0, 2)]);
h.steps(2);
assert_ne!(h.hart.pc(), BASE);
h.hart.reset(ResetKind::Cold);
assert_eq!(h.hart.pc(), BASE);
assert_eq!(h.hart.x(10), 0);
assert_eq!(h.hart.priv_mode(), Priv::Machine);
}
#[test]
fn a_reset_request_is_latched_and_acted_on_at_the_next_step() {
let h = Harness::rv64i(&[addi(10, 0, 1), addi(10, 0, 2)]);
h.steps(2);
h.hart.request_reset();
assert_ne!(h.hart.pc(), BASE, "nothing happened yet");
h.steps(1);
assert_eq!(h.hart.pc(), BASE + 4, "the reset ran, then one instruction");
assert_eq!(h.hart.x(10), 1);
}
#[test]
fn a_budget_carries_its_overshoot_into_the_next_one() {
let h = Harness::rv64i(&[addi(10, 0, 1); 16]);
let mut total = 0;
for _ in 0..8 {
total += h.hart.run_budget(1);
}
assert_eq!(total, 8, "the scheduler is never told more than it granted");
assert_eq!(h.hart.cycles(), 8);
}
#[test]
fn construction_from_properties_validates_what_it_is_given() {
let hart = Hart::from_props(&Props::new().with("xlen", "rv32")).unwrap();
assert_eq!(hart.config().xlen, Xlen::Rv32);
let hart = Hart::from_props(&Props::new().with("isa", "imac")).unwrap();
assert!(hart.config().ext.m && hart.config().ext.a && hart.config().ext.c);
assert!(!hart.config().ext.f);
let hart = Hart::from_props(&Props::new().with("isa", "g")).unwrap();
assert_eq!(hart.config().isa_string(), "rv64imafd");
assert!(Hart::from_props(&Props::new().with("isa", "v")).is_err());
assert!(Hart::from_props(&Props::new().with("nonsense", 1u64)).is_err());
assert!(Hart::from_props(&Props::new().with("pmp", 99u64)).is_err());
}
#[test]
fn d_without_f_is_corrected_rather_than_honoured() {
let hart = Hart::new(Config::rv64i().with_ext(Extensions {
d: true,
..Extensions::I
}));
assert!(hart.config().ext.f, "D implies F");
}
#[test]
fn register_names_round_trip() {
assert_eq!(x_by_name("a0"), Some(10));
assert_eq!(x_by_name("x31"), Some(31));
assert_eq!(x_by_name("fp"), Some(8));
assert_eq!(x_by_name("zero"), Some(0));
assert_eq!(x_by_name("nonsense"), None);
assert_eq!(x_by_name("x32"), None);
for (n, name) in X_NAMES.iter().enumerate() {
assert_eq!(x_by_name(name), Some(n as u32));
}
}
#[test]
fn the_isa_description_covers_the_whole_table() {
let text = super::describe_isa();
let lines: Vec<&str> = text.lines().collect();
assert_eq!(
lines.len(),
super::isa::TABLE.len() + super::isa::CTABLE.len()
);
assert!(text.contains("fmadd.d"));
assert!(text.contains("c.addi4spn"));
}
#[test]
fn a_hart_with_no_address_space_reports_no_progress() {
let hart = Hart::new(Config::rv64i());
assert_eq!(
hart.step(),
0,
"a caller must treat this as stop, not retry"
);
}