use crate::common::PhysAddr;
use crate::config::Config;
use crate::isa::encoding::rv64i::{funct3 as i_f3, opcodes as i_op};
use crate::isa::encoding::zicboz::{
CBO_CLEAN_IMM, CBO_FLUSH_IMM, CBO_INVAL_IMM, CBO_ZERO_IMM, CBOZ_BLOCK_SIZE,
};
use crate::tests::support::harness::TestContext;
const RAM_BASE: u64 = 0x8000_0000;
const RAM_SIZE: usize = 0x4000;
const NOP: u32 = 0x0000_0013;
const JAL_SELF: u32 = 0x0000_006F;
const X10: u32 = 10;
fn cbo(imm: i64, rs1: u32) -> u32 {
((imm as u32 & 0xFFF) << 20) | ((rs1 & 0x1F) << 15) | (i_f3::CBO << 12) | i_op::OP_MISC_MEM
}
fn cbo_zero(rs1: u32) -> u32 {
cbo(CBO_ZERO_IMM, rs1)
}
fn cbo_inval(rs1: u32) -> u32 {
cbo(CBO_INVAL_IMM, rs1)
}
fn cbo_clean(rs1: u32) -> u32 {
cbo(CBO_CLEAN_IMM, rs1)
}
fn cbo_flush(rs1: u32) -> u32 {
cbo(CBO_FLUSH_IMM, rs1)
}
fn fill_pattern(ctx: &mut TestContext, base: u64, len: u64) {
for off in (0..len).step_by(8) {
let val = 0xDEAD_BEEF_CAFE_F00Du64.wrapping_add(off);
ctx.sim.probe_mem_store(PhysAddr::new(base + off), val, 8);
}
}
fn read_u64(ctx: &mut TestContext, addr: u64) -> u64 {
ctx.sim.probe_mem_load(PhysAddr::new(addr), 8)
}
fn run_cbo(ctx: &mut TestContext, inst: u32, addr_in_x10: u64) {
let park_offset = 4u64;
ctx.sim.probe_mem_store(PhysAddr::new(RAM_BASE), u64::from(inst), 4);
ctx.sim.probe_mem_store(PhysAddr::new(RAM_BASE + park_offset), u64::from(JAL_SELF), 4);
for i in 2..16 {
ctx.sim.probe_mem_store(PhysAddr::new(RAM_BASE + i * 4), u64::from(NOP), 4);
}
ctx.set_reg(X10 as usize, addr_in_x10);
ctx.sim.set_pc(0, RAM_BASE);
ctx.cpu_mut().harts[0].csrs.mtvec = RAM_BASE + park_offset;
ctx.run(1000);
}
fn run_cbo_zero(ctx: &mut TestContext, addr_in_x10: u64) {
run_cbo(ctx, cbo_zero(X10), addr_in_x10);
}
#[test]
fn cbo_zero_aligned_zeroes_one_block_only() {
let data_addr = RAM_BASE + 0x1000;
let mut ctx = TestContext::new_with_config(&Config::default()).with_memory(RAM_SIZE, RAM_BASE);
fill_pattern(&mut ctx, data_addr, 2 * CBOZ_BLOCK_SIZE);
run_cbo_zero(&mut ctx, data_addr);
for off in (0..CBOZ_BLOCK_SIZE).step_by(8) {
assert_eq!(read_u64(&mut ctx, data_addr + off), 0, "block must be zeroed at +{off}");
}
for off in (CBOZ_BLOCK_SIZE..2 * CBOZ_BLOCK_SIZE).step_by(8) {
let expected = 0xDEAD_BEEF_CAFE_F00Du64.wrapping_add(off);
assert_eq!(
read_u64(&mut ctx, data_addr + off),
expected,
"neighbour block must be untouched at +{off}"
);
}
}
#[test]
fn cbo_zero_ignores_low_address_bits() {
let block_addr = RAM_BASE + 0x1000;
let unaligned = block_addr + 17; let mut ctx = TestContext::new_with_config(&Config::default()).with_memory(RAM_SIZE, RAM_BASE);
fill_pattern(&mut ctx, block_addr, 2 * CBOZ_BLOCK_SIZE);
run_cbo_zero(&mut ctx, unaligned);
for off in (0..CBOZ_BLOCK_SIZE).step_by(8) {
assert_eq!(
read_u64(&mut ctx, block_addr + off),
0,
"block at rs1 & !63 must be zeroed at +{off}"
);
}
for off in (CBOZ_BLOCK_SIZE..2 * CBOZ_BLOCK_SIZE).step_by(8) {
let expected = 0xDEAD_BEEF_CAFE_F00Du64.wrapping_add(off);
assert_eq!(
read_u64(&mut ctx, block_addr + off),
expected,
"next block must be untouched at +{off}"
);
}
}
#[test]
fn cbo_zero_in_machine_mode_ignores_menvcfg_cbze() {
let data_addr = RAM_BASE + 0x1000;
let mut ctx = TestContext::new_with_config(&Config::default()).with_memory(RAM_SIZE, RAM_BASE);
assert_eq!(ctx.cpu().harts[0].csrs.menvcfg & crate::isa::csr::MENVCFG_CBZE, 0);
fill_pattern(&mut ctx, data_addr, CBOZ_BLOCK_SIZE);
run_cbo_zero(&mut ctx, data_addr);
for off in (0..CBOZ_BLOCK_SIZE).step_by(8) {
assert_eq!(read_u64(&mut ctx, data_addr + off), 0);
}
}
#[test]
fn cbo_inval_in_machine_mode_does_not_trap() {
let data_addr = RAM_BASE + 0x1000;
let mut ctx = TestContext::new_with_config(&Config::default()).with_memory(RAM_SIZE, RAM_BASE);
fill_pattern(&mut ctx, data_addr, CBOZ_BLOCK_SIZE);
run_cbo(&mut ctx, cbo_inval(X10), data_addr);
assert_eq!(ctx.cpu().harts[0].csrs.mcause, 0);
let expected = 0xDEAD_BEEF_CAFE_F00Du64;
assert_eq!(read_u64(&mut ctx, data_addr), expected);
}
#[test]
fn cbo_clean_in_machine_mode_does_not_trap() {
let data_addr = RAM_BASE + 0x1000;
let mut ctx = TestContext::new_with_config(&Config::default()).with_memory(RAM_SIZE, RAM_BASE);
fill_pattern(&mut ctx, data_addr, CBOZ_BLOCK_SIZE);
run_cbo(&mut ctx, cbo_clean(X10), data_addr);
assert_eq!(ctx.cpu().harts[0].csrs.mcause, 0);
}
#[test]
fn cbo_flush_in_machine_mode_does_not_trap() {
let data_addr = RAM_BASE + 0x1000;
let mut ctx = TestContext::new_with_config(&Config::default()).with_memory(RAM_SIZE, RAM_BASE);
fill_pattern(&mut ctx, data_addr, CBOZ_BLOCK_SIZE);
run_cbo(&mut ctx, cbo_flush(X10), data_addr);
assert_eq!(ctx.cpu().harts[0].csrs.mcause, 0);
}
fn check_load_after_cbo_zero_reads_zero(backend: crate::config::BackendKind) {
use crate::tests::support::builder::instruction::InstructionBuilder;
const X11: u32 = 11;
const X12: u32 = 12;
let data_addr = RAM_BASE + 0x1000;
let mut config = Config::default();
config.pipeline.backend = backend;
let mut ctx = TestContext::new_with_config(&config).with_memory(RAM_SIZE, RAM_BASE);
fill_pattern(&mut ctx, data_addr, CBOZ_BLOCK_SIZE);
let i = InstructionBuilder::new;
let program =
[cbo_zero(X10), i().ld(X11, X10, 8).build(), i().addi(X12, 0, 1).build(), JAL_SELF];
for (n, inst) in program.iter().enumerate() {
ctx.sim.probe_mem_store(PhysAddr::new(RAM_BASE + 4 * n as u64), u64::from(*inst), 4);
}
ctx.set_reg(X10 as usize, data_addr);
ctx.set_reg(X11 as usize, u64::MAX);
ctx.sim.set_pc(0, RAM_BASE);
ctx.cpu_mut().harts[0].csrs.mtvec = RAM_BASE + 12;
ctx.sim.sync_arch_regs();
let finished = ctx.run_until(2_000, |ctx| ctx.get_reg(X12 as usize) == 1);
assert!(finished.is_some(), "{backend:?}: the program never finished");
assert_eq!(ctx.cpu().harts[0].csrs.mcause, 0, "{backend:?}: no trap was taken");
assert_eq!(ctx.get_reg(X11 as usize), 0, "{backend:?}: the load read the zeroed block");
}
#[test]
fn load_after_cbo_zero_reads_zero_in_order() {
check_load_after_cbo_zero_reads_zero(crate::config::BackendKind::InOrder);
}
#[test]
fn load_after_cbo_zero_reads_zero_out_of_order() {
check_load_after_cbo_zero_reads_zero(crate::config::BackendKind::OutOfOrder);
}