use super::program::{
A1, A2, BACKENDS, HANDLER, MEPC, MHARTID, MTVEC, PROGRAM_BASE, T0, T1, T2, config,
exit_sequence, run_to_exit, store_words, system, system_with, three_handled_ecalls,
};
use super::{Recorder, accounting_checks};
use crate::config::BackendKind;
use crate::isa::csr::MSTATUS_MPP;
use crate::isa::privileged::PrivilegeMode;
use crate::tests::support::builder::instruction::{ECALL, InstructionBuilder, MRET};
use crate::tests::support::harness::TestContext;
const LOOPS: i32 = 5;
const WIDE: usize = 4;
const STRAIGHT_LINE: u32 = 160;
const BODY: u64 = PROGRAM_BASE + 0x100;
const _: () = assert!(BODY + 4 * (STRAIGHT_LINE as u64 + 1) <= HANDLER);
const PMP_TOR_RWX: u8 = 0b0000_1111;
const FLD: u32 = 0x0005_b087;
const FSD: u32 = 0x0015_b427;
const FADD_D: u32 = 0x0210_f153;
const FMADD_D: u32 = 0x1220_f1c3;
const FDIV_D: u32 = 0x1a11_7253;
const FSQRT_D: u32 = 0x5a01_72d3;
const VSETVLI: u32 = 0x0d80_72d7;
const VLE64: u32 = 0x0205_f087;
const VADD_VV: u32 = 0x0210_8157;
const VFADD_VV: u32 = 0x0210_91d7;
const VSE64: u32 = 0x0206_7127;
const VANDN_VV: u32 = 0x0610_8357;
const VSETVLI_E32: u32 = 0x0d00_72d7;
const VAESZ_VS: u32 = 0xa623_a2f7;
fn every_class() -> Vec<u32> {
let i = InstructionBuilder::new;
let mut program = vec![
i().addi(T1, 0, LOOPS).build(),
i().ld(T2, A1, 0).build(),
i().addi(T2, T2, 1).build(),
i().sd(A1, T2, 16).build(),
i().addi(T1, T1, -1).build(),
i().bne(T1, 0, -16).build(),
i().jal(0, 4).build(),
FLD,
FSD,
FADD_D,
FMADD_D,
FDIV_D,
FSQRT_D,
VSETVLI,
VLE64,
VADD_VV,
VFADD_VV,
VSE64,
VANDN_VV,
VSETVLI_E32,
VAESZ_VS,
i().amoadd_d(T0, A2, T1).build(),
i().lr_d(T0, A2).build(),
i().sc_d(T0, A2, T1).build(),
i().csrrs(T0, MHARTID, 0).build(),
];
program.extend(exit_sequence());
program
}
fn retired_instructions_are_counted_by_class(rec: &mut Recorder) {
let loops = LOOPS as u64;
let expected = [
("core0.commit.op.alu", 1 + 2 * loops + 2),
("core0.commit.op.load", loops),
("core0.commit.op.store", loops),
("core0.commit.op.branch", loops + 1),
("core0.commit.op.system", 1),
("core0.commit.op.atomic", 3),
("core0.commit.fp.load", 1),
("core0.commit.fp.store", 1),
("core0.commit.fp.arith", 1),
("core0.commit.fp.fma", 1),
("core0.commit.fp.div_sqrt", 2),
("core0.commit.vec.misc", 2),
("core0.commit.vec.load", 1),
("core0.commit.vec.int", 2),
("core0.commit.vec.crypto", 1),
("core0.commit.vec.fp", 1),
("core0.commit.vec.store", 1),
];
let total: u64 = expected.iter().map(|(_, n)| n).sum();
for backend in BACKENDS {
let context = format!("{backend:?}");
let mut ctx = system(backend, &every_class(), &[]);
run_to_exit(&mut ctx, &context);
for (path, count) in expected {
rec.expect(&ctx.sim, path, count, &context);
}
rec.expect(&ctx.sim, "hart0.retired_insts", total, &context);
rec.expect(&ctx.sim, "system.retired_insts", total, &context);
}
}
fn a_trapping_ecall_counts_a_trap_and_does_not_retire(rec: &mut Recorder) {
for backend in BACKENDS {
let context = format!("{backend:?}");
let (program, handler) = three_handled_ecalls();
let mut ctx = system(backend, &program, &handler);
run_to_exit(&mut ctx, &context);
rec.expect(&ctx.sim, "hart0.traps", 3, &context);
rec.expect(&ctx.sim, "system.traps", 3, &context);
rec.expect(&ctx.sim, "core0.commit.op.system", 1 + 3 * 3, &context);
rec.expect(&ctx.sim, "core0.commit.op.alu", 1 + 3 + 2 + 3, &context);
rec.expect(&ctx.sim, "core0.commit.op.branch", 3, &context);
rec.expect(&ctx.sim, "hart0.retired_insts", 22, &context);
}
}
fn run_in(backend: BackendKind, width: usize, mode: PrivilegeMode) -> TestContext {
let i = InstructionBuilder::new;
let program = [
i().auipc(T1, 0).build(),
i().addi(T0, T1, (BODY - PROGRAM_BASE) as i32).build(),
i().csrrw(0, MEPC, T0).build(),
MRET,
];
let mut spin: Vec<u32> =
(0..STRAIGHT_LINE).map(|n| i().addi(T0 + n % 3, 0, n as i32).build()).collect();
spin.push(ECALL);
let mut handler = vec![i().csrrw(0, MTVEC, 0).build()];
handler.extend(exit_sequence());
let mut config = config(backend);
config.pipeline.width = width;
let mut ctx = system_with(&config, &program, &handler);
store_words(&mut ctx, BODY, &spin);
let hart = &mut ctx.sim.state.harts[0];
hart.csrs.mstatus = (hart.csrs.mstatus & !MSTATUS_MPP) | (u64::from(mode.to_u8()) << 11);
hart.pmp.set_addr(0, u64::MAX >> 10);
hart.pmp.set_cfg(0, PMP_TOR_RWX);
ctx
}
fn every_cycle_is_counted_once_by_mode_and_once_by_retire_width(rec: &mut Recorder) {
for (backend, width) in
[(BackendKind::InOrder, 1), (BackendKind::OutOfOrder, 1), (BackendKind::OutOfOrder, WIDE)]
{
for (mode, user, kernel) in
[(PrivilegeMode::User, true, false), (PrivilegeMode::Supervisor, false, true)]
{
let context = format!("{backend:?} width {width} {mode:?}");
let mut ctx = run_in(backend, width, mode);
run_to_exit(&mut ctx, &context);
let total = rec.read(&ctx.sim, "core0.pipeline.cycles.total");
let by_mode = [
rec.read(&ctx.sim, "hart0.cycles.user"),
rec.read(&ctx.sim, "hart0.cycles.kernel"),
rec.read(&ctx.sim, "hart0.cycles.machine"),
];
assert_eq!(by_mode.iter().sum::<u64>(), total, "{context}: {by_mode:?}");
assert_eq!((by_mode[0] > 0, by_mode[1] > 0), (user, kernel), "{context}: {by_mode:?}");
assert!(by_mode[2] > 0, "{context}");
let by_width = [
rec.read(&ctx.sim, "core0.commit.retire_histogram.zero"),
rec.read(&ctx.sim, "core0.commit.retire_histogram.one"),
rec.read(&ctx.sim, "core0.commit.retire_histogram.two"),
rec.read(&ctx.sim, "core0.commit.retire_histogram.three_plus"),
];
assert_eq!(by_width.iter().sum::<u64>(), total, "{context}: {by_width:?}");
if width == WIDE {
assert!(by_width[2] > 0 && by_width[3] > 0, "{context}: {by_width:?}");
}
let retired = rec.read(&ctx.sim, "hart0.retired_insts");
let width = width as u64;
let least = by_width[1] + 2 * by_width[2] + 3 * by_width[3];
let most = by_width[1] + 2 * by_width[2] + width * by_width[3];
assert!((least..=most).contains(&retired), "{context}: {retired} vs {by_width:?}");
let (retired, total) = (retired as f64, total as f64);
rec.expect_ratio(&ctx.sim, "core0.ipc", retired / total, &context);
rec.expect_ratio(&ctx.sim, "core0.cpi", total / retired, &context);
}
}
}
accounting_checks!(
retired_instructions_are_counted_by_class,
a_trapping_ecall_counts_a_trap_and_does_not_retire,
every_cycle_is_counted_once_by_mode_and_once_by_retire_width,
);