use super::program::{
A1, BACKENDS, T0, T1, T2, config, ending_in_spin, exit_sequence, run_to_exit, run_to_pc,
system, system_with,
};
use super::{Recorder, accounting_checks};
use crate::config::{BackendKind, LoadPrefetcherConfig, MemDepPredictorKind, PageBoundary};
use crate::tests::integration::translation::load_prefetch_paging::{Data, Mode, run_system};
use crate::tests::support::builder::instruction::InstructionBuilder;
const LOOPS: i32 = 5;
fn mispredicted_loop() -> Vec<u32> {
let i = InstructionBuilder::new;
let mut program = vec![
i().addi(T1, 0, LOOPS).build(),
i().addi(T1, T1, -1).build(),
i().bne(T1, 0, -4).build(),
];
program.extend(exit_sequence());
program
}
fn branch_outcomes_are_counted_at_resolution_and_at_commit(rec: &mut Recorder) {
let (wrong, right) = (LOOPS as u64 - 1, 1);
for backend in BACKENDS {
let context = format!("{backend:?}");
let mut ctx = system(backend, &mispredicted_loop(), &[]);
run_to_exit(&mut ctx, &context);
rec.expect(&ctx.sim, "core0.bp.committed.mispredicts", wrong, &context);
rec.expect(&ctx.sim, "core0.bp.committed.hits", right, &context);
let accuracy = right as f64 / (right + wrong) as f64;
rec.expect_ratio(&ctx.sim, "core0.bp.committed.accuracy", accuracy, &context);
let spec_wrong = rec.read(&ctx.sim, "core0.bp.spec.mispredicts");
let spec_right = rec.read(&ctx.sim, "core0.bp.spec.hits");
assert!(spec_wrong >= wrong && spec_right >= right, "{context}: wrong-path ones too");
let spec_accuracy = spec_right as f64 / (spec_right + spec_wrong) as f64;
rec.expect_ratio(&ctx.sim, "core0.bp.spec.accuracy", spec_accuracy, &context);
}
}
fn decode_redirects_fetch_to_each_jump_it_finds(rec: &mut Recorder) {
let i = InstructionBuilder::new;
let jumps = 3;
let mut program = Vec::new();
for _ in 0..jumps {
program.extend([i().jal(0, 8).build(), i().addi(T0, 0, 1).build()]);
}
program.extend(exit_sequence());
for backend in BACKENDS {
let context = format!("{backend:?}");
let mut ctx = system(backend, &program, &[]);
let mut straight = system(backend, &mispredicted_loop(), &[]);
run_to_exit(&mut ctx, &context);
run_to_exit(&mut straight, &context);
rec.expect(&ctx.sim, "core0.bp.decode_redirects", jumps, &context);
rec.expect(&ctx.sim, "core0.bp.committed.hits", jumps, &context);
rec.expect(&straight.sim, "core0.bp.decode_redirects", 0, &context);
}
}
fn load_past_unresolved_store() -> Vec<u32> {
let i = InstructionBuilder::new;
let mut program = vec![
i().addi(T0, 0, 7).build(),
i().div(T1, T0, T0).build(),
i().add(T2, A1, T1).build(),
i().addi(T2, T2, -1).build(),
i().sd(T2, T0, 0).build(),
i().ld(T1, A1, 0).build(),
];
program.extend(exit_sequence());
program
}
fn a_violation_trains_store_sets_to_make_the_load_wait(rec: &mut Recorder) {
let context = "OutOfOrder";
let mut ctx = system(BackendKind::OutOfOrder, &load_past_unresolved_store(), &[]);
run_to_exit(&mut ctx, context);
rec.expect(&ctx.sim, "core0.mdp.predictions.bypass", 1, context);
rec.expect(&ctx.sim, "core0.mdp.violations", 1, context);
rec.expect(&ctx.sim, "core0.mdp.predictions.wait_for", 1, context);
rec.expect(&ctx.sim, "core0.mdp.predictions.wait_all", 0, context);
}
fn loads(count: i32) -> Vec<u32> {
let i = InstructionBuilder::new;
let mut program: Vec<u32> = (0..count).map(|n| i().ld(T0, A1, 8 * n).build()).collect();
program.extend(exit_sequence());
program
}
fn the_blind_predictor_makes_every_load_wait_for_all_stores(rec: &mut Recorder) {
let context = "OutOfOrder blind";
let mut config = config(BackendKind::OutOfOrder);
config.pipeline.mem_dep_predictor = MemDepPredictorKind::Blind;
let mut ctx = system_with(&config, &loads(3), &[]);
run_to_exit(&mut ctx, context);
rec.expect(&ctx.sim, "core0.mdp.predictions.wait_all", 3, context);
rec.expect(&ctx.sim, "core0.mdp.predictions.bypass", 0, context);
}
fn predictor_counts_start_again_after_a_stats_reset(rec: &mut Recorder) {
let context = "OutOfOrder";
let i = InstructionBuilder::new;
let before: Vec<u32> = (0..3).map(|n| i().ld(T0, A1, 8 * n).build()).collect();
let (program, end) = ending_in_spin(before);
let mut after: Vec<u32> = (0..2).map(|n| i().ld(T0, A1, 64 + 8 * n).build()).collect();
after.extend(exit_sequence());
let mut ctx = system(BackendKind::OutOfOrder, &program, &[]);
let continuation = super::program::PROGRAM_BASE + 0x100;
super::program::store_words(&mut ctx, continuation, &after);
run_to_pc(&mut ctx, end, context);
ctx.sim.reset_stats();
ctx.sim.set_pc(0, continuation);
run_to_exit(&mut ctx, context);
rec.expect(&ctx.sim, "core0.mdp.predictions.bypass", 2, context);
}
fn a_store_whose_data_comes_late_issues_in_two_halves(rec: &mut Recorder) {
let i = InstructionBuilder::new;
let late_data = |late: bool| {
let mut program = vec![i().addi(T0, 0, 7).build(), i().div(T1, T0, T0).build()];
let data = if late { T1 } else { T0 };
program.push(i().sd(A1, data, 0).build());
program.extend(exit_sequence());
program
};
let context = "OutOfOrder";
let mut late = system(BackendKind::OutOfOrder, &late_data(true), &[]);
let mut ready = system(BackendKind::OutOfOrder, &late_data(false), &[]);
run_to_exit(&mut late, context);
run_to_exit(&mut ready, context);
rec.expect(&late.sim, "core0.lsq.split_stores", 1, context);
rec.expect(&ready.sim, "core0.lsq.split_stores", 0, context);
}
const MTIME: u64 = 0xBFF8;
fn a_device_read_waiting_to_be_oldest_counts_one_wait(rec: &mut Recorder) {
let i = InstructionBuilder::new;
for backend in BACKENDS {
let context = format!("{backend:?}");
let mtime = config(backend).system.clint_base + MTIME;
let mut program = vec![
i().addi(T0, 0, 7).build(),
i().div(T1, T0, T0).build(),
i().lui(T0, ((mtime + 8) >> 12) as i32).build(),
i().ld(T2, T0, -8).build(),
];
program.extend(exit_sequence());
let mut ctx = system(backend, &program, &[]);
let mut independent = system(backend, &loads(2), &[]);
run_to_exit(&mut ctx, &context);
run_to_exit(&mut independent, &context);
rec.expect(&ctx.sim, "core0.lsq.rescheduled_mem_ops", 1, &context);
rec.expect(&independent.sim, "core0.lsq.rescheduled_mem_ops", 0, &context);
}
}
fn stores(lines: i32, per_line: i32) -> Vec<u32> {
let i = InstructionBuilder::new;
let mut program = Vec::new();
for line in 0..lines {
for word in 0..per_line {
program.push(i().sd(A1, T0, 64 * line + 8 * word).build());
}
}
program.extend(exit_sequence());
program
}
fn the_write_combining_buffer_counts_merges_into_held_lines_and_line_writes(rec: &mut Recorder) {
for backend in BACKENDS {
for (lines, per_line) in [(1, 4), (4, 1)] {
let context = format!("{backend:?} {lines} lines of {per_line}");
let mut config = config(backend);
config.cache.wcb_entries = 8;
let mut ctx = system_with(&config, &stores(lines, per_line), &[]);
run_to_exit(&mut ctx, &context);
let merged = (lines * (per_line - 1)) as u64;
rec.expect(&ctx.sim, "core0.wcb.coalesces", merged, &context);
rec.expect(&ctx.sim, "core0.wcb.drains", lines as u64, &context);
}
}
}
const STREAM_LOADS: i32 = 24;
const RAM_SIZE: usize = 0x1_0000;
fn line_stride() -> Vec<u32> {
let i = InstructionBuilder::new;
let mut program = vec![
i().addi(T1, 0, STREAM_LOADS).build(),
i().ld(T2, A1, 0).build(),
i().add(A1, A1, T2).build(),
i().addi(A1, A1, 64).build(),
i().addi(T1, T1, -1).build(),
i().bne(T1, 0, -16).build(),
];
program.extend(exit_sequence());
program
}
fn prefetching(backend: BackendKind, page_boundary: PageBoundary) -> crate::config::Config {
let mut config = config(backend);
config.cache.l1_d.enabled = true;
config.cache.l1_d.size_bytes = 32 * 1024;
config.cache.l1_d.ways = 8;
config.cache.l2.enabled = true;
config.cache.load_prefetcher =
LoadPrefetcherConfig::Stride { table_size: 64, l1_lines: 2, l2_lines: 4, page_boundary };
config.memory.ram_size = RAM_SIZE;
config
}
fn a_stride_stream_is_prefetched_into_each_level_and_dropped_where_it_must_be(rec: &mut Recorder) {
let ram_end = super::program::PROGRAM_BASE + RAM_SIZE as u64;
for backend in BACKENDS {
let context = format!("{backend:?}");
let mut inside =
system_with(&prefetching(backend, PageBoundary::Stop), &line_stride(), &[]);
let mut to_the_end =
system_with(&prefetching(backend, PageBoundary::CrossWithTlb), &line_stride(), &[]);
to_the_end.set_reg(A1 as usize, ram_end - 64 * STREAM_LOADS as u64);
to_the_end.sim.sync_arch_regs();
run_to_exit(&mut inside, &context);
run_to_exit(&mut to_the_end, &context);
let lines = STREAM_LOADS as u64;
let into_l1 = rec.read(&inside.sim, "core0.prefetch.loads.l1");
let into_l2 = rec.read(&inside.sim, "core0.prefetch.loads.l2");
assert!((lines - 4..=lines + 2).contains(&into_l1), "{context}: {into_l1} into the L1D");
assert!((lines - 4..=lines + 4).contains(&into_l2), "{context}: {into_l2} into the L2");
rec.expect(&inside.sim, "core0.prefetch.loads.dropped.not_ram", 0, &context);
rec.expect(&inside.sim, "core0.prefetch.loads.dropped.page_boundary", 0, &context);
let past_ram = rec.read(&to_the_end.sim, "core0.prefetch.loads.dropped.not_ram");
assert!(past_ram > 0, "{context}");
}
}
fn a_stream_stops_at_the_page_or_drops_what_the_tlb_cannot_place(rec: &mut Recorder) {
for backend in BACKENDS {
let context = format!("{backend:?}");
let stopped = run_system(backend, Mode::Sv39, PageBoundary::Stop, Data::TouchedSecondPage);
let missed =
run_system(backend, Mode::Sv39, PageBoundary::CrossWithTlb, Data::UntouchedSecondPage);
let denied = run_system(
backend,
Mode::Sv39,
PageBoundary::CrossWithTlb,
Data::ExecuteOnlySecondPage,
);
let crossed =
run_system(backend, Mode::Sv39, PageBoundary::CrossWithTlb, Data::TouchedSecondPage);
for (ctx, dropped) in
[(&stopped, "page_boundary"), (&missed, "tlb_miss"), (&denied, "denied")]
{
let path = format!("core0.prefetch.loads.dropped.{dropped}");
assert!(rec.read(&ctx.sim, &path) > 0, "{context}: {path}");
}
for dropped in ["page_boundary", "tlb_miss", "denied"] {
let path = format!("core0.prefetch.loads.dropped.{dropped}");
rec.expect(&crossed.sim, &path, 0, &context);
}
}
}
accounting_checks!(
branch_outcomes_are_counted_at_resolution_and_at_commit,
decode_redirects_fetch_to_each_jump_it_finds,
a_violation_trains_store_sets_to_make_the_load_wait,
the_blind_predictor_makes_every_load_wait_for_all_stores,
predictor_counts_start_again_after_a_stats_reset,
a_store_whose_data_comes_late_issues_in_two_halves,
a_device_read_waiting_to_be_oldest_counts_one_wait,
the_write_combining_buffer_counts_merges_into_held_lines_and_line_writes,
a_stride_stream_is_prefetched_into_each_level_and_dropped_where_it_must_be,
a_stream_stops_at_the_page_or_drops_what_the_tlb_cannot_place,
);