aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu/rtl/core/mmu
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-11-07 04:31:31 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-11-07 04:31:31 +0800
commita7d5b2468553c6d3570ca2aa45c27cb93e26cb88 (patch)
tree7031b1fac52e4f858c4e92041485135242768f7b /rv64cpu/rtl/core/mmu
parent4ca723bc9dc1b9db1bd03e3b3db0c58079c3f3c4 (diff)
parent3b0b67af63af86c5ec9b2a63defa06b70923b2e3 (diff)
downloadrcc-project-a7d5b2468553c6d3570ca2aa45c27cb93e26cb88.tar.gz
rcc-project-a7d5b2468553c6d3570ca2aa45c27cb93e26cb88.tar.xz
Merge remote-tracking branch 'rv64cpu-import/master'
Diffstat (limited to 'rv64cpu/rtl/core/mmu')
-rw-r--r--rv64cpu/rtl/core/mmu/mmu.sv359
-rw-r--r--rv64cpu/rtl/core/mmu/ptw.sv416
-rw-r--r--rv64cpu/rtl/core/mmu/tlb.sv382
3 files changed, 1157 insertions, 0 deletions
diff --git a/rv64cpu/rtl/core/mmu/mmu.sv b/rv64cpu/rtl/core/mmu/mmu.sv
new file mode 100644
index 0000000..60c4afa
--- /dev/null
+++ b/rv64cpu/rtl/core/mmu/mmu.sv
@@ -0,0 +1,359 @@
+// 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/mmu_top.sv
+// Description: MMU top-level integrating iTLB + dTLB + shared PTW
+
+module mmu
+ import rv64_pkg::*;
+#(
+ parameter int ITLB_ENTRIES = 16,
+ parameter int DTLB_ENTRIES = 32,
+ parameter int ASID_WIDTH = 16
+)(
+ input logic clk,
+ input logic rst,
+
+ // CSR/context inputs
+ input logic [XLEN-1:0] satp_value,
+ input logic [1:0] priv_mode,
+ input logic sum,
+ input logic mxr,
+
+ // i-side translation request
+ input logic i_req_valid,
+ output logic i_req_ready,
+ input logic [XLEN-1:0] i_req_vaddr,
+ output logic i_resp_valid,
+ output logic i_resp_hit,
+ output logic i_resp_page_fault,
+ output logic [XLEN-1:0] i_resp_vaddr,
+ output logic [XLEN-1:0] i_resp_paddr,
+
+ // d-side translation request
+ input logic d_req_valid,
+ output logic d_req_ready,
+ input logic [XLEN-1:0] d_req_vaddr,
+ input logic d_req_is_store,
+ output logic d_resp_valid,
+ output logic d_resp_hit,
+ output logic d_resp_page_fault,
+ output logic [XLEN-1:0] d_resp_paddr,
+
+ // sfence / invalidation
+ 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,
+
+ // PTW memory interface
+ output logic ptw_mem_req_valid,
+ input logic ptw_mem_req_ready,
+ output logic [XLEN-1:0] ptw_mem_req_addr,
+ input logic ptw_mem_resp_valid,
+ input logic [XLEN-1:0] ptw_mem_resp_data
+);
+
+ localparam logic [3:0] SATP_MODE_BARE = 4'd0;
+
+ typedef enum logic [0:0] {
+ PTW_SRC_I = 1'b0,
+ PTW_SRC_D = 1'b1
+ } ptw_src_t;
+
+ logic [ASID_WIDTH-1:0] req_asid;
+ logic [3:0] req_mode;
+
+ logic i_tlb_resp_valid;
+ logic i_tlb_resp_hit;
+ logic [XLEN-1:0] i_tlb_resp_paddr;
+ logic i_tlb_resp_page_fault;
+ logic i_tlb_resp_need_ptw;
+
+ logic d_tlb_resp_valid;
+ logic d_tlb_resp_hit;
+ logic [XLEN-1:0] d_tlb_resp_paddr;
+ logic d_tlb_resp_page_fault;
+ logic d_tlb_resp_need_ptw;
+
+ logic i_pending_q;
+ logic d_pending_q;
+ logic [XLEN-1:0] i_pending_vaddr_q;
+ logic [XLEN-1:0] d_pending_vaddr_q;
+ logic [1:0] i_pending_priv_q;
+ logic [1:0] d_pending_priv_q;
+ logic i_pending_sum_q;
+ logic d_pending_sum_q;
+ logic i_pending_mxr_q;
+ logic d_pending_mxr_q;
+ logic [ASID_WIDTH-1:0] i_pending_asid_q;
+ logic [ASID_WIDTH-1:0] d_pending_asid_q;
+ logic [3:0] i_pending_mode_q;
+ logic [3:0] d_pending_mode_q;
+ logic d_pending_is_store_q;
+
+ logic ptw_req_valid;
+ logic ptw_req_ready;
+ logic ptw_resp_valid;
+ logic ptw_resp_fault;
+ logic [XLEN-1:0] ptw_resp_paddr;
+
+ logic ptw_refill_valid;
+ logic [44:0] ptw_refill_vpn;
+ logic [43:0] ptw_refill_ppn;
+ logic [2:0] ptw_refill_level;
+ logic [3:0] ptw_refill_mode;
+ logic [ASID_WIDTH-1:0] ptw_refill_asid;
+ logic ptw_refill_g;
+ logic ptw_refill_u;
+ logic ptw_refill_r;
+ logic ptw_refill_w;
+ logic ptw_refill_x;
+ logic ptw_refill_a;
+ logic ptw_refill_d;
+
+ logic ptw_busy_q;
+ ptw_src_t ptw_owner_q;
+ ptw_src_t ptw_owner_next;
+
+ logic i_accept;
+ logic d_accept;
+ logic i_fast_resp_valid;
+ logic d_fast_resp_valid;
+ logic i_ptw_resp_sel;
+ logic d_ptw_resp_sel;
+
+ logic i_refill_valid;
+ logic d_refill_valid;
+
+ assign req_mode = satp_value[63:60];
+ assign req_asid = satp_value[59 -: ASID_WIDTH];
+
+ assign i_req_ready = !i_pending_q;
+ assign d_req_ready = !d_pending_q;
+ assign i_accept = i_req_valid && i_req_ready;
+ assign d_accept = d_req_valid && d_req_ready;
+
+ assign i_fast_resp_valid = i_accept && (i_tlb_resp_hit || i_tlb_resp_page_fault);
+ assign d_fast_resp_valid = d_accept && (d_tlb_resp_hit || d_tlb_resp_page_fault);
+
+ assign i_ptw_resp_sel = ptw_resp_valid && (ptw_owner_q == PTW_SRC_I);
+ assign d_ptw_resp_sel = ptw_resp_valid && (ptw_owner_q == PTW_SRC_D);
+
+ assign i_resp_valid = i_fast_resp_valid || i_ptw_resp_sel;
+ assign i_resp_hit = i_fast_resp_valid ? i_tlb_resp_hit : (i_ptw_resp_sel && !ptw_resp_fault);
+ assign i_resp_page_fault = i_fast_resp_valid ? i_tlb_resp_page_fault : (i_ptw_resp_sel && ptw_resp_fault);
+ assign i_resp_vaddr = i_fast_resp_valid ? i_req_vaddr : i_pending_vaddr_q;
+ assign i_resp_paddr = i_fast_resp_valid ? i_tlb_resp_paddr : ptw_resp_paddr;
+
+ assign d_resp_valid = d_fast_resp_valid || d_ptw_resp_sel;
+ assign d_resp_hit = d_fast_resp_valid ? d_tlb_resp_hit : (d_ptw_resp_sel && !ptw_resp_fault);
+ assign d_resp_page_fault = d_fast_resp_valid ? d_tlb_resp_page_fault : (d_ptw_resp_sel && ptw_resp_fault);
+ assign d_resp_paddr = d_fast_resp_valid ? d_tlb_resp_paddr : ptw_resp_paddr;
+
+ always_comb begin
+ if (d_pending_q) begin
+ ptw_owner_next = PTW_SRC_D;
+ end else begin
+ ptw_owner_next = PTW_SRC_I;
+ end
+ end
+
+ assign ptw_req_valid = !ptw_busy_q && (d_pending_q || i_pending_q);
+
+ assign i_refill_valid = ptw_refill_valid && (ptw_owner_q == PTW_SRC_I);
+ assign d_refill_valid = ptw_refill_valid && (ptw_owner_q == PTW_SRC_D);
+
+ always_ff @(posedge clk) begin
+ if (rst) begin
+ i_pending_q <= 1'b0;
+ d_pending_q <= 1'b0;
+ ptw_busy_q <= 1'b0;
+ ptw_owner_q <= PTW_SRC_I;
+ i_pending_vaddr_q <= '0;
+ d_pending_vaddr_q <= '0;
+ i_pending_priv_q <= '0;
+ d_pending_priv_q <= '0;
+ i_pending_sum_q <= 1'b0;
+ d_pending_sum_q <= 1'b0;
+ i_pending_mxr_q <= 1'b0;
+ d_pending_mxr_q <= 1'b0;
+ i_pending_asid_q <= '0;
+ d_pending_asid_q <= '0;
+ i_pending_mode_q <= SATP_MODE_BARE;
+ d_pending_mode_q <= SATP_MODE_BARE;
+ d_pending_is_store_q <= 1'b0;
+ end else begin
+ if (i_accept && i_tlb_resp_need_ptw) begin
+ i_pending_q <= 1'b1;
+ i_pending_vaddr_q <= i_req_vaddr;
+ i_pending_priv_q <= priv_mode;
+ i_pending_sum_q <= sum;
+ i_pending_mxr_q <= mxr;
+ i_pending_asid_q <= req_asid;
+ i_pending_mode_q <= req_mode;
+ end
+
+ if (d_accept && d_tlb_resp_need_ptw) begin
+ d_pending_q <= 1'b1;
+ d_pending_vaddr_q <= d_req_vaddr;
+ d_pending_priv_q <= priv_mode;
+ d_pending_sum_q <= sum;
+ d_pending_mxr_q <= mxr;
+ d_pending_asid_q <= req_asid;
+ d_pending_mode_q <= req_mode;
+ d_pending_is_store_q <= d_req_is_store;
+ end
+
+ if (ptw_req_valid && ptw_req_ready) begin
+ ptw_busy_q <= 1'b1;
+ ptw_owner_q <= ptw_owner_next;
+ end
+
+ if (ptw_resp_valid) begin
+ ptw_busy_q <= 1'b0;
+ if (ptw_owner_q == PTW_SRC_I) begin
+ i_pending_q <= 1'b0;
+ end else begin
+ d_pending_q <= 1'b0;
+ end
+ end
+ end
+ end
+
+ tlb #(
+ .ENTRIES(ITLB_ENTRIES),
+ .ASID_WIDTH(ASID_WIDTH)
+ ) u_itlb (
+ .clk (clk),
+ .rst (rst),
+ .req_valid (i_accept),
+ .req_vaddr (i_req_vaddr),
+ .req_asid (req_asid),
+ .req_mode (req_mode),
+ .req_priv (priv_mode),
+ .req_is_fetch (1'b1),
+ .req_is_store (1'b0),
+ .req_sum (sum),
+ .req_mxr (mxr),
+ .resp_valid (i_tlb_resp_valid),
+ .resp_hit (i_tlb_resp_hit),
+ .resp_paddr (i_tlb_resp_paddr),
+ .resp_page_fault (i_tlb_resp_page_fault),
+ .resp_need_ptw (i_tlb_resp_need_ptw),
+ .refill_valid (i_refill_valid),
+ .refill_vpn (ptw_refill_vpn),
+ .refill_ppn (ptw_refill_ppn),
+ .refill_level (ptw_refill_level),
+ .refill_mode (ptw_refill_mode),
+ .refill_asid (ptw_refill_asid),
+ .refill_g (ptw_refill_g),
+ .refill_u (ptw_refill_u),
+ .refill_r (ptw_refill_r),
+ .refill_w (ptw_refill_w),
+ .refill_x (ptw_refill_x),
+ .refill_a (ptw_refill_a),
+ .refill_d (ptw_refill_d),
+ .flush_all (flush_all),
+ .flush_asid_valid (flush_asid_valid),
+ .flush_asid (flush_asid),
+ .flush_vpn_valid (flush_vpn_valid),
+ .flush_vpn (flush_vpn),
+ .flush_mode (req_mode)
+ );
+
+ tlb #(
+ .ENTRIES(DTLB_ENTRIES),
+ .ASID_WIDTH(ASID_WIDTH)
+ ) u_dtlb (
+ .clk (clk),
+ .rst (rst),
+ .req_valid (d_accept),
+ .req_vaddr (d_req_vaddr),
+ .req_asid (req_asid),
+ .req_mode (req_mode),
+ .req_priv (priv_mode),
+ .req_is_fetch (1'b0),
+ .req_is_store (d_req_is_store),
+ .req_sum (sum),
+ .req_mxr (mxr),
+ .resp_valid (d_tlb_resp_valid),
+ .resp_hit (d_tlb_resp_hit),
+ .resp_paddr (d_tlb_resp_paddr),
+ .resp_page_fault (d_tlb_resp_page_fault),
+ .resp_need_ptw (d_tlb_resp_need_ptw),
+ .refill_valid (d_refill_valid),
+ .refill_vpn (ptw_refill_vpn),
+ .refill_ppn (ptw_refill_ppn),
+ .refill_level (ptw_refill_level),
+ .refill_mode (ptw_refill_mode),
+ .refill_asid (ptw_refill_asid),
+ .refill_g (ptw_refill_g),
+ .refill_u (ptw_refill_u),
+ .refill_r (ptw_refill_r),
+ .refill_w (ptw_refill_w),
+ .refill_x (ptw_refill_x),
+ .refill_a (ptw_refill_a),
+ .refill_d (ptw_refill_d),
+ .flush_all (flush_all),
+ .flush_asid_valid (flush_asid_valid),
+ .flush_asid (flush_asid),
+ .flush_vpn_valid (flush_vpn_valid),
+ .flush_vpn (flush_vpn),
+ .flush_mode (req_mode)
+ );
+
+ ptw #(
+ .ASID_WIDTH(ASID_WIDTH)
+ ) u_ptw (
+ .clk (clk),
+ .rst (rst),
+ .req_valid (ptw_req_valid),
+ .req_ready (ptw_req_ready),
+ .req_vaddr ((ptw_owner_next == PTW_SRC_D) ? d_pending_vaddr_q : i_pending_vaddr_q),
+ .req_asid ((ptw_owner_next == PTW_SRC_D) ? d_pending_asid_q : i_pending_asid_q),
+ .req_mode ((ptw_owner_next == PTW_SRC_D) ? d_pending_mode_q : i_pending_mode_q),
+ .req_priv ((ptw_owner_next == PTW_SRC_D) ? d_pending_priv_q : i_pending_priv_q),
+ .req_is_fetch (ptw_owner_next == PTW_SRC_I),
+ .req_is_store ((ptw_owner_next == PTW_SRC_D) ? d_pending_is_store_q : 1'b0),
+ .req_sum ((ptw_owner_next == PTW_SRC_D) ? d_pending_sum_q : i_pending_sum_q),
+ .req_mxr ((ptw_owner_next == PTW_SRC_D) ? d_pending_mxr_q : i_pending_mxr_q),
+ .satp_value (satp_value),
+ .mem_req_valid (ptw_mem_req_valid),
+ .mem_req_ready (ptw_mem_req_ready),
+ .mem_req_addr (ptw_mem_req_addr),
+ .mem_resp_valid(ptw_mem_resp_valid),
+ .mem_resp_data (ptw_mem_resp_data),
+ .resp_valid (ptw_resp_valid),
+ .resp_fault (ptw_resp_fault),
+ .resp_paddr (ptw_resp_paddr),
+ .refill_valid (ptw_refill_valid),
+ .refill_vpn (ptw_refill_vpn),
+ .refill_ppn (ptw_refill_ppn),
+ .refill_level (ptw_refill_level),
+ .refill_mode (ptw_refill_mode),
+ .refill_asid (ptw_refill_asid),
+ .refill_g (ptw_refill_g),
+ .refill_u (ptw_refill_u),
+ .refill_r (ptw_refill_r),
+ .refill_w (ptw_refill_w),
+ .refill_x (ptw_refill_x),
+ .refill_a (ptw_refill_a),
+ .refill_d (ptw_refill_d)
+ );
+
+endmodule
diff --git a/rv64cpu/rtl/core/mmu/ptw.sv b/rv64cpu/rtl/core/mmu/ptw.sv
new file mode 100644
index 0000000..6447b5a
--- /dev/null
+++ b/rv64cpu/rtl/core/mmu/ptw.sv
@@ -0,0 +1,416 @@
+// 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/ptw.sv
+// Description: Page-table walker for Sv39. Sv48/Sv57/Sv64 are explicit stubs.
+
+module ptw
+ import rv64_pkg::*;
+#(
+ parameter int ASID_WIDTH = 16
+)(
+ input logic clk,
+ input logic rst,
+
+ input logic req_valid,
+ output logic req_ready,
+ input logic [XLEN-1:0] req_vaddr,
+ input logic [ASID_WIDTH-1:0] req_asid,
+ input logic [3:0] req_mode,
+ input logic [1:0] req_priv,
+ input logic req_is_fetch,
+ input logic req_is_store,
+ input logic req_sum,
+ input logic req_mxr,
+ input logic [XLEN-1:0] satp_value,
+
+ output logic mem_req_valid,
+ input logic mem_req_ready,
+ output logic [XLEN-1:0] mem_req_addr,
+ input logic mem_resp_valid,
+ input logic [XLEN-1:0] mem_resp_data,
+
+ output logic resp_valid,
+ output logic resp_fault,
+ output logic [XLEN-1:0] resp_paddr,
+
+ output logic refill_valid,
+ output logic [44:0] refill_vpn,
+ output logic [43:0] refill_ppn,
+ output logic [2:0] refill_level,
+ output logic [3:0] refill_mode,
+ output logic [ASID_WIDTH-1:0] refill_asid,
+ output logic refill_g,
+ output logic refill_u,
+ output logic refill_r,
+ output logic refill_w,
+ output logic refill_x,
+ output logic refill_a,
+ output logic refill_d
+);
+
+ localparam logic [3:0] SATP_MODE_BARE = 4'd0;
+ localparam logic [3:0] SATP_MODE_SV39 = 4'd8;
+ localparam logic [3:0] SATP_MODE_SV48 = 4'd9;
+ localparam logic [3:0] SATP_MODE_SV57 = 4'd10;
+ localparam logic [3:0] SATP_MODE_SV64 = 4'd11; // Reserved/future stub.
+
+ typedef enum logic [2:0] {
+ PTW_IDLE,
+ PTW_ISSUE,
+ PTW_WAIT,
+ PTW_DONE
+ } ptw_state_t;
+
+ ptw_state_t state_q;
+
+ logic [XLEN-1:0] req_vaddr_q;
+ logic [ASID_WIDTH-1:0] req_asid_q;
+ logic [3:0] req_mode_q;
+ logic [1:0] req_priv_q;
+ logic req_is_fetch_q;
+ logic req_is_store_q;
+ logic req_sum_q;
+ logic req_mxr_q;
+
+ logic [43:0] table_ppn_q;
+ logic [2:0] level_q;
+
+ logic pte_v;
+ logic pte_r;
+ logic pte_w;
+ logic pte_x;
+ logic pte_u;
+ logic pte_g;
+ logic pte_a;
+ logic pte_d;
+ logic [43:0] pte_ppn;
+
+ logic leaf_pte;
+ logic bad_pte;
+ logic superpage_misaligned;
+
+ logic [44:0] req_vpn_q;
+ logic [XLEN-1:0] next_paddr;
+
+ logic resp_fault_q;
+ logic [XLEN-1:0] resp_paddr_q;
+ logic refill_valid_q;
+
+ logic [44:0] refill_vpn_q;
+ logic [43:0] refill_ppn_q;
+ logic [2:0] refill_level_q;
+ logic [3:0] refill_mode_q;
+ logic [ASID_WIDTH-1:0] refill_asid_q;
+ logic refill_g_q;
+ logic refill_u_q;
+ logic refill_r_q;
+ logic refill_w_q;
+ logic refill_x_q;
+ logic refill_a_q;
+ logic refill_d_q;
+
+ function automatic logic is_supported_mode(input logic [3:0] mode);
+ return (mode == SATP_MODE_SV39);
+ endfunction
+
+ function automatic int vpn_stride(input logic [3:0] mode);
+ case (mode)
+ SATP_MODE_SV39: vpn_stride = 9;
+ default: vpn_stride = 0;
+ endcase
+ 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_SV39: vpn[26:0] = vaddr[38:12];
+ default: vpn = '0;
+ endcase
+ return vpn;
+ end
+ endfunction
+
+ function automatic logic [9:0] vpn_index(
+ input logic [XLEN-1:0] vaddr,
+ input logic [3:0] mode,
+ input logic [2:0] level
+ );
+ logic [9:0] idx;
+ begin
+ idx = '0;
+ case (mode)
+ SATP_MODE_SV39: idx = {1'b0, vaddr[12 + level * 9 +: 9]};
+ default: idx = '0;
+ endcase
+ return idx;
+ end
+ endfunction
+
+ function automatic logic vaddr_is_canonical(
+ input logic [XLEN-1:0] vaddr,
+ input logic [3:0] mode
+ );
+ case (mode)
+ SATP_MODE_SV39: vaddr_is_canonical = (vaddr[63:39] == {25{vaddr[38]}});
+ default: vaddr_is_canonical = 1'b1;
+ endcase
+ 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);
+ return 6'(12 + level * stride);
+ end
+ endfunction
+
+ function automatic logic allow_access(
+ input logic is_fetch,
+ input logic is_store,
+ input logic [1:0] priv,
+ 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_u;
+ logic priv_ok;
+ begin
+ is_load = ~is_fetch && ~is_store;
+ readable = r || (mxr && x);
+ priv_u = (priv == 2'b00);
+
+ 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
+
+ return a && priv_ok && (
+ (is_fetch && x) ||
+ (is_store && w && d) ||
+ (is_load && readable)
+ );
+ end
+ endfunction
+
+ assign pte_v = mem_resp_data[0];
+ assign pte_r = mem_resp_data[1];
+ assign pte_w = mem_resp_data[2];
+ assign pte_x = mem_resp_data[3];
+ assign pte_u = mem_resp_data[4];
+ assign pte_g = mem_resp_data[5];
+ assign pte_a = mem_resp_data[6];
+ assign pte_d = mem_resp_data[7];
+ assign pte_ppn = mem_resp_data[53:10];
+
+ assign leaf_pte = pte_r || pte_x;
+ assign bad_pte = (!pte_v) || (pte_w && !pte_r);
+
+ always_comb begin
+ int stride;
+ int lower_bits;
+ logic [63:0] ppn_mask;
+ stride = vpn_stride(req_mode_q);
+ lower_bits = level_q * stride;
+ ppn_mask = '0;
+ superpage_misaligned = 1'b0;
+ if (leaf_pte && (lower_bits > 0)) begin
+ if (lower_bits >= 44) begin
+ ppn_mask = 64'h0000_0FFF_FFFF_FFFF;
+ end else begin
+ ppn_mask = (64'd1 << lower_bits) - 1;
+ end
+ superpage_misaligned = |({20'b0, pte_ppn} & ppn_mask);
+ end
+ end
+
+ always_comb begin
+ logic [5:0] shift;
+ logic [XLEN-1:0] low_mask;
+ int shift_index;
+ shift = page_shift(req_mode_q, level_q);
+ shift_index = int'(shift);
+ if (shift_index >= XLEN) begin
+ low_mask = {XLEN{1'b1}};
+ end else begin
+ low_mask = ({{(XLEN-1){1'b0}}, 1'b1} << shift_index) - 1;
+ end
+ next_paddr = (({{(XLEN-56){1'b0}}, pte_ppn, 12'b0}) & ~low_mask) | (req_vaddr_q & low_mask);
+ end
+
+ assign req_ready = (state_q == PTW_IDLE);
+ assign mem_req_valid = (state_q == PTW_ISSUE);
+ assign mem_req_addr = {{(XLEN-56){1'b0}}, table_ppn_q, 12'b0} +
+ ({{(XLEN-10){1'b0}}, vpn_index(req_vaddr_q, req_mode_q, level_q)} << 3);
+
+ assign resp_valid = (state_q == PTW_DONE);
+ assign resp_fault = resp_fault_q;
+ assign resp_paddr = resp_paddr_q;
+
+ assign refill_valid = refill_valid_q && (state_q == PTW_DONE);
+ assign refill_vpn = refill_vpn_q;
+ assign refill_ppn = refill_ppn_q;
+ assign refill_level = refill_level_q;
+ assign refill_mode = refill_mode_q;
+ assign refill_asid = refill_asid_q;
+ assign refill_g = refill_g_q;
+ assign refill_u = refill_u_q;
+ assign refill_r = refill_r_q;
+ assign refill_w = refill_w_q;
+ assign refill_x = refill_x_q;
+ assign refill_a = refill_a_q;
+ assign refill_d = refill_d_q;
+
+ always_ff @(posedge clk) begin
+ if (rst) begin
+ state_q <= PTW_IDLE;
+ req_vaddr_q <= '0;
+ req_asid_q <= '0;
+ req_mode_q <= SATP_MODE_BARE;
+ req_priv_q <= '0;
+ req_is_fetch_q <= 1'b0;
+ req_is_store_q <= 1'b0;
+ req_sum_q <= 1'b0;
+ req_mxr_q <= 1'b0;
+ table_ppn_q <= '0;
+ level_q <= '0;
+ req_vpn_q <= '0;
+ resp_fault_q <= 1'b0;
+ resp_paddr_q <= '0;
+ refill_valid_q <= 1'b0;
+ refill_vpn_q <= '0;
+ refill_ppn_q <= '0;
+ refill_level_q <= '0;
+ refill_mode_q <= SATP_MODE_BARE;
+ refill_asid_q <= '0;
+ refill_g_q <= 1'b0;
+ refill_u_q <= 1'b0;
+ refill_r_q <= 1'b0;
+ refill_w_q <= 1'b0;
+ refill_x_q <= 1'b0;
+ refill_a_q <= 1'b0;
+ refill_d_q <= 1'b0;
+ end else begin
+ case (state_q)
+ PTW_IDLE: begin
+ refill_valid_q <= 1'b0;
+ if (req_valid) begin
+ req_vaddr_q <= req_vaddr;
+ req_asid_q <= req_asid;
+ req_mode_q <= req_mode;
+ req_priv_q <= req_priv;
+ req_is_fetch_q <= req_is_fetch;
+ req_is_store_q <= req_is_store;
+ req_sum_q <= req_sum;
+ req_mxr_q <= req_mxr;
+ req_vpn_q <= vaddr_to_vpn(req_vaddr, req_mode);
+
+ if (!is_supported_mode(req_mode) || !vaddr_is_canonical(req_vaddr, req_mode)) begin
+ resp_fault_q <= 1'b1;
+ resp_paddr_q <= '0;
+ state_q <= PTW_DONE;
+ end else begin
+ table_ppn_q <= satp_value[43:0];
+ case (req_mode)
+ SATP_MODE_SV39: level_q <= 3'd2;
+ default: level_q <= '0;
+ endcase
+ state_q <= PTW_ISSUE;
+ end
+ end
+ end
+
+ PTW_ISSUE: begin
+ if (mem_req_ready) begin
+ state_q <= PTW_WAIT;
+ end
+ end
+
+ PTW_WAIT: begin
+ if (mem_resp_valid) begin
+ if (bad_pte) begin
+ resp_fault_q <= 1'b1;
+ resp_paddr_q <= '0;
+ refill_valid_q <= 1'b0;
+ state_q <= PTW_DONE;
+ end else if (leaf_pte) begin
+ if (superpage_misaligned ||
+ !allow_access(req_is_fetch_q, req_is_store_q, req_priv_q, req_sum_q, req_mxr_q,
+ pte_u, pte_r, pte_w, pte_x, pte_a, pte_d)) begin
+ resp_fault_q <= 1'b1;
+ resp_paddr_q <= '0;
+ refill_valid_q <= 1'b0;
+ end else begin
+ resp_fault_q <= 1'b0;
+ resp_paddr_q <= next_paddr;
+ refill_valid_q <= 1'b1;
+ refill_vpn_q <= req_vpn_q;
+ refill_ppn_q <= pte_ppn;
+ refill_level_q <= level_q;
+ refill_mode_q <= req_mode_q;
+ refill_asid_q <= req_asid_q;
+ refill_g_q <= pte_g;
+ refill_u_q <= pte_u;
+ refill_r_q <= pte_r;
+ refill_w_q <= pte_w;
+ refill_x_q <= pte_x;
+ refill_a_q <= pte_a;
+ refill_d_q <= pte_d;
+ end
+ state_q <= PTW_DONE;
+ end else if (level_q == 0) begin
+ resp_fault_q <= 1'b1;
+ resp_paddr_q <= '0;
+ refill_valid_q <= 1'b0;
+ state_q <= PTW_DONE;
+ end else begin
+ table_ppn_q <= pte_ppn;
+ level_q <= level_q - 1'b1;
+ state_q <= PTW_ISSUE;
+ end
+ end
+ end
+
+ PTW_DONE: begin
+ state_q <= PTW_IDLE;
+ end
+
+ default: state_q <= PTW_IDLE;
+ endcase
+ end
+ end
+
+endmodule
diff --git a/rv64cpu/rtl/core/mmu/tlb.sv b/rv64cpu/rtl/core/mmu/tlb.sv
new file mode 100644
index 0000000..06eb947
--- /dev/null
+++ b/rv64cpu/rtl/core/mmu/tlb.sv
@@ -0,0 +1,382 @@
+// 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 (Bare + Sv39, higher modes stubbed)
+
+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_SV39 = 4'd8;
+ localparam logic [3:0] SATP_MODE_SV48 = 4'd9;
+ localparam logic [3:0] SATP_MODE_SV57 = 4'd10;
+ localparam logic [3:0] SATP_MODE_SV64 = 4'd11; // Reserved/future stub.
+
+ 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_index;
+
+ function automatic logic is_supported_mode(input logic [3:0] mode);
+ return (mode == SATP_MODE_BARE) || (mode == SATP_MODE_SV39);
+ endfunction
+
+ function automatic int vpn_width(input logic [3:0] mode);
+ case (mode)
+ SATP_MODE_SV39: vpn_width = 27;
+ default: vpn_width = 0;
+ endcase
+ endfunction
+
+ function automatic int vpn_stride(input logic [3:0] mode);
+ case (mode)
+ SATP_MODE_SV39: 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_SV39: vpn[26:0] = vaddr[38:12];
+ default: vpn = '0;
+ endcase
+ vaddr_to_vpn = vpn;
+ end
+ endfunction
+
+ function automatic logic vaddr_is_canonical(
+ input logic [XLEN-1:0] vaddr,
+ input logic [3:0] mode
+ );
+ case (mode)
+ SATP_MODE_SV39: vaddr_is_canonical = (vaddr[63:39] == {25{vaddr[38]}});
+ default: vaddr_is_canonical = 1'b1;
+ endcase
+ 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_index = 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) || !vaddr_is_canonical(req_vaddr, 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_index = int'(shift);
+ if (shift_index >= XLEN) begin
+ low_mask = {XLEN{1'b1}};
+ end else begin
+ low_mask = ({{(XLEN-1){1'b0}}, 1'b1} << shift_index) - 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