aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu
diff options
context:
space:
mode:
Diffstat (limited to 'rv64cpu')
-rw-r--r--rv64cpu/rtl/core/mmu/tlb.sv378
-rw-r--r--rv64cpu/tb/CMakeLists.txt9
-rw-r--r--rv64cpu/tb/unit/tb_tlb.cc418
3 files changed, 805 insertions, 0 deletions
diff --git a/rv64cpu/rtl/core/mmu/tlb.sv b/rv64cpu/rtl/core/mmu/tlb.sv
new file mode 100644
index 0000000..521b08a
--- /dev/null
+++ b/rv64cpu/rtl/core/mmu/tlb.sv
@@ -0,0 +1,378 @@
+// 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/>.
+
+// File path: rtl/core/mmu/tlb.sv
+// Description: Parameterized L1 TLB (Sv32/Sv48/Sv57)
+
+module tlb
+ import rv64_pkg::*;
+#(
+ parameter int ENTRIES = 32,
+ parameter int ASID_WIDTH = 16
+)(
+ input logic clk,
+ input logic rst,
+
+ // Translation request
+ input logic req_valid,
+ input logic [XLEN-1:0] req_vaddr,
+ input logic [ASID_WIDTH-1:0] req_asid,
+ input logic [3:0] req_mode, // satp.MODE
+ input logic [1:0] req_priv, // 0=U,1=S,3=M
+ input logic req_is_fetch,
+ input logic req_is_store,
+ input logic req_sum,
+ input logic req_mxr,
+
+ // Translation response
+ output logic resp_valid,
+ output logic resp_hit,
+ output logic [XLEN-1:0] resp_paddr,
+ output logic resp_page_fault,
+ output logic resp_need_ptw,
+
+ // Refill from PTW
+ input logic refill_valid,
+ input logic [44:0] refill_vpn, // normalized VPN, max Sv57 width
+ input logic [43:0] refill_ppn,
+ input logic [2:0] refill_level, // 0=4KB leaf, 1=next superpage ...
+ input logic [3:0] refill_mode,
+ input logic [ASID_WIDTH-1:0] refill_asid,
+ input logic refill_g,
+ input logic refill_u,
+ input logic refill_r,
+ input logic refill_w,
+ input logic refill_x,
+ input logic refill_a,
+ input logic refill_d,
+
+ // sfence / invalidation hooks
+ input logic flush_all,
+ input logic flush_asid_valid,
+ input logic [ASID_WIDTH-1:0] flush_asid,
+ input logic flush_vpn_valid,
+ input logic [44:0] flush_vpn,
+ input logic [3:0] flush_mode
+);
+
+ localparam logic [3:0] SATP_MODE_BARE = 4'd0;
+ localparam logic [3:0] SATP_MODE_SV32 = 4'd1;
+ localparam logic [3:0] SATP_MODE_SV48 = 4'd9;
+ localparam logic [3:0] SATP_MODE_SV57 = 4'd10;
+
+ typedef struct packed {
+ logic valid;
+ logic [3:0] mode;
+ logic [ASID_WIDTH-1:0] asid;
+ logic [44:0] vpn;
+ logic [43:0] ppn;
+ logic [2:0] level;
+ logic g;
+ logic u;
+ logic r;
+ logic w;
+ logic x;
+ logic a;
+ logic d;
+ } tlb_entry_t;
+
+ tlb_entry_t entries [0:ENTRIES-1];
+ logic [$clog2(ENTRIES)-1:0] repl_ptr;
+
+ logic [44:0] req_vpn;
+ logic [43:0] hit_ppn;
+ logic [2:0] hit_level;
+ logic [3:0] hit_mode;
+ logic hit_u, hit_r, hit_w, hit_x, hit_a, hit_d;
+ logic hit_found;
+ logic [5:0] shift;
+ logic [XLEN-1:0] low_mask;
+ logic req_priv_u;
+ logic [XLEN-1:0] hit_ppn_xlen;
+ int unsigned shift_int;
+
+ function automatic logic is_supported_mode(input logic [3:0] mode);
+ return (mode == SATP_MODE_BARE) || (mode == SATP_MODE_SV32) ||
+ (mode == SATP_MODE_SV48) || (mode == SATP_MODE_SV57);
+ endfunction
+
+ function automatic int vpn_width(input logic [3:0] mode);
+ case (mode)
+ SATP_MODE_SV32: vpn_width = 20;
+ SATP_MODE_SV48: vpn_width = 36;
+ SATP_MODE_SV57: vpn_width = 45;
+ default: vpn_width = 0;
+ endcase
+ endfunction
+
+ function automatic int vpn_stride(input logic [3:0] mode);
+ case (mode)
+ SATP_MODE_SV32: vpn_stride = 10;
+ SATP_MODE_SV48,
+ SATP_MODE_SV57: vpn_stride = 9;
+ default: vpn_stride = 0;
+ endcase
+ endfunction
+
+ function automatic logic [44:0] make_vpn_mask(
+ input logic [3:0] mode,
+ input logic [2:0] level
+ );
+ logic [44:0] mask;
+ int width;
+ int stride;
+ int ignore;
+ int i;
+ begin
+ mask = '0;
+ width = vpn_width(mode);
+ stride = vpn_stride(mode);
+ ignore = level * stride;
+
+ if (width > 0) begin
+ for (i = ignore; i < width; i++) begin
+ mask[i] = 1'b1;
+ end
+ end
+
+ make_vpn_mask = mask;
+ end
+ endfunction
+
+ function automatic logic [44:0] vaddr_to_vpn(
+ input logic [XLEN-1:0] vaddr,
+ input logic [3:0] mode
+ );
+ logic [44:0] vpn;
+ begin
+ vpn = '0;
+ case (mode)
+ SATP_MODE_SV32: vpn[19:0] = vaddr[31:12];
+ SATP_MODE_SV48: vpn[35:0] = vaddr[47:12];
+ SATP_MODE_SV57: vpn[44:0] = vaddr[56:12];
+ default: vpn = '0;
+ endcase
+ vaddr_to_vpn = vpn;
+ end
+ endfunction
+
+ function automatic logic [5:0] page_shift(
+ input logic [3:0] mode,
+ input logic [2:0] level
+ );
+ int stride;
+ begin
+ stride = vpn_stride(mode);
+ page_shift = 6'(12 + level * stride);
+ end
+ endfunction
+
+ function automatic logic entry_match(
+ input tlb_entry_t e,
+ input logic [44:0] vpn,
+ input logic [ASID_WIDTH-1:0] asid,
+ input logic [3:0] mode
+ );
+ logic [44:0] mask;
+ begin
+ mask = make_vpn_mask(e.mode, e.level);
+ entry_match = e.valid && (e.mode == mode) && (e.g || (e.asid == asid)) &&
+ ((((e.vpn ^ vpn) & mask) == '0));
+ end
+ endfunction
+
+ function automatic logic allow_access(
+ input logic is_fetch,
+ input logic is_store,
+ input logic priv_u,
+ input logic sum,
+ input logic mxr,
+ input logic u,
+ input logic r,
+ input logic w,
+ input logic x,
+ input logic a,
+ input logic d
+ );
+ logic is_load;
+ logic readable;
+ logic priv_ok;
+ begin
+ is_load = ~is_fetch && ~is_store;
+ readable = r || (mxr && x);
+
+ // U=1 page: only U-mode unless SUM permits S-mode data access
+ // U=0 page: S/M-mode only
+ if (u) begin
+ if (priv_u) begin
+ priv_ok = 1'b1;
+ end else begin
+ priv_ok = ~is_fetch && sum;
+ end
+ end else begin
+ priv_ok = ~priv_u;
+ end
+
+ allow_access = a && priv_ok && (
+ (is_fetch && x) ||
+ (is_store && w && d) ||
+ (is_load && readable)
+ );
+ end
+ endfunction
+
+ integer k;
+ always_comb begin
+ req_vpn = vaddr_to_vpn(req_vaddr, req_mode);
+
+ resp_valid = req_valid;
+ resp_hit = 1'b0;
+ resp_paddr = req_vaddr;
+ resp_page_fault = 1'b0;
+ resp_need_ptw = 1'b0;
+
+ shift = '0;
+ low_mask = '0;
+ req_priv_u = 1'b0;
+ hit_ppn_xlen = '0;
+ shift_int = 0;
+
+ hit_found = 1'b0;
+ hit_ppn = '0;
+ hit_level = '0;
+ hit_mode = '0;
+ hit_u = 1'b0;
+ hit_r = 1'b0;
+ hit_w = 1'b0;
+ hit_x = 1'b0;
+ hit_a = 1'b0;
+ hit_d = 1'b0;
+
+ if (req_valid) begin
+ if (!is_supported_mode(req_mode)) begin
+ resp_page_fault = 1'b1;
+ end else if (req_mode == SATP_MODE_BARE || req_priv == 2'b11) begin
+ // Bare mode or M-mode bypasses translation.
+ resp_hit = 1'b1;
+ resp_paddr = req_vaddr;
+ end else begin
+ for (k = 0; k < ENTRIES; k++) begin
+ if (!hit_found && entry_match(entries[k], req_vpn, req_asid, req_mode)) begin
+ hit_found = 1'b1;
+ hit_ppn = entries[k].ppn;
+ hit_level = entries[k].level;
+ hit_mode = entries[k].mode;
+ hit_u = entries[k].u;
+ hit_r = entries[k].r;
+ hit_w = entries[k].w;
+ hit_x = entries[k].x;
+ hit_a = entries[k].a;
+ hit_d = entries[k].d;
+ end
+ end
+
+ if (hit_found) begin
+ shift = page_shift(hit_mode, hit_level);
+ shift_int = int'(shift);
+ if (shift_int >= XLEN) begin
+ low_mask = {XLEN{1'b1}};
+ end else begin
+ low_mask = ({{(XLEN-1){1'b0}}, 1'b1} << shift_int) - 1;
+ end
+
+ hit_ppn_xlen = {{(XLEN-56){1'b0}}, hit_ppn, 12'b0};
+
+ req_priv_u = (req_priv == 2'b00);
+ if (!allow_access(req_is_fetch, req_is_store, req_priv_u, req_sum, req_mxr,
+ hit_u, hit_r, hit_w, hit_x, hit_a, hit_d)) begin
+ resp_page_fault = 1'b1;
+ end else begin
+ resp_hit = 1'b1;
+ resp_paddr = (hit_ppn_xlen & ~low_mask) | (req_vaddr & low_mask);
+ end
+ end else begin
+ resp_need_ptw = 1'b1;
+ end
+ end
+ end
+ end
+
+ integer i;
+ always_ff @(posedge clk) begin
+ if (rst) begin
+ repl_ptr <= '0;
+ for (i = 0; i < ENTRIES; i++) begin
+ entries[i].valid <= 1'b0;
+ entries[i].mode <= SATP_MODE_BARE;
+ entries[i].asid <= '0;
+ entries[i].vpn <= '0;
+ entries[i].ppn <= '0;
+ entries[i].level <= '0;
+ entries[i].g <= 1'b0;
+ entries[i].u <= 1'b0;
+ entries[i].r <= 1'b0;
+ entries[i].w <= 1'b0;
+ entries[i].x <= 1'b0;
+ entries[i].a <= 1'b0;
+ entries[i].d <= 1'b0;
+ end
+ end else begin
+ if (flush_all) begin
+ for (i = 0; i < ENTRIES; i++) begin
+ entries[i].valid <= 1'b0;
+ end
+ end else if (flush_asid_valid || flush_vpn_valid) begin
+ for (i = 0; i < ENTRIES; i++) begin
+ logic asid_match;
+ logic vpn_match;
+ logic mode_match;
+ logic [44:0] mask;
+
+ asid_match = !flush_asid_valid || entries[i].g || (entries[i].asid == flush_asid);
+ mode_match = !flush_vpn_valid || (entries[i].mode == flush_mode);
+ if (flush_vpn_valid) begin
+ mask = make_vpn_mask(entries[i].mode, entries[i].level);
+ vpn_match = (((entries[i].vpn ^ flush_vpn) & mask) == '0);
+ end else begin
+ vpn_match = 1'b1;
+ end
+
+ if (entries[i].valid && asid_match && mode_match && vpn_match) begin
+ entries[i].valid <= 1'b0;
+ end
+ end
+ end
+
+ if (refill_valid && is_supported_mode(refill_mode) && (refill_mode != SATP_MODE_BARE)) begin
+ entries[repl_ptr].valid <= 1'b1;
+ entries[repl_ptr].mode <= refill_mode;
+ entries[repl_ptr].asid <= refill_asid;
+ entries[repl_ptr].vpn <= refill_vpn;
+ entries[repl_ptr].ppn <= refill_ppn;
+ entries[repl_ptr].level <= refill_level;
+ entries[repl_ptr].g <= refill_g;
+ entries[repl_ptr].u <= refill_u;
+ entries[repl_ptr].r <= refill_r;
+ entries[repl_ptr].w <= refill_w;
+ entries[repl_ptr].x <= refill_x;
+ entries[repl_ptr].a <= refill_a;
+ entries[repl_ptr].d <= refill_d;
+ repl_ptr <= repl_ptr + 1'b1;
+ end
+ end
+ end
+
+endmodule
diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt
index ebcd9dc..1312860 100644
--- a/rv64cpu/tb/CMakeLists.txt
+++ b/rv64cpu/tb/CMakeLists.txt
@@ -180,6 +180,15 @@ add_verilator_target(
TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_csr.cc
)
+add_verilator_target(
+ NAME tb_tlb
+ TOP_MODULE tlb
+ SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
+ ${RTL_CORE_DIR}/mmu/tlb.sv
+ INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
+ TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_tlb.cc
+)
+
# ═══════════════════════════════════════════════════════════════════════════════════
# Core Integration Test
# ═══════════════════════════════════════════════════════════════════════════════════
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;
+}