diff options
Diffstat (limited to 'rv64cpu')
| -rw-r--r-- | rv64cpu/rtl/core/mmu/tlb.sv | 378 | ||||
| -rw-r--r-- | rv64cpu/tb/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | rv64cpu/tb/unit/tb_tlb.cc | 418 |
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; +} |
