aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu/tb/unit
diff options
context:
space:
mode:
Diffstat (limited to 'rv64cpu/tb/unit')
-rw-r--r--rv64cpu/tb/unit/tb_tlb.cc418
1 files changed, 418 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>.
+
+
+#include <cassert>
+#include <cstdint>
+#include <iostream>
+
+#include <verilated.h>
+
+#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;
+}