diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-11-07 04:31:31 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-11-07 04:31:31 +0800 |
| commit | a7d5b2468553c6d3570ca2aa45c27cb93e26cb88 (patch) | |
| tree | 7031b1fac52e4f858c4e92041485135242768f7b /rv64cpu/rtl/core/mmu | |
| parent | 4ca723bc9dc1b9db1bd03e3b3db0c58079c3f3c4 (diff) | |
| parent | 3b0b67af63af86c5ec9b2a63defa06b70923b2e3 (diff) | |
| download | rcc-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.sv | 359 | ||||
| -rw-r--r-- | rv64cpu/rtl/core/mmu/ptw.sv | 416 | ||||
| -rw-r--r-- | rv64cpu/rtl/core/mmu/tlb.sv | 382 |
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 |
