// Richard CPU // Copyright (C) 2026 Richard Qin // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include #include "Vtlb.h" namespace { constexpr uint8_t MODE_BARE = 0; constexpr uint8_t MODE_SV32 = 1; constexpr uint8_t MODE_SV48 = 9; constexpr uint8_t MODE_SV57 = 10; int g_pass_count = 0; void check(bool cond, const char* name) { if (cond) { std::cout << "\033[32m[PASS] " << name << "\033[0m\n"; ++g_pass_count; } else { std::cout << "\033[31m[FAIL] " << name << "\033[0m\n"; assert(false); } } void eval_comb(Vtlb* dut) { dut->eval(); } void tick(Vtlb* dut) { dut->clk = 0; dut->eval(); dut->clk = 1; dut->eval(); } uint64_t make_paddr(uint64_t ppn, uint64_t vaddr) { return (ppn << 12) | (vaddr & 0xFFFULL); } uint64_t sv48_vpn(uint64_t vaddr) { return (vaddr >> 12) & ((1ULL << 36) - 1ULL); } uint64_t sv32_vpn(uint64_t vaddr) { return (vaddr >> 12) & ((1ULL << 20) - 1ULL); } uint64_t sv57_vpn(uint64_t vaddr) { return (vaddr >> 12) & ((1ULL << 45) - 1ULL); } void clear_req(Vtlb* dut) { dut->req_valid = 0; dut->req_vaddr = 0; dut->req_asid = 0; dut->req_mode = MODE_SV48; dut->req_priv = 1; dut->req_is_fetch = 0; dut->req_is_store = 0; dut->req_sum = 0; dut->req_mxr = 0; } void clear_refill(Vtlb* dut) { dut->refill_valid = 0; dut->refill_vpn = 0; dut->refill_ppn = 0; dut->refill_level = 0; dut->refill_mode = MODE_SV48; dut->refill_asid = 0; dut->refill_g = 0; dut->refill_u = 0; dut->refill_r = 0; dut->refill_w = 0; dut->refill_x = 0; dut->refill_a = 0; dut->refill_d = 0; } void clear_flush(Vtlb* dut) { dut->flush_all = 0; dut->flush_asid_valid = 0; dut->flush_asid = 0; dut->flush_vpn_valid = 0; dut->flush_vpn = 0; dut->flush_mode = MODE_SV48; } void apply_flush_all(Vtlb* dut) { clear_flush(dut); dut->flush_all = 1; tick(dut); dut->flush_all = 0; eval_comb(dut); } void apply_flush_asid(Vtlb* dut, uint16_t asid) { clear_flush(dut); dut->flush_asid_valid = 1; dut->flush_asid = asid; tick(dut); clear_flush(dut); eval_comb(dut); } void apply_flush_vpn(Vtlb* dut, uint64_t vpn, uint8_t mode) { clear_flush(dut); dut->flush_vpn_valid = 1; dut->flush_vpn = vpn; dut->flush_mode = mode; tick(dut); clear_flush(dut); eval_comb(dut); } void reset(Vtlb* dut) { dut->rst = 1; clear_req(dut); clear_refill(dut); clear_flush(dut); tick(dut); tick(dut); dut->rst = 0; eval_comb(dut); } void do_refill( Vtlb* dut, uint64_t vpn, uint64_t ppn, uint8_t level, uint8_t mode, uint16_t asid, bool g, bool u, bool r, bool w, bool x, bool a, bool d ) { clear_refill(dut); dut->refill_valid = 1; dut->refill_vpn = vpn; dut->refill_ppn = ppn; dut->refill_level = level; dut->refill_mode = mode; dut->refill_asid = asid; dut->refill_g = g; dut->refill_u = u; dut->refill_r = r; dut->refill_w = w; dut->refill_x = x; dut->refill_a = a; dut->refill_d = d; tick(dut); dut->refill_valid = 0; eval_comb(dut); } void issue_load(Vtlb* dut, uint64_t vaddr, uint16_t asid, uint8_t mode) { clear_req(dut); dut->req_valid = 1; dut->req_vaddr = vaddr; dut->req_asid = asid; dut->req_mode = mode; dut->req_priv = 1; // S mode dut->req_is_fetch = 0; dut->req_is_store = 0; eval_comb(dut); } void issue_req( Vtlb* dut, uint64_t vaddr, uint16_t asid, uint8_t mode, uint8_t priv, bool is_fetch, bool is_store, bool sum, bool mxr ) { clear_req(dut); dut->req_valid = 1; dut->req_vaddr = vaddr; dut->req_asid = asid; dut->req_mode = mode; dut->req_priv = priv; dut->req_is_fetch = is_fetch; dut->req_is_store = is_store; dut->req_sum = sum; dut->req_mxr = mxr; eval_comb(dut); } } // namespace int main(int argc, char** argv) { Verilated::commandArgs(argc, argv); auto* dut = new Vtlb; std::cout << "----------------------------------\n"; std::cout << " L1 TLB Unit Test\n"; std::cout << "----------------------------------\n"; reset(dut); // 1) Bare mode bypass clear_req(dut); dut->req_valid = 1; dut->req_mode = MODE_BARE; dut->req_vaddr = 0x1234'5678'9ABC'DEF0ULL; dut->req_priv = 1; eval_comb(dut); check(dut->resp_hit == 1, "Bare_Bypass_Hit"); check(dut->resp_page_fault == 0, "Bare_Bypass_NoFault"); check(dut->resp_need_ptw == 0, "Bare_Bypass_NoPTW"); check(dut->resp_paddr == dut->req_vaddr, "Bare_Bypass_PaddrEqualsVaddr"); // 2) Unsupported mode faults clear_req(dut); dut->req_valid = 1; dut->req_mode = 4; dut->req_vaddr = 0x8000'0000ULL; eval_comb(dut); check(dut->resp_page_fault == 1, "UnsupportedMode_PageFault"); // 3) Miss => need PTW issue_load(dut, 0x0000'0000'8040'1000ULL, 1, MODE_SV48); check(dut->resp_hit == 0, "Miss_NoHit"); check(dut->resp_need_ptw == 1, "Miss_NeedPTW"); check(dut->resp_page_fault == 0, "Miss_NoFault"); // 4) Refill then hit const uint64_t va1 = 0x0000'0000'8040'1234ULL; const uint64_t vpn1 = sv48_vpn(va1); const uint64_t ppn1 = 0x12345ULL; do_refill(dut, vpn1, ppn1, 0, MODE_SV48, 1, false, false, true, true, false, true, true); issue_load(dut, va1, 1, MODE_SV48); check(dut->resp_hit == 1, "Refill_Hit"); check(dut->resp_page_fault == 0, "Refill_NoFault"); check(dut->resp_paddr == make_paddr(ppn1, va1), "Refill_PaddrMatch"); // 5) ASID mismatch miss when non-global issue_load(dut, va1, 2, MODE_SV48); check(dut->resp_hit == 0, "AsidMismatch_Miss"); check(dut->resp_need_ptw == 1, "AsidMismatch_NeedPTW"); // 6) Global entry ignores ASID const uint64_t va2 = 0x0000'0000'9000'3456ULL; const uint64_t vpn2 = sv48_vpn(va2); const uint64_t ppn2 = 0x23456ULL; do_refill(dut, vpn2, ppn2, 0, MODE_SV48, 9, true, false, true, false, true, true, true); issue_load(dut, va2, 99, MODE_SV48); check(dut->resp_hit == 1, "Global_AsidIgnore_Hit"); // 7) Store permission requires W and D const uint64_t va3 = 0x0000'0000'A000'2222ULL; const uint64_t vpn3 = sv48_vpn(va3); do_refill(dut, vpn3, 0x34567ULL, 0, MODE_SV48, 3, false, false, true, true, false, true, false); clear_req(dut); dut->req_valid = 1; dut->req_vaddr = va3; dut->req_asid = 3; dut->req_mode = MODE_SV48; dut->req_priv = 1; dut->req_is_store = 1; eval_comb(dut); check(dut->resp_page_fault == 1, "Store_DBitFault"); // 8) MXR allows load from execute-only page const uint64_t va4 = 0x0000'0000'B000'0100ULL; const uint64_t vpn4 = sv48_vpn(va4); do_refill(dut, vpn4, 0x45678ULL, 0, MODE_SV48, 4, false, false, false, false, true, true, true); clear_req(dut); dut->req_valid = 1; dut->req_vaddr = va4; dut->req_asid = 4; dut->req_mode = MODE_SV48; dut->req_priv = 1; dut->req_mxr = 1; eval_comb(dut); check(dut->resp_hit == 1, "MXR_LoadFromXOnly_Hit"); check(dut->resp_page_fault == 0, "MXR_LoadFromXOnly_NoFault"); // 9) Superpage match (Sv48 level=1) ignores low VPN[8:0] const uint64_t va5a = 0x0000'0000'C123'4000ULL; const uint64_t va5b = 0x0000'0000'C123'8ABCULL; // same high VPN, different low VPN bits const uint64_t vpn5 = sv48_vpn(va5a); do_refill(dut, vpn5, 0x56780ULL, 1, MODE_SV48, 5, false, false, true, true, true, true, true); issue_load(dut, va5b, 5, MODE_SV48); check(dut->resp_hit == 1, "Superpage_Level1_Hit"); // 10) Flush all invalidates apply_flush_all(dut); issue_load(dut, va1, 1, MODE_SV48); check(dut->resp_hit == 0, "FlushAll_Invalidates"); // 11) Flush by ASID only invalidates matching non-global entries const uint64_t va6a = 0x0000'0000'D000'1111ULL; const uint64_t va6b = 0x0000'0000'D000'2222ULL; do_refill(dut, sv48_vpn(va6a), 0x60100ULL, 0, MODE_SV48, 7, false, false, true, true, false, true, true); do_refill(dut, sv48_vpn(va6b), 0x60200ULL, 0, MODE_SV48, 8, false, false, true, true, false, true, true); apply_flush_asid(dut, 7); issue_load(dut, va6a, 7, MODE_SV48); check(dut->resp_hit == 0, "FlushAsid_TargetInvalidated"); issue_load(dut, va6b, 8, MODE_SV48); check(dut->resp_hit == 1, "FlushAsid_OtherAsidPreserved"); // 12) Flush by VPN only invalidates matching VPN in the selected mode const uint64_t va7a = 0x0000'0000'E100'0123ULL; const uint64_t va7b = 0x0000'0000'E200'0456ULL; do_refill(dut, sv48_vpn(va7a), 0x70100ULL, 0, MODE_SV48, 9, false, false, true, true, false, true, true); do_refill(dut, sv48_vpn(va7b), 0x70200ULL, 0, MODE_SV48, 9, false, false, true, true, false, true, true); apply_flush_vpn(dut, sv48_vpn(va7a), MODE_SV48); issue_load(dut, va7a, 9, MODE_SV48); check(dut->resp_hit == 0, "FlushVpn_TargetInvalidated"); issue_load(dut, va7b, 9, MODE_SV48); check(dut->resp_hit == 1, "FlushVpn_OtherVpnPreserved"); // 13) Sv32 superpage boundary: level=1 ignores low VPN[9:0] const uint64_t va32a = 0x0000000081234000ULL; const uint64_t va32b = 0x00000000813FFABCULL; do_refill(dut, sv32_vpn(va32a), 0x31000ULL, 1, MODE_SV32, 11, false, false, true, true, false, true, true); issue_load(dut, va32b, 11, MODE_SV32); check(dut->resp_hit == 1, "Sv32_Superpage_Level1_Hit"); check(dut->resp_page_fault == 0, "Sv32_Superpage_NoFault"); // 14) Sv57 superpage boundary: level=2 ignores low VPN[17:0] const uint64_t va57a = 0x0012'3456'7800'4000ULL; const uint64_t va57b = 0x0012'3456'7BFF'8ABCULL; do_refill(dut, sv57_vpn(va57a), 0x4A1234ULL, 2, MODE_SV57, 12, false, false, true, true, false, true, true); issue_load(dut, va57b, 12, MODE_SV57); check(dut->resp_hit == 1, "Sv57_Superpage_Level2_Hit"); check(dut->resp_page_fault == 0, "Sv57_Superpage_NoFault"); // 15) Flush VPN mode filter: Sv32 flush must not touch Sv48 entry with same low VPN const uint64_t va8 = 0x0000'0000'8123'4567ULL; const uint64_t vpn32 = sv32_vpn(va8); const uint64_t vpn48_same_low = vpn32; do_refill(dut, vpn32, 0x81000ULL, 0, MODE_SV32, 13, false, false, true, true, false, true, true); do_refill(dut, vpn48_same_low, 0x82000ULL, 0, MODE_SV48, 13, false, false, true, true, false, true, true); apply_flush_vpn(dut, vpn32, MODE_SV32); issue_load(dut, va8, 13, MODE_SV32); check(dut->resp_hit == 0, "FlushVpn_ModeFilter_Sv32Invalidated"); issue_load(dut, va8, 13, MODE_SV48); check(dut->resp_hit == 1, "FlushVpn_ModeFilter_Sv48Preserved"); // 16) S-mode access to U=1 page requires SUM for data access const uint64_t va9 = 0x0000'0000'8333'0100ULL; do_refill(dut, sv48_vpn(va9), 0x83000ULL, 0, MODE_SV48, 14, false, true, true, true, false, true, true); issue_req(dut, va9, 14, MODE_SV48, 1, false, false, false, false); check(dut->resp_page_fault == 1, "SUM_SmodeLoadUPage_Sum0_Fault"); issue_req(dut, va9, 14, MODE_SV48, 1, false, false, true, false); check(dut->resp_hit == 1, "SUM_SmodeLoadUPage_Sum1_Hit"); check(dut->resp_page_fault == 0, "SUM_SmodeLoadUPage_Sum1_NoFault"); // 17) SUM must not allow S-mode instruction fetch from U page issue_req(dut, va9, 14, MODE_SV48, 1, true, false, true, false); check(dut->resp_page_fault == 1, "SUM_SmodeFetchUPage_AlwaysFault"); // 18) U-mode cannot access U=0 page const uint64_t va10 = 0x0000'0000'8444'0200ULL; do_refill(dut, sv48_vpn(va10), 0x84000ULL, 0, MODE_SV48, 15, false, false, true, true, true, true, true); issue_req(dut, va10, 15, MODE_SV48, 0, false, false, false, false); check(dut->resp_page_fault == 1, "Umode_LoadSupervisorPage_Fault"); // 19) U-mode can load U=1 page when permissions allow const uint64_t va11 = 0x0000'0000'8555'0300ULL; do_refill(dut, sv48_vpn(va11), 0x85000ULL, 0, MODE_SV48, 16, false, true, true, false, false, true, true); issue_req(dut, va11, 16, MODE_SV48, 0, false, false, false, false); check(dut->resp_hit == 1, "Umode_LoadUserPage_Hit"); check(dut->resp_page_fault == 0, "Umode_LoadUserPage_NoFault"); // 20) MXR matrix: execute-only page load faults when mxr=0, passes when mxr=1 const uint64_t va12 = 0x0000'0000'8666'0400ULL; do_refill(dut, sv48_vpn(va12), 0x86000ULL, 0, MODE_SV48, 17, false, false, false, false, true, true, true); issue_req(dut, va12, 17, MODE_SV48, 1, false, false, false, false); check(dut->resp_page_fault == 1, "MXR_LoadXOnly_Mxr0_Fault"); issue_req(dut, va12, 17, MODE_SV48, 1, false, false, false, true); check(dut->resp_hit == 1, "MXR_LoadXOnly_Mxr1_Hit"); check(dut->resp_page_fault == 0, "MXR_LoadXOnly_Mxr1_NoFault"); // 21) A-bit required for any access type (load/fetch) const uint64_t va13 = 0x0000'0000'8777'0500ULL; do_refill(dut, sv48_vpn(va13), 0x87000ULL, 0, MODE_SV48, 18, false, false, true, true, true, false, true); issue_req(dut, va13, 18, MODE_SV48, 1, false, false, false, false); check(dut->resp_page_fault == 1, "ABit_LoadFault_WhenA0"); issue_req(dut, va13, 18, MODE_SV48, 1, true, false, false, false); check(dut->resp_page_fault == 1, "ABit_FetchFault_WhenA0"); // 22) ASID-targeted flush must preserve global entries const uint64_t va14 = 0x0000'0000'8888'0600ULL; do_refill(dut, sv48_vpn(va14), 0x88000ULL, 0, MODE_SV48, 33, true, false, true, true, false, true, true); apply_flush_asid(dut, 33); issue_load(dut, va14, 99, MODE_SV48); check(dut->resp_hit == 1, "FlushAsid_GlobalPreserved"); // 23) Refill pressure should eventually evict oldest entry (round-robin) apply_flush_all(dut); const uint64_t va15 = 0x0000'0000'9000'1000ULL; do_refill(dut, sv48_vpn(va15), 0x90001ULL, 0, MODE_SV48, 40, false, false, true, true, false, true, true); for (int n = 0; n < 32; ++n) { const uint64_t va_fill = 0x0000'0001'0000'0000ULL + (static_cast(n) << 12); const uint64_t ppn_fill = 0x91000ULL + static_cast(n); do_refill(dut, sv48_vpn(va_fill), ppn_fill, 0, MODE_SV48, static_cast(50 + n), false, false, true, true, false, true, true); } issue_load(dut, va15, 40, MODE_SV48); check(dut->resp_hit == 0, "ReplacementPressure_OldestEvicted"); // 24) Newest refill remains accessible after pressure const uint64_t va16 = 0x0000'0001'0001'F000ULL; do_refill(dut, sv48_vpn(va16), 0xA001FULL, 0, MODE_SV48, 77, false, false, true, true, false, true, true); issue_load(dut, va16, 77, MODE_SV48); check(dut->resp_hit == 1, "ReplacementPressure_NewestEntryHits"); // 25) flush_vpn should invalidate Sv48 level-1 superpage for any VPN within same range apply_flush_all(dut); const uint64_t va17_base = 0x0000'0001'2340'0000ULL; const uint64_t va17_same_superpage = 0x0000'0001'2341'2ABCULL; const uint64_t va17_other_superpage = 0x0000'0001'2440'0555ULL; do_refill(dut, sv48_vpn(va17_base), 0xB1000ULL, 1, MODE_SV48, 88, false, false, true, true, false, true, true); do_refill(dut, sv48_vpn(va17_other_superpage), 0xB2000ULL, 1, MODE_SV48, 88, false, false, true, true, false, true, true); apply_flush_vpn(dut, sv48_vpn(va17_same_superpage), MODE_SV48); issue_load(dut, va17_base, 88, MODE_SV48); check(dut->resp_hit == 0, "FlushVpn_Sv48L1_SameSuperpageInvalidated"); issue_load(dut, va17_other_superpage, 88, MODE_SV48); check(dut->resp_hit == 1, "FlushVpn_Sv48L1_OtherSuperpagePreserved"); // 26) flush_vpn should invalidate Sv57 level-2 superpage with 18-bit VPN wildcard apply_flush_all(dut); const uint64_t va18_base = 0x0012'3456'7800'1000ULL; const uint64_t va18_same_superpage = 0x0012'3456'7BFF'8FFFULL; const uint64_t va18_other_superpage = 0x0012'345A'0000'2000ULL; do_refill(dut, sv57_vpn(va18_base), 0xC1234ULL, 2, MODE_SV57, 99, false, false, true, true, false, true, true); do_refill(dut, sv57_vpn(va18_other_superpage), 0xC5678ULL, 2, MODE_SV57, 99, false, false, true, true, false, true, true); apply_flush_vpn(dut, sv57_vpn(va18_same_superpage), MODE_SV57); issue_load(dut, va18_base, 99, MODE_SV57); check(dut->resp_hit == 0, "FlushVpn_Sv57L2_SameSuperpageInvalidated"); issue_load(dut, va18_other_superpage, 99, MODE_SV57); check(dut->resp_hit == 1, "FlushVpn_Sv57L2_OtherSuperpagePreserved"); // 27) flush_vpn with exact-page entry must not over-invalidate neighboring pages apply_flush_all(dut); const uint64_t va19_a = 0x0000'0000'9900'1000ULL; const uint64_t va19_b = 0x0000'0000'9900'2000ULL; do_refill(dut, sv48_vpn(va19_a), 0xD1000ULL, 0, MODE_SV48, 120, false, false, true, true, false, true, true); do_refill(dut, sv48_vpn(va19_b), 0xD2000ULL, 0, MODE_SV48, 120, false, false, true, true, false, true, true); apply_flush_vpn(dut, sv48_vpn(va19_a), MODE_SV48); issue_load(dut, va19_a, 120, MODE_SV48); check(dut->resp_hit == 0, "FlushVpn_Level0_OnlyTargetInvalidated"); issue_load(dut, va19_b, 120, MODE_SV48); check(dut->resp_hit == 1, "FlushVpn_Level0_NeighborPreserved"); std::cout << "\n----------------------------------\n"; std::cout << "TLB tests passed: " << g_pass_count << "\n"; std::cout << "----------------------------------\n"; delete dut; return 0; }