From 9c34902e4a763109d7d6251781bcb92f6bc68c56 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Wed, 1 Apr 2026 15:32:40 +0800 Subject: feat(tlb): add parameterized L1 TLB module and corresponding unit tests --- rv64cpu/tb/unit/tb_tlb.cc | 418 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 rv64cpu/tb/unit/tb_tlb.cc (limited to 'rv64cpu/tb/unit') diff --git a/rv64cpu/tb/unit/tb_tlb.cc b/rv64cpu/tb/unit/tb_tlb.cc new file mode 100644 index 0000000..ba6af6d --- /dev/null +++ b/rv64cpu/tb/unit/tb_tlb.cc @@ -0,0 +1,418 @@ +// 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"); + + std::cout << "\n----------------------------------\n"; + std::cout << "TLB tests passed: " << g_pass_count << "\n"; + std::cout << "----------------------------------\n"; + + delete dut; + return 0; +} -- cgit v1.2.3