use crate::common::{Asid, Ppn, Vpn};
use crate::uarch::mmu::tlb::{PageSize, Tlb, TlbGeometry, TlbHit};
const PTE_V: u64 = 1 << 0;
const PTE_R: u64 = 1 << 1;
const PTE_W: u64 = 1 << 2;
const PTE_X: u64 = 1 << 3;
const PTE_U: u64 = 1 << 4;
const PTE_G: u64 = 1 << 5;
fn fully_associative(entries: usize) -> Tlb {
Tlb::new(TlbGeometry { entries, ways: 0 })
}
fn make_pte(r: bool, w: bool, x: bool, u: bool) -> u64 {
let mut pte = PTE_V;
if r {
pte |= PTE_R;
}
if w {
pte |= PTE_W;
}
if x {
pte |= PTE_X;
}
if u {
pte |= PTE_U;
}
pte
}
#[test]
fn lookup_miss_on_empty() {
let mut tlb = fully_associative(16);
assert_eq!(tlb.lookup(Vpn::new(0x100), Asid::new(0)), None);
}
#[test]
fn insert_and_lookup_hit() {
let mut tlb = fully_associative(16);
let vpn = Vpn::new(0xABC);
let ppn = Ppn::new(0x123);
let pte = make_pte(true, false, true, false);
tlb.insert(vpn, ppn, pte, Asid::new(0), PageSize::Kib4);
match tlb.lookup(vpn, Asid::new(0)) {
Some(TlbHit { ppn: found_ppn, r, w, x, u, .. }) => {
assert_eq!(found_ppn, ppn);
assert!(r);
assert!(!w);
assert!(x);
assert!(!u);
}
None => panic!("Should hit after insert"),
}
}
#[test]
fn permissions_extracted_correctly() {
let mut tlb = fully_associative(16);
tlb.insert(
Vpn::new(0x10),
Ppn::new(0x100),
make_pte(true, false, false, false),
Asid::new(0),
PageSize::Kib4,
);
let hit = tlb.lookup(Vpn::new(0x10), Asid::new(0)).unwrap();
assert_eq!((hit.r, hit.w, hit.x, hit.u), (true, false, false, false));
tlb.insert(
Vpn::new(0x11),
Ppn::new(0x101),
make_pte(true, true, false, false),
Asid::new(0),
PageSize::Kib4,
);
let hit = tlb.lookup(Vpn::new(0x11), Asid::new(0)).unwrap();
assert_eq!((hit.r, hit.w, hit.x, hit.u), (true, true, false, false));
tlb.insert(
Vpn::new(0x12),
Ppn::new(0x102),
make_pte(true, false, true, false),
Asid::new(0),
PageSize::Kib4,
);
let hit = tlb.lookup(Vpn::new(0x12), Asid::new(0)).unwrap();
assert_eq!((hit.r, hit.w, hit.x, hit.u), (true, false, true, false));
tlb.insert(
Vpn::new(0x13),
Ppn::new(0x103),
make_pte(true, true, true, true),
Asid::new(0),
PageSize::Kib4,
);
assert!(tlb.lookup(Vpn::new(0x13), Asid::new(0)).unwrap().u);
}
#[test]
fn flush_clears_entries() {
let mut tlb = fully_associative(16);
tlb.insert(Vpn::new(0x1), Ppn::new(0x100), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
tlb.insert(Vpn::new(0x2), Ppn::new(0x200), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
assert!(tlb.lookup(Vpn::new(0x1), Asid::new(0)).is_some());
assert!(tlb.lookup(Vpn::new(0x2), Asid::new(0)).is_some());
tlb.flush();
assert_eq!(tlb.lookup(Vpn::new(0x1), Asid::new(0)), None);
assert_eq!(tlb.lookup(Vpn::new(0x2), Asid::new(0)), None);
}
#[test]
fn fill_capacity() {
let size = 32;
let mut tlb = fully_associative(size);
for i in 0..size {
tlb.insert(
Vpn::new(i as u64),
Ppn::new(0x1000 + i as u64),
PTE_V | PTE_R,
Asid::new(0),
PageSize::Kib4,
);
}
for i in 0..size {
assert!(
tlb.lookup(Vpn::new(i as u64), Asid::new(0)).is_some(),
"Entry {} should be present",
i
);
}
}
#[test]
fn asid_isolation() {
let mut tlb = fully_associative(256);
let vpn = Vpn::new(0x42);
tlb.insert(vpn, Ppn::new(0x100), PTE_V | PTE_R, Asid::new(1), PageSize::Kib4);
assert!(tlb.lookup(vpn, Asid::new(1)).is_some(), "Same ASID should hit");
assert_eq!(tlb.lookup(vpn, Asid::new(2)), None, "Different ASID should miss");
}
#[test]
fn global_bit_matches_any_asid() {
let mut tlb = fully_associative(256);
let vpn = Vpn::new(0x42);
tlb.insert(vpn, Ppn::new(0x100), PTE_V | PTE_R | PTE_G, Asid::new(1), PageSize::Kib4);
assert!(tlb.lookup(vpn, Asid::new(1)).is_some(), "Same ASID should hit");
assert!(tlb.lookup(vpn, Asid::new(2)).is_some(), "Different ASID should hit (global)");
assert!(tlb.lookup(vpn, Asid::new(0)).is_some(), "ASID 0 should hit (global)");
}
#[test]
fn flush_asid_only_affects_matching() {
let mut tlb = fully_associative(256);
tlb.insert(Vpn::new(0x10), Ppn::new(0x100), PTE_V | PTE_R, Asid::new(1), PageSize::Kib4);
tlb.insert(Vpn::new(0x20), Ppn::new(0x200), PTE_V | PTE_R, Asid::new(2), PageSize::Kib4);
tlb.flush_asid(Asid::new(1));
assert_eq!(tlb.lookup(Vpn::new(0x10), Asid::new(1)), None, "ASID 1 entry should be flushed");
assert!(tlb.lookup(Vpn::new(0x20), Asid::new(2)).is_some(), "ASID 2 entry should survive");
}
#[test]
fn flush_asid_preserves_global() {
let mut tlb = fully_associative(256);
tlb.insert(
Vpn::new(0x10),
Ppn::new(0x100),
PTE_V | PTE_R | PTE_G,
Asid::new(1),
PageSize::Kib4,
);
tlb.flush_asid(Asid::new(1));
assert!(
tlb.lookup(Vpn::new(0x10), Asid::new(1)).is_some(),
"Global entry should survive ASID flush"
);
}
#[test]
fn flush_vaddr_asid() {
let mut tlb = fully_associative(256);
tlb.insert(Vpn::new(0x10), Ppn::new(0x100), PTE_V | PTE_R, Asid::new(1), PageSize::Kib4);
tlb.insert(Vpn::new(0x20), Ppn::new(0x200), PTE_V | PTE_R, Asid::new(1), PageSize::Kib4);
tlb.flush_vaddr_asid(Vpn::new(0x10), Asid::new(1));
assert_eq!(tlb.lookup(Vpn::new(0x10), Asid::new(1)), None, "Targeted entry should be flushed");
assert!(tlb.lookup(Vpn::new(0x20), Asid::new(1)).is_some(), "Other entry should survive");
}
#[test]
fn flush_vaddr_asid_preserves_global() {
let mut tlb = fully_associative(256);
tlb.insert(
Vpn::new(0x10),
Ppn::new(0x100),
PTE_V | PTE_R | PTE_G,
Asid::new(1),
PageSize::Kib4,
);
tlb.flush_vaddr_asid(Vpn::new(0x10), Asid::new(1));
assert!(
tlb.lookup(Vpn::new(0x10), Asid::new(1)).is_some(),
"Global entry should survive vaddr+ASID flush"
);
}
#[test]
fn vpns_that_share_low_bits_coexist() {
let mut tlb = fully_associative(16);
tlb.insert(Vpn::new(0), Ppn::new(0x100), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
tlb.insert(Vpn::new(16), Ppn::new(0x200), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
let both = (tlb.lookup(Vpn::new(0), Asid::new(0)), tlb.lookup(Vpn::new(16), Asid::new(0)));
assert!(both.0.is_some() && both.1.is_some(), "a fully associative TLB holds both");
}
#[test]
fn the_least_recently_used_entry_is_evicted_when_full() {
let mut tlb = fully_associative(4);
for vpn in 0..4 {
tlb.insert(
Vpn::new(vpn),
Ppn::new(0x100 + vpn),
PTE_V | PTE_R,
Asid::new(0),
PageSize::Kib4,
);
}
let _ = tlb.lookup(Vpn::new(0), Asid::new(0));
tlb.insert(Vpn::new(9), Ppn::new(0x900), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
assert!(tlb.lookup(Vpn::new(0), Asid::new(0)).is_some(), "recently used entry kept");
assert_eq!(tlb.lookup(Vpn::new(1), Asid::new(0)), None, "least recently used evicted");
}
#[test]
fn a_superpage_entry_translates_every_page_it_covers() {
let mut tlb = fully_associative(4);
tlb.insert(Vpn::new(0x234), Ppn::new(0x8_0034), PTE_V | PTE_R, Asid::new(0), PageSize::Mib2);
let first = tlb.lookup(Vpn::new(0x200), Asid::new(0)).map(|hit| hit.ppn);
let last = tlb.lookup(Vpn::new(0x3FF), Asid::new(0)).map(|hit| hit.ppn);
let outside = tlb.lookup(Vpn::new(0x400), Asid::new(0));
assert_eq!((first, last, outside), (Some(Ppn::new(0x8_0000)), Some(Ppn::new(0x8_01FF)), None));
}
#[test]
fn flushing_an_address_removes_the_superpage_covering_it() {
let mut tlb = fully_associative(4);
tlb.insert(Vpn::new(0x200), Ppn::new(0x8_0000), PTE_V | PTE_R, Asid::new(1), PageSize::Mib2);
tlb.flush_vaddr(Vpn::new(0x3A0));
assert_eq!(tlb.lookup(Vpn::new(0x200), Asid::new(1)), None);
}
#[test]
fn a_set_associative_tlb_holds_superpages_alongside_base_pages() {
let mut l2 = Tlb::new(TlbGeometry { entries: 64, ways: 4 });
l2.insert(Vpn::new(0x40000), Ppn::new(0x80000), PTE_V | PTE_R, Asid::new(0), PageSize::Gib1);
l2.insert(Vpn::new(0x123), Ppn::new(0x456), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
let in_gigapage = l2.lookup(Vpn::new(0x4_1234), Asid::new(0)).map(|hit| hit.ppn);
let base_page = l2.lookup(Vpn::new(0x123), Asid::new(0)).map(|hit| hit.ppn);
assert_eq!((in_gigapage, base_page), (Some(Ppn::new(0x8_1234)), Some(Ppn::new(0x456))));
}
#[test]
fn a_tlb_with_no_entries_never_hits() {
let mut tlb = Tlb::new(TlbGeometry { entries: 0, ways: 0 });
tlb.insert(Vpn::new(1), Ppn::new(2), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
assert_eq!(tlb.lookup(Vpn::new(1), Asid::new(0)), None);
}
#[test]
fn a_direct_mapped_tlb_evicts_on_an_index_conflict() {
let mut tlb = Tlb::new(TlbGeometry { entries: 16, ways: 1 });
tlb.insert(Vpn::new(0), Ppn::new(0x100), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
tlb.insert(Vpn::new(16), Ppn::new(0x200), PTE_V | PTE_R, Asid::new(0), PageSize::Kib4);
assert_eq!(tlb.lookup(Vpn::new(0), Asid::new(0)), None);
}