mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-17 19:04:48 -04:00
integrate unified mmu with H extension (#1958)
This commit is contained in:
parent
821e2ebc3f
commit
9142fdd03a
36 changed files with 2344 additions and 4655 deletions
20
Bender.yml
20
Bender.yml
|
@ -65,23 +65,11 @@ sources:
|
|||
- core/cva6_accel_first_pass_decoder_stub.sv
|
||||
|
||||
# MMU
|
||||
- target: any(cv64a6_imafdcv_sv39, cv64a6_imafdc_sv39, cv64a6_imafdc_sv39_wb)
|
||||
- target: any(cv64a6_imafdcv_sv39, cv64a6_imafdc_sv39, cv64a6_imafdc_sv39_wb, cv64a6_imafdch_sv39, cv64a6_imafdch_sv39_wb, cv32a6_imac_sv0, cv32a6_imac_sv32, cv32a6_imafc_sv32)
|
||||
files:
|
||||
- core/mmu_sv39/tlb.sv
|
||||
- core/mmu_sv39/mmu.sv
|
||||
- core/mmu_sv39/ptw.sv
|
||||
|
||||
- target: any(cv64a6_imafdch_sv39, cv64a6_imafdch_sv39_wb)
|
||||
files:
|
||||
- core/mmu_sv39x4/cva6_tlb_sv39x4.sv
|
||||
- core/mmu_sv39x4/cva6_mmu_sv39x4.sv
|
||||
- core/mmu_sv39x4/cva6_ptw_sv39x4.sv
|
||||
|
||||
- target: any(cv32a6_imac_sv0, cv32a6_imac_sv32, cv32a6_imafc_sv32)
|
||||
files:
|
||||
- core/mmu_sv32/cva6_tlb_sv32.sv
|
||||
- core/mmu_sv32/cva6_mmu_sv32.sv
|
||||
- core/mmu_sv32/cva6_ptw_sv32.sv
|
||||
- core/cva6_mmu/cva6_tlb.sv
|
||||
- core/cva6_mmu/cva6_mmu.sv
|
||||
- core/cva6_mmu/cva6_ptw.sv
|
||||
|
||||
# Packages
|
||||
- core/include/wt_cache_pkg.sv
|
||||
|
|
|
@ -33,20 +33,17 @@ filesets:
|
|||
- src/lsu_arbiter.sv
|
||||
- src/lsu.sv
|
||||
- src/miss_handler.sv
|
||||
- src/mmu_sv39/mmu.sv
|
||||
- src/mmu_sv32/cva6_mmu_sv32.sv
|
||||
- src/cva6_mmu/cva6_mmu.sv
|
||||
- src/mult.sv
|
||||
- src/nbdcache.sv
|
||||
- src/pcgen_stage.sv
|
||||
- src/perf_counters.sv
|
||||
- src/mmu_sv39/ptw.sv
|
||||
- src/mmu_sv32/cva6_ptw_sv32.sv
|
||||
- src/cva6_mmu/cva6_ptw.sv
|
||||
- src/regfile_ff.sv
|
||||
- src/scoreboard.sv
|
||||
- src/store_buffer.sv
|
||||
- src/store_unit.sv
|
||||
- src/mmu_sv39/tlb.sv
|
||||
- src/mmu_sv32/cva6_tlb_sv32.sv
|
||||
- src/cva6_mmu/cva6_tlb.sv
|
||||
file_type : systemVerilogSource
|
||||
depend :
|
||||
- pulp-platform.org::axi_mem_if
|
||||
|
|
|
@ -183,20 +183,10 @@ ${CVA6_REPO_DIR}/common/local/util/tc_sram_wrapper.sv
|
|||
${CVA6_REPO_DIR}/vendor/pulp-platform/tech_cells_generic/src/rtl/tc_sram.sv
|
||||
${CVA6_REPO_DIR}/common/local/util/sram.sv
|
||||
|
||||
// MMU Sv39
|
||||
${CVA6_REPO_DIR}/core/mmu_sv39/mmu.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv39/ptw.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv39/tlb.sv
|
||||
|
||||
// MMU Sv39x4
|
||||
${CVA6_REPO_DIR}/core/mmu_sv39x4/cva6_mmu_sv39x4.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv39x4/cva6_ptw_sv39x4.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv39x4/cva6_tlb_sv39x4.sv
|
||||
|
||||
// MMU Sv32
|
||||
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_mmu_sv32.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_ptw_sv32.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_tlb_sv32.sv
|
||||
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_shared_tlb_sv32.sv
|
||||
// MMU
|
||||
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_mmu.sv
|
||||
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_ptw.sv
|
||||
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_tlb.sv
|
||||
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_shared_tlb.sv
|
||||
|
||||
// end of manifest
|
||||
|
|
860
core/cva6_mmu/cva6_mmu.sv
Normal file
860
core/cva6_mmu/cva6_mmu.sv
Normal file
|
@ -0,0 +1,860 @@
|
|||
// Copyright (c) 2018 ETH Zurich and University of Bologna.
|
||||
// Copyright (c) 2021 Thales.
|
||||
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
|
||||
// Copyright (c) 2024 PlanV Technology
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Angela Gonzalez, PlanV Technology
|
||||
// Date: 26/02/2024
|
||||
//
|
||||
// Description: Memory Management Unit for CVA6, contains TLB and
|
||||
// address translation unit. SV32 SV39 and SV39x4 as defined in RISC-V
|
||||
// privilege specification 1.11-WIP.
|
||||
// This module is an merge of the MMU Sv39 developed
|
||||
// by Florian Zaruba, the MMU Sv32 developed by Sebastien Jacq and the MMU Sv39x4 developed by Bruno Sá.
|
||||
|
||||
|
||||
module cva6_mmu
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type icache_areq_t = logic,
|
||||
parameter type icache_arsp_t = logic,
|
||||
parameter type icache_dreq_t = logic,
|
||||
parameter type icache_drsp_t = logic,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter type exception_t = logic,
|
||||
parameter int unsigned HYP_EXT = 0
|
||||
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_ni,
|
||||
input logic flush_i,
|
||||
input logic enable_translation_i,
|
||||
input logic enable_g_translation_i,
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
input logic en_ld_st_g_translation_i, // enable G-Stage translation for load/stores
|
||||
// IF interface
|
||||
input icache_arsp_t icache_areq_i,
|
||||
output icache_areq_t icache_areq_o,
|
||||
// LSU interface
|
||||
// this is a more minimalistic interface because the actual addressing logic is handled
|
||||
// in the LSU as we distinguish load and stores, what we do here is simple address translation
|
||||
input exception_t misaligned_ex_i,
|
||||
input logic lsu_req_i, // request address translation
|
||||
input logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_i, // virtual address in
|
||||
input logic [31:0] lsu_tinst_i, // transformed instruction in
|
||||
input logic lsu_is_store_i, // the translation is requested by a store
|
||||
output logic csr_hs_ld_st_inst_o, // hyp load store instruction
|
||||
// if we need to walk the page table we can't grant in the same cycle
|
||||
// Cycle 0
|
||||
output logic lsu_dtlb_hit_o, // sent in same cycle as the request if translation hits in DTLB
|
||||
output logic [CVA6Cfg.PPNW-1:0] lsu_dtlb_ppn_o, // ppn (send same cycle as hit)
|
||||
// Cycle 1
|
||||
output logic lsu_valid_o, // translation is valid
|
||||
output logic [CVA6Cfg.PLEN-1:0] lsu_paddr_o, // translated address
|
||||
output exception_t lsu_exception_o, // address translation threw an exception
|
||||
// General control signals
|
||||
input riscv::priv_lvl_t priv_lvl_i,
|
||||
input logic v_i,
|
||||
input riscv::priv_lvl_t ld_st_priv_lvl_i,
|
||||
input logic ld_st_v_i,
|
||||
input logic sum_i,
|
||||
input logic vs_sum_i,
|
||||
input logic mxr_i,
|
||||
input logic vmxr_i,
|
||||
input logic hlvx_inst_i,
|
||||
input logic hs_ld_st_inst_i,
|
||||
// input logic flag_mprv_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] vsatp_ppn_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] hgatp_ppn_i,
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] vs_asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.GPLEN-1:0] gpaddr_to_be_flushed_i,
|
||||
|
||||
input logic flush_tlb_i,
|
||||
input logic flush_tlb_vvma_i,
|
||||
input logic flush_tlb_gvma_i,
|
||||
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
// PMP
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i
|
||||
);
|
||||
|
||||
// memory management, pte for cva6
|
||||
localparam type pte_cva6_t = struct packed {
|
||||
logic [CVA6Cfg.PPNW-1:0] ppn; // PPN length for
|
||||
logic [1:0] rsw;
|
||||
logic d;
|
||||
logic a;
|
||||
logic g;
|
||||
logic u;
|
||||
logic x;
|
||||
logic w;
|
||||
logic r;
|
||||
logic v;
|
||||
};
|
||||
|
||||
localparam type tlb_update_cva6_t = struct packed {
|
||||
logic valid;
|
||||
logic [CVA6Cfg.PtLevels-2:0][HYP_EXT:0] is_page;
|
||||
logic [CVA6Cfg.VpnLen-1:0] vpn;
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
|
||||
logic [HYP_EXT*2:0] v_st_enbl; // v_i,g-stage enabled, s-stage enabled
|
||||
pte_cva6_t content;
|
||||
pte_cva6_t g_content;
|
||||
};
|
||||
|
||||
logic iaccess_err; // insufficient privilege to access this instruction page
|
||||
logic i_g_st_access_err; // insufficient privilege at g stage to access this instruction page
|
||||
logic daccess_err; // insufficient privilege to access this data page
|
||||
logic d_g_st_access_err; // insufficient privilege to access this data page
|
||||
logic ptw_active; // PTW is currently walking a page table
|
||||
logic walking_instr; // PTW is walking because of an ITLB miss
|
||||
logic ptw_error; // PTW threw an exception
|
||||
logic ptw_error_at_g_st; // PTW threw an exception at the G-Stage
|
||||
logic ptw_err_at_g_int_st; // PTW threw an exception at the G-Stage during S-Stage translation
|
||||
logic ptw_access_exception; // PTW threw an access exception (PMPs)
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_bad_paddr; // PTW page fault bad physical addr
|
||||
logic [CVA6Cfg.GPLEN-1:0] ptw_bad_gpaddr; // PTW guest page fault bad guest physical addr
|
||||
|
||||
logic [CVA6Cfg.VLEN-1:0] update_vaddr, shared_tlb_vaddr;
|
||||
|
||||
tlb_update_cva6_t update_itlb, update_dtlb, update_shared_tlb;
|
||||
|
||||
logic itlb_lu_access;
|
||||
pte_cva6_t itlb_content;
|
||||
pte_cva6_t itlb_g_content;
|
||||
logic [ CVA6Cfg.PtLevels-2:0] itlb_is_page;
|
||||
logic itlb_lu_hit;
|
||||
logic [ CVA6Cfg.GPLEN-1:0] itlb_gpaddr;
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] itlb_lu_asid;
|
||||
|
||||
logic dtlb_lu_access;
|
||||
pte_cva6_t dtlb_content;
|
||||
pte_cva6_t dtlb_g_content;
|
||||
logic [ CVA6Cfg.PtLevels-2:0] dtlb_is_page;
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] dtlb_lu_asid;
|
||||
logic dtlb_lu_hit;
|
||||
logic [ CVA6Cfg.GPLEN-1:0] dtlb_gpaddr;
|
||||
|
||||
logic shared_tlb_access, shared_tlb_miss;
|
||||
logic shared_tlb_hit, itlb_req;
|
||||
|
||||
// Assignments
|
||||
|
||||
assign itlb_lu_access = icache_areq_i.fetch_req;
|
||||
assign dtlb_lu_access = lsu_req_i;
|
||||
assign itlb_lu_asid = v_i ? vs_asid_i : asid_i;
|
||||
assign dtlb_lu_asid = (ld_st_v_i || flush_tlb_vvma_i) ? vs_asid_i : asid_i;
|
||||
|
||||
|
||||
cva6_tlb #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.pte_cva6_t (pte_cva6_t),
|
||||
.tlb_update_cva6_t(tlb_update_cva6_t),
|
||||
.TLB_ENTRIES (CVA6Cfg.InstrTlbEntries),
|
||||
.HYP_EXT (HYP_EXT)
|
||||
) i_itlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i (flush_tlb_i),
|
||||
.flush_vvma_i (flush_tlb_vvma_i),
|
||||
.flush_gvma_i (flush_tlb_gvma_i),
|
||||
.s_st_enbl_i (enable_translation_i),
|
||||
.g_st_enbl_i (enable_g_translation_i),
|
||||
.v_i (v_i),
|
||||
.update_i (update_itlb),
|
||||
.lu_access_i (itlb_lu_access),
|
||||
.lu_asid_i (itlb_lu_asid),
|
||||
.lu_vmid_i (vmid_i),
|
||||
.lu_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
.lu_content_o (itlb_content),
|
||||
.lu_g_content_o(itlb_g_content),
|
||||
.lu_gpaddr_o (itlb_gpaddr),
|
||||
.lu_is_page_o (itlb_is_page),
|
||||
.lu_hit_o (itlb_lu_hit),
|
||||
.*
|
||||
);
|
||||
|
||||
cva6_tlb #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.pte_cva6_t (pte_cva6_t),
|
||||
.tlb_update_cva6_t(tlb_update_cva6_t),
|
||||
.TLB_ENTRIES (CVA6Cfg.DataTlbEntries),
|
||||
.HYP_EXT (HYP_EXT)
|
||||
) i_dtlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i (flush_tlb_i),
|
||||
.flush_vvma_i (flush_tlb_vvma_i),
|
||||
.flush_gvma_i (flush_tlb_gvma_i),
|
||||
.s_st_enbl_i (en_ld_st_translation_i),
|
||||
.g_st_enbl_i (en_ld_st_g_translation_i),
|
||||
.v_i (ld_st_v_i),
|
||||
.update_i (update_dtlb),
|
||||
.lu_access_i (dtlb_lu_access),
|
||||
.lu_asid_i (itlb_lu_asid),
|
||||
.lu_vmid_i (vmid_i),
|
||||
.lu_vaddr_i (lsu_vaddr_i),
|
||||
.lu_content_o (dtlb_content),
|
||||
.lu_g_content_o(dtlb_g_content),
|
||||
.lu_gpaddr_o (dtlb_gpaddr),
|
||||
.lu_is_page_o (dtlb_is_page),
|
||||
.lu_hit_o (dtlb_lu_hit),
|
||||
.*
|
||||
);
|
||||
|
||||
|
||||
cva6_shared_tlb #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.SHARED_TLB_WAYS (2),
|
||||
.HYP_EXT (HYP_EXT),
|
||||
.pte_cva6_t (pte_cva6_t),
|
||||
.tlb_update_cva6_t(tlb_update_cva6_t)
|
||||
) i_shared_tlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i (flush_tlb_i),
|
||||
.flush_vvma_i (flush_tlb_vvma_i),
|
||||
.flush_gvma_i (flush_tlb_gvma_i),
|
||||
.s_st_enbl_i (enable_translation_i),
|
||||
.g_st_enbl_i (enable_g_translation_i),
|
||||
.v_i (v_i),
|
||||
.s_ld_st_enbl_i(en_ld_st_translation_i),
|
||||
.g_ld_st_enbl_i(en_ld_st_g_translation_i),
|
||||
.ld_st_v_i (ld_st_v_i),
|
||||
|
||||
.dtlb_asid_i (dtlb_lu_asid),
|
||||
.itlb_asid_i (itlb_lu_asid),
|
||||
.lu_vmid_i (vmid_i),
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
.itlb_access_i(itlb_lu_access),
|
||||
.itlb_hit_i (itlb_lu_hit),
|
||||
.itlb_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
|
||||
.dtlb_access_i(dtlb_lu_access),
|
||||
.dtlb_hit_i (dtlb_lu_hit),
|
||||
.dtlb_vaddr_i (lsu_vaddr_i),
|
||||
|
||||
// to TLBs, update logic
|
||||
.itlb_update_o(update_itlb),
|
||||
.dtlb_update_o(update_dtlb),
|
||||
|
||||
// Performance counters
|
||||
.itlb_miss_o(itlb_miss_o),
|
||||
.dtlb_miss_o(dtlb_miss_o),
|
||||
.shared_tlb_miss_i(shared_tlb_miss),
|
||||
|
||||
.shared_tlb_access_o(shared_tlb_access),
|
||||
.shared_tlb_hit_o (shared_tlb_hit),
|
||||
.shared_tlb_vaddr_o (shared_tlb_vaddr),
|
||||
|
||||
.itlb_req_o (itlb_req),
|
||||
// to update shared tlb
|
||||
.shared_tlb_update_i(update_shared_tlb)
|
||||
);
|
||||
|
||||
cva6_ptw #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.pte_cva6_t (pte_cva6_t),
|
||||
.tlb_update_cva6_t(tlb_update_cva6_t),
|
||||
.dcache_req_i_t (dcache_req_i_t),
|
||||
.dcache_req_o_t (dcache_req_o_t),
|
||||
.HYP_EXT (HYP_EXT)
|
||||
) i_ptw (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni(rst_ni),
|
||||
|
||||
.ptw_active_o (ptw_active),
|
||||
.walking_instr_o (walking_instr),
|
||||
.ptw_error_o (ptw_error),
|
||||
.ptw_error_at_g_st_o (ptw_error_at_g_st),
|
||||
.ptw_err_at_g_int_st_o (ptw_err_at_g_int_st),
|
||||
.ptw_access_exception_o(ptw_access_exception),
|
||||
|
||||
.lsu_is_store_i(lsu_is_store_i),
|
||||
// PTW memory interface
|
||||
.req_port_i (req_port_i),
|
||||
.req_port_o (req_port_o),
|
||||
|
||||
.update_vaddr_o(update_vaddr),
|
||||
|
||||
// to Shared TLB, update logic
|
||||
.shared_tlb_update_o(update_shared_tlb),
|
||||
|
||||
|
||||
// from shared TLB
|
||||
// did we miss?
|
||||
.shared_tlb_access_i(shared_tlb_access),
|
||||
.shared_tlb_hit_i (shared_tlb_hit),
|
||||
.shared_tlb_vaddr_i (shared_tlb_vaddr),
|
||||
|
||||
.itlb_req_i(itlb_req),
|
||||
|
||||
.hlvx_inst_i(hlvx_inst_i),
|
||||
|
||||
// Performance counters
|
||||
.shared_tlb_miss_o(shared_tlb_miss), //open for now
|
||||
|
||||
// PMP
|
||||
.pmpcfg_i (pmpcfg_i),
|
||||
.pmpaddr_i (pmpaddr_i),
|
||||
.bad_paddr_o(ptw_bad_paddr),
|
||||
.bad_gpaddr_o(ptw_bad_gpaddr),
|
||||
.*
|
||||
|
||||
);
|
||||
|
||||
//-----------------------
|
||||
// Instruction Interface
|
||||
//-----------------------
|
||||
logic match_any_execute_region;
|
||||
logic pmp_instr_allow;
|
||||
localparam int PPNWMin = (CVA6Cfg.PPNW - 1 > 29) ? 29 : CVA6Cfg.PPNW - 1;
|
||||
|
||||
// The instruction interface is a simple request response interface
|
||||
always_comb begin : instr_interface
|
||||
// MMU disabled: just pass through
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
icache_areq_o.fetch_paddr = CVA6Cfg.PLEN'(icache_areq_i.fetch_vaddr[((CVA6Cfg.PLEN > CVA6Cfg.VLEN) ? CVA6Cfg.VLEN -1: CVA6Cfg.PLEN -1 ):0]);
|
||||
// two potential exception sources:
|
||||
// 1. HPTW threw an exception -> signal with a page fault exception
|
||||
// 2. We got an access error because of insufficient permissions -> throw an access exception
|
||||
icache_areq_o.fetch_exception = '0;
|
||||
// Check whether we are allowed to access this memory region from a fetch perspective
|
||||
iaccess_err = icache_areq_i.fetch_req && enable_translation_i && //
|
||||
(((priv_lvl_i == riscv::PRIV_LVL_U) && ~itlb_content.u) //
|
||||
|| ((priv_lvl_i == riscv::PRIV_LVL_S) && itlb_content.u));
|
||||
|
||||
if (CVA6Cfg.RVH)
|
||||
i_g_st_access_err = icache_areq_i.fetch_req && enable_g_translation_i && !itlb_g_content.u;
|
||||
// MMU enabled: address from TLB, request delayed until hit. Error when TLB
|
||||
// hit and no access right or TLB hit and translated address not valid (e.g.
|
||||
// AXI decode error), or when PTW performs walk due to ITLB miss and raises
|
||||
// an error.
|
||||
if ((enable_translation_i || enable_g_translation_i)) begin
|
||||
// we work with SV39 or SV32, so if VM is enabled, check that all bits [CVA6Cfg.VLEN-1:CVA6Cfg.SV-1] are equal
|
||||
if (icache_areq_i.fetch_req && !((&icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b1 || (|icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b0)) begin
|
||||
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(icache_areq_i.fetch_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = '0;
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end
|
||||
|
||||
icache_areq_o.fetch_valid = 1'b0;
|
||||
|
||||
icache_areq_o.fetch_paddr = {
|
||||
(enable_g_translation_i && CVA6Cfg.RVH) ? itlb_g_content.ppn : itlb_content.ppn,
|
||||
icache_areq_i.fetch_vaddr[11:0]
|
||||
};
|
||||
|
||||
if (CVA6Cfg.PtLevels == 3 && itlb_is_page[CVA6Cfg.PtLevels-2]) begin
|
||||
|
||||
icache_areq_o.fetch_paddr[PPNWMin-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):9+CVA6Cfg.PtLevels] = icache_areq_i.fetch_vaddr[PPNWMin-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):9+CVA6Cfg.PtLevels];
|
||||
|
||||
end
|
||||
|
||||
if (itlb_is_page[0]) begin
|
||||
|
||||
icache_areq_o.fetch_paddr[PPNWMin:12] = icache_areq_i.fetch_vaddr[PPNWMin:12];
|
||||
|
||||
end
|
||||
// ---------//
|
||||
// ITLB Hit
|
||||
// --------//
|
||||
// if we hit the ITLB output the request signal immediately
|
||||
if (itlb_lu_hit) begin
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
if (CVA6Cfg.RVH && i_g_st_access_err) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_GUEST_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(icache_areq_i.fetch_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = itlb_gpaddr[CVA6Cfg.GPLEN-1:0];
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
|
||||
// we got an access error
|
||||
end else if (iaccess_err) begin
|
||||
// throw a page fault
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(icache_areq_i.fetch_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = '0;
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end else if (!pmp_instr_allow) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(icache_areq_i.fetch_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = '0;
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end
|
||||
end else if (ptw_active && walking_instr) begin
|
||||
// ---------//
|
||||
// ITLB Miss
|
||||
// ---------//
|
||||
// watch out for exceptions happening during walking the page table
|
||||
icache_areq_o.fetch_valid = ptw_error | ptw_access_exception;
|
||||
if (ptw_error) begin
|
||||
if (CVA6Cfg.RVH && ptw_error_at_g_st) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_GUEST_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(update_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = ptw_bad_gpaddr[CVA6Cfg.GPLEN-1:0];
|
||||
icache_areq_o.fetch_exception.tinst=(ptw_err_at_g_int_st ? (CVA6Cfg.IS_XLEN64 ? riscv::READ_64_PSEUDOINSTRUCTION : riscv::READ_32_PSEUDOINSTRUCTION) : '0);
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end else begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(update_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = '0;
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) //To confirm this is the right TVAL
|
||||
icache_areq_o.fetch_exception.tval = CVA6Cfg.XLEN'(update_vaddr);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = '0;
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// if it didn't match any execute region throw an `Instruction Access Fault`
|
||||
// or: if we are not translating, check PMPs immediately on the paddr
|
||||
if ((!match_any_execute_region && !ptw_error) || (!(enable_translation_i || enable_g_translation_i) && !pmp_instr_allow)) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) //To confirm this is the right TVAL
|
||||
icache_areq_o.fetch_exception.tval=CVA6Cfg.XLEN'(icache_areq_o.fetch_paddr[CVA6Cfg.PLEN-1:(CVA6Cfg.PLEN > CVA6Cfg.VLEN) ? (CVA6Cfg.PLEN - CVA6Cfg.VLEN) : 0]);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
icache_areq_o.fetch_exception.tval2 = '0;
|
||||
icache_areq_o.fetch_exception.tinst = '0;
|
||||
icache_areq_o.fetch_exception.gva = v_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// check for execute flag on memory
|
||||
assign match_any_execute_region = config_pkg::is_inside_execute_regions(
|
||||
CVA6Cfg, {{64 - CVA6Cfg.PLEN{1'b0}}, icache_areq_o.fetch_paddr}
|
||||
);
|
||||
|
||||
// Instruction fetch
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg), //comment for hypervisor extension
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
// .NR_ENTRIES ( ArianeCfg.NrPMPEntries ) // configuration used in hypervisor extension
|
||||
) i_pmp_if (
|
||||
.addr_i (icache_areq_o.fetch_paddr),
|
||||
.priv_lvl_i,
|
||||
// we will always execute on the instruction fetch port
|
||||
.access_type_i(riscv::ACCESS_EXEC),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_instr_allow)
|
||||
);
|
||||
|
||||
//-----------------------
|
||||
// Data Interface
|
||||
//-----------------------
|
||||
logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_n, lsu_vaddr_q;
|
||||
logic [CVA6Cfg.GPLEN-1:0] lsu_gpaddr_n, lsu_gpaddr_q;
|
||||
logic [31:0] lsu_tinst_n, lsu_tinst_q;
|
||||
logic hs_ld_st_inst_n, hs_ld_st_inst_q;
|
||||
pte_cva6_t dtlb_pte_n, dtlb_pte_q;
|
||||
pte_cva6_t dtlb_gpte_n, dtlb_gpte_q;
|
||||
exception_t misaligned_ex_n, misaligned_ex_q;
|
||||
logic lsu_req_n, lsu_req_q;
|
||||
logic lsu_is_store_n, lsu_is_store_q;
|
||||
logic dtlb_hit_n, dtlb_hit_q;
|
||||
logic [CVA6Cfg.PtLevels-2:0] dtlb_is_page_n, dtlb_is_page_q;
|
||||
|
||||
// check if we need to do translation or if we are always ready (e.g.: we are not translating anything)
|
||||
assign lsu_dtlb_hit_o = (en_ld_st_translation_i || en_ld_st_g_translation_i) ? dtlb_lu_hit : 1'b1;
|
||||
|
||||
// Wires to PMP checks
|
||||
riscv::pmp_access_t pmp_access_type;
|
||||
logic pmp_data_allow;
|
||||
|
||||
|
||||
// The data interface is simpler and only consists of a request/response interface
|
||||
always_comb begin : data_interface
|
||||
// save request and DTLB response
|
||||
lsu_vaddr_n = lsu_vaddr_i;
|
||||
lsu_req_n = lsu_req_i;
|
||||
misaligned_ex_n = misaligned_ex_i;
|
||||
dtlb_pte_n = dtlb_content;
|
||||
dtlb_hit_n = dtlb_lu_hit;
|
||||
lsu_is_store_n = lsu_is_store_i;
|
||||
dtlb_is_page_n = dtlb_is_page;
|
||||
|
||||
lsu_valid_o = lsu_req_q;
|
||||
lsu_exception_o = misaligned_ex_q;
|
||||
pmp_access_type = lsu_is_store_q ? riscv::ACCESS_WRITE : riscv::ACCESS_READ;
|
||||
|
||||
// mute misaligned exceptions if there is no request otherwise they will throw accidental exceptions
|
||||
misaligned_ex_n.valid = misaligned_ex_i.valid & lsu_req_i;
|
||||
|
||||
// Check if the User flag is set, then we may only access it in supervisor mode
|
||||
// if SUM is enabled
|
||||
daccess_err = en_ld_st_translation_i &&
|
||||
((ld_st_priv_lvl_i == riscv::PRIV_LVL_S && (ld_st_v_i ? !vs_sum_i : !sum_i ) && dtlb_pte_q.u) || // SUM is not set and we are trying to access a user page in supervisor mode
|
||||
(ld_st_priv_lvl_i == riscv::PRIV_LVL_U && !dtlb_pte_q.u));
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_tinst_n = lsu_tinst_i;
|
||||
hs_ld_st_inst_n = hs_ld_st_inst_i;
|
||||
lsu_gpaddr_n[(CVA6Cfg.XLEN == 32 ? CVA6Cfg.VLEN: CVA6Cfg.GPLEN)-1:0] = dtlb_gpaddr[(CVA6Cfg.XLEN == 32 ? CVA6Cfg.VLEN: CVA6Cfg.GPLEN)-1:0];
|
||||
csr_hs_ld_st_inst_o = hs_ld_st_inst_i || hs_ld_st_inst_q;
|
||||
d_g_st_access_err = en_ld_st_g_translation_i && !dtlb_gpte_q.u;
|
||||
dtlb_gpte_n = dtlb_g_content;
|
||||
end
|
||||
|
||||
lsu_paddr_o = (CVA6Cfg.PLEN)'(lsu_vaddr_q[((CVA6Cfg.PLEN > CVA6Cfg.VLEN) ? CVA6Cfg.VLEN -1: CVA6Cfg.PLEN -1 ):0]);
|
||||
lsu_dtlb_ppn_o = (CVA6Cfg.PPNW)'(lsu_vaddr_n[((CVA6Cfg.PLEN > CVA6Cfg.VLEN) ? CVA6Cfg.VLEN -1: CVA6Cfg.PLEN -1 ):12]);
|
||||
|
||||
// translation is enabled and no misaligned exception occurred
|
||||
if ((en_ld_st_translation_i || en_ld_st_g_translation_i) && !misaligned_ex_q.valid) begin
|
||||
lsu_valid_o = 1'b0;
|
||||
|
||||
lsu_dtlb_ppn_o = (en_ld_st_g_translation_i && CVA6Cfg.RVH)? dtlb_g_content.ppn :dtlb_content.ppn;
|
||||
lsu_paddr_o = {
|
||||
(en_ld_st_g_translation_i && CVA6Cfg.RVH) ? dtlb_gpte_q.ppn : dtlb_pte_q.ppn,
|
||||
lsu_vaddr_q[11:0]
|
||||
};
|
||||
|
||||
if (CVA6Cfg.PtLevels == 3 && dtlb_is_page_q[CVA6Cfg.PtLevels-2]) begin
|
||||
lsu_paddr_o[PPNWMin-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):9+CVA6Cfg.PtLevels] = lsu_vaddr_q[PPNWMin-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):9+CVA6Cfg.PtLevels];
|
||||
lsu_dtlb_ppn_o[PPNWMin-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):9+CVA6Cfg.PtLevels] = lsu_vaddr_n[PPNWMin-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):9+CVA6Cfg.PtLevels];
|
||||
end
|
||||
|
||||
if (dtlb_is_page_q[0]) begin
|
||||
lsu_dtlb_ppn_o[PPNWMin:12] = lsu_vaddr_n[PPNWMin:12];
|
||||
lsu_paddr_o[PPNWMin:12] = lsu_vaddr_q[PPNWMin:12];
|
||||
end
|
||||
|
||||
|
||||
|
||||
// ---------
|
||||
// DTLB Hit
|
||||
// --------
|
||||
if (dtlb_hit_q && lsu_req_q) begin
|
||||
lsu_valid_o = 1'b1;
|
||||
// exception priority:
|
||||
// PAGE_FAULTS have higher priority than ACCESS_FAULTS
|
||||
// virtual memory based exceptions are PAGE_FAULTS
|
||||
// physical memory based exceptions are ACCESS_FAULTS (PMA/PMP)
|
||||
|
||||
// this is a store
|
||||
if (lsu_is_store_q) begin
|
||||
// check if the page is write-able and we are not violating privileges
|
||||
// also check if the dirty flag is set
|
||||
if(CVA6Cfg.RVH && en_ld_st_g_translation_i && (!dtlb_gpte_q.w || d_g_st_access_err || !dtlb_gpte_q.d)) begin
|
||||
lsu_exception_o.cause = riscv::STORE_GUEST_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2= CVA6Cfg.GPLEN'(lsu_gpaddr_q[(CVA6Cfg.XLEN==32?CVA6Cfg.VLEN : CVA6Cfg.GPLEN)-1:0]);
|
||||
lsu_exception_o.tinst = '0;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end else if ((en_ld_st_translation_i || !CVA6Cfg.RVH) && (!dtlb_pte_q.w || daccess_err || !dtlb_pte_q.d)) begin
|
||||
lsu_exception_o.cause = riscv::STORE_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval=CVA6Cfg.XLEN'(lsu_paddr_o[CVA6Cfg.PLEN-1:(CVA6Cfg.PLEN > CVA6Cfg.VLEN) ? (CVA6Cfg.PLEN - CVA6Cfg.VLEN) : 0]);
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end
|
||||
// this is a load
|
||||
end else begin
|
||||
if (CVA6Cfg.RVH && d_g_st_access_err) begin
|
||||
lsu_exception_o.cause = riscv::LOAD_GUEST_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2= CVA6Cfg.GPLEN'(lsu_gpaddr_q[(CVA6Cfg.XLEN==32?CVA6Cfg.VLEN : CVA6Cfg.GPLEN)-1:0]);
|
||||
lsu_exception_o.tinst = '0;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
// check for sufficient access privileges - throw a page fault if necessary
|
||||
end else if (daccess_err) begin
|
||||
lsu_exception_o.cause = riscv::LOAD_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) //to confirm that this is the right TVAL
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval= CVA6Cfg.XLEN'(lsu_paddr_o[CVA6Cfg.PLEN-1:(CVA6Cfg.PLEN>CVA6Cfg.VLEN)?(CVA6Cfg.PLEN-CVA6Cfg.VLEN) : 0]);
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
end else
|
||||
|
||||
// ---------
|
||||
// DTLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions
|
||||
if (ptw_active && !walking_instr) begin
|
||||
// page table walker threw an exception
|
||||
if (ptw_error) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// the page table walker can only throw page faults
|
||||
if (lsu_is_store_q) begin
|
||||
if (CVA6Cfg.RVH && ptw_error_at_g_st) begin
|
||||
lsu_exception_o.cause = riscv::STORE_GUEST_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = ptw_bad_gpaddr[CVA6Cfg.GPLEN-1:0];
|
||||
lsu_exception_o.tinst= (ptw_err_at_g_int_st ? (CVA6Cfg.IS_XLEN64 ? riscv::READ_64_PSEUDOINSTRUCTION : riscv::READ_32_PSEUDOINSTRUCTION) : '0);
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::STORE_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
if (CVA6Cfg.RVH && ptw_error_at_g_st) begin
|
||||
lsu_exception_o.cause = riscv::LOAD_GUEST_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = ptw_bad_gpaddr[CVA6Cfg.GPLEN-1:0];
|
||||
lsu_exception_o.tinst= (ptw_err_at_g_int_st ? (CVA6Cfg.IS_XLEN64 ? riscv::READ_64_PSEUDOINSTRUCTION : riscv::READ_32_PSEUDOINSTRUCTION) : '0);
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LOAD_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if (ptw_access_exception) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// Any fault of the page table walk should be based of the original access type
|
||||
if (lsu_is_store_q && !CVA6Cfg.RVH && CVA6Cfg.PtLevels == 3) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, lsu_vaddr_n};
|
||||
end else begin
|
||||
// the page table walker can only throw page faults
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) //to confirm that this is the right TVAL
|
||||
lsu_exception_o.tval = {{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, lsu_vaddr_n};
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
// If translation is not enabled, check the paddr immediately against PMPs
|
||||
end else if (lsu_req_q && !misaligned_ex_q.valid && !pmp_data_allow) begin
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) //to confirm that this is the right TVAL
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) //to confirm that this is the right TVAL
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_exception_o.tval2 = '0;
|
||||
lsu_exception_o.tinst = lsu_tinst_q;
|
||||
lsu_exception_o.gva = ld_st_v_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Load/store PMP check
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_data (
|
||||
.addr_i (lsu_paddr_o),
|
||||
.priv_lvl_i (ld_st_priv_lvl_i),
|
||||
.access_type_i(pmp_access_type),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_data_allow)
|
||||
);
|
||||
|
||||
// ----------
|
||||
// Registers
|
||||
// ----------
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
lsu_vaddr_q <= '0;
|
||||
lsu_gpaddr_q <= '0;
|
||||
lsu_req_q <= '0;
|
||||
misaligned_ex_q <= '0;
|
||||
dtlb_pte_q <= '0;
|
||||
dtlb_gpte_q <= '0;
|
||||
dtlb_hit_q <= '0;
|
||||
lsu_is_store_q <= '0;
|
||||
dtlb_is_page_q <= '0;
|
||||
lsu_tinst_q <= '0;
|
||||
hs_ld_st_inst_q <= '0;
|
||||
end else begin
|
||||
lsu_vaddr_q <= lsu_vaddr_n;
|
||||
lsu_req_q <= lsu_req_n;
|
||||
misaligned_ex_q <= misaligned_ex_n;
|
||||
dtlb_pte_q <= dtlb_pte_n;
|
||||
dtlb_hit_q <= dtlb_hit_n;
|
||||
lsu_is_store_q <= lsu_is_store_n;
|
||||
dtlb_is_page_q <= dtlb_is_page_n;
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
lsu_tinst_q <= lsu_tinst_n;
|
||||
hs_ld_st_inst_q <= hs_ld_st_inst_n;
|
||||
dtlb_gpte_q <= dtlb_gpte_n;
|
||||
lsu_gpaddr_q <= lsu_gpaddr_n;
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
659
core/cva6_mmu/cva6_ptw.sv
Normal file
659
core/cva6_mmu/cva6_ptw.sv
Normal file
|
@ -0,0 +1,659 @@
|
|||
// Copyright (c) 2018 ETH Zurich and University of Bologna.
|
||||
// Copyright (c) 2021 Thales.
|
||||
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
|
||||
// Copyright (c) 2024 PlanV Technology
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Angela Gonzalez, PlanV Technology
|
||||
// Date: 26/02/2024
|
||||
// Description: Hardware-PTW (Page-Table-Walker) for CVA6 supporting sv32, sv39 and sv39x4.
|
||||
// This module is an merge of the PTW Sv39 developed by Florian Zaruba,
|
||||
// the PTW Sv32 developed by Sebastien Jacq and the PTW Sv39x4 by Bruno Sá.
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
|
||||
module cva6_ptw
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type pte_cva6_t = logic,
|
||||
parameter type tlb_update_cva6_t = logic,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter int unsigned HYP_EXT = 0
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // flush everything, we need to do this because
|
||||
// actually everything we do is speculative at this stage
|
||||
// e.g.: there could be a CSR instruction that changes everything
|
||||
output logic ptw_active_o,
|
||||
output logic walking_instr_o, // set when walking for TLB
|
||||
output logic ptw_error_o, // set when an error occurred
|
||||
output logic ptw_error_at_g_st_o, // set when an error occurred at the G-Stage
|
||||
output logic ptw_err_at_g_int_st_o, // set when an error occurred at the G-Stage during S-Stage translation
|
||||
output logic ptw_access_exception_o, // set when an PMP access exception occured
|
||||
input logic enable_translation_i, // CSRs indicate to enable SV39 VS-Stage translation
|
||||
input logic enable_g_translation_i, // CSRs indicate to enable SV39 G-Stage translation
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
input logic en_ld_st_g_translation_i, // enable G-Stage translation for load/stores
|
||||
input logic v_i, // current virtualization mode bit
|
||||
input logic ld_st_v_i, // load/store virtualization mode bit
|
||||
input logic hlvx_inst_i, // is a HLVX load/store instruction
|
||||
|
||||
input logic lsu_is_store_i, // this translation was triggered by a store
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
|
||||
|
||||
// to TLBs, update logic
|
||||
output tlb_update_cva6_t shared_tlb_update_o,
|
||||
|
||||
output logic [CVA6Cfg.VLEN-1:0] update_vaddr_o,
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] vs_asid_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_i,
|
||||
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
input logic shared_tlb_access_i,
|
||||
input logic shared_tlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr_i,
|
||||
|
||||
input logic itlb_req_i,
|
||||
|
||||
// from CSR file
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i, // ppn from satp
|
||||
input logic [CVA6Cfg.PPNW-1:0] vsatp_ppn_i, // ppn from satp
|
||||
input logic [CVA6Cfg.PPNW-1:0] hgatp_ppn_i, // ppn from hgatp
|
||||
input logic mxr_i,
|
||||
input logic vmxr_i,
|
||||
|
||||
// Performance counters
|
||||
output logic shared_tlb_miss_o,
|
||||
|
||||
// PMP
|
||||
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i,
|
||||
output logic [CVA6Cfg.PLEN-1:0] bad_paddr_o,
|
||||
output logic [CVA6Cfg.GPLEN-1:0] bad_gpaddr_o
|
||||
|
||||
);
|
||||
|
||||
// input registers
|
||||
logic data_rvalid_q;
|
||||
logic [CVA6Cfg.XLEN-1:0] data_rdata_q;
|
||||
|
||||
pte_cva6_t pte;
|
||||
// register to perform context switch between stages
|
||||
pte_cva6_t gpte_q, gpte_d;
|
||||
assign pte = pte_cva6_t'(data_rdata_q[CVA6Cfg.PPNW+9:0]);
|
||||
|
||||
enum logic [2:0] {
|
||||
IDLE,
|
||||
WAIT_GRANT,
|
||||
PTE_LOOKUP,
|
||||
WAIT_RVALID,
|
||||
PROPAGATE_ERROR,
|
||||
PROPAGATE_ACCESS_ERROR,
|
||||
LATENCY
|
||||
}
|
||||
state_q, state_d;
|
||||
|
||||
logic [CVA6Cfg.PtLevels-2:0] misaligned_page;
|
||||
logic [HYP_EXT:0][CVA6Cfg.PtLevels-2:0] ptw_lvl_n, ptw_lvl_q;
|
||||
|
||||
// define 3 PTW stages to be used in sv39x4. sv32 and sv39 are always in S_STAGE
|
||||
// S_STAGE -> S/VS-stage normal translation controlled by the satp/vsatp CSRs
|
||||
// G_INTERMED_STAGE -> Converts the S/VS-stage non-leaf GPA pointers to HPA (controlled by hgatp)
|
||||
// G_FINAL_STAGE -> Converts the S/VS-stage final GPA to HPA (controlled by hgatp)
|
||||
enum logic [1:0] {
|
||||
S_STAGE,
|
||||
G_INTERMED_STAGE,
|
||||
G_FINAL_STAGE
|
||||
}
|
||||
ptw_stage_q, ptw_stage_d;
|
||||
|
||||
// is this an instruction page table walk?
|
||||
logic is_instr_ptw_q, is_instr_ptw_n;
|
||||
logic global_mapping_q, global_mapping_n;
|
||||
// latched tag signal
|
||||
logic tag_valid_n, tag_valid_q;
|
||||
// register the ASID
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] tlb_update_asid_q, tlb_update_asid_n;
|
||||
// register the VMID
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] tlb_update_vmid_q, tlb_update_vmid_n;
|
||||
// register the VPN we need to walk, SV39 defines a 39 bit virtual address
|
||||
logic [CVA6Cfg.VLEN-1:0] vaddr_q, vaddr_n;
|
||||
logic [HYP_EXT*2:0][CVA6Cfg.PtLevels-2:0][(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] vaddr_lvl;
|
||||
// register the VPN we need to walk, SV39x4 defines a 41 bit virtual address for the G-Stage
|
||||
logic [CVA6Cfg.GPLEN-1:0] gpaddr_q, gpaddr_n, gpaddr_base;
|
||||
logic [CVA6Cfg.PtLevels-1:0][CVA6Cfg.GPLEN-1:0] gpaddr;
|
||||
// 4 byte aligned physical pointer
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_pptr_q, ptw_pptr_n;
|
||||
logic [CVA6Cfg.PLEN-1:0] gptw_pptr_q, gptw_pptr_n;
|
||||
|
||||
// Assignments
|
||||
assign update_vaddr_o = vaddr_q;
|
||||
|
||||
assign ptw_active_o = (state_q != IDLE);
|
||||
assign walking_instr_o = is_instr_ptw_q;
|
||||
// directly output the correct physical address
|
||||
assign req_port_o.address_index = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH-1:0];
|
||||
assign req_port_o.address_tag = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH+CVA6Cfg.DCACHE_TAG_WIDTH-1:CVA6Cfg.DCACHE_INDEX_WIDTH];
|
||||
// we are never going to kill this request
|
||||
assign req_port_o.kill_req = '0;
|
||||
// we are never going to write with the HPTW
|
||||
assign req_port_o.data_wdata = '0;
|
||||
// we only issue one single request at a time
|
||||
assign req_port_o.data_id = '0;
|
||||
|
||||
// -----------
|
||||
// TLB Update
|
||||
// -----------
|
||||
|
||||
assign gpaddr_base = {pte.ppn[CVA6Cfg.GPPNW-1:0], vaddr_q[11:0]};
|
||||
assign gpaddr[CVA6Cfg.PtLevels-1] = gpaddr_base;
|
||||
assign shared_tlb_update_o.vpn = CVA6Cfg.VpnLen'(vaddr_q[CVA6Cfg.SV+HYP_EXT*2-1:12]);
|
||||
|
||||
genvar z, w;
|
||||
generate
|
||||
for (z = 0; z < CVA6Cfg.PtLevels - 1; z++) begin
|
||||
|
||||
// check if the ppn is correctly aligned:
|
||||
// 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned superpage; stop and raise a page-fault
|
||||
// exception.
|
||||
assign misaligned_page[z] = (ptw_lvl_q[0] == (z)) && (pte.ppn[(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-1-z)-1:0] != '0);
|
||||
|
||||
//record the vaddr corresponding to each level
|
||||
for (w = 0; w < HYP_EXT * 2 + 1; w++) begin
|
||||
assign vaddr_lvl[w][z] = w==0 ? vaddr_q[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-z-1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-z-2))] :
|
||||
w==1 ? gptw_pptr_q[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-z-1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-z-2))]:
|
||||
gpaddr_q[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-z-1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(CVA6Cfg.PtLevels-z-2))];
|
||||
end
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
assign gpaddr[z][CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):0]= (ptw_lvl_q[0] == z) ? vaddr_q[CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):0] : gpaddr_base[CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels):0];
|
||||
assign gpaddr[z][CVA6Cfg.VpnLen:CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)+1]= (ptw_lvl_q[0] == 0) ? vaddr_q[CVA6Cfg.VpnLen:CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)+1] : gpaddr_base[CVA6Cfg.VpnLen:CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)+1];
|
||||
assign gpaddr[z][CVA6Cfg.GPLEN-1:CVA6Cfg.VpnLen+1] = gpaddr_base[CVA6Cfg.GPLEN-1:CVA6Cfg.VpnLen+1];
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
always_comb begin : tlb_update
|
||||
// update the correct page table level
|
||||
for (int unsigned y = 0; y < HYP_EXT + 1; y++) begin
|
||||
for (int unsigned x = 0; x < CVA6Cfg.PtLevels - 1; x++) begin
|
||||
if((enable_g_translation_i && enable_translation_i) || (en_ld_st_g_translation_i && en_ld_st_translation_i) && CVA6Cfg.RVH) begin
|
||||
shared_tlb_update_o.is_page[x][y] = (ptw_lvl_q[y==HYP_EXT?0 : 1] == x);
|
||||
end else if (enable_translation_i || en_ld_st_translation_i || !CVA6Cfg.RVH) begin
|
||||
shared_tlb_update_o.is_page[x][y] = y == 0 ? (ptw_lvl_q[0] == x) : 1'b0;
|
||||
end else begin
|
||||
shared_tlb_update_o.is_page[x][y] = y != 0 ? (ptw_lvl_q[0] == x) : 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// set the global mapping bit
|
||||
if ((enable_g_translation_i || en_ld_st_g_translation_i) && CVA6Cfg.RVH) begin
|
||||
shared_tlb_update_o.content = gpte_q | (global_mapping_q << 5);
|
||||
shared_tlb_update_o.g_content = pte;
|
||||
end else begin
|
||||
shared_tlb_update_o.content = (pte | (global_mapping_q << 5));
|
||||
shared_tlb_update_o.g_content = '0;
|
||||
end
|
||||
|
||||
// output the correct ASIDs
|
||||
shared_tlb_update_o.asid = tlb_update_asid_q;
|
||||
shared_tlb_update_o.vmid = tlb_update_vmid_q;
|
||||
|
||||
bad_paddr_o = ptw_access_exception_o ? ptw_pptr_q : 'b0;
|
||||
if (CVA6Cfg.RVH)
|
||||
bad_gpaddr_o[CVA6Cfg.GPLEN-1:0] = ptw_error_at_g_st_o ? ((ptw_stage_q == G_INTERMED_STAGE) ? gptw_pptr_q[CVA6Cfg.GPLEN-1:0] : gpaddr_q) : 'b0;
|
||||
end
|
||||
|
||||
assign req_port_o.tag_valid = tag_valid_q;
|
||||
|
||||
logic allow_access;
|
||||
|
||||
|
||||
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_ptw (
|
||||
.addr_i (ptw_pptr_q),
|
||||
// PTW access are always checked as if in S-Mode...
|
||||
.priv_lvl_i (riscv::PRIV_LVL_S),
|
||||
// ...and they are always loads
|
||||
.access_type_i(riscv::ACCESS_READ),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (allow_access)
|
||||
);
|
||||
|
||||
|
||||
assign req_port_o.data_be = CVA6Cfg.XLEN == 32 ? be_gen_32(
|
||||
req_port_o.address_index[1:0], req_port_o.data_size
|
||||
) : '1;
|
||||
|
||||
|
||||
|
||||
//-------------------
|
||||
// Page table walker
|
||||
//-------------------
|
||||
// A virtual address va is translated into a physical address pa as follows:
|
||||
// 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39,
|
||||
// PAGESIZE=2^12 and LEVELS=3.)
|
||||
// 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. (For
|
||||
// Sv32, PTESIZE=4.)
|
||||
// 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, or if any bits or encodings
|
||||
// that are reserved for future standard use are set within pte, stop and raise
|
||||
// a page-fault exception corresponding to the original access type.
|
||||
// 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to step 5.
|
||||
// Otherwise, this PTE is a pointer to the next level of the page table.
|
||||
// Let i=i-1. If i < 0, stop and raise an access exception. Otherwise, let
|
||||
// a = pte.ppn × PAGESIZE and go to step 2.
|
||||
// 5. A leaf PTE has been found. Determine if the requested memory access
|
||||
// is allowed by the pte.r, pte.w, and pte.x bits. If not, stop and
|
||||
// raise an access exception. Otherwise, the translation is successful.
|
||||
// Set pte.a to 1, and, if the memory access is a store, set pte.d to 1.
|
||||
// The translated physical address is given as follows:
|
||||
// - pa.pgoff = va.pgoff.
|
||||
// - If i > 0, then this is a superpage translation and
|
||||
// pa.ppn[i-1:0] = va.vpn[i-1:0].
|
||||
// - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i].
|
||||
always_comb begin : ptw
|
||||
automatic logic [CVA6Cfg.PLEN-1:0] pptr;
|
||||
// automatic logic [CVA6Cfg.GPLEN-1:0] gpaddr;
|
||||
// default assignments
|
||||
// PTW memory interface
|
||||
tag_valid_n = 1'b0;
|
||||
req_port_o.data_req = 1'b0;
|
||||
req_port_o.data_size = 2'(CVA6Cfg.PtLevels);
|
||||
req_port_o.data_we = 1'b0;
|
||||
ptw_error_o = 1'b0;
|
||||
ptw_error_at_g_st_o = 1'b0;
|
||||
ptw_err_at_g_int_st_o = 1'b0;
|
||||
ptw_access_exception_o = 1'b0;
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
is_instr_ptw_n = is_instr_ptw_q;
|
||||
ptw_lvl_n = ptw_lvl_q;
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
state_d = state_q;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
global_mapping_n = global_mapping_q;
|
||||
// input registers
|
||||
tlb_update_asid_n = tlb_update_asid_q;
|
||||
tlb_update_vmid_n = tlb_update_vmid_q;
|
||||
vaddr_n = vaddr_q;
|
||||
pptr = ptw_pptr_q;
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
gpaddr_n = gpaddr_q;
|
||||
gptw_pptr_n = gptw_pptr_q;
|
||||
gpte_d = gpte_q;
|
||||
end
|
||||
|
||||
shared_tlb_miss_o = 1'b0;
|
||||
|
||||
|
||||
case (state_q)
|
||||
|
||||
IDLE: begin
|
||||
// by default we start with the top-most page table
|
||||
ptw_lvl_n = '0;
|
||||
global_mapping_n = 1'b0;
|
||||
is_instr_ptw_n = 1'b0;
|
||||
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
gpte_d = '0;
|
||||
gpaddr_n = '0;
|
||||
end
|
||||
|
||||
|
||||
// if we got an ITLB miss
|
||||
if (((enable_translation_i | enable_g_translation_i) || (en_ld_st_translation_i || en_ld_st_g_translation_i) || !CVA6Cfg.RVH) && shared_tlb_access_i && ~shared_tlb_hit_i) begin
|
||||
if (((enable_translation_i && enable_g_translation_i) || (en_ld_st_translation_i && en_ld_st_g_translation_i)) && CVA6Cfg.RVH) begin
|
||||
ptw_stage_d = G_INTERMED_STAGE;
|
||||
pptr = {
|
||||
vsatp_ppn_i,
|
||||
shared_tlb_vaddr_i[CVA6Cfg.SV-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
gptw_pptr_n = pptr;
|
||||
ptw_pptr_n = {
|
||||
hgatp_ppn_i[CVA6Cfg.PPNW-1:2],
|
||||
pptr[CVA6Cfg.SV+HYP_EXT*2-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
end else if ((((enable_translation_i | enable_g_translation_i) && !enable_translation_i) || ((en_ld_st_translation_i || en_ld_st_g_translation_i) && !en_ld_st_translation_i)) && CVA6Cfg.RVH) begin
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
gpaddr_n = shared_tlb_vaddr_i[CVA6Cfg.SV+HYP_EXT*2-1:0];
|
||||
ptw_pptr_n = {
|
||||
hgatp_ppn_i[CVA6Cfg.PPNW-1:2],
|
||||
shared_tlb_vaddr_i[CVA6Cfg.SV+HYP_EXT*2-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
end else begin
|
||||
ptw_stage_d = S_STAGE;
|
||||
if ((v_i || ld_st_v_i) && CVA6Cfg.RVH)
|
||||
ptw_pptr_n = {
|
||||
vsatp_ppn_i,
|
||||
shared_tlb_vaddr_i[CVA6Cfg.SV-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
else
|
||||
ptw_pptr_n = {
|
||||
satp_ppn_i,
|
||||
shared_tlb_vaddr_i[CVA6Cfg.SV-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
end
|
||||
|
||||
is_instr_ptw_n = itlb_req_i;
|
||||
vaddr_n = shared_tlb_vaddr_i;
|
||||
state_d = WAIT_GRANT;
|
||||
shared_tlb_miss_o = 1'b1;
|
||||
|
||||
if (itlb_req_i) begin
|
||||
tlb_update_asid_n = v_i ? vs_asid_i : asid_i;
|
||||
if (CVA6Cfg.RVH) tlb_update_vmid_n = vmid_i;
|
||||
end else begin
|
||||
tlb_update_asid_n = ld_st_v_i ? vs_asid_i : asid_i;
|
||||
if (CVA6Cfg.RVH) tlb_update_vmid_n = vmid_i;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WAIT_GRANT: begin
|
||||
// send a request out
|
||||
req_port_o.data_req = 1'b1;
|
||||
// wait for the WAIT_GRANT
|
||||
if (req_port_i.data_gnt) begin
|
||||
// send the tag valid signal one cycle later
|
||||
tag_valid_n = 1'b1;
|
||||
state_d = PTE_LOOKUP;
|
||||
end
|
||||
end
|
||||
|
||||
PTE_LOOKUP: begin
|
||||
// we wait for the valid signal
|
||||
if (data_rvalid_q) begin
|
||||
|
||||
// check if the global mapping bit is set
|
||||
if (pte.g && (ptw_stage_q == S_STAGE || !CVA6Cfg.RVH)) global_mapping_n = 1'b1;
|
||||
|
||||
// -------------
|
||||
// Invalid PTE
|
||||
// -------------
|
||||
// If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise a page-fault exception.
|
||||
if (!pte.v || (!pte.r && pte.w)) // || (|pte.reserved))
|
||||
state_d = PROPAGATE_ERROR;
|
||||
// -----------
|
||||
// Valid PTE
|
||||
// -----------
|
||||
else begin
|
||||
state_d = LATENCY;
|
||||
// it is a valid PTE
|
||||
// if pte.r = 1 or pte.x = 1 it is a valid PTE
|
||||
if (pte.r || pte.x) begin
|
||||
if (CVA6Cfg.RVH) begin
|
||||
case (ptw_stage_q)
|
||||
S_STAGE: begin
|
||||
if ((is_instr_ptw_q && enable_g_translation_i) || (!is_instr_ptw_q && en_ld_st_g_translation_i)) begin
|
||||
state_d = WAIT_GRANT;
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
if (CVA6Cfg.RVH) gpte_d = pte;
|
||||
ptw_lvl_n[HYP_EXT] = ptw_lvl_q[0];
|
||||
gpaddr_n = gpaddr[ptw_lvl_q[0]];
|
||||
ptw_pptr_n = {
|
||||
hgatp_ppn_i[CVA6Cfg.PPNW-1:2],
|
||||
gpaddr[ptw_lvl_q[0]][CVA6Cfg.SV+HYP_EXT*2-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
ptw_lvl_n[0] = '0;
|
||||
end
|
||||
end
|
||||
G_INTERMED_STAGE: begin
|
||||
state_d = WAIT_GRANT;
|
||||
ptw_stage_d = S_STAGE;
|
||||
ptw_lvl_n[0] = ptw_lvl_q[HYP_EXT];
|
||||
pptr = {pte.ppn[CVA6Cfg.GPPNW-1:0], gptw_pptr_q[11:0]};
|
||||
if (ptw_lvl_q[0] == 1) pptr[20:0] = gptw_pptr_q[20:0];
|
||||
if (ptw_lvl_q[0] == 0) pptr[29:0] = gptw_pptr_q[29:0];
|
||||
ptw_pptr_n = pptr;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
// Valid translation found (either 1G, 2M or 4K entry)
|
||||
if (is_instr_ptw_q) begin
|
||||
// ------------
|
||||
// Update ITLB
|
||||
// ------------
|
||||
// If page is not executable, we can directly raise an error. This
|
||||
// doesn't put a useless entry into the TLB. The same idea applies
|
||||
// to the access flag since we let the access flag be managed by SW.
|
||||
if (!pte.x || !pte.a) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
if (CVA6Cfg.RVH) ptw_stage_d = ptw_stage_q;
|
||||
end else if (CVA6Cfg.RVH && ((ptw_stage_q == G_FINAL_STAGE) || !enable_g_translation_i) || !CVA6Cfg.RVH)
|
||||
shared_tlb_update_o.valid = 1'b1;
|
||||
|
||||
end else begin
|
||||
// ------------
|
||||
// Update DTLB
|
||||
// ------------
|
||||
// Check if the access flag has been set, otherwise throw a page-fault
|
||||
// and let the software handle those bits.
|
||||
// If page is not readable (there are no write-only pages)
|
||||
// we can directly raise an error. This doesn't put a useless
|
||||
// entry into the TLB.
|
||||
if (pte.a && ((pte.r && !hlvx_inst_i) || (pte.x && (mxr_i || hlvx_inst_i || (ptw_stage_q == S_STAGE && vmxr_i && ld_st_v_i && CVA6Cfg.RVH))))) begin
|
||||
if (CVA6Cfg.RVH && ((ptw_stage_q == G_FINAL_STAGE) || !en_ld_st_g_translation_i) || !CVA6Cfg.RVH)
|
||||
shared_tlb_update_o.valid = 1'b1;
|
||||
end else begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
if (CVA6Cfg.RVH) ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
// Request is a store: perform some additional checks
|
||||
// If the request was a store and the page is not write-able, raise an error
|
||||
// the same applies if the dirty flag is not set
|
||||
if (lsu_is_store_i && (!pte.w || !pte.d)) begin
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
if (CVA6Cfg.RVH) ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
end
|
||||
|
||||
//if there is a misaligned page, propagate error
|
||||
if (|misaligned_page) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
if (CVA6Cfg.RVH) ptw_stage_d = ptw_stage_q;
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
end
|
||||
|
||||
// check if 63:41 are all zeros
|
||||
if (CVA6Cfg.RVH) begin
|
||||
if (((v_i && is_instr_ptw_q) || (ld_st_v_i && !is_instr_ptw_q)) && ptw_stage_q == S_STAGE && !((|pte.ppn[CVA6Cfg.PPNW-1:CVA6Cfg.GPPNW-1+HYP_EXT]) == 1'b0)) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
end
|
||||
end
|
||||
// this is a pointer to the next TLB level
|
||||
end else begin
|
||||
// pointer to next level of page table
|
||||
|
||||
if (ptw_lvl_q[0] == CVA6Cfg.PtLevels - 1) begin
|
||||
// Should already be the last level page table => Error
|
||||
ptw_lvl_n[0] = ptw_lvl_q[0];
|
||||
state_d = PROPAGATE_ERROR;
|
||||
if (CVA6Cfg.RVH) ptw_stage_d = ptw_stage_q;
|
||||
|
||||
|
||||
end else begin
|
||||
ptw_lvl_n[0] = ptw_lvl_q[0] + 1'b1;
|
||||
state_d = WAIT_GRANT;
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
case (ptw_stage_q)
|
||||
S_STAGE: begin
|
||||
if (CVA6Cfg.RVH && ((is_instr_ptw_q && enable_g_translation_i) || (!is_instr_ptw_q && en_ld_st_g_translation_i))) begin
|
||||
ptw_stage_d = G_INTERMED_STAGE;
|
||||
if (CVA6Cfg.RVH) gpte_d = pte;
|
||||
ptw_lvl_n[HYP_EXT] = ptw_lvl_q[0] + 1;
|
||||
pptr = {pte.ppn, vaddr_lvl[0][ptw_lvl_q[0]], (CVA6Cfg.PtLevels)'(0)};
|
||||
gptw_pptr_n = pptr;
|
||||
ptw_pptr_n = {
|
||||
hgatp_ppn_i[CVA6Cfg.PPNW-1:2],
|
||||
pptr[CVA6Cfg.SV+HYP_EXT*2-1:CVA6Cfg.SV-(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)],
|
||||
(CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
ptw_lvl_n[0] = '0;
|
||||
end else begin
|
||||
ptw_pptr_n = {pte.ppn, vaddr_lvl[0][ptw_lvl_q[0]], (CVA6Cfg.PtLevels)'(0)};
|
||||
end
|
||||
end
|
||||
G_INTERMED_STAGE: begin
|
||||
ptw_pptr_n = {
|
||||
pte.ppn, vaddr_lvl[HYP_EXT][ptw_lvl_q[0]], (CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
end
|
||||
G_FINAL_STAGE: begin
|
||||
ptw_pptr_n = {
|
||||
pte.ppn, vaddr_lvl[HYP_EXT*2][ptw_lvl_q[0]], (CVA6Cfg.PtLevels)'(0)
|
||||
};
|
||||
end
|
||||
endcase
|
||||
end else ptw_pptr_n = {pte.ppn, vaddr_lvl[0][ptw_lvl_q[0]], (CVA6Cfg.PtLevels)'(0)};
|
||||
|
||||
if (CVA6Cfg.RVH && (pte.a || pte.d || pte.u)) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
// check if 63:41 are all zeros
|
||||
if (CVA6Cfg.RVH) begin
|
||||
if (((v_i && is_instr_ptw_q) || (ld_st_v_i && !is_instr_ptw_q)) && ptw_stage_q == S_STAGE && !((|pte.ppn[CVA6Cfg.PPNW-1:CVA6Cfg.GPPNW-1+HYP_EXT]) == 1'b0)) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Check if this access was actually allowed from a PMP perspective
|
||||
if (!allow_access) begin
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
// we have to return the failed address in bad_addr
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
if (CVA6Cfg.RVH) ptw_stage_d = ptw_stage_q;
|
||||
state_d = PROPAGATE_ACCESS_ERROR;
|
||||
end
|
||||
end
|
||||
// we've got a data WAIT_GRANT so tell the cache that the tag is valid
|
||||
end
|
||||
// Propagate error to MMU/LSU
|
||||
PROPAGATE_ERROR: begin
|
||||
state_d = LATENCY;
|
||||
ptw_error_o = 1'b1;
|
||||
if (CVA6Cfg.RVH) begin
|
||||
ptw_error_at_g_st_o = (ptw_stage_q != S_STAGE) ? 1'b1 : 1'b0;
|
||||
ptw_err_at_g_int_st_o = (ptw_stage_q == G_INTERMED_STAGE) ? 1'b1 : 1'b0;
|
||||
end
|
||||
end
|
||||
PROPAGATE_ACCESS_ERROR: begin
|
||||
state_d = LATENCY;
|
||||
ptw_access_exception_o = 1'b1;
|
||||
end
|
||||
// wait for the rvalid before going back to IDLE
|
||||
WAIT_RVALID: begin
|
||||
if (data_rvalid_q) state_d = IDLE;
|
||||
end
|
||||
LATENCY: begin
|
||||
state_d = IDLE;
|
||||
end
|
||||
default: begin
|
||||
state_d = IDLE;
|
||||
end
|
||||
endcase
|
||||
|
||||
// -------
|
||||
// Flush
|
||||
// -------
|
||||
// should we have flushed before we got an rvalid, wait for it until going back to IDLE
|
||||
if (flush_i) begin
|
||||
// on a flush check whether we are
|
||||
// 1. in the PTE Lookup check whether we still need to wait for an rvalid
|
||||
// 2. waiting for a grant, if so: wait for it
|
||||
// if not, go back to idle
|
||||
if (((state_q inside {PTE_LOOKUP, WAIT_RVALID}) && !data_rvalid_q) || ((state_q == WAIT_GRANT) && req_port_i.data_gnt))
|
||||
state_d = WAIT_RVALID;
|
||||
else state_d = LATENCY;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
state_q <= IDLE;
|
||||
is_instr_ptw_q <= 1'b0;
|
||||
ptw_lvl_q <= '0;
|
||||
tag_valid_q <= 1'b0;
|
||||
tlb_update_asid_q <= '0;
|
||||
tlb_update_vmid_q <= '0;
|
||||
vaddr_q <= '0;
|
||||
ptw_pptr_q <= '0;
|
||||
global_mapping_q <= 1'b0;
|
||||
data_rdata_q <= '0;
|
||||
data_rvalid_q <= 1'b0;
|
||||
if (CVA6Cfg.RVH) begin
|
||||
gpaddr_q <= '0;
|
||||
gptw_pptr_q <= '0;
|
||||
ptw_stage_q <= S_STAGE;
|
||||
gpte_q <= '0;
|
||||
end
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
ptw_pptr_q <= ptw_pptr_n;
|
||||
is_instr_ptw_q <= is_instr_ptw_n;
|
||||
ptw_lvl_q <= ptw_lvl_n;
|
||||
tag_valid_q <= tag_valid_n;
|
||||
tlb_update_asid_q <= tlb_update_asid_n;
|
||||
vaddr_q <= vaddr_n;
|
||||
global_mapping_q <= global_mapping_n;
|
||||
data_rdata_q <= req_port_i.data_rdata;
|
||||
data_rvalid_q <= req_port_i.data_rvalid;
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
gpaddr_q <= gpaddr_n;
|
||||
gptw_pptr_q <= gptw_pptr_n;
|
||||
ptw_stage_q <= ptw_stage_d;
|
||||
gpte_q <= gpte_d;
|
||||
tlb_update_vmid_q <= tlb_update_vmid_n;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
/* verilator lint_on WIDTH */
|
502
core/cva6_mmu/cva6_shared_tlb.sv
Normal file
502
core/cva6_mmu/cva6_shared_tlb.sv
Normal file
|
@ -0,0 +1,502 @@
|
|||
// Copyright (c) 2023 Thales.
|
||||
// Copyright (c) 2024, PlanV Technology
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Angela Gonzalez PlanV Technology
|
||||
// Date: 26/02/2024
|
||||
//
|
||||
// Description: N-way associative shared TLB, it allows to reduce the number
|
||||
// of ITLB and DTLB entries. This module is an update of the
|
||||
// shared TLB sv32 developed by Sebastien Jacq (Thales Research & Technology)
|
||||
// to be used with sv32, sv39 and sv39x4.
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
|
||||
module cva6_shared_tlb #(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type pte_cva6_t = logic,
|
||||
parameter type tlb_update_cva6_t = logic,
|
||||
parameter int SHARED_TLB_WAYS = 2,
|
||||
parameter int unsigned HYP_EXT = 0
|
||||
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // Flush normal translations signal
|
||||
input logic flush_vvma_i, // Flush vs stage signal
|
||||
input logic flush_gvma_i, // Flush g stage signal
|
||||
input logic s_st_enbl_i, // s-stage enabled
|
||||
input logic g_st_enbl_i, // g-stage enabled
|
||||
input logic v_i, // virtualization mode
|
||||
input logic s_ld_st_enbl_i, // s-stage enabled for load stores
|
||||
input logic g_ld_st_enbl_i, // g-stage enabled for load stores
|
||||
input logic ld_st_v_i, // virtualization mode for load stores
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] dtlb_asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] itlb_asid_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] lu_vmid_i,
|
||||
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
input logic itlb_access_i,
|
||||
input logic itlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] itlb_vaddr_i,
|
||||
|
||||
input logic dtlb_access_i,
|
||||
input logic dtlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] dtlb_vaddr_i,
|
||||
|
||||
input logic shared_tlb_miss_i,
|
||||
|
||||
// to TLBs, update logic
|
||||
output tlb_update_cva6_t itlb_update_o,
|
||||
output tlb_update_cva6_t dtlb_update_o,
|
||||
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
|
||||
output logic shared_tlb_access_o,
|
||||
output logic shared_tlb_hit_o,
|
||||
output logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr_o,
|
||||
|
||||
output logic itlb_req_o,
|
||||
|
||||
// Update shared TLB in case of miss
|
||||
input tlb_update_cva6_t shared_tlb_update_i
|
||||
|
||||
);
|
||||
|
||||
function logic [SHARED_TLB_WAYS-1:0] shared_tlb_way_bin2oh(input logic [$clog2(SHARED_TLB_WAYS
|
||||
)-1:0] in);
|
||||
logic [SHARED_TLB_WAYS-1:0] out;
|
||||
out = '0;
|
||||
out[in] = 1'b1;
|
||||
return out;
|
||||
endfunction
|
||||
|
||||
typedef struct packed {
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
|
||||
logic [CVA6Cfg.PtLevels+HYP_EXT-1:0][(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] vpn;
|
||||
logic [CVA6Cfg.PtLevels-2:0][HYP_EXT:0] is_page;
|
||||
logic [HYP_EXT*2:0] v_st_enbl; // v_i,g-stage enabled, s-stage enabled
|
||||
} shared_tag_t;
|
||||
|
||||
shared_tag_t shared_tag_wr;
|
||||
shared_tag_t [SHARED_TLB_WAYS-1:0] shared_tag_rd;
|
||||
|
||||
logic [CVA6Cfg.SharedTlbDepth-1:0][SHARED_TLB_WAYS-1:0] shared_tag_valid_q, shared_tag_valid_d;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] shared_tag_valid;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_wr_en;
|
||||
logic [$clog2(CVA6Cfg.SharedTlbDepth)-1:0] tag_wr_addr;
|
||||
logic [ $bits(shared_tag_t)-1:0] tag_wr_data;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_rd_en;
|
||||
logic [$clog2(CVA6Cfg.SharedTlbDepth)-1:0] tag_rd_addr;
|
||||
logic [ $bits(shared_tag_t)-1:0] tag_rd_data [SHARED_TLB_WAYS-1:0];
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_req;
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_we;
|
||||
logic [$clog2(CVA6Cfg.SharedTlbDepth)-1:0] tag_addr;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_wr_en;
|
||||
logic [$clog2(CVA6Cfg.SharedTlbDepth)-1:0] pte_wr_addr;
|
||||
logic [ $bits(pte_cva6_t)-1:0] pte_wr_data [ 1:0];
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_rd_en;
|
||||
logic [$clog2(CVA6Cfg.SharedTlbDepth)-1:0] pte_rd_addr;
|
||||
logic [ $bits(pte_cva6_t)-1:0] pte_rd_data [SHARED_TLB_WAYS-1:0] [HYP_EXT:0];
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_req;
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_we;
|
||||
logic [$clog2(CVA6Cfg.SharedTlbDepth)-1:0] pte_addr;
|
||||
|
||||
logic [CVA6Cfg.PtLevels+HYP_EXT-1:0][(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] vpn_d, vpn_q;
|
||||
logic [SHARED_TLB_WAYS-1:0][CVA6Cfg.PtLevels-1:0] vpn_match;
|
||||
logic [SHARED_TLB_WAYS-1:0][CVA6Cfg.PtLevels-1:0] page_match;
|
||||
logic [SHARED_TLB_WAYS-1:0][CVA6Cfg.PtLevels-1:0] level_match;
|
||||
|
||||
logic [SHARED_TLB_WAYS-1:0] match_asid;
|
||||
logic [SHARED_TLB_WAYS-1:0] match_vmid;
|
||||
logic [SHARED_TLB_WAYS-1:0] match_stage;
|
||||
|
||||
pte_cva6_t [SHARED_TLB_WAYS-1:0][HYP_EXT:0] pte;
|
||||
|
||||
logic [CVA6Cfg.VLEN-1-12:0] itlb_vpn_q;
|
||||
logic [CVA6Cfg.VLEN-1-12:0] dtlb_vpn_q;
|
||||
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] tlb_update_asid_q, tlb_update_asid_d;
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] tlb_update_vmid_q, tlb_update_vmid_d;
|
||||
|
||||
logic shared_tlb_access_q, shared_tlb_access_d;
|
||||
logic shared_tlb_hit_d;
|
||||
logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr_q, shared_tlb_vaddr_d;
|
||||
|
||||
logic itlb_req_d, itlb_req_q;
|
||||
logic dtlb_req_d, dtlb_req_q;
|
||||
|
||||
int i_req_d, i_req_q;
|
||||
|
||||
logic [1:0][2:0] v_st_enbl;
|
||||
|
||||
// replacement strategy
|
||||
logic [SHARED_TLB_WAYS-1:0] way_valid;
|
||||
logic update_lfsr; // shift the LFSR
|
||||
logic [$clog2(SHARED_TLB_WAYS)-1:0] inv_way; // first non-valid encountered
|
||||
logic [$clog2(SHARED_TLB_WAYS)-1:0] rnd_way; // random index for replacement
|
||||
logic [$clog2(SHARED_TLB_WAYS)-1:0] repl_way; // way to replace
|
||||
logic [SHARED_TLB_WAYS-1:0] repl_way_oh_d; // way to replace (onehot)
|
||||
logic all_ways_valid; // we need to switch repl strategy since all are valid
|
||||
|
||||
assign shared_tlb_access_o = shared_tlb_access_q;
|
||||
assign shared_tlb_hit_o = shared_tlb_hit_d;
|
||||
assign shared_tlb_vaddr_o = shared_tlb_vaddr_q;
|
||||
assign itlb_req_o = itlb_req_q;
|
||||
assign v_st_enbl = {{v_i, g_st_enbl_i, s_st_enbl_i}, {ld_st_v_i, g_ld_st_enbl_i, s_ld_st_enbl_i}};
|
||||
|
||||
genvar i, x;
|
||||
generate
|
||||
for (i = 0; i < SHARED_TLB_WAYS; i++) begin : gen_match_tlb_ways
|
||||
//identify page_match for all TLB Entries
|
||||
|
||||
for (x = 0; x < CVA6Cfg.PtLevels; x++) begin : gen_match
|
||||
assign page_match[i][x] = x==0 ? 1 :((HYP_EXT==0 || x==(CVA6Cfg.PtLevels-1)) ? // PAGE_MATCH CONTAINS THE MATCH INFORMATION FOR EACH TAG OF is_1G and is_2M in sv39x4. HIGHER LEVEL (Giga page), THEN THERE IS THE Mega page AND AT THE LOWER LEVEL IS ALWAYS 1
|
||||
&(shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x] | (~v_st_enbl[i_req_q][HYP_EXT:0])):
|
||||
((&v_st_enbl[i_req_q][HYP_EXT:0]) ?
|
||||
((shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x][0] && (shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-2-x][HYP_EXT] || shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT]))
|
||||
|| (shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT] && (shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-2-x][0] || shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x][0]))):
|
||||
shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x][0] && v_st_enbl[i_req_q][0] || shared_tag_rd[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT] && v_st_enbl[i_req_q][HYP_EXT]));
|
||||
|
||||
//identify if vpn matches at all PT levels for all TLB entries
|
||||
assign vpn_match[i][x] = (HYP_EXT==1 && x==(CVA6Cfg.PtLevels-1) && ~v_st_enbl[i_req_q][0]) ? //
|
||||
vpn_q[x] == shared_tag_rd[i].vpn[x] && vpn_q[x+HYP_EXT][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-HYP_EXT:0] == shared_tag_rd[i].vpn[x+HYP_EXT][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-HYP_EXT:0]: //
|
||||
vpn_q[x] == shared_tag_rd[i].vpn[x];
|
||||
|
||||
//identify if there is a hit at each PT level for all TLB entries
|
||||
assign level_match[i][x] = &vpn_match[i][CVA6Cfg.PtLevels-1:x] && page_match[i][x];
|
||||
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
genvar w;
|
||||
generate
|
||||
for (w = 0; w < CVA6Cfg.PtLevels; w++) begin
|
||||
assign vpn_d[w] = ((|v_st_enbl[1][HYP_EXT:0]) && itlb_access_i && ~itlb_hit_i && ~dtlb_access_i) ? //
|
||||
itlb_vaddr_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(w+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*w)] : //
|
||||
(((|v_st_enbl[0][HYP_EXT:0]) && dtlb_access_i && ~dtlb_hit_i) ? //
|
||||
dtlb_vaddr_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(w+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*w)] : vpn_q[w]);
|
||||
end
|
||||
endgenerate
|
||||
|
||||
if (CVA6Cfg.RVH) //THIS UPDATES THE EXTRA BITS OF VPN IN SV39x4
|
||||
assign vpn_d[CVA6Cfg.PtLevels][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-1:0] = ((|v_st_enbl[1][HYP_EXT:0]) && itlb_access_i && ~itlb_hit_i && ~dtlb_access_i) ? //
|
||||
itlb_vaddr_i[CVA6Cfg.VpnLen-1:CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)] : //
|
||||
(((|v_st_enbl[0][HYP_EXT:0]) && dtlb_access_i && ~dtlb_hit_i) ? //
|
||||
dtlb_vaddr_i[CVA6Cfg.VpnLen-1: CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)] : vpn_q[CVA6Cfg.PtLevels][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-1:0]);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// tag comparison, hit generation
|
||||
///////////////////////////////////////////////////////
|
||||
always_comb begin : itlb_dtlb_miss
|
||||
itlb_miss_o = 1'b0;
|
||||
dtlb_miss_o = 1'b0;
|
||||
|
||||
tag_rd_en = '0;
|
||||
pte_rd_en = '0;
|
||||
|
||||
itlb_req_d = 1'b0;
|
||||
dtlb_req_d = 1'b0;
|
||||
|
||||
tlb_update_asid_d = tlb_update_asid_q;
|
||||
tlb_update_vmid_d = tlb_update_vmid_q;
|
||||
|
||||
shared_tlb_access_d = '0;
|
||||
shared_tlb_vaddr_d = shared_tlb_vaddr_q;
|
||||
|
||||
tag_rd_addr = '0;
|
||||
pte_rd_addr = '0;
|
||||
i_req_d = i_req_q;
|
||||
|
||||
// if we got an ITLB miss
|
||||
if ((|v_st_enbl[1][HYP_EXT:0]) & itlb_access_i & ~itlb_hit_i & ~dtlb_access_i) begin
|
||||
tag_rd_en = '1;
|
||||
tag_rd_addr = itlb_vaddr_i[12+:$clog2(CVA6Cfg.SharedTlbDepth)];
|
||||
pte_rd_en = '1;
|
||||
pte_rd_addr = itlb_vaddr_i[12+:$clog2(CVA6Cfg.SharedTlbDepth)];
|
||||
|
||||
itlb_miss_o = shared_tlb_miss_i;
|
||||
itlb_req_d = 1'b1;
|
||||
tlb_update_asid_d = itlb_asid_i;
|
||||
tlb_update_vmid_d = lu_vmid_i;
|
||||
|
||||
shared_tlb_access_d = '1;
|
||||
shared_tlb_vaddr_d = itlb_vaddr_i;
|
||||
i_req_d = 1;
|
||||
|
||||
// we got an DTLB miss
|
||||
end else if ((|v_st_enbl[0][HYP_EXT:0]) & dtlb_access_i & ~dtlb_hit_i) begin
|
||||
tag_rd_en = '1;
|
||||
tag_rd_addr = dtlb_vaddr_i[12+:$clog2(CVA6Cfg.SharedTlbDepth)];
|
||||
pte_rd_en = '1;
|
||||
pte_rd_addr = dtlb_vaddr_i[12+:$clog2(CVA6Cfg.SharedTlbDepth)];
|
||||
|
||||
dtlb_miss_o = shared_tlb_miss_i;
|
||||
dtlb_req_d = 1'b1;
|
||||
tlb_update_asid_d = dtlb_asid_i;
|
||||
tlb_update_vmid_d = lu_vmid_i;
|
||||
|
||||
shared_tlb_access_d = '1;
|
||||
shared_tlb_vaddr_d = dtlb_vaddr_i;
|
||||
i_req_d = 0;
|
||||
end
|
||||
end //itlb_dtlb_miss
|
||||
|
||||
always_comb begin : tag_comparison
|
||||
shared_tlb_hit_d = 1'b0;
|
||||
dtlb_update_o = '0;
|
||||
itlb_update_o = '0;
|
||||
match_asid = '{default: 0};
|
||||
match_vmid = CVA6Cfg.RVH ? '{default: 0} : '{default: 1};
|
||||
|
||||
|
||||
if (!CVA6Cfg.UseSharedTlb) begin
|
||||
if (shared_tlb_update_i.valid) begin
|
||||
shared_tlb_hit_d = 1'b1;
|
||||
if (itlb_req_q) begin
|
||||
itlb_update_o.valid = 1'b1;
|
||||
itlb_update_o.vpn = shared_tlb_update_i.vpn;
|
||||
itlb_update_o.is_page = shared_tlb_update_i.is_page;
|
||||
itlb_update_o.content = shared_tlb_update_i.content;
|
||||
itlb_update_o.g_content = shared_tlb_update_i.g_content;
|
||||
itlb_update_o.v_st_enbl = v_st_enbl[i_req_q][HYP_EXT*2:0];
|
||||
itlb_update_o.asid = shared_tlb_update_i.asid;
|
||||
itlb_update_o.vmid = shared_tlb_update_i.vmid;
|
||||
|
||||
end else if (dtlb_req_q) begin
|
||||
dtlb_update_o.valid = 1'b1;
|
||||
dtlb_update_o.vpn = shared_tlb_update_i.vpn;
|
||||
dtlb_update_o.is_page = shared_tlb_update_i.is_page;
|
||||
dtlb_update_o.content = shared_tlb_update_i.content;
|
||||
dtlb_update_o.g_content = shared_tlb_update_i.g_content;
|
||||
dtlb_update_o.v_st_enbl = v_st_enbl[i_req_q][HYP_EXT*2:0];
|
||||
dtlb_update_o.asid = shared_tlb_update_i.asid;
|
||||
dtlb_update_o.vmid = shared_tlb_update_i.vmid;
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
|
||||
//number of ways
|
||||
for (int unsigned i = 0; i < SHARED_TLB_WAYS; i++) begin
|
||||
// first level match, this may be a giga page, check the ASID flags as well
|
||||
// if the entry is associated to a global address, don't match the ASID (ASID is don't care)
|
||||
match_asid[i] = (((tlb_update_asid_q == shared_tag_rd[i].asid) || pte[i][0].g) && v_st_enbl[i_req_q][0]) || !v_st_enbl[i_req_q][0];
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
match_vmid[i] = (tlb_update_vmid_q == shared_tag_rd[i].vmid && v_st_enbl[i_req_q][HYP_EXT]) || !v_st_enbl[i_req_q][HYP_EXT];
|
||||
end
|
||||
|
||||
// check if translation is a: S-Stage and G-Stage, S-Stage only or G-Stage only translation and virtualization mode is on/off
|
||||
match_stage[i] = shared_tag_rd[i].v_st_enbl == v_st_enbl[i_req_q][HYP_EXT*2:0];
|
||||
|
||||
if (shared_tag_valid[i] && match_asid && match_vmid && match_stage[i]) begin
|
||||
if (|level_match[i]) begin
|
||||
shared_tlb_hit_d = 1'b1;
|
||||
if (itlb_req_q) begin
|
||||
itlb_update_o.valid = 1'b1;
|
||||
itlb_update_o.vpn = itlb_vpn_q;
|
||||
itlb_update_o.is_page = shared_tag_rd[i].is_page;
|
||||
itlb_update_o.content = pte[i][0];
|
||||
itlb_update_o.g_content = pte[i][HYP_EXT];
|
||||
itlb_update_o.v_st_enbl = shared_tag_rd[i].v_st_enbl;
|
||||
itlb_update_o.asid = tlb_update_asid_q;
|
||||
itlb_update_o.vmid = tlb_update_vmid_q;
|
||||
end else if (dtlb_req_q) begin
|
||||
dtlb_update_o.valid = 1'b1;
|
||||
dtlb_update_o.vpn = dtlb_vpn_q;
|
||||
dtlb_update_o.is_page = shared_tag_rd[i].is_page;
|
||||
dtlb_update_o.content = pte[i][0];
|
||||
dtlb_update_o.g_content = pte[i][HYP_EXT];
|
||||
dtlb_update_o.v_st_enbl = shared_tag_rd[i].v_st_enbl;
|
||||
dtlb_update_o.asid = tlb_update_asid_q;
|
||||
dtlb_update_o.vmid = tlb_update_vmid_q;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end //tag_comparison
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
itlb_vpn_q <= '0;
|
||||
dtlb_vpn_q <= '0;
|
||||
tlb_update_asid_q <= '{default: 0};
|
||||
tlb_update_vmid_q <= '{default: 0};
|
||||
shared_tlb_access_q <= '0;
|
||||
shared_tlb_vaddr_q <= '0;
|
||||
shared_tag_valid_q <= '0;
|
||||
vpn_q <= 0;
|
||||
itlb_req_q <= '0;
|
||||
dtlb_req_q <= '0;
|
||||
i_req_q <= 0;
|
||||
shared_tag_valid <= '0;
|
||||
end else begin
|
||||
itlb_vpn_q <= itlb_vaddr_i[CVA6Cfg.SV-1:12];
|
||||
dtlb_vpn_q <= dtlb_vaddr_i[CVA6Cfg.SV-1:12];
|
||||
tlb_update_asid_q <= tlb_update_asid_d;
|
||||
shared_tlb_access_q <= shared_tlb_access_d;
|
||||
shared_tlb_vaddr_q <= shared_tlb_vaddr_d;
|
||||
shared_tag_valid_q <= shared_tag_valid_d;
|
||||
vpn_q <= vpn_d;
|
||||
itlb_req_q <= itlb_req_d;
|
||||
dtlb_req_q <= dtlb_req_d;
|
||||
i_req_q <= i_req_d;
|
||||
shared_tag_valid <= shared_tag_valid_q[tag_rd_addr];
|
||||
|
||||
if (CVA6Cfg.RVH) tlb_update_vmid_q <= tlb_update_vmid_d;
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------
|
||||
// Update and Flush
|
||||
// ------------------
|
||||
always_comb begin : update_flush
|
||||
shared_tag_valid_d = shared_tag_valid_q;
|
||||
tag_wr_en = '0;
|
||||
pte_wr_en = '0;
|
||||
|
||||
if (flush_i || flush_vvma_i || flush_gvma_i) begin
|
||||
shared_tag_valid_d = '0;
|
||||
end else if (shared_tlb_update_i.valid) begin
|
||||
for (int unsigned i = 0; i < SHARED_TLB_WAYS; i++) begin
|
||||
if (repl_way_oh_d[i]) begin
|
||||
shared_tag_valid_d[shared_tlb_update_i.vpn[$clog2(CVA6Cfg.SharedTlbDepth)-1:0]][i] = 1'b1;
|
||||
tag_wr_en[i] = 1'b1;
|
||||
pte_wr_en[i] = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end //update_flush
|
||||
|
||||
assign shared_tag_wr.asid = shared_tlb_update_i.asid;
|
||||
assign shared_tag_wr.vmid = shared_tlb_update_i.vmid;
|
||||
assign shared_tag_wr.is_page = shared_tlb_update_i.is_page;
|
||||
assign shared_tag_wr.v_st_enbl = v_st_enbl[i_req_q][HYP_EXT*2:0];
|
||||
|
||||
genvar z;
|
||||
generate
|
||||
for (z = 0; z < CVA6Cfg.PtLevels; z++) begin : gen_shared_tag
|
||||
assign shared_tag_wr.vpn[z] = shared_tlb_update_i.vpn[((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(z+1))-1:((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*z)];
|
||||
end
|
||||
if (CVA6Cfg.RVH) begin : gen_shared_tag_hyp
|
||||
//THIS UPDATES THE EXTRA BITS OF VPN IN SV39x4
|
||||
assign shared_tag_wr.vpn[CVA6Cfg.PtLevels][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-1:0] = shared_tlb_update_i.vpn[CVA6Cfg.VpnLen-1: CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)];
|
||||
end
|
||||
endgenerate
|
||||
|
||||
|
||||
assign tag_wr_addr = shared_tlb_update_i.vpn[$clog2(CVA6Cfg.SharedTlbDepth)-1:0];
|
||||
assign tag_wr_data = shared_tag_wr;
|
||||
|
||||
assign pte_wr_addr = shared_tlb_update_i.vpn[$clog2(CVA6Cfg.SharedTlbDepth)-1:0];
|
||||
|
||||
assign pte_wr_data[0] = shared_tlb_update_i.content;
|
||||
assign pte_wr_data[1] = shared_tlb_update_i.g_content;
|
||||
|
||||
|
||||
|
||||
assign way_valid = shared_tag_valid_q[shared_tlb_update_i.vpn[$clog2(
|
||||
CVA6Cfg.SharedTlbDepth
|
||||
)-1:0]];
|
||||
assign repl_way = (all_ways_valid) ? rnd_way : inv_way;
|
||||
assign update_lfsr = shared_tlb_update_i.valid & all_ways_valid;
|
||||
assign repl_way_oh_d = (shared_tlb_update_i.valid) ? shared_tlb_way_bin2oh(repl_way) : '0;
|
||||
|
||||
lzc #(
|
||||
.WIDTH(SHARED_TLB_WAYS)
|
||||
) i_lzc (
|
||||
.in_i (~way_valid),
|
||||
.cnt_o (inv_way),
|
||||
.empty_o(all_ways_valid)
|
||||
);
|
||||
|
||||
lfsr #(
|
||||
.LfsrWidth(8),
|
||||
.OutWidth ($clog2(SHARED_TLB_WAYS))
|
||||
) i_lfsr (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni(rst_ni),
|
||||
.en_i (update_lfsr),
|
||||
.out_o (rnd_way)
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// memory arrays and regs
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
assign tag_req = tag_wr_en | tag_rd_en;
|
||||
assign tag_we = tag_wr_en;
|
||||
assign tag_addr = tag_wr_en ? tag_wr_addr : tag_rd_addr;
|
||||
|
||||
assign pte_req = pte_wr_en | pte_rd_en;
|
||||
assign pte_we = pte_wr_en;
|
||||
assign pte_addr = pte_wr_en ? pte_wr_addr : pte_rd_addr;
|
||||
|
||||
for (genvar i = 0; i < SHARED_TLB_WAYS; i++) begin : gen_sram
|
||||
if (CVA6Cfg.UseSharedTlb) begin
|
||||
// Tag RAM
|
||||
sram #(
|
||||
.DATA_WIDTH($bits(shared_tag_t)),
|
||||
.NUM_WORDS (CVA6Cfg.SharedTlbDepth)
|
||||
) tag_sram (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.req_i (tag_req[i]),
|
||||
.we_i (tag_we[i]),
|
||||
.addr_i (tag_addr),
|
||||
.wuser_i('0),
|
||||
.wdata_i(tag_wr_data),
|
||||
.be_i ('1),
|
||||
.ruser_o(),
|
||||
.rdata_o(tag_rd_data[i])
|
||||
);
|
||||
|
||||
assign shared_tag_rd[i] = shared_tag_t'(tag_rd_data[i]);
|
||||
|
||||
for (genvar a = 0; a < HYP_EXT + 1; a++) begin : g_content_sram
|
||||
// PTE RAM
|
||||
sram #(
|
||||
.DATA_WIDTH($bits(pte_cva6_t)),
|
||||
.NUM_WORDS (CVA6Cfg.SharedTlbDepth)
|
||||
) pte_sram (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.req_i (pte_req[i]),
|
||||
.we_i (pte_we[i]),
|
||||
.addr_i (pte_addr),
|
||||
.wuser_i('0),
|
||||
.wdata_i(pte_wr_data[a]),
|
||||
.be_i ('1),
|
||||
.ruser_o(),
|
||||
.rdata_o(pte_rd_data[i][a])
|
||||
);
|
||||
assign pte[i][a] = pte_cva6_t'(pte_rd_data[i][a]);
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
/* verilator lint_on WIDTH */
|
|
@ -1,4 +1,8 @@
|
|||
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
|
||||
// Copyright (c) 2018 ETH Zurich and University of Bologna.
|
||||
// Copyright (c) 2021 Thales.
|
||||
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
|
||||
// Copyright (c) 2024 PlanV Technology
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
|
@ -8,21 +12,23 @@
|
|||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Bruno Sá
|
||||
// Date: 14/08/2022
|
||||
// Acknowledges: Technology Innovation Institute (TII)
|
||||
// Author: Angela Gonzalez PlanV Technology
|
||||
// Date: 26/02/2024
|
||||
//
|
||||
// Description: Translation Lookaside Buffer, Sv39x4 , fully set-associative
|
||||
// This module is an adaptation of the Sv39 TLB developed
|
||||
// by Florian Zaruba and David Schaffenrath to the Sv39x4 standard.
|
||||
// Description: Translation Lookaside Buffer, parameterizable to Sv32 or Sv39 ,
|
||||
// or sv39x4 fully set-associative
|
||||
// This module is an merge of the Sv32 TLB developed by Sebastien
|
||||
// Jacq (Thales Research & Technology), the Sv39 TLB developed
|
||||
// by Florian Zaruba and David Schaffenrath and the Sv39x4 by Bruno Sá.
|
||||
|
||||
|
||||
module cva6_tlb_sv39x4
|
||||
module cva6_tlb
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type tlb_update_t = logic,
|
||||
parameter int unsigned TLB_ENTRIES = 4
|
||||
parameter type pte_cva6_t = logic,
|
||||
parameter type tlb_update_cva6_t = logic,
|
||||
parameter int unsigned TLB_ENTRIES = 4,
|
||||
parameter int unsigned HYP_EXT = 0
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
|
@ -33,146 +39,173 @@ module cva6_tlb_sv39x4
|
|||
input logic g_st_enbl_i, // g-stage enabled
|
||||
input logic v_i, // virtualization mode
|
||||
// Update TLB
|
||||
input tlb_update_t update_i,
|
||||
input tlb_update_cva6_t update_i,
|
||||
// Lookup signals
|
||||
input logic lu_access_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] lu_asid_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] lu_vmid_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] lu_vaddr_i,
|
||||
output logic [CVA6Cfg.GPLEN-1:0] lu_gpaddr_o,
|
||||
output riscv::pte_t lu_content_o,
|
||||
output riscv::pte_t lu_g_content_o,
|
||||
output pte_cva6_t lu_content_o,
|
||||
output pte_cva6_t lu_g_content_o,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.GPLEN-1:0] gpaddr_to_be_flushed_i,
|
||||
output logic lu_is_2M_o,
|
||||
output logic lu_is_1G_o,
|
||||
output logic [CVA6Cfg.PtLevels-2:0] lu_is_page_o,
|
||||
output logic lu_hit_o
|
||||
);
|
||||
localparam VPN2 = (CVA6Cfg.VLEN - 31 < 8) ? CVA6Cfg.VLEN - 31 : 8;
|
||||
localparam GPPN2 = (CVA6Cfg.XLEN == 32) ? CVA6Cfg.VLEN - 33 : 10;
|
||||
|
||||
// SV39 defines three levels of page tables
|
||||
struct packed {
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
|
||||
logic [GPPN2:0] vpn2;
|
||||
logic [8:0] vpn1;
|
||||
logic [8:0] vpn0;
|
||||
logic is_s_2M;
|
||||
logic is_s_1G;
|
||||
logic is_g_2M;
|
||||
logic is_g_1G;
|
||||
logic s_st_enbl; // s-stage translation
|
||||
logic g_st_enbl; // g-stage translation
|
||||
logic v; // virtualization mode
|
||||
logic valid;
|
||||
logic [CVA6Cfg.PtLevels+HYP_EXT-1:0][(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] vpn;
|
||||
logic [CVA6Cfg.PtLevels-2:0][HYP_EXT:0] is_page;
|
||||
logic [HYP_EXT*2:0] v_st_enbl; // v_i,g-stage enabled, s-stage enabled
|
||||
logic valid;
|
||||
} [TLB_ENTRIES-1:0]
|
||||
tags_q, tags_n;
|
||||
|
||||
struct packed {
|
||||
riscv::pte_t pte;
|
||||
riscv::pte_t gpte;
|
||||
pte_cva6_t pte;
|
||||
pte_cva6_t gpte;
|
||||
} [TLB_ENTRIES-1:0]
|
||||
content_q, content_n;
|
||||
|
||||
logic [8:0] vpn0, vpn1;
|
||||
logic [GPPN2:0] vpn2;
|
||||
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] vpn_match;
|
||||
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] level_match;
|
||||
logic [TLB_ENTRIES-1:0][HYP_EXT:0][CVA6Cfg.PtLevels-1:0] vaddr_vpn_match;
|
||||
logic [TLB_ENTRIES-1:0][HYP_EXT:0][CVA6Cfg.PtLevels-1:0] vaddr_level_match;
|
||||
logic [TLB_ENTRIES-1:0] lu_hit; // to replacement logic
|
||||
logic [TLB_ENTRIES-1:0] replace_en; // replace the following entry, set by replacement strategy
|
||||
logic [TLB_ENTRIES-1:0] match_vmid;
|
||||
logic [TLB_ENTRIES-1:0] match_asid;
|
||||
logic [TLB_ENTRIES-1:0] is_1G;
|
||||
logic [TLB_ENTRIES-1:0] is_2M;
|
||||
logic [TLB_ENTRIES-1:0] match_vmid;
|
||||
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] page_match;
|
||||
logic [TLB_ENTRIES-1:0][HYP_EXT:0][CVA6Cfg.PtLevels-1:0] vpage_match;
|
||||
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-2:0] is_page_o;
|
||||
logic [TLB_ENTRIES-1:0] match_stage;
|
||||
riscv::pte_t g_content;
|
||||
pte_cva6_t g_content;
|
||||
logic [TLB_ENTRIES-1:0][(CVA6Cfg.GPPNW-1):0] gppn;
|
||||
logic [2:0] v_st_enbl;
|
||||
|
||||
assign v_st_enbl = (CVA6Cfg.RVH) ? {v_i, g_st_enbl_i, s_st_enbl_i} : '1;
|
||||
//-------------
|
||||
// Translation
|
||||
//-------------
|
||||
|
||||
genvar i, x, z, w;
|
||||
generate
|
||||
for (i = 0; i < TLB_ENTRIES; i++) begin
|
||||
for (x = 0; x < CVA6Cfg.PtLevels; x++) begin
|
||||
//identify page_match for all TLB Entries
|
||||
assign page_match[i][x] = x==0 ? 1 :((HYP_EXT==0 || x==(CVA6Cfg.PtLevels-1)) ? // PAGE_MATCH CONTAINS THE MATCH INFORMATION FOR EACH TAG OF is_1G and is_2M in sv39x4. HIGHER LEVEL (Giga page), THEN THERE IS THE Mega page AND AT THE LOWER LEVEL IS ALWAYS 1
|
||||
&(tags_q[i].is_page[CVA6Cfg.PtLevels-1-x] | (~v_st_enbl[HYP_EXT:0])):
|
||||
((&v_st_enbl[HYP_EXT:0]) ?
|
||||
((tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][0] && (tags_q[i].is_page[CVA6Cfg.PtLevels-2-x][HYP_EXT] || tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT]))
|
||||
|| (tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT] && (tags_q[i].is_page[CVA6Cfg.PtLevels-2-x][0] || tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][0]))):
|
||||
tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][0] && s_st_enbl_i || tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT] && g_st_enbl_i));
|
||||
|
||||
//identify if vpn matches at all PT levels for all TLB entries
|
||||
assign vpn_match[i][x] = (CVA6Cfg.RVH && x == (CVA6Cfg.PtLevels - 1) && ~s_st_enbl_i) ? //
|
||||
lu_vaddr_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(x+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*x)] == tags_q[i].vpn[x] && lu_vaddr_i[12+HYP_EXT*(CVA6Cfg.VpnLen-1): 12+HYP_EXT*(CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels))] == tags_q[i].vpn[x+HYP_EXT][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-HYP_EXT:0]: //
|
||||
lu_vaddr_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(x+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*x)] == tags_q[i].vpn[x];
|
||||
|
||||
//identify if there is a hit at each PT level for all TLB entries
|
||||
assign level_match[i][x] = &vpn_match[i][CVA6Cfg.PtLevels-1:x] && page_match[i][x];
|
||||
|
||||
//identify vpage_match for all TLB Entries and vaddr_level match (if there is a hit at each PT level for all TLB entries on the vaddr)
|
||||
for (z = 0; z < HYP_EXT + 1; z++) begin
|
||||
assign vpage_match[i][z][x] = x == 0 ? 1 : tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][z];
|
||||
assign vaddr_level_match[i][z][x]= &vaddr_vpn_match[i][z][CVA6Cfg.PtLevels-1:x] && vpage_match[i][z][x];
|
||||
|
||||
end
|
||||
//identify if virtual address vpn matches at all PT levels for all TLB entries
|
||||
assign vaddr_vpn_match[i][0][x] = vaddr_to_be_flushed_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(x+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*x)] == tags_q[i].vpn[x];
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
//identify if GPADDR matches the GPPN
|
||||
assign vaddr_vpn_match[i][HYP_EXT][0] = (gpaddr_to_be_flushed_i[20:12] == gppn[i][8:0]);
|
||||
assign vaddr_vpn_match[i][HYP_EXT][HYP_EXT] = (gpaddr_to_be_flushed_i[29:21] == gppn[i][17:9]);
|
||||
assign vaddr_vpn_match[i][HYP_EXT][HYP_EXT*2] = (gpaddr_to_be_flushed_i[30+GPPN2:30] == gppn[i][18+GPPN2:18]);
|
||||
|
||||
end
|
||||
|
||||
for (w = 0; w < CVA6Cfg.PtLevels - 1; w++) begin
|
||||
assign is_page_o[i][w] = page_match[i][CVA6Cfg.PtLevels - 1 - w]; //THIS REORGANIZES THE PAGES TO MATCH THE OUTPUT STRUCTURE (2M,1G)
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
always_comb begin : translation
|
||||
automatic logic [GPPN2:0] mask_pn2;
|
||||
mask_pn2 = s_st_enbl_i ? ((2 ** (VPN2 + 1)) - 1) : ((2 ** (GPPN2 + 1)) - 1);
|
||||
vpn0 = lu_vaddr_i[20:12];
|
||||
vpn1 = lu_vaddr_i[29:21];
|
||||
vpn2 = lu_vaddr_i[30+GPPN2:30] & mask_pn2;
|
||||
|
||||
// default assignment
|
||||
lu_hit = '{default: 0};
|
||||
lu_hit_o = 1'b0;
|
||||
lu_content_o = '{default: 0};
|
||||
lu_g_content_o = '{default: 0};
|
||||
lu_is_1G_o = 1'b0;
|
||||
lu_is_2M_o = 1'b0;
|
||||
lu_is_page_o = '{default: 0};
|
||||
match_asid = '{default: 0};
|
||||
match_vmid = '{default: 0};
|
||||
match_vmid = CVA6Cfg.RVH ? '{default: 0} : '{default: 1};
|
||||
match_stage = '{default: 0};
|
||||
is_1G = '{default: 0};
|
||||
is_2M = '{default: 0};
|
||||
g_content = '{default: 0};
|
||||
lu_gpaddr_o = '{default: 0};
|
||||
|
||||
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
||||
// first level match, this may be a giga page, check the ASID flags as well
|
||||
// if the entry is associated to a global address, don't match the ASID (ASID is don't care)
|
||||
match_asid[i] = (((lu_asid_i == tags_q[i].asid) || content_q[i].pte.g) && s_st_enbl_i) || !s_st_enbl_i;
|
||||
match_vmid[i] = (lu_vmid_i == tags_q[i].vmid && g_st_enbl_i) || !g_st_enbl_i;
|
||||
is_1G[i] = is_trans_1G(s_st_enbl_i, g_st_enbl_i, tags_q[i].is_s_1G, tags_q[i].is_g_1G);
|
||||
is_2M[i] = is_trans_2M(
|
||||
s_st_enbl_i,
|
||||
g_st_enbl_i,
|
||||
tags_q[i].is_s_1G,
|
||||
tags_q[i].is_s_2M,
|
||||
tags_q[i].is_g_1G,
|
||||
tags_q[i].is_g_2M
|
||||
);
|
||||
match_asid[i] = ((lu_asid_i == tags_q[i].asid || content_q[i].pte.g) && s_st_enbl_i) || !s_st_enbl_i;
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
match_vmid[i] = (lu_vmid_i == tags_q[i].vmid && g_st_enbl_i) || !g_st_enbl_i;
|
||||
end
|
||||
|
||||
// check if translation is a: S-Stage and G-Stage, S-Stage only or G-Stage only translation and virtualization mode is on/off
|
||||
match_stage[i] = (tags_q[i].v == v_i) && (tags_q[i].g_st_enbl == g_st_enbl_i) && (tags_q[i].s_st_enbl == s_st_enbl_i);
|
||||
if (tags_q[i].valid && match_asid[i] && match_vmid[i] && match_stage[i] && (vpn2 == (tags_q[i].vpn2 & mask_pn2))) begin
|
||||
lu_gpaddr_o = make_gpaddr(s_st_enbl_i, tags_q[i].is_s_1G, tags_q[i].is_s_2M, lu_vaddr_i,
|
||||
content_q[i].pte);
|
||||
if (is_1G[i]) begin
|
||||
lu_is_1G_o = is_1G[i];
|
||||
lu_content_o = content_q[i].pte;
|
||||
lu_g_content_o = content_q[i].gpte;
|
||||
lu_hit_o = 1'b1;
|
||||
lu_hit[i] = 1'b1;
|
||||
// not a giga page hit so check further
|
||||
end else if (vpn1 == tags_q[i].vpn1) begin
|
||||
// this could be a 2 mega page hit or a 4 kB hit
|
||||
// output accordingly
|
||||
if (is_2M[i] || vpn0 == tags_q[i].vpn0) begin
|
||||
lu_is_2M_o = is_2M[i];
|
||||
match_stage[i] = tags_q[i].v_st_enbl[HYP_EXT*2:0] == v_st_enbl[HYP_EXT*2:0];
|
||||
|
||||
if (tags_q[i].valid && match_asid[i] && match_vmid[i] && match_stage[i]) begin
|
||||
|
||||
if (CVA6Cfg.RVH && vpn_match[i][HYP_EXT*2]) begin
|
||||
if (s_st_enbl_i) begin
|
||||
lu_gpaddr_o = {content_q[i].pte.ppn[(CVA6Cfg.GPPNW-1):0], lu_vaddr_i[11:0]};
|
||||
// Giga page
|
||||
if (tags_q[i].is_page[0][0])
|
||||
lu_gpaddr_o[12+2*CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12] = lu_vaddr_i[12+2*CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12];
|
||||
// Mega page
|
||||
if (tags_q[i].is_page[HYP_EXT][0])
|
||||
lu_gpaddr_o[12+CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12] = lu_vaddr_i[12+CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12];
|
||||
end else begin
|
||||
lu_gpaddr_o =CVA6Cfg.GPLEN'(lu_vaddr_i[(CVA6Cfg.XLEN == 32 ? CVA6Cfg.VLEN: CVA6Cfg.GPLEN)-1:0]);
|
||||
end
|
||||
end
|
||||
|
||||
if (|level_match[i]) begin
|
||||
lu_is_page_o = is_page_o[i];
|
||||
lu_content_o = content_q[i].pte;
|
||||
lu_hit_o = 1'b1;
|
||||
lu_hit[i] = 1'b1;
|
||||
|
||||
if (CVA6Cfg.RVH) begin
|
||||
// Compute G-Stage PPN based on the gpaddr
|
||||
g_content = content_q[i].gpte;
|
||||
if (tags_q[i].is_g_2M) g_content.ppn[8:0] = lu_gpaddr_o[20:12];
|
||||
if (tags_q[i].is_g_1G) g_content.ppn[17:0] = lu_gpaddr_o[29:12];
|
||||
g_content = content_q[i].gpte;
|
||||
if (tags_q[i].is_page[HYP_EXT][HYP_EXT]) g_content.ppn[8:0] = lu_gpaddr_o[20:12];
|
||||
if (tags_q[i].is_page[0][HYP_EXT]) g_content.ppn[17:0] = lu_gpaddr_o[29:12];
|
||||
// Output G-stage and S-stage content
|
||||
lu_g_content_o = g_content;
|
||||
lu_content_o = content_q[i].pte;
|
||||
lu_hit_o = 1'b1;
|
||||
lu_hit[i] = 1'b1;
|
||||
lu_g_content_o = level_match[i][CVA6Cfg.PtLevels-1] ? content_q[i].gpte : g_content;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
logic asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
|
||||
logic vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
|
||||
logic [HYP_EXT:0]asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
|
||||
logic [HYP_EXT:0] vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
|
||||
logic vmid_to_be_flushed_is0; // indicates that the VMID provided is 0, active high
|
||||
logic gpaddr_to_be_flushed_is0; // indicates that the GPADDR provided is 0, active high
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn0_match;
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn1_match;
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn2_match;
|
||||
logic [TLB_ENTRIES-1:0] gpaddr_gppn0_match;
|
||||
logic [TLB_ENTRIES-1:0] gpaddr_gppn1_match;
|
||||
logic [TLB_ENTRIES-1:0] gpaddr_gppn2_match;
|
||||
logic [TLB_ENTRIES-1:0][(CVA6Cfg.GPPNW-1):0] gppn;
|
||||
|
||||
|
||||
assign asid_to_be_flushed_is0 = ~(|asid_to_be_flushed_i);
|
||||
assign vaddr_to_be_flushed_is0 = ~(|vaddr_to_be_flushed_i);
|
||||
|
@ -188,64 +221,64 @@ module cva6_tlb_sv39x4
|
|||
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
||||
|
||||
vaddr_vpn0_match[i] = (vaddr_to_be_flushed_i[20:12] == tags_q[i].vpn0);
|
||||
vaddr_vpn1_match[i] = (vaddr_to_be_flushed_i[29:21] == tags_q[i].vpn1);
|
||||
vaddr_vpn2_match[i] = (vaddr_to_be_flushed_i[30+VPN2:30] == tags_q[i].vpn2[VPN2:0]);
|
||||
|
||||
gppn[i] = make_gppn(
|
||||
tags_q[i].s_st_enbl,
|
||||
tags_q[i].is_s_1G,
|
||||
tags_q[i].is_s_2M,
|
||||
{
|
||||
tags_q[i].vpn2, tags_q[i].vpn1, tags_q[i].vpn0
|
||||
},
|
||||
content_q[i].pte
|
||||
);
|
||||
gpaddr_gppn0_match[i] = (gpaddr_to_be_flushed_i[20:12] == gppn[i][8:0]);
|
||||
gpaddr_gppn1_match[i] = (gpaddr_to_be_flushed_i[29:21] == gppn[i][17:9]);
|
||||
gpaddr_gppn2_match[i] = (gpaddr_to_be_flushed_i[30+GPPN2:30] == gppn[i][18+GPPN2:18]);
|
||||
if (CVA6Cfg.RVH) begin
|
||||
|
||||
if (tags_q[i].v_st_enbl[0]) begin
|
||||
gppn[i] = content_q[i].pte.ppn[(CVA6Cfg.GPPNW-1):0];
|
||||
if (tags_q[i].is_page[HYP_EXT][0])
|
||||
gppn[i][CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:0] = tags_q[i].vpn[0];
|
||||
if (tags_q[i].is_page[0][0])
|
||||
gppn[i][2*(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] = {
|
||||
tags_q[i].vpn[HYP_EXT], tags_q[i].vpn[0]
|
||||
};
|
||||
end else begin
|
||||
gppn[i][CVA6Cfg.VpnLen-1:0] = CVA6Cfg.VpnLen'(tags_q[i].vpn);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if (flush_i) begin
|
||||
if (!tags_q[i].v) begin
|
||||
if (!tags_q[i].v_st_enbl[HYP_EXT*2] || HYP_EXT == 0) begin
|
||||
// invalidate logic
|
||||
// flush everything if ASID is 0 and vaddr is 0 ("SFENCE.VMA x0 x0" case)
|
||||
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0) tags_n[i].valid = 1'b0;
|
||||
// flush vaddr in all addressing space ("SFENCE.VMA vaddr x0" case), it should happen only for leaf pages
|
||||
else if (asid_to_be_flushed_is0 && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M) ) && (~vaddr_to_be_flushed_is0))
|
||||
else if (asid_to_be_flushed_is0 && (|vaddr_level_match[i][0] ) && (~vaddr_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global and asid and vaddr both matches with the entry to be flushed ("SFENCE.VMA vaddr asid" case)
|
||||
else if ((!content_q[i].pte.g) && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M)) && (asid_to_be_flushed_i == tags_q[i].asid) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
|
||||
else if ((!content_q[i].pte.g) && (|vaddr_level_match[i][0]) && (asid_to_be_flushed_i == tags_q[i].asid ) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global, and the asid matches and vaddr is 0. ("SFENCE.VMA 0 asid" case)
|
||||
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid) && (!asid_to_be_flushed_is0))
|
||||
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid ) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
end
|
||||
end else if (flush_vvma_i) begin
|
||||
if (tags_q[i].v && tags_q[i].s_st_enbl) begin
|
||||
end else if (flush_vvma_i && CVA6Cfg.RVH) begin
|
||||
if (tags_q[i].v_st_enbl[HYP_EXT*2] && tags_q[i].v_st_enbl[0]) begin
|
||||
// invalidate logic
|
||||
// flush everything if current VMID matches and ASID is 0 and vaddr is 0 ("SFENCE.VMA/HFENCE.VVMA x0 x0" case)
|
||||
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0 && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl))
|
||||
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0 && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT]))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// flush vaddr in all addressing space if current VMID matches ("SFENCE.VMA/HFENCE.VVMA vaddr x0" case), it should happen only for leaf pages
|
||||
else if (asid_to_be_flushed_is0 && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M) ) && (~vaddr_to_be_flushed_is0) && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl))
|
||||
else if (asid_to_be_flushed_is0 && (|vaddr_level_match[i][0]) && (~vaddr_to_be_flushed_is0) && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT]))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global and asid and vaddr and current VMID matches with the entry to be flushed ("SFENCE.VMA/HFENCE.VVMA vaddr asid" case)
|
||||
else if ((!content_q[i].pte.g) && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M)) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl)) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
|
||||
else if ((!content_q[i].pte.g) && (|vaddr_level_match[i][0]) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT])) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global, and the asid and the current VMID matches and vaddr is 0. ("SFENCE.VMA/HFENCE.VVMA 0 asid" case)
|
||||
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl)) && (!asid_to_be_flushed_is0))
|
||||
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT])) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
end
|
||||
end else if (flush_gvma_i) begin
|
||||
if (tags_q[i].g_st_enbl) begin
|
||||
end else if (flush_gvma_i && CVA6Cfg.RVH) begin
|
||||
if (tags_q[i].v_st_enbl[HYP_EXT]) begin
|
||||
// invalidate logic
|
||||
// flush everything if vmid is 0 and addr is 0 ("HFENCE.GVMA x0 x0" case)
|
||||
if (vmid_to_be_flushed_is0 && gpaddr_to_be_flushed_is0) tags_n[i].valid = 1'b0;
|
||||
// flush gpaddr in all addressing space ("HFENCE.GVMA gpaddr x0" case), it should happen only for leaf pages
|
||||
else if (vmid_to_be_flushed_is0 && ((gpaddr_gppn0_match[i] && gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i]) || (gpaddr_gppn2_match[i] && tags_q[i].is_g_1G) || (gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i] && tags_q[i].is_g_2M) ) && (~gpaddr_to_be_flushed_is0))
|
||||
else if (vmid_to_be_flushed_is0 && (|vaddr_level_match[i][HYP_EXT] ) && (~gpaddr_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry vmid and gpaddr both matches with the entry to be flushed ("HFENCE.GVMA gpaddr vmid" case)
|
||||
else if (((gpaddr_gppn0_match[i] && gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i]) || (gpaddr_gppn2_match[i] && tags_q[i].is_g_1G) || (gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i] && tags_q[i].is_g_2M)) && (vmid_to_be_flushed_i == tags_q[i].vmid) && (~gpaddr_to_be_flushed_is0) && (~vmid_to_be_flushed_is0))
|
||||
else if ((|vaddr_level_match[i][HYP_EXT]) && (vmid_to_be_flushed_i == tags_q[i].vmid) && (~gpaddr_to_be_flushed_is0) && (~vmid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if the vmid matches and gpaddr is 0. ("HFENCE.GVMA 0 vmid" case)
|
||||
else if ((gpaddr_to_be_flushed_is0) && (vmid_to_be_flushed_i == tags_q[i].vmid) && (!vmid_to_be_flushed_is0))
|
||||
|
@ -253,25 +286,19 @@ module cva6_tlb_sv39x4
|
|||
end
|
||||
// normal replacement
|
||||
end else if (update_i.valid & replace_en[i]) begin
|
||||
// update tag array
|
||||
tags_n[i] = '{
|
||||
asid: update_i.asid,
|
||||
vmid: update_i.vmid,
|
||||
vpn2: update_i.vpn[18+GPPN2:18],
|
||||
vpn1: update_i.vpn[17:9],
|
||||
vpn0: update_i.vpn[8:0],
|
||||
s_st_enbl: s_st_enbl_i,
|
||||
g_st_enbl: g_st_enbl_i,
|
||||
v: v_i,
|
||||
is_s_1G: update_i.is_s_1G,
|
||||
is_s_2M: update_i.is_s_2M,
|
||||
is_g_1G: update_i.is_g_1G,
|
||||
is_g_2M: update_i.is_g_2M,
|
||||
valid: 1'b1
|
||||
// end else if (update_i.valid & replace_en[i] && !lu_hit_o) begin //to add this fix
|
||||
//update tag
|
||||
tags_n[i] = {
|
||||
update_i.asid,
|
||||
update_i.vmid,
|
||||
((CVA6Cfg.PtLevels + HYP_EXT) * (CVA6Cfg.VpnLen / CVA6Cfg.PtLevels))'(update_i.vpn),
|
||||
update_i.is_page,
|
||||
update_i.v_st_enbl,
|
||||
1'b1
|
||||
};
|
||||
// and content as well
|
||||
// update content as well
|
||||
content_n[i].pte = update_i.content;
|
||||
content_n[i].gpte = update_i.g_content;
|
||||
if (CVA6Cfg.RVH) content_n[i].gpte = update_i.g_content;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -285,27 +312,27 @@ module cva6_tlb_sv39x4
|
|||
// The PLRU-tree indexing:
|
||||
// lvl0 0
|
||||
// / \
|
||||
// / \
|
||||
// lvl1 1 2
|
||||
// / \ / \
|
||||
// lvl2 3 4 5 6
|
||||
// / \ /\/\ /\
|
||||
// ... ... ... ...
|
||||
// Just predefine which nodes will be set/cleared
|
||||
// E.g. for a TLB with 8 entries, the for-loop is semantically
|
||||
// equivalent to the following pseudo-code:
|
||||
// unique case (1'b1)
|
||||
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
|
||||
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
|
||||
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
|
||||
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
|
||||
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
|
||||
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
|
||||
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
|
||||
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
|
||||
// default: begin /* No hit */ end
|
||||
// endcase
|
||||
for (
|
||||
// / \
|
||||
// lvl1 1 2
|
||||
// / \ / \
|
||||
// lvl2 3 4 5 6
|
||||
// / \ /\/\ /\
|
||||
// ... ... ... ...
|
||||
// Just predefine which nodes will be set/cleared
|
||||
// E.g. for a TLB with 8 entries, the for-loop is semantically
|
||||
// equivalent to the following pseudo-code:
|
||||
// unique case (1'b1)
|
||||
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
|
||||
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
|
||||
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
|
||||
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
|
||||
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
|
||||
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
|
||||
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
|
||||
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
|
||||
// default: begin /* No hit */ end
|
||||
// endcase
|
||||
for (
|
||||
int unsigned i = 0; i < TLB_ENTRIES; i++
|
||||
) begin
|
||||
automatic int unsigned idx_base, shift, new_index;
|
|
@ -571,12 +571,6 @@ package ariane_pkg;
|
|||
endcase
|
||||
endfunction
|
||||
|
||||
// ---------------
|
||||
// MMU instanciation
|
||||
// ---------------
|
||||
localparam int unsigned INSTR_TLB_ENTRIES = cva6_config_pkg::CVA6ConfigInstrTlbEntries;
|
||||
localparam int unsigned DATA_TLB_ENTRIES = cva6_config_pkg::CVA6ConfigDataTlbEntries;
|
||||
|
||||
// -------------------
|
||||
// Performance counter
|
||||
// -------------------
|
||||
|
|
|
@ -25,6 +25,10 @@ package build_config_pkg;
|
|||
int unsigned DCACHE_INDEX_WIDTH = $clog2(CVA6Cfg.DcacheByteSize / CVA6Cfg.DcacheSetAssoc);
|
||||
int unsigned DCACHE_OFFSET_WIDTH = $clog2(CVA6Cfg.DcacheLineWidth / 8);
|
||||
|
||||
// MMU
|
||||
int unsigned VpnLen = (CVA6Cfg.XLEN == 64) ? (CVA6Cfg.RVH ? 29 : 27) : 20;
|
||||
int unsigned PtLevels = (CVA6Cfg.XLEN == 64) ? 3 : 2;
|
||||
|
||||
config_pkg::cva6_cfg_t cfg;
|
||||
|
||||
cfg.XLEN = CVA6Cfg.XLEN;
|
||||
|
@ -147,6 +151,10 @@ package build_config_pkg;
|
|||
cfg.SVX = (cfg.MODE_SV == config_pkg::ModeSv32) ? 34 : 41;
|
||||
cfg.InstrTlbEntries = CVA6Cfg.InstrTlbEntries;
|
||||
cfg.DataTlbEntries = CVA6Cfg.DataTlbEntries;
|
||||
cfg.UseSharedTlb = CVA6Cfg.UseSharedTlb;
|
||||
cfg.SharedTlbDepth = CVA6Cfg.SharedTlbDepth;
|
||||
cfg.VpnLen = VpnLen;
|
||||
cfg.PtLevels = PtLevels;
|
||||
|
||||
return cfg;
|
||||
endfunction
|
||||
|
|
|
@ -184,6 +184,10 @@ package config_pkg;
|
|||
int unsigned InstrTlbEntries;
|
||||
// MMU data TLB entries
|
||||
int unsigned DataTlbEntries;
|
||||
// MMU option to use shared TLB
|
||||
bit unsigned UseSharedTlb;
|
||||
// MMU depth of shared TLB
|
||||
int unsigned SharedTlbDepth;
|
||||
} cva6_user_cfg_t;
|
||||
|
||||
typedef struct packed {
|
||||
|
@ -253,6 +257,10 @@ package config_pkg;
|
|||
int unsigned BHTEntries;
|
||||
int unsigned InstrTlbEntries;
|
||||
int unsigned DataTlbEntries;
|
||||
bit unsigned UseSharedTlb;
|
||||
int unsigned SharedTlbDepth;
|
||||
int unsigned VpnLen;
|
||||
int unsigned PtLevels;
|
||||
|
||||
logic [63:0] DmBaseAddress;
|
||||
bit TvalEn;
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
||||
localparam CVA6ConfigDataTlbEntries = 2;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 0;
|
||||
localparam CVA6ConfigBTBEntries = 0;
|
||||
localparam CVA6ConfigBHTEntries = 0;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -26,9 +26,6 @@ package cva6_config_pkg;
|
|||
|
||||
localparam CVA6ConfigNrScoreboardEntries = 4; // cvxif_pkg.sv
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2; // MMU
|
||||
localparam CVA6ConfigDataTlbEntries = 2; // MMU
|
||||
|
||||
localparam config_pkg::cva6_user_cfg_t cva6_cfg = '{
|
||||
XLEN: unsigned'(CVA6ConfigXlen),
|
||||
FpgaEn: bit'(0),
|
||||
|
@ -94,8 +91,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(2),
|
||||
FetchUserWidth: unsigned'(32),
|
||||
FetchUserEn: unsigned'(0),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(0),
|
||||
NrStorePipeRegs: int'(0),
|
||||
DcacheIdWidth: int'(1)
|
||||
|
|
|
@ -57,9 +57,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 1;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
||||
localparam CVA6ConfigDataTlbEntries = 2;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 0;
|
||||
localparam CVA6ConfigBHTEntries = 32;
|
||||
|
@ -141,8 +138,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
||||
localparam CVA6ConfigDataTlbEntries = 2;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrLoadPipeRegs = 1;
|
||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
||||
localparam CVA6ConfigDataTlbEntries = 2;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
||||
localparam CVA6ConfigDataTlbEntries = 2;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
||||
localparam CVA6ConfigDataTlbEntries = 2;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(2),
|
||||
DataTlbEntries: int'(2),
|
||||
UseSharedTlb: bit'(1),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -65,9 +65,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 8;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -149,8 +146,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
|||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||
|
||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
||||
localparam CVA6ConfigDataTlbEntries = 16;
|
||||
|
||||
localparam CVA6ConfigRASDepth = 2;
|
||||
localparam CVA6ConfigBTBEntries = 32;
|
||||
localparam CVA6ConfigBHTEntries = 128;
|
||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
|||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||
DCacheType: CVA6ConfigDcacheType,
|
||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
||||
InstrTlbEntries: int'(16),
|
||||
DataTlbEntries: int'(16),
|
||||
UseSharedTlb: bit'(0),
|
||||
SharedTlbDepth: int'(64),
|
||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||
|
|
|
@ -228,120 +228,65 @@ module load_store_unit
|
|||
|
||||
logic hs_ld_st_inst;
|
||||
logic hlvx_inst;
|
||||
|
||||
logic [2:0] enable_translation, en_ld_st_translation, flush_tlb;
|
||||
logic [1:0] sum, mxr;
|
||||
logic [CVA6Cfg.PPNW-1:0] satp_ppn[2:0];
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid[2:0], asid_to_be_flushed[1:0];
|
||||
logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed[1:0];
|
||||
// -------------------
|
||||
// MMU e.g.: TLBs/PTW
|
||||
// -------------------
|
||||
if (CVA6Cfg.MmuPresent && CVA6Cfg.RVH && (CVA6Cfg.XLEN == 64)) begin : gen_mmu_sv39x4
|
||||
cva6_mmu_sv39x4 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.exception_t (exception_t),
|
||||
.icache_areq_t (icache_areq_t),
|
||||
.icache_arsp_t (icache_arsp_t),
|
||||
.icache_dreq_t (icache_dreq_t),
|
||||
.icache_drsp_t (icache_drsp_t),
|
||||
.dcache_req_i_t (dcache_req_i_t),
|
||||
.dcache_req_o_t (dcache_req_o_t),
|
||||
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
|
||||
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
|
||||
|
||||
if (CVA6Cfg.MmuPresent) begin : gen_mmu
|
||||
localparam HYP_EXT = CVA6Cfg.RVH ? 1 : 0;
|
||||
|
||||
cva6_mmu #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.exception_t (exception_t),
|
||||
.icache_areq_t (icache_areq_t),
|
||||
.icache_arsp_t (icache_arsp_t),
|
||||
.icache_dreq_t (icache_dreq_t),
|
||||
.icache_drsp_t (icache_drsp_t),
|
||||
.dcache_req_i_t(dcache_req_i_t),
|
||||
.dcache_req_o_t(dcache_req_o_t),
|
||||
.HYP_EXT (HYP_EXT)
|
||||
) i_cva6_mmu (
|
||||
.clk_i(clk_i),
|
||||
.rst_ni(rst_ni),
|
||||
.flush_i(flush_i),
|
||||
.icache_areq_i(icache_areq_i),
|
||||
.icache_areq_o(icache_areq_o),
|
||||
// misaligned bypass
|
||||
.misaligned_ex_i(misaligned_exception),
|
||||
.lsu_is_store_i (st_translation_req),
|
||||
.lsu_req_i (translation_req),
|
||||
.lsu_vaddr_i (mmu_vaddr),
|
||||
.lsu_tinst_i (mmu_tinst),
|
||||
.lsu_req_i(translation_req),
|
||||
.lsu_vaddr_i(mmu_vaddr),
|
||||
.lsu_tinst_i(mmu_tinst),
|
||||
.lsu_is_store_i(st_translation_req),
|
||||
.csr_hs_ld_st_inst_o(csr_hs_ld_st_inst_o),
|
||||
.lsu_dtlb_hit_o(dtlb_hit), // send in the same cycle as the request
|
||||
.lsu_dtlb_ppn_o(dtlb_ppn), // send in the same cycle as the request
|
||||
|
||||
.lsu_valid_o (translation_valid),
|
||||
.lsu_paddr_o (mmu_paddr),
|
||||
.lsu_exception_o(mmu_exception),
|
||||
.lsu_dtlb_hit_o (dtlb_hit), // send in the same cycle as the request
|
||||
.lsu_dtlb_ppn_o (dtlb_ppn), // send in the same cycle as the request
|
||||
// connecting PTW to D$ IF
|
||||
.req_port_i (dcache_req_ports_i[0]),
|
||||
.req_port_o (dcache_req_ports_o[0]),
|
||||
// icache address translation requests
|
||||
.icache_areq_i (icache_areq_i),
|
||||
.asid_to_be_flushed_i,
|
||||
.vmid_to_be_flushed_i,
|
||||
.vaddr_to_be_flushed_i,
|
||||
.gpaddr_to_be_flushed_i,
|
||||
.icache_areq_o (icache_areq_o),
|
||||
.pmpcfg_i,
|
||||
.pmpaddr_i,
|
||||
// Hypervisor load/store signals
|
||||
|
||||
.priv_lvl_i (priv_lvl_i),
|
||||
.ld_st_priv_lvl_i(ld_st_priv_lvl_i),
|
||||
|
||||
.hlvx_inst_i (mmu_hlvx_inst),
|
||||
.hs_ld_st_inst_i(mmu_hs_ld_st_inst),
|
||||
.*
|
||||
);
|
||||
end else if (CVA6Cfg.MmuPresent && (CVA6Cfg.XLEN == 64)) begin : gen_mmu_sv39
|
||||
mmu #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.exception_t (exception_t),
|
||||
.icache_areq_t (icache_areq_t),
|
||||
.icache_arsp_t (icache_arsp_t),
|
||||
.icache_dreq_t (icache_dreq_t),
|
||||
.icache_drsp_t (icache_drsp_t),
|
||||
.dcache_req_i_t (dcache_req_i_t),
|
||||
.dcache_req_o_t (dcache_req_o_t),
|
||||
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
|
||||
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
|
||||
) i_cva6_mmu (
|
||||
// misaligned bypass
|
||||
.misaligned_ex_i(misaligned_exception),
|
||||
.lsu_is_store_i (st_translation_req),
|
||||
.lsu_req_i (translation_req),
|
||||
.lsu_vaddr_i (mmu_vaddr),
|
||||
.lsu_valid_o (translation_valid),
|
||||
.lsu_paddr_o (mmu_paddr),
|
||||
.lsu_exception_o(mmu_exception),
|
||||
.lsu_dtlb_hit_o (dtlb_hit), // send in the same cycle as the request
|
||||
.lsu_dtlb_ppn_o (dtlb_ppn), // send in the same cycle as the request
|
||||
// connecting PTW to D$ IF
|
||||
.req_port_i (dcache_req_ports_i[0]),
|
||||
.req_port_o (dcache_req_ports_o[0]),
|
||||
// icache address translation requests
|
||||
.icache_areq_i (icache_areq_i),
|
||||
.asid_to_be_flushed_i,
|
||||
.vaddr_to_be_flushed_i,
|
||||
.icache_areq_o (icache_areq_o),
|
||||
.pmpcfg_i,
|
||||
.pmpaddr_i,
|
||||
.*
|
||||
);
|
||||
end else if (CVA6Cfg.MmuPresent && (CVA6Cfg.XLEN == 32)) begin : gen_mmu_sv32
|
||||
cva6_mmu_sv32 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.exception_t (exception_t),
|
||||
.icache_areq_t (icache_areq_t),
|
||||
.icache_arsp_t (icache_arsp_t),
|
||||
.icache_dreq_t (icache_dreq_t),
|
||||
.icache_drsp_t (icache_drsp_t),
|
||||
.dcache_req_i_t (dcache_req_i_t),
|
||||
.dcache_req_o_t (dcache_req_o_t),
|
||||
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
|
||||
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
|
||||
) i_cva6_mmu (
|
||||
// misaligned bypass
|
||||
.misaligned_ex_i(misaligned_exception),
|
||||
.lsu_is_store_i (st_translation_req),
|
||||
.lsu_req_i (translation_req),
|
||||
.lsu_vaddr_i (mmu_vaddr),
|
||||
.lsu_valid_o (translation_valid),
|
||||
.lsu_paddr_o (mmu_paddr),
|
||||
.lsu_exception_o(mmu_exception),
|
||||
.lsu_dtlb_hit_o (dtlb_hit), // send in the same cycle as the request
|
||||
.lsu_dtlb_ppn_o (dtlb_ppn), // send in the same cycle as the request
|
||||
// connecting PTW to D$ IF
|
||||
.req_port_i (dcache_req_ports_i[0]),
|
||||
.req_port_o (dcache_req_ports_o[0]),
|
||||
// icache address translation requests
|
||||
.icache_areq_i (icache_areq_i),
|
||||
.asid_to_be_flushed_i,
|
||||
.vaddr_to_be_flushed_i,
|
||||
.icache_areq_o (icache_areq_o),
|
||||
|
||||
.itlb_miss_o(itlb_miss_o),
|
||||
.dtlb_miss_o(dtlb_miss_o),
|
||||
|
||||
.req_port_i(dcache_req_ports_i[0]),
|
||||
.req_port_o(dcache_req_ports_o[0]),
|
||||
.pmpcfg_i,
|
||||
.pmpaddr_i,
|
||||
.*
|
||||
);
|
||||
|
||||
end else begin : gen_no_mmu
|
||||
|
||||
if (CVA6Cfg.VLEN > CVA6Cfg.PLEN) begin
|
||||
|
|
|
@ -1,592 +0,0 @@
|
|||
// Copyright (c) 2021 Thales.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Sebastien Jacq Thales Research & Technology
|
||||
// Date: 17/07/2021
|
||||
//
|
||||
// Additional contributions by:
|
||||
// Sebastien Jacq - sjthales on github.com
|
||||
//
|
||||
// Description: Memory Management Unit for CV32A6, contains TLB and
|
||||
// address translation unit. Sv32 as defined in RISC-V
|
||||
// privilege specification 1.11-WIP.
|
||||
// This module is an adaptation of the MMU Sv39 developed
|
||||
// by Florian Zaruba to the Sv32 standard.
|
||||
//
|
||||
// =========================================================================== //
|
||||
// Revisions :
|
||||
// Date Version Author Description
|
||||
// 2020-02-17 0.1 S.Jacq MMU Sv32 for CV32A6
|
||||
// =========================================================================== //
|
||||
|
||||
module cva6_mmu_sv32
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type exception_t = logic,
|
||||
parameter type icache_areq_t = logic,
|
||||
parameter type icache_arsp_t = logic,
|
||||
parameter type icache_dreq_t = logic,
|
||||
parameter type icache_drsp_t = logic,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter int unsigned INSTR_TLB_ENTRIES = 2,
|
||||
parameter int unsigned DATA_TLB_ENTRIES = 2
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_ni,
|
||||
input logic flush_i,
|
||||
input logic enable_translation_i,
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
// IF interface
|
||||
input icache_arsp_t icache_areq_i,
|
||||
output icache_areq_t icache_areq_o,
|
||||
// LSU interface
|
||||
// this is a more minimalistic interface because the actual addressing logic is handled
|
||||
// in the LSU as we distinguish load and stores, what we do here is simple address translation
|
||||
input exception_t misaligned_ex_i,
|
||||
input logic lsu_req_i, // request address translation
|
||||
input logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_i, // virtual address in
|
||||
input logic lsu_is_store_i, // the translation is requested by a store
|
||||
// if we need to walk the page table we can't grant in the same cycle
|
||||
// Cycle 0
|
||||
output logic lsu_dtlb_hit_o, // sent in the same cycle as the request if translation hits in the DTLB
|
||||
output logic [CVA6Cfg.PPNW-1:0] lsu_dtlb_ppn_o, // ppn (send same cycle as hit)
|
||||
// Cycle 1
|
||||
output logic lsu_valid_o, // translation is valid
|
||||
output logic [CVA6Cfg.PLEN-1:0] lsu_paddr_o, // translated address
|
||||
output exception_t lsu_exception_o, // address translation threw an exception
|
||||
// General control signals
|
||||
input riscv::priv_lvl_t priv_lvl_i,
|
||||
input riscv::priv_lvl_t ld_st_priv_lvl_i,
|
||||
input logic sum_i,
|
||||
input logic mxr_i,
|
||||
// input logic flag_mprv_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
input logic flush_tlb_i,
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
// PMP
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i
|
||||
);
|
||||
|
||||
logic iaccess_err; // insufficient privilege to access this instruction page
|
||||
logic daccess_err; // insufficient privilege to access this data page
|
||||
logic ptw_active; // PTW is currently walking a page table
|
||||
logic walking_instr; // PTW is walking because of an ITLB miss
|
||||
logic ptw_error; // PTW threw an exception
|
||||
logic ptw_access_exception; // PTW threw an access exception (PMPs)
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_bad_paddr; // PTW PMP exception bad physical addr
|
||||
|
||||
logic [CVA6Cfg.VLEN-1:0] update_vaddr;
|
||||
tlb_update_sv32_t update_itlb, update_dtlb, update_shared_tlb;
|
||||
|
||||
logic itlb_lu_access;
|
||||
riscv::pte_sv32_t itlb_content;
|
||||
logic itlb_is_4M;
|
||||
logic itlb_lu_hit;
|
||||
|
||||
logic dtlb_lu_access;
|
||||
riscv::pte_sv32_t dtlb_content;
|
||||
logic dtlb_is_4M;
|
||||
logic dtlb_lu_hit;
|
||||
|
||||
logic shared_tlb_access;
|
||||
logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr;
|
||||
logic shared_tlb_hit;
|
||||
|
||||
logic itlb_req;
|
||||
|
||||
|
||||
// Assignments
|
||||
assign itlb_lu_access = icache_areq_i.fetch_req;
|
||||
assign dtlb_lu_access = lsu_req_i;
|
||||
|
||||
|
||||
cva6_tlb_sv32 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.TLB_ENTRIES(INSTR_TLB_ENTRIES)
|
||||
) i_itlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i(flush_tlb_i),
|
||||
|
||||
.update_i(update_itlb),
|
||||
|
||||
.lu_access_i (itlb_lu_access),
|
||||
.lu_asid_i (asid_i),
|
||||
.asid_to_be_flushed_i (asid_to_be_flushed_i),
|
||||
.vaddr_to_be_flushed_i(vaddr_to_be_flushed_i),
|
||||
.lu_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
.lu_content_o (itlb_content),
|
||||
|
||||
.lu_is_4M_o(itlb_is_4M),
|
||||
.lu_hit_o (itlb_lu_hit)
|
||||
);
|
||||
|
||||
cva6_tlb_sv32 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.TLB_ENTRIES(DATA_TLB_ENTRIES)
|
||||
) i_dtlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i(flush_tlb_i),
|
||||
|
||||
.update_i(update_dtlb),
|
||||
|
||||
.lu_access_i (dtlb_lu_access),
|
||||
.lu_asid_i (asid_i),
|
||||
.asid_to_be_flushed_i (asid_to_be_flushed_i),
|
||||
.vaddr_to_be_flushed_i(vaddr_to_be_flushed_i),
|
||||
.lu_vaddr_i (lsu_vaddr_i),
|
||||
.lu_content_o (dtlb_content),
|
||||
|
||||
.lu_is_4M_o(dtlb_is_4M),
|
||||
.lu_hit_o (dtlb_lu_hit)
|
||||
);
|
||||
|
||||
cva6_shared_tlb_sv32 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.SHARED_TLB_DEPTH(64),
|
||||
.SHARED_TLB_WAYS (2)
|
||||
) i_shared_tlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i(flush_tlb_i),
|
||||
|
||||
.enable_translation_i (enable_translation_i),
|
||||
.en_ld_st_translation_i(en_ld_st_translation_i),
|
||||
|
||||
.asid_i (asid_i),
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
.itlb_access_i(itlb_lu_access),
|
||||
.itlb_hit_i (itlb_lu_hit),
|
||||
.itlb_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
|
||||
.dtlb_access_i(dtlb_lu_access),
|
||||
.dtlb_hit_i (dtlb_lu_hit),
|
||||
.dtlb_vaddr_i (lsu_vaddr_i),
|
||||
|
||||
// to TLBs, update logic
|
||||
.itlb_update_o(update_itlb),
|
||||
.dtlb_update_o(update_dtlb),
|
||||
|
||||
// Performance counters
|
||||
.itlb_miss_o(itlb_miss_o),
|
||||
.dtlb_miss_o(dtlb_miss_o),
|
||||
|
||||
.shared_tlb_access_o(shared_tlb_access),
|
||||
.shared_tlb_hit_o (shared_tlb_hit),
|
||||
.shared_tlb_vaddr_o (shared_tlb_vaddr),
|
||||
|
||||
.itlb_req_o (itlb_req),
|
||||
// to update shared tlb
|
||||
.shared_tlb_update_i(update_shared_tlb)
|
||||
);
|
||||
|
||||
cva6_ptw_sv32 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.dcache_req_i_t(dcache_req_i_t),
|
||||
.dcache_req_o_t(dcache_req_o_t)
|
||||
) i_ptw (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i(flush_i),
|
||||
|
||||
.ptw_active_o (ptw_active),
|
||||
.walking_instr_o (walking_instr),
|
||||
.ptw_error_o (ptw_error),
|
||||
.ptw_access_exception_o(ptw_access_exception),
|
||||
|
||||
.lsu_is_store_i(lsu_is_store_i),
|
||||
// PTW memory interface
|
||||
.req_port_i (req_port_i),
|
||||
.req_port_o (req_port_o),
|
||||
|
||||
// to Shared TLB, update logic
|
||||
.shared_tlb_update_o(update_shared_tlb),
|
||||
|
||||
.update_vaddr_o(update_vaddr),
|
||||
|
||||
.asid_i(asid_i),
|
||||
|
||||
// from shared TLB
|
||||
// did we miss?
|
||||
.shared_tlb_access_i(shared_tlb_access),
|
||||
.shared_tlb_hit_i (shared_tlb_hit),
|
||||
.shared_tlb_vaddr_i (shared_tlb_vaddr),
|
||||
|
||||
.itlb_req_i(itlb_req),
|
||||
|
||||
// from CSR file
|
||||
.satp_ppn_i(satp_ppn_i), // ppn from satp
|
||||
.mxr_i (mxr_i),
|
||||
|
||||
// Performance counters
|
||||
.shared_tlb_miss_o(), //open for now
|
||||
|
||||
// PMP
|
||||
.pmpcfg_i (pmpcfg_i),
|
||||
.pmpaddr_i (pmpaddr_i),
|
||||
.bad_paddr_o(ptw_bad_paddr)
|
||||
|
||||
);
|
||||
|
||||
// ila_1 i_ila_1 (
|
||||
// .clk(clk_i), // input wire clk
|
||||
// .probe0({req_port_o.address_tag, req_port_o.address_index}),
|
||||
// .probe1(req_port_o.data_req), // input wire [63:0] probe1
|
||||
// .probe2(req_port_i.data_gnt), // input wire [0:0] probe2
|
||||
// .probe3(req_port_i.data_rdata), // input wire [0:0] probe3
|
||||
// .probe4(req_port_i.data_rvalid), // input wire [0:0] probe4
|
||||
// .probe5(ptw_error), // input wire [1:0] probe5
|
||||
// .probe6(update_vaddr), // input wire [0:0] probe6
|
||||
// .probe7(update_itlb.valid), // input wire [0:0] probe7
|
||||
// .probe8(update_dtlb.valid), // input wire [0:0] probe8
|
||||
// .probe9(dtlb_lu_access), // input wire [0:0] probe9
|
||||
// .probe10(lsu_vaddr_i), // input wire [0:0] probe10
|
||||
// .probe11(dtlb_lu_hit), // input wire [0:0] probe11
|
||||
// .probe12(itlb_lu_access), // input wire [0:0] probe12
|
||||
// .probe13(icache_areq_i.fetch_vaddr), // input wire [0:0] probe13
|
||||
// .probe14(itlb_lu_hit) // input wire [0:0] probe13
|
||||
// );
|
||||
|
||||
//-----------------------
|
||||
// Instruction Interface
|
||||
//-----------------------
|
||||
logic match_any_execute_region;
|
||||
logic pmp_instr_allow;
|
||||
|
||||
// The instruction interface is a simple request response interface
|
||||
always_comb begin : instr_interface
|
||||
// MMU disabled: just pass through
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
if (CVA6Cfg.PLEN > CVA6Cfg.VLEN)
|
||||
icache_areq_o.fetch_paddr = {
|
||||
{CVA6Cfg.PLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr
|
||||
}; // play through in case we disabled address translation
|
||||
else
|
||||
icache_areq_o.fetch_paddr = {
|
||||
2'b00, icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:0]
|
||||
}; // play through in case we disabled address translation
|
||||
// two potential exception sources:
|
||||
// 1. HPTW threw an exception -> signal with a page fault exception
|
||||
// 2. We got an access error because of insufficient permissions -> throw an access exception
|
||||
icache_areq_o.fetch_exception = '0;
|
||||
// Check whether we are allowed to access this memory region from a fetch perspective
|
||||
iaccess_err = icache_areq_i.fetch_req && (((priv_lvl_i == riscv::PRIV_LVL_U) && ~itlb_content.u)
|
||||
|| ((priv_lvl_i == riscv::PRIV_LVL_S) && itlb_content.u));
|
||||
|
||||
// MMU enabled: address from TLB, request delayed until hit. Error when TLB
|
||||
// hit and no access right or TLB hit and translated address not valid (e.g.
|
||||
// AXI decode error), or when PTW performs walk due to ITLB miss and raises
|
||||
// an error.
|
||||
if (enable_translation_i) begin
|
||||
// we work with SV32, so if VM is enabled, check that all bits [CVA6Cfg.VLEN-1:CVA6Cfg.SV-1] are equal
|
||||
if (icache_areq_i.fetch_req && !((&icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b1 || (|icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b0)) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr
|
||||
};
|
||||
end
|
||||
|
||||
icache_areq_o.fetch_valid = 1'b0;
|
||||
|
||||
// 4K page
|
||||
icache_areq_o.fetch_paddr = {itlb_content.ppn, icache_areq_i.fetch_vaddr[11:0]};
|
||||
// Mega page
|
||||
if (itlb_is_4M) begin
|
||||
icache_areq_o.fetch_paddr[21:12] = icache_areq_i.fetch_vaddr[21:12];
|
||||
end
|
||||
|
||||
|
||||
// ---------
|
||||
// ITLB Hit
|
||||
// --------
|
||||
// if we hit the ITLB output the request signal immediately
|
||||
if (itlb_lu_hit) begin
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
// we got an access error
|
||||
if (iaccess_err) begin
|
||||
// throw a page fault
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr
|
||||
};
|
||||
//to check on wave --> not connected
|
||||
end else if (!pmp_instr_allow) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) icache_areq_o.fetch_exception.tval = icache_areq_i.fetch_vaddr;
|
||||
//to check on wave --> not connected
|
||||
end
|
||||
end else
|
||||
// ---------
|
||||
// ITLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions happening during walking the page table
|
||||
if (ptw_active && walking_instr) begin
|
||||
icache_areq_o.fetch_valid = ptw_error | ptw_access_exception;
|
||||
if (ptw_error) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, update_vaddr
|
||||
};
|
||||
end //to check on wave
|
||||
// TODO(moschn,zarubaf): What should the value of tval be in this case?
|
||||
else begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) icache_areq_o.fetch_exception.tval = ptw_bad_paddr[CVA6Cfg.PLEN-1:2];
|
||||
end
|
||||
end
|
||||
end
|
||||
// if it didn't match any execute region throw an `Instruction Access Fault`
|
||||
// or: if we are not translating, check PMPs immediately on the paddr
|
||||
if (!match_any_execute_region || (!enable_translation_i && !pmp_instr_allow)) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = icache_areq_o.fetch_paddr[CVA6Cfg.PLEN-1:2];
|
||||
//to check on wave --> not connected
|
||||
end
|
||||
end
|
||||
|
||||
// check for execute flag on memory
|
||||
assign match_any_execute_region = config_pkg::is_inside_execute_regions(
|
||||
CVA6Cfg, {{64 - CVA6Cfg.PLEN{1'b0}}, icache_areq_o.fetch_paddr}
|
||||
);
|
||||
|
||||
// Instruction fetch
|
||||
pmp #(
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_if (
|
||||
.addr_i (icache_areq_o.fetch_paddr),
|
||||
.priv_lvl_i,
|
||||
// we will always execute on the instruction fetch port
|
||||
.access_type_i(riscv::ACCESS_EXEC),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_instr_allow)
|
||||
);
|
||||
|
||||
//-----------------------
|
||||
// Data Interface
|
||||
//-----------------------
|
||||
logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_n, lsu_vaddr_q;
|
||||
riscv::pte_sv32_t dtlb_pte_n, dtlb_pte_q;
|
||||
exception_t misaligned_ex_n, misaligned_ex_q;
|
||||
logic lsu_req_n, lsu_req_q;
|
||||
logic lsu_is_store_n, lsu_is_store_q;
|
||||
logic dtlb_hit_n, dtlb_hit_q;
|
||||
logic dtlb_is_4M_n, dtlb_is_4M_q;
|
||||
|
||||
// check if we need to do translation or if we are always ready (e.g.: we are not translating anything)
|
||||
assign lsu_dtlb_hit_o = (en_ld_st_translation_i) ? dtlb_lu_hit : 1'b1;
|
||||
|
||||
// Wires to PMP checks
|
||||
riscv::pmp_access_t pmp_access_type;
|
||||
logic pmp_data_allow;
|
||||
localparam PPNWMin = (CVA6Cfg.PPNW - 1 > 29) ? 29 : CVA6Cfg.PPNW - 1;
|
||||
// The data interface is simpler and only consists of a request/response interface
|
||||
always_comb begin : data_interface
|
||||
// save request and DTLB response
|
||||
lsu_vaddr_n = lsu_vaddr_i;
|
||||
lsu_req_n = lsu_req_i;
|
||||
misaligned_ex_n = misaligned_ex_i;
|
||||
dtlb_pte_n = dtlb_content;
|
||||
dtlb_hit_n = dtlb_lu_hit;
|
||||
lsu_is_store_n = lsu_is_store_i;
|
||||
dtlb_is_4M_n = dtlb_is_4M;
|
||||
|
||||
if (CVA6Cfg.PLEN > CVA6Cfg.VLEN) begin
|
||||
lsu_paddr_o = {{CVA6Cfg.PLEN - CVA6Cfg.VLEN{1'b0}}, lsu_vaddr_q};
|
||||
lsu_dtlb_ppn_o = {{CVA6Cfg.PLEN - CVA6Cfg.VLEN{1'b0}}, lsu_vaddr_n[CVA6Cfg.VLEN-1:12]};
|
||||
end else begin
|
||||
lsu_paddr_o = {2'b00, lsu_vaddr_q[CVA6Cfg.VLEN-1:0]};
|
||||
lsu_dtlb_ppn_o = lsu_vaddr_n[CVA6Cfg.PPNW-1:0];
|
||||
end
|
||||
lsu_valid_o = lsu_req_q;
|
||||
lsu_exception_o = misaligned_ex_q;
|
||||
pmp_access_type = lsu_is_store_q ? riscv::ACCESS_WRITE : riscv::ACCESS_READ;
|
||||
|
||||
// mute misaligned exceptions if there is no request otherwise they will throw accidental exceptions
|
||||
misaligned_ex_n.valid = misaligned_ex_i.valid & lsu_req_i;
|
||||
|
||||
// Check if the User flag is set, then we may only access it in supervisor mode
|
||||
// if SUM is enabled
|
||||
daccess_err = (ld_st_priv_lvl_i == riscv::PRIV_LVL_S && !sum_i && dtlb_pte_q.u) || // SUM is not set and we are trying to access a user page in supervisor mode
|
||||
(ld_st_priv_lvl_i == riscv::PRIV_LVL_U && !dtlb_pte_q.u); // this is not a user page but we are in user mode and trying to access it
|
||||
// translation is enabled and no misaligned exception occurred
|
||||
if (en_ld_st_translation_i && !misaligned_ex_q.valid) begin
|
||||
lsu_valid_o = 1'b0;
|
||||
// 4K page
|
||||
lsu_paddr_o = {dtlb_pte_q.ppn, lsu_vaddr_q[11:0]};
|
||||
lsu_dtlb_ppn_o = dtlb_content.ppn;
|
||||
// Mega page
|
||||
if (dtlb_is_4M_q) begin
|
||||
lsu_paddr_o[21:12] = lsu_vaddr_q[21:12];
|
||||
lsu_dtlb_ppn_o[21:12] = lsu_vaddr_n[21:12];
|
||||
end
|
||||
// ---------
|
||||
// DTLB Hit
|
||||
// --------
|
||||
if (dtlb_hit_q && lsu_req_q) begin
|
||||
lsu_valid_o = 1'b1;
|
||||
// exception priority:
|
||||
// PAGE_FAULTS have higher priority than ACCESS_FAULTS
|
||||
// virtual memory based exceptions are PAGE_FAULTS
|
||||
// physical memory based exceptions are ACCESS_FAULTS (PMA/PMP)
|
||||
|
||||
// this is a store
|
||||
if (lsu_is_store_q) begin
|
||||
// check if the page is write-able and we are not violating privileges
|
||||
// also check if the dirty flag is set
|
||||
if (!dtlb_pte_q.w || daccess_err || !dtlb_pte_q.d) begin
|
||||
lsu_exception_o.cause = riscv::STORE_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
// to check on wave
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) lsu_exception_o.tval = lsu_paddr_o[CVA6Cfg.PLEN-1:2];
|
||||
//only 32 bits on 34b of lsu_paddr_o are returned.
|
||||
end
|
||||
|
||||
// this is a load
|
||||
end else begin
|
||||
// check for sufficient access privileges - throw a page fault if necessary
|
||||
if (daccess_err) begin
|
||||
lsu_exception_o.cause = riscv::LOAD_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) lsu_exception_o.tval = lsu_paddr_o[CVA6Cfg.PLEN-1:2];
|
||||
end
|
||||
end
|
||||
end else
|
||||
|
||||
// ---------
|
||||
// DTLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions
|
||||
if (ptw_active && !walking_instr) begin
|
||||
// page table walker threw an exception
|
||||
if (ptw_error) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// the page table walker can only throw page faults
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o.cause = riscv::STORE_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LOAD_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
if (ptw_access_exception) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// the page table walker can only throw page faults
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) lsu_exception_o.tval = ptw_bad_paddr[CVA6Cfg.PLEN-1:2];
|
||||
end
|
||||
end
|
||||
end // If translation is not enabled, check the paddr immediately against PMPs
|
||||
else if (lsu_req_q && !misaligned_ex_q.valid && !pmp_data_allow) begin
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) lsu_exception_o.tval = lsu_paddr_o[CVA6Cfg.PLEN-1:2];
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn) lsu_exception_o.tval = lsu_paddr_o[CVA6Cfg.PLEN-1:2];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Load/store PMP check
|
||||
pmp #(
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_data (
|
||||
.addr_i (lsu_paddr_o),
|
||||
.priv_lvl_i (ld_st_priv_lvl_i),
|
||||
.access_type_i(pmp_access_type),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_data_allow)
|
||||
);
|
||||
|
||||
// ----------
|
||||
// Registers
|
||||
// ----------
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
lsu_vaddr_q <= '0;
|
||||
lsu_req_q <= '0;
|
||||
misaligned_ex_q <= '0;
|
||||
dtlb_pte_q <= '0;
|
||||
dtlb_hit_q <= '0;
|
||||
lsu_is_store_q <= '0;
|
||||
dtlb_is_4M_q <= '0;
|
||||
end else begin
|
||||
lsu_vaddr_q <= lsu_vaddr_n;
|
||||
lsu_req_q <= lsu_req_n;
|
||||
misaligned_ex_q <= misaligned_ex_n;
|
||||
dtlb_pte_q <= dtlb_pte_n;
|
||||
dtlb_hit_q <= dtlb_hit_n;
|
||||
lsu_is_store_q <= lsu_is_store_n;
|
||||
dtlb_is_4M_q <= dtlb_is_4M_n;
|
||||
end
|
||||
end
|
||||
endmodule
|
|
@ -1,401 +0,0 @@
|
|||
// Copyright (c) 2021 Thales.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Sebastien Jacq Thales Research & Technology
|
||||
// Date: 17/07/2021
|
||||
//
|
||||
// Additional contributions by:
|
||||
// Sebastien Jacq - sjthales on github.com
|
||||
//
|
||||
// Description: Hardware-PTW (Page-Table-Walker) for MMU Sv32.
|
||||
// This module is an adaptation of the Sv39 PTW developed
|
||||
// by Florian Zaruba and David Schaffenrath to the Sv32 standard.
|
||||
//
|
||||
// =========================================================================== //
|
||||
// Revisions :
|
||||
// Date Version Author Description
|
||||
// 2020-02-17 0.1 S.Jacq PTW Sv32 for CV32A6
|
||||
// =========================================================================== //
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
|
||||
module cva6_ptw_sv32
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // flush everything, we need to do this because
|
||||
// actually everything we do is speculative at this stage
|
||||
// e.g.: there could be a CSR instruction that changes everything
|
||||
output logic ptw_active_o,
|
||||
output logic walking_instr_o, // set when walking for TLB
|
||||
output logic ptw_error_o, // set when an error occurred
|
||||
output logic ptw_access_exception_o, // set when an PMP access exception occured
|
||||
|
||||
input logic lsu_is_store_i, // this translation was triggered by a store
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
|
||||
// to Shared TLB, update logic
|
||||
output tlb_update_sv32_t shared_tlb_update_o,
|
||||
|
||||
output logic [CVA6Cfg.VLEN-1:0] update_vaddr_o,
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
|
||||
// from shared TLB
|
||||
input logic shared_tlb_access_i,
|
||||
input logic shared_tlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr_i,
|
||||
|
||||
input logic itlb_req_i,
|
||||
|
||||
// from CSR file
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i, // ppn from satp
|
||||
input logic mxr_i,
|
||||
|
||||
// Performance counters
|
||||
output logic shared_tlb_miss_o,
|
||||
|
||||
// PMP
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i,
|
||||
output logic [CVA6Cfg.PLEN-1:0] bad_paddr_o
|
||||
|
||||
);
|
||||
|
||||
// input registers
|
||||
logic data_rvalid_q;
|
||||
logic [CVA6Cfg.XLEN-1:0] data_rdata_q;
|
||||
|
||||
riscv::pte_sv32_t pte;
|
||||
assign pte = riscv::pte_sv32_t'(data_rdata_q);
|
||||
|
||||
|
||||
enum logic [2:0] {
|
||||
IDLE,
|
||||
WAIT_GRANT,
|
||||
PTE_LOOKUP,
|
||||
WAIT_RVALID,
|
||||
PROPAGATE_ERROR,
|
||||
PROPAGATE_ACCESS_ERROR,
|
||||
LATENCY
|
||||
}
|
||||
state_q, state_d;
|
||||
|
||||
// SV32 defines two levels of page tables
|
||||
enum logic {
|
||||
LVL1,
|
||||
LVL2
|
||||
}
|
||||
ptw_lvl_q, ptw_lvl_n;
|
||||
|
||||
// is this an instruction page table walk?
|
||||
logic is_instr_ptw_q, is_instr_ptw_n;
|
||||
logic global_mapping_q, global_mapping_n;
|
||||
// latched tag signal
|
||||
logic tag_valid_n, tag_valid_q;
|
||||
// register the ASID
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] tlb_update_asid_q, tlb_update_asid_n;
|
||||
// register the VPN we need to walk, SV32 defines a 32 bit virtual address
|
||||
logic [CVA6Cfg.VLEN-1:0] vaddr_q, vaddr_n;
|
||||
// 4 byte aligned physical pointer
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_pptr_q, ptw_pptr_n;
|
||||
|
||||
// Assignments
|
||||
assign update_vaddr_o = vaddr_q;
|
||||
|
||||
assign ptw_active_o = (state_q != IDLE);
|
||||
//assign walking_instr_o = is_instr_ptw_q;
|
||||
assign walking_instr_o = is_instr_ptw_q;
|
||||
// directly output the correct physical address
|
||||
assign req_port_o.address_index = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH-1:0];
|
||||
assign req_port_o.address_tag = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH+CVA6Cfg.DCACHE_TAG_WIDTH-1:CVA6Cfg.DCACHE_INDEX_WIDTH];
|
||||
// we are never going to kill this request
|
||||
assign req_port_o.kill_req = '0;
|
||||
// we are never going to write with the HPTW
|
||||
assign req_port_o.data_wdata = '0;
|
||||
// we only issue one single request at a time
|
||||
assign req_port_o.data_id = '0;
|
||||
|
||||
// -----------
|
||||
// Shared TLB Update
|
||||
// -----------
|
||||
assign shared_tlb_update_o.vpn = vaddr_q[CVA6Cfg.SV-1:12];
|
||||
// update the correct page table level
|
||||
assign shared_tlb_update_o.is_4M = (ptw_lvl_q == LVL1);
|
||||
// output the correct ASID
|
||||
assign shared_tlb_update_o.asid = tlb_update_asid_q;
|
||||
// set the global mapping bit
|
||||
assign shared_tlb_update_o.content = pte | (global_mapping_q << 5);
|
||||
|
||||
|
||||
assign req_port_o.tag_valid = tag_valid_q;
|
||||
|
||||
logic allow_access;
|
||||
|
||||
assign bad_paddr_o = ptw_access_exception_o ? ptw_pptr_q : 'b0;
|
||||
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_ptw (
|
||||
.addr_i (ptw_pptr_q),
|
||||
// PTW access are always checked as if in S-Mode...
|
||||
.priv_lvl_i (riscv::PRIV_LVL_S),
|
||||
// ...and they are always loads
|
||||
.access_type_i(riscv::ACCESS_READ),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (allow_access)
|
||||
);
|
||||
|
||||
|
||||
assign req_port_o.data_be = be_gen_32(req_port_o.address_index[1:0], req_port_o.data_size);
|
||||
|
||||
//-------------------
|
||||
// Page table walker
|
||||
//-------------------
|
||||
// A virtual address va is translated into a physical address pa as follows:
|
||||
// 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39,
|
||||
// PAGESIZE=2^12 and LEVELS=3.)
|
||||
// 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. (For
|
||||
// Sv32, PTESIZE=4.)
|
||||
// 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise an access
|
||||
// exception.
|
||||
// 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to step 5.
|
||||
// Otherwise, this PTE is a pointer to the next level of the page table.
|
||||
// Let i=i-1. If i < 0, stop and raise an access exception. Otherwise, let
|
||||
// a = pte.ppn × PAGESIZE and go to step 2.
|
||||
// 5. A leaf PTE has been found. Determine if the requested memory access
|
||||
// is allowed by the pte.r, pte.w, and pte.x bits. If not, stop and
|
||||
// raise an access exception. Otherwise, the translation is successful.
|
||||
// Set pte.a to 1, and, if the memory access is a store, set pte.d to 1.
|
||||
// The translated physical address is given as follows:
|
||||
// - pa.pgoff = va.pgoff.
|
||||
// - If i > 0, then this is a superpage translation and
|
||||
// pa.ppn[i-1:0] = va.vpn[i-1:0].
|
||||
// - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i].
|
||||
always_comb begin : ptw
|
||||
// default assignments
|
||||
// PTW memory interface
|
||||
tag_valid_n = 1'b0;
|
||||
req_port_o.data_req = 1'b0;
|
||||
req_port_o.data_size = 2'b10;
|
||||
req_port_o.data_we = 1'b0;
|
||||
ptw_error_o = 1'b0;
|
||||
ptw_access_exception_o = 1'b0;
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
is_instr_ptw_n = is_instr_ptw_q;
|
||||
ptw_lvl_n = ptw_lvl_q;
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
state_d = state_q;
|
||||
global_mapping_n = global_mapping_q;
|
||||
// input registers
|
||||
tlb_update_asid_n = tlb_update_asid_q;
|
||||
vaddr_n = vaddr_q;
|
||||
|
||||
shared_tlb_miss_o = 1'b0;
|
||||
|
||||
case (state_q)
|
||||
|
||||
IDLE: begin
|
||||
// by default we start with the top-most page table
|
||||
ptw_lvl_n = LVL1;
|
||||
global_mapping_n = 1'b0;
|
||||
is_instr_ptw_n = 1'b0;
|
||||
// if we got a Shared TLB miss
|
||||
if (shared_tlb_access_i & ~shared_tlb_hit_i) begin
|
||||
ptw_pptr_n = {
|
||||
satp_ppn_i, shared_tlb_vaddr_i[CVA6Cfg.SV-1:22], 2'b0
|
||||
}; // SATP.PPN * PAGESIZE + VPN*PTESIZE = SATP.PPN * 2^(12) + VPN*4
|
||||
is_instr_ptw_n = itlb_req_i;
|
||||
tlb_update_asid_n = asid_i;
|
||||
vaddr_n = shared_tlb_vaddr_i;
|
||||
state_d = WAIT_GRANT;
|
||||
shared_tlb_miss_o = 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
WAIT_GRANT: begin
|
||||
// send a request out
|
||||
req_port_o.data_req = 1'b1;
|
||||
// wait for the WAIT_GRANT
|
||||
if (req_port_i.data_gnt) begin
|
||||
// send the tag valid signal one cycle later
|
||||
tag_valid_n = 1'b1;
|
||||
state_d = PTE_LOOKUP;
|
||||
end
|
||||
end
|
||||
|
||||
PTE_LOOKUP: begin
|
||||
// we wait for the valid signal
|
||||
if (data_rvalid_q) begin
|
||||
|
||||
// check if the global mapping bit is set
|
||||
if (pte.g) global_mapping_n = 1'b1;
|
||||
|
||||
// -------------
|
||||
// Invalid PTE
|
||||
// -------------
|
||||
// If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise a page-fault exception.
|
||||
if (!pte.v || (!pte.r && pte.w)) state_d = PROPAGATE_ERROR;
|
||||
// -----------
|
||||
// Valid PTE
|
||||
// -----------
|
||||
else begin
|
||||
//state_d = IDLE;
|
||||
state_d = LATENCY;
|
||||
// it is a valid PTE
|
||||
// if pte.r = 1 or pte.x = 1 it is a valid PTE
|
||||
if (pte.r || pte.x) begin
|
||||
// Valid translation found (either 4M or 4K entry)
|
||||
if (is_instr_ptw_q) begin
|
||||
// ------------
|
||||
// Update ITLB
|
||||
// ------------
|
||||
// If page is not executable, we can directly raise an error. This
|
||||
// doesn't put a useless entry into the TLB. The same idea applies
|
||||
// to the access flag since we let the access flag be managed by SW.
|
||||
if (!pte.x || !pte.a) state_d = PROPAGATE_ERROR;
|
||||
else shared_tlb_update_o.valid = 1'b1;
|
||||
|
||||
end else begin
|
||||
// ------------
|
||||
// Update DTLB
|
||||
// ------------
|
||||
// Check if the access flag has been set, otherwise throw a page-fault
|
||||
// and let the software handle those bits.
|
||||
// If page is not readable (there are no write-only pages)
|
||||
// we can directly raise an error. This doesn't put a useless
|
||||
// entry into the TLB.
|
||||
if (pte.a && (pte.r || (pte.x && mxr_i))) begin
|
||||
shared_tlb_update_o.valid = 1'b1;
|
||||
end else begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
end
|
||||
// Request is a store: perform some additional checks
|
||||
// If the request was a store and the page is not write-able, raise an error
|
||||
// the same applies if the dirty flag is not set
|
||||
if (lsu_is_store_i && (!pte.w || !pte.d)) begin
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
end
|
||||
end
|
||||
// check if the ppn is correctly aligned:
|
||||
// 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned superpage; stop and raise a page-fault
|
||||
// exception.
|
||||
if (ptw_lvl_q == LVL1 && pte.ppn[9:0] != '0) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
end
|
||||
// this is a pointer to the next TLB level
|
||||
end else begin
|
||||
// pointer to next level of page table
|
||||
if (ptw_lvl_q == LVL1) begin
|
||||
// we are in the second level now
|
||||
ptw_lvl_n = LVL2;
|
||||
ptw_pptr_n = {pte.ppn, vaddr_q[21:12], 2'b0};
|
||||
end
|
||||
|
||||
state_d = WAIT_GRANT;
|
||||
|
||||
if (ptw_lvl_q == LVL2) begin
|
||||
// Should already be the last level page table => Error
|
||||
ptw_lvl_n = LVL2;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Check if this access was actually allowed from a PMP perspective
|
||||
if (!allow_access) begin
|
||||
shared_tlb_update_o.valid = 1'b0;
|
||||
// we have to return the failed address in bad_addr
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
state_d = PROPAGATE_ACCESS_ERROR;
|
||||
end
|
||||
end
|
||||
// we've got a data WAIT_GRANT so tell the cache that the tag is valid
|
||||
end
|
||||
// Propagate error to MMU/LSU
|
||||
PROPAGATE_ERROR: begin
|
||||
state_d = LATENCY;
|
||||
ptw_error_o = 1'b1;
|
||||
end
|
||||
PROPAGATE_ACCESS_ERROR: begin
|
||||
state_d = LATENCY;
|
||||
ptw_access_exception_o = 1'b1;
|
||||
end
|
||||
// wait for the rvalid before going back to IDLE
|
||||
WAIT_RVALID: begin
|
||||
if (data_rvalid_q) state_d = IDLE;
|
||||
end
|
||||
LATENCY: begin
|
||||
state_d = IDLE;
|
||||
end
|
||||
default: begin
|
||||
state_d = IDLE;
|
||||
end
|
||||
endcase
|
||||
|
||||
// -------
|
||||
// Flush
|
||||
// -------
|
||||
// should we have flushed before we got an rvalid, wait for it until going back to IDLE
|
||||
if (flush_i) begin
|
||||
// on a flush check whether we are
|
||||
// 1. in the PTE Lookup check whether we still need to wait for an rvalid
|
||||
// 2. waiting for a grant, if so: wait for it
|
||||
// if not, go back to idle
|
||||
if (((state_q inside {PTE_LOOKUP, WAIT_RVALID}) && !data_rvalid_q) ||
|
||||
((state_q == WAIT_GRANT) && req_port_i.data_gnt))
|
||||
state_d = WAIT_RVALID;
|
||||
else state_d = LATENCY;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
state_q <= IDLE;
|
||||
is_instr_ptw_q <= 1'b0;
|
||||
ptw_lvl_q <= LVL1;
|
||||
tag_valid_q <= 1'b0;
|
||||
tlb_update_asid_q <= '0;
|
||||
vaddr_q <= '0;
|
||||
ptw_pptr_q <= '0;
|
||||
global_mapping_q <= 1'b0;
|
||||
data_rdata_q <= '0;
|
||||
data_rvalid_q <= 1'b0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
ptw_pptr_q <= ptw_pptr_n;
|
||||
is_instr_ptw_q <= is_instr_ptw_n;
|
||||
ptw_lvl_q <= ptw_lvl_n;
|
||||
tag_valid_q <= tag_valid_n;
|
||||
tlb_update_asid_q <= tlb_update_asid_n;
|
||||
vaddr_q <= vaddr_n;
|
||||
global_mapping_q <= global_mapping_n;
|
||||
data_rdata_q <= req_port_i.data_rdata;
|
||||
data_rvalid_q <= req_port_i.data_rvalid;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
/* verilator lint_on WIDTH */
|
|
@ -1,366 +0,0 @@
|
|||
// Copyright (c) 2023 Thales.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Sebastien Jacq - Thales Research & Technology
|
||||
// Date: 08/03/2023
|
||||
//
|
||||
// Description: N-way associative shared TLB, it allows to reduce the number
|
||||
// of ITLB and DTLB entries.
|
||||
//
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
|
||||
module cva6_shared_tlb_sv32
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter int SHARED_TLB_DEPTH = 64,
|
||||
parameter int SHARED_TLB_WAYS = 2
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i,
|
||||
|
||||
input logic enable_translation_i, // CSRs indicate to enable SV32
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
input logic itlb_access_i,
|
||||
input logic itlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] itlb_vaddr_i,
|
||||
|
||||
input logic dtlb_access_i,
|
||||
input logic dtlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] dtlb_vaddr_i,
|
||||
|
||||
// to TLBs, update logic
|
||||
output tlb_update_sv32_t itlb_update_o,
|
||||
output tlb_update_sv32_t dtlb_update_o,
|
||||
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
|
||||
output logic shared_tlb_access_o,
|
||||
output logic shared_tlb_hit_o,
|
||||
output logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr_o,
|
||||
|
||||
output logic itlb_req_o,
|
||||
|
||||
// Update shared TLB in case of miss
|
||||
input tlb_update_sv32_t shared_tlb_update_i
|
||||
|
||||
);
|
||||
|
||||
function logic [SHARED_TLB_WAYS-1:0] shared_tlb_way_bin2oh(input logic [$clog2(SHARED_TLB_WAYS
|
||||
)-1:0] in);
|
||||
logic [SHARED_TLB_WAYS-1:0] out;
|
||||
out = '0;
|
||||
out[in] = 1'b1;
|
||||
return out;
|
||||
endfunction
|
||||
|
||||
typedef struct packed {
|
||||
logic [8:0] asid; //9 bits wide
|
||||
logic [9:0] vpn1; //10 bits wide
|
||||
logic [9:0] vpn0; //10 bits wide
|
||||
logic is_4M;
|
||||
} shared_tag_t;
|
||||
|
||||
shared_tag_t shared_tag_wr;
|
||||
shared_tag_t [SHARED_TLB_WAYS-1:0] shared_tag_rd;
|
||||
|
||||
logic [SHARED_TLB_DEPTH-1:0][SHARED_TLB_WAYS-1:0] shared_tag_valid_q, shared_tag_valid_d;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] shared_tag_valid;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_wr_en;
|
||||
logic [$clog2(SHARED_TLB_DEPTH)-1:0] tag_wr_addr;
|
||||
logic [ $bits(shared_tag_t)-1:0] tag_wr_data;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_rd_en;
|
||||
logic [$clog2(SHARED_TLB_DEPTH)-1:0] tag_rd_addr;
|
||||
logic [ $bits(shared_tag_t)-1:0] tag_rd_data [SHARED_TLB_WAYS-1:0];
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_req;
|
||||
logic [ SHARED_TLB_WAYS-1:0] tag_we;
|
||||
logic [$clog2(SHARED_TLB_DEPTH)-1:0] tag_addr;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_wr_en;
|
||||
logic [$clog2(SHARED_TLB_DEPTH)-1:0] pte_wr_addr;
|
||||
logic [$bits(riscv::pte_sv32_t)-1:0] pte_wr_data;
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_rd_en;
|
||||
logic [$clog2(SHARED_TLB_DEPTH)-1:0] pte_rd_addr;
|
||||
logic [$bits(riscv::pte_sv32_t)-1:0] pte_rd_data [SHARED_TLB_WAYS-1:0];
|
||||
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_req;
|
||||
logic [ SHARED_TLB_WAYS-1:0] pte_we;
|
||||
logic [$clog2(SHARED_TLB_DEPTH)-1:0] pte_addr;
|
||||
|
||||
logic [9:0] vpn0_d, vpn1_d, vpn0_q, vpn1_q;
|
||||
|
||||
riscv::pte_sv32_t [SHARED_TLB_WAYS-1:0] pte;
|
||||
|
||||
logic [CVA6Cfg.VLEN-1-12:0] itlb_vpn_q;
|
||||
logic [CVA6Cfg.VLEN-1-12:0] dtlb_vpn_q;
|
||||
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] tlb_update_asid_q, tlb_update_asid_d;
|
||||
|
||||
logic shared_tlb_access_q, shared_tlb_access_d;
|
||||
logic shared_tlb_hit_d;
|
||||
logic [CVA6Cfg.VLEN-1:0] shared_tlb_vaddr_q, shared_tlb_vaddr_d;
|
||||
|
||||
logic itlb_req_d, itlb_req_q;
|
||||
logic dtlb_req_d, dtlb_req_q;
|
||||
|
||||
// replacement strategy
|
||||
logic [SHARED_TLB_WAYS-1:0] way_valid;
|
||||
logic update_lfsr; // shift the LFSR
|
||||
logic [$clog2(SHARED_TLB_WAYS)-1:0] inv_way; // first non-valid encountered
|
||||
logic [$clog2(SHARED_TLB_WAYS)-1:0] rnd_way; // random index for replacement
|
||||
logic [$clog2(SHARED_TLB_WAYS)-1:0] repl_way; // way to replace
|
||||
logic [SHARED_TLB_WAYS-1:0] repl_way_oh_d; // way to replace (onehot)
|
||||
logic all_ways_valid; // we need to switch repl strategy since all are valid
|
||||
|
||||
assign shared_tlb_access_o = shared_tlb_access_q;
|
||||
assign shared_tlb_hit_o = shared_tlb_hit_d;
|
||||
assign shared_tlb_vaddr_o = shared_tlb_vaddr_q;
|
||||
|
||||
assign itlb_req_o = itlb_req_q;
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// tag comparison, hit generation
|
||||
///////////////////////////////////////////////////////
|
||||
always_comb begin : itlb_dtlb_miss
|
||||
itlb_miss_o = 1'b0;
|
||||
dtlb_miss_o = 1'b0;
|
||||
vpn0_d = vpn0_q;
|
||||
vpn1_d = vpn1_q;
|
||||
|
||||
tag_rd_en = '0;
|
||||
pte_rd_en = '0;
|
||||
|
||||
itlb_req_d = 1'b0;
|
||||
dtlb_req_d = 1'b0;
|
||||
|
||||
tlb_update_asid_d = tlb_update_asid_q;
|
||||
|
||||
shared_tlb_access_d = '0;
|
||||
shared_tlb_vaddr_d = shared_tlb_vaddr_q;
|
||||
|
||||
tag_rd_addr = '0;
|
||||
pte_rd_addr = '0;
|
||||
|
||||
// if we got an ITLB miss
|
||||
if (enable_translation_i & itlb_access_i & ~itlb_hit_i & ~dtlb_access_i) begin
|
||||
tag_rd_en = '1;
|
||||
tag_rd_addr = itlb_vaddr_i[12+:$clog2(SHARED_TLB_DEPTH)];
|
||||
pte_rd_en = '1;
|
||||
pte_rd_addr = itlb_vaddr_i[12+:$clog2(SHARED_TLB_DEPTH)];
|
||||
|
||||
vpn0_d = itlb_vaddr_i[21:12];
|
||||
vpn1_d = itlb_vaddr_i[31:22];
|
||||
|
||||
itlb_miss_o = 1'b1;
|
||||
itlb_req_d = 1'b1;
|
||||
|
||||
tlb_update_asid_d = asid_i;
|
||||
|
||||
shared_tlb_access_d = 1'b1;
|
||||
shared_tlb_vaddr_d = itlb_vaddr_i;
|
||||
|
||||
// we got an DTLB miss
|
||||
end else if (en_ld_st_translation_i & dtlb_access_i & ~dtlb_hit_i) begin
|
||||
tag_rd_en = '1;
|
||||
tag_rd_addr = dtlb_vaddr_i[12+:$clog2(SHARED_TLB_DEPTH)];
|
||||
pte_rd_en = '1;
|
||||
pte_rd_addr = dtlb_vaddr_i[12+:$clog2(SHARED_TLB_DEPTH)];
|
||||
|
||||
vpn0_d = dtlb_vaddr_i[21:12];
|
||||
vpn1_d = dtlb_vaddr_i[31:22];
|
||||
|
||||
dtlb_miss_o = 1'b1;
|
||||
dtlb_req_d = 1'b1;
|
||||
|
||||
tlb_update_asid_d = asid_i;
|
||||
|
||||
shared_tlb_access_d = 1'b1;
|
||||
shared_tlb_vaddr_d = dtlb_vaddr_i;
|
||||
end
|
||||
end //itlb_dtlb_miss
|
||||
|
||||
always_comb begin : tag_comparison
|
||||
shared_tlb_hit_d = 1'b0;
|
||||
dtlb_update_o = '0;
|
||||
itlb_update_o = '0;
|
||||
//number of ways
|
||||
for (int unsigned i = 0; i < SHARED_TLB_WAYS; i++) begin
|
||||
if (shared_tag_valid[i] && ((tlb_update_asid_q == shared_tag_rd[i].asid) || pte[i].g) && vpn1_q == shared_tag_rd[i].vpn1) begin
|
||||
if (shared_tag_rd[i].is_4M || vpn0_q == shared_tag_rd[i].vpn0) begin
|
||||
shared_tlb_hit_d = 1'b1;
|
||||
if (itlb_req_q) begin
|
||||
itlb_update_o.valid = 1'b1;
|
||||
itlb_update_o.vpn = itlb_vpn_q;
|
||||
itlb_update_o.is_4M = shared_tag_rd[i].is_4M;
|
||||
itlb_update_o.asid = tlb_update_asid_q;
|
||||
itlb_update_o.content = pte[i];
|
||||
end else if (dtlb_req_q) begin
|
||||
dtlb_update_o.valid = 1'b1;
|
||||
dtlb_update_o.vpn = dtlb_vpn_q;
|
||||
dtlb_update_o.is_4M = shared_tag_rd[i].is_4M;
|
||||
dtlb_update_o.asid = tlb_update_asid_q;
|
||||
dtlb_update_o.content = pte[i];
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end //tag_comparison
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
itlb_vpn_q <= '0;
|
||||
dtlb_vpn_q <= '0;
|
||||
tlb_update_asid_q <= '0;
|
||||
shared_tlb_access_q <= '0;
|
||||
shared_tlb_vaddr_q <= '0;
|
||||
shared_tag_valid_q <= '0;
|
||||
vpn0_q <= '0;
|
||||
vpn1_q <= '0;
|
||||
itlb_req_q <= '0;
|
||||
dtlb_req_q <= '0;
|
||||
shared_tag_valid <= '0;
|
||||
end else begin
|
||||
itlb_vpn_q <= itlb_vaddr_i[CVA6Cfg.SV-1:12];
|
||||
dtlb_vpn_q <= dtlb_vaddr_i[CVA6Cfg.SV-1:12];
|
||||
tlb_update_asid_q <= tlb_update_asid_d;
|
||||
shared_tlb_access_q <= shared_tlb_access_d;
|
||||
shared_tlb_vaddr_q <= shared_tlb_vaddr_d;
|
||||
shared_tag_valid_q <= shared_tag_valid_d;
|
||||
vpn0_q <= vpn0_d;
|
||||
vpn1_q <= vpn1_d;
|
||||
itlb_req_q <= itlb_req_d;
|
||||
dtlb_req_q <= dtlb_req_d;
|
||||
shared_tag_valid <= shared_tag_valid_q[tag_rd_addr];
|
||||
end
|
||||
end
|
||||
|
||||
// ------------------
|
||||
// Update and Flush
|
||||
// ------------------
|
||||
always_comb begin : update_flush
|
||||
shared_tag_valid_d = shared_tag_valid_q;
|
||||
tag_wr_en = '0;
|
||||
pte_wr_en = '0;
|
||||
|
||||
if (flush_i) begin
|
||||
shared_tag_valid_d = '0;
|
||||
end else if (shared_tlb_update_i.valid) begin
|
||||
for (int unsigned i = 0; i < SHARED_TLB_WAYS; i++) begin
|
||||
if (repl_way_oh_d[i]) begin
|
||||
shared_tag_valid_d[shared_tlb_update_i.vpn[$clog2(SHARED_TLB_DEPTH)-1:0]][i] = 1'b1;
|
||||
tag_wr_en[i] = 1'b1;
|
||||
pte_wr_en[i] = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end //update_flush
|
||||
|
||||
assign shared_tag_wr.asid = shared_tlb_update_i.asid;
|
||||
assign shared_tag_wr.vpn1 = shared_tlb_update_i.vpn[19:10];
|
||||
assign shared_tag_wr.vpn0 = shared_tlb_update_i.vpn[9:0];
|
||||
assign shared_tag_wr.is_4M = shared_tlb_update_i.is_4M;
|
||||
|
||||
assign tag_wr_addr = shared_tlb_update_i.vpn[$clog2(SHARED_TLB_DEPTH)-1:0];
|
||||
assign tag_wr_data = shared_tag_wr;
|
||||
|
||||
assign pte_wr_addr = shared_tlb_update_i.vpn[$clog2(SHARED_TLB_DEPTH)-1:0];
|
||||
assign pte_wr_data = shared_tlb_update_i.content;
|
||||
|
||||
assign way_valid = shared_tag_valid_q[shared_tlb_update_i.vpn[$clog2(SHARED_TLB_DEPTH)-1:0]];
|
||||
assign repl_way = (all_ways_valid) ? rnd_way : inv_way;
|
||||
assign update_lfsr = shared_tlb_update_i.valid & all_ways_valid;
|
||||
assign repl_way_oh_d = (shared_tlb_update_i.valid) ? shared_tlb_way_bin2oh(repl_way) : '0;
|
||||
|
||||
lzc #(
|
||||
.WIDTH(SHARED_TLB_WAYS)
|
||||
) i_lzc (
|
||||
.in_i (~way_valid),
|
||||
.cnt_o (inv_way),
|
||||
.empty_o(all_ways_valid)
|
||||
);
|
||||
|
||||
lfsr #(
|
||||
.LfsrWidth(8),
|
||||
.OutWidth ($clog2(SHARED_TLB_WAYS))
|
||||
) i_lfsr (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni(rst_ni),
|
||||
.en_i (update_lfsr),
|
||||
.out_o (rnd_way)
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// memory arrays and regs
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
assign tag_req = tag_wr_en | tag_rd_en;
|
||||
assign tag_we = tag_wr_en;
|
||||
assign tag_addr = tag_wr_en ? tag_wr_addr : tag_rd_addr;
|
||||
|
||||
assign pte_req = pte_wr_en | pte_rd_en;
|
||||
assign pte_we = pte_wr_en;
|
||||
assign pte_addr = pte_wr_en ? pte_wr_addr : pte_rd_addr;
|
||||
|
||||
for (genvar i = 0; i < SHARED_TLB_WAYS; i++) begin : gen_sram
|
||||
// Tag RAM
|
||||
sram #(
|
||||
.DATA_WIDTH($bits(shared_tag_t)),
|
||||
.NUM_WORDS (SHARED_TLB_DEPTH)
|
||||
) tag_sram (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.req_i (tag_req[i]),
|
||||
.we_i (tag_we[i]),
|
||||
.addr_i (tag_addr),
|
||||
.wuser_i('0),
|
||||
.wdata_i(tag_wr_data),
|
||||
.be_i ('1),
|
||||
.ruser_o(),
|
||||
.rdata_o(tag_rd_data[i])
|
||||
);
|
||||
|
||||
assign shared_tag_rd[i] = shared_tag_t'(tag_rd_data[i]);
|
||||
|
||||
// PTE RAM
|
||||
sram #(
|
||||
.DATA_WIDTH($bits(riscv::pte_sv32_t)),
|
||||
.NUM_WORDS (SHARED_TLB_DEPTH)
|
||||
) pte_sram (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.req_i (pte_req[i]),
|
||||
.we_i (pte_we[i]),
|
||||
.addr_i (pte_addr),
|
||||
.wuser_i('0),
|
||||
.wdata_i(pte_wr_data),
|
||||
.be_i ('1),
|
||||
.ruser_o(),
|
||||
.rdata_o(pte_rd_data[i])
|
||||
);
|
||||
assign pte[i] = riscv::pte_sv32_t'(pte_rd_data[i]);
|
||||
end
|
||||
endmodule
|
||||
|
||||
/* verilator lint_on WIDTH */
|
|
@ -1,280 +0,0 @@
|
|||
// Copyright (c) 2021 Thales.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Sebastien Jacq Thales Research & Technology
|
||||
// Date: 17/07/2021
|
||||
//
|
||||
// Additional contributions by:
|
||||
// Sebastien Jacq - sjthales on github.com
|
||||
//
|
||||
// Description: Translation Lookaside Buffer, Sv32 , fully set-associative
|
||||
// This module is an adaptation of the Sv39 TLB developed
|
||||
// by Florian Zaruba and David Schaffenrath to the Sv32 standard.
|
||||
//
|
||||
// =========================================================================== //
|
||||
// Revisions :
|
||||
// Date Version Author Description
|
||||
// 2020-02-17 0.1 S.Jacq TLB Sv32 for CV32A6
|
||||
// =========================================================================== //
|
||||
|
||||
module cva6_tlb_sv32
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter int unsigned TLB_ENTRIES = 4
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // Flush signal
|
||||
// Update TLB
|
||||
input tlb_update_sv32_t update_i,
|
||||
// Lookup signals
|
||||
input logic lu_access_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] lu_asid_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] lu_vaddr_i,
|
||||
output riscv::pte_sv32_t lu_content_o,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
output logic lu_is_4M_o,
|
||||
output logic lu_hit_o
|
||||
);
|
||||
|
||||
// Sv32 defines two levels of page tables
|
||||
struct packed {
|
||||
logic [8:0] asid; //9 bits wide
|
||||
logic [9:0] vpn1; //10 bits wide
|
||||
logic [9:0] vpn0; //10 bits wide
|
||||
logic is_4M;
|
||||
logic valid;
|
||||
} [TLB_ENTRIES-1:0]
|
||||
tags_q, tags_n;
|
||||
|
||||
riscv::pte_sv32_t [TLB_ENTRIES-1:0] content_q, content_n;
|
||||
logic [9:0] vpn0, vpn1;
|
||||
logic [TLB_ENTRIES-1:0] lu_hit; // to replacement logic
|
||||
logic [TLB_ENTRIES-1:0] replace_en; // replace the following entry, set by replacement strategy
|
||||
//-------------
|
||||
// Translation
|
||||
//-------------
|
||||
always_comb begin : translation
|
||||
vpn0 = lu_vaddr_i[21:12];
|
||||
vpn1 = lu_vaddr_i[31:22];
|
||||
|
||||
|
||||
// default assignment
|
||||
lu_hit = '{default: 0};
|
||||
lu_hit_o = 1'b0;
|
||||
lu_content_o = '{default: 0};
|
||||
lu_is_4M_o = 1'b0;
|
||||
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
||||
// first level match, this may be a mega page, check the ASID flags as well
|
||||
// if the entry is associated to a global address, don't match the ASID (ASID is don't care)
|
||||
if (tags_q[i].valid && ((lu_asid_i == tags_q[i].asid[CVA6Cfg.ASID_WIDTH-1:0]) || content_q[i].g) && vpn1 == tags_q[i].vpn1) begin
|
||||
if (tags_q[i].is_4M || vpn0 == tags_q[i].vpn0) begin
|
||||
lu_is_4M_o = tags_q[i].is_4M;
|
||||
lu_content_o = content_q[i];
|
||||
lu_hit_o = 1'b1;
|
||||
lu_hit[i] = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
logic asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
|
||||
logic vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn0_match;
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn1_match;
|
||||
|
||||
|
||||
assign asid_to_be_flushed_is0 = ~(|asid_to_be_flushed_i);
|
||||
assign vaddr_to_be_flushed_is0 = ~(|vaddr_to_be_flushed_i);
|
||||
|
||||
// ------------------
|
||||
// Update and Flush
|
||||
// ------------------
|
||||
always_comb begin : update_flush
|
||||
tags_n = tags_q;
|
||||
content_n = content_q;
|
||||
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
||||
|
||||
vaddr_vpn0_match[i] = (vaddr_to_be_flushed_i[21:12] == tags_q[i].vpn0);
|
||||
vaddr_vpn1_match[i] = (vaddr_to_be_flushed_i[31:22] == tags_q[i].vpn1);
|
||||
|
||||
if (flush_i) begin
|
||||
// invalidate logic
|
||||
// flush everything if ASID is 0 and vaddr is 0 ("SFENCE.VMA x0 x0" case)
|
||||
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0) tags_n[i].valid = 1'b0;
|
||||
// flush vaddr in all addressing space ("SFENCE.VMA vaddr x0" case), it should happen only for leaf pages
|
||||
else if (asid_to_be_flushed_is0 && ( (vaddr_vpn0_match[i] && vaddr_vpn1_match[i]) || (vaddr_vpn1_match[i] && tags_q[i].is_4M) ) && (~vaddr_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global and asid and vaddr both matches with the entry to be flushed ("SFENCE.VMA vaddr asid" case)
|
||||
else if ((!content_q[i].g) && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i]) || (vaddr_vpn1_match[i] && tags_q[i].is_4M)) && (asid_to_be_flushed_i == tags_q[i].asid[CVA6Cfg.ASID_WIDTH-1:0]) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global, and the asid matches and vaddr is 0. ("SFENCE.VMA 0 asid" case)
|
||||
else if ((!content_q[i].g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid[CVA6Cfg.ASID_WIDTH-1:0]) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// normal replacement
|
||||
end else if (update_i.valid & replace_en[i]) begin
|
||||
// update tag array
|
||||
tags_n[i] = '{
|
||||
asid: update_i.asid,
|
||||
vpn1: update_i.vpn[19:10],
|
||||
vpn0: update_i.vpn[9:0],
|
||||
is_4M: update_i.is_4M,
|
||||
valid: 1'b1
|
||||
};
|
||||
// and content as well
|
||||
content_n[i] = update_i.content;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// -----------------------------------------------
|
||||
// PLRU - Pseudo Least Recently Used Replacement
|
||||
// -----------------------------------------------
|
||||
logic [2*(TLB_ENTRIES-1)-1:0] plru_tree_q, plru_tree_n;
|
||||
logic en;
|
||||
int unsigned idx_base, shift, new_index;
|
||||
always_comb begin : plru_replacement
|
||||
plru_tree_n = plru_tree_q;
|
||||
en = '0;
|
||||
idx_base = '0;
|
||||
shift = '0;
|
||||
new_index = '0;
|
||||
// The PLRU-tree indexing:
|
||||
// lvl0 0
|
||||
// / \
|
||||
// / \
|
||||
// lvl1 1 2
|
||||
// / \ / \
|
||||
// lvl2 3 4 5 6
|
||||
// / \ /\/\ /\
|
||||
// ... ... ... ...
|
||||
// Just predefine which nodes will be set/cleared
|
||||
// E.g. for a TLB with 8 entries, the for-loop is semantically
|
||||
// equivalent to the following pseudo-code:
|
||||
// unique case (1'b1)
|
||||
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
|
||||
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
|
||||
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
|
||||
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
|
||||
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
|
||||
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
|
||||
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
|
||||
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
|
||||
// default: begin /* No hit */ end
|
||||
// endcase
|
||||
for (
|
||||
int unsigned i = 0; i < TLB_ENTRIES; i++
|
||||
) begin
|
||||
// we got a hit so update the pointer as it was least recently used
|
||||
if (lu_hit[i] & lu_access_i) begin
|
||||
// Set the nodes to the values we would expect
|
||||
for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin
|
||||
idx_base = $unsigned((2 ** lvl) - 1);
|
||||
// lvl0 <=> MSB, lvl1 <=> MSB-1, ...
|
||||
shift = $clog2(TLB_ENTRIES) - lvl;
|
||||
// to circumvent the 32 bit integer arithmetic assignment
|
||||
new_index = ~((i >> (shift - 1)) & 32'b1);
|
||||
plru_tree_n[idx_base+(i>>shift)] = new_index[0];
|
||||
end
|
||||
end
|
||||
end
|
||||
// Decode tree to write enable signals
|
||||
// Next for-loop basically creates the following logic for e.g. an 8 entry
|
||||
// TLB (note: pseudo-code obviously):
|
||||
// replace_en[7] = &plru_tree_q[ 6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,1}
|
||||
// replace_en[6] = &plru_tree_q[~6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,0}
|
||||
// replace_en[5] = &plru_tree_q[ 5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,1}
|
||||
// replace_en[4] = &plru_tree_q[~5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,0}
|
||||
// replace_en[3] = &plru_tree_q[ 4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,1}
|
||||
// replace_en[2] = &plru_tree_q[~4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,0}
|
||||
// replace_en[1] = &plru_tree_q[ 3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,1}
|
||||
// replace_en[0] = &plru_tree_q[~3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,0}
|
||||
// For each entry traverse the tree. If every tree-node matches,
|
||||
// the corresponding bit of the entry's index, this is
|
||||
// the next entry to replace.
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i += 1) begin
|
||||
en = 1'b1;
|
||||
for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin
|
||||
idx_base = $unsigned((2 ** lvl) - 1);
|
||||
// lvl0 <=> MSB, lvl1 <=> MSB-1, ...
|
||||
shift = $clog2(TLB_ENTRIES) - lvl;
|
||||
|
||||
// en &= plru_tree_q[idx_base + (i>>shift)] == ((i >> (shift-1)) & 1'b1);
|
||||
new_index = (i >> (shift - 1)) & 32'b1;
|
||||
if (new_index[0]) begin
|
||||
en &= plru_tree_q[idx_base+(i>>shift)];
|
||||
end else begin
|
||||
en &= ~plru_tree_q[idx_base+(i>>shift)];
|
||||
end
|
||||
end
|
||||
replace_en[i] = en;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
tags_q <= '{default: 0};
|
||||
content_q <= '{default: 0};
|
||||
plru_tree_q <= '{default: 0};
|
||||
end else begin
|
||||
tags_q <= tags_n;
|
||||
content_q <= content_n;
|
||||
plru_tree_q <= plru_tree_n;
|
||||
end
|
||||
end
|
||||
//--------------
|
||||
// Sanity checks
|
||||
//--------------
|
||||
|
||||
//pragma translate_off
|
||||
`ifndef VERILATOR
|
||||
|
||||
initial begin : p_assertions
|
||||
assert ((TLB_ENTRIES % 2 == 0) && (TLB_ENTRIES > 1))
|
||||
else begin
|
||||
$error("TLB size must be a multiple of 2 and greater than 1");
|
||||
$stop();
|
||||
end
|
||||
assert (CVA6Cfg.ASID_WIDTH >= 1)
|
||||
else begin
|
||||
$error("ASID width must be at least 1");
|
||||
$stop();
|
||||
end
|
||||
end
|
||||
|
||||
// Just for checking
|
||||
function int countSetBits(logic [TLB_ENTRIES-1:0] vector);
|
||||
automatic int count = 0;
|
||||
foreach (vector[idx]) begin
|
||||
count += vector[idx];
|
||||
end
|
||||
return count;
|
||||
endfunction
|
||||
|
||||
assert property (@(posedge clk_i) (countSetBits(lu_hit) <= 1))
|
||||
else begin
|
||||
$error("More then one hit in TLB!");
|
||||
$stop();
|
||||
end
|
||||
assert property (@(posedge clk_i) (countSetBits(replace_en) <= 1))
|
||||
else begin
|
||||
$error("More then one TLB entry selected for next replace!");
|
||||
$stop();
|
||||
end
|
||||
|
||||
`endif
|
||||
//pragma translate_on
|
||||
|
||||
endmodule
|
|
@ -1,557 +0,0 @@
|
|||
// Copyright 2018 ETH Zurich and University of Bologna.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Florian Zaruba, ETH Zurich
|
||||
// Date: 19/04/2017
|
||||
// Description: Memory Management Unit for Ariane, contains TLB and
|
||||
// address translation unit. SV39 as defined in RISC-V
|
||||
// privilege specification 1.11-WIP
|
||||
|
||||
|
||||
module mmu
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type icache_areq_t = logic,
|
||||
parameter type icache_arsp_t = logic,
|
||||
parameter type icache_dreq_t = logic,
|
||||
parameter type icache_drsp_t = logic,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter type exception_t = logic,
|
||||
parameter int unsigned INSTR_TLB_ENTRIES = 4,
|
||||
parameter int unsigned DATA_TLB_ENTRIES = 4
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_ni,
|
||||
input logic flush_i,
|
||||
input logic enable_translation_i,
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
// IF interface
|
||||
input icache_arsp_t icache_areq_i,
|
||||
output icache_areq_t icache_areq_o,
|
||||
// LSU interface
|
||||
// this is a more minimalistic interface because the actual addressing logic is handled
|
||||
// in the LSU as we distinguish load and stores, what we do here is simple address translation
|
||||
input exception_t misaligned_ex_i,
|
||||
input logic lsu_req_i, // request address translation
|
||||
input logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_i, // virtual address in
|
||||
input logic lsu_is_store_i, // the translation is requested by a store
|
||||
// if we need to walk the page table we can't grant in the same cycle
|
||||
// Cycle 0
|
||||
output logic lsu_dtlb_hit_o, // sent in the same cycle as the request if translation hits in the DTLB
|
||||
output logic [CVA6Cfg.PPNW-1:0] lsu_dtlb_ppn_o, // ppn (send same cycle as hit)
|
||||
// Cycle 1
|
||||
output logic lsu_valid_o, // translation is valid
|
||||
output logic [CVA6Cfg.PLEN-1:0] lsu_paddr_o, // translated address
|
||||
output exception_t lsu_exception_o, // address translation threw an exception
|
||||
// General control signals
|
||||
input riscv::priv_lvl_t priv_lvl_i,
|
||||
input riscv::priv_lvl_t ld_st_priv_lvl_i,
|
||||
input logic sum_i,
|
||||
input logic mxr_i,
|
||||
// input logic flag_mprv_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
input logic flush_tlb_i,
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
// PMP
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i
|
||||
);
|
||||
|
||||
localparam type tlb_update_t = struct packed {
|
||||
logic valid; // valid flag
|
||||
logic is_2M; //
|
||||
logic is_1G; //
|
||||
logic [27-1:0] vpn; // VPN (39bits) = 27bits + 12bits offset
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||
riscv::pte_t content;
|
||||
};
|
||||
|
||||
logic iaccess_err; // insufficient privilege to access this instruction page
|
||||
logic daccess_err; // insufficient privilege to access this data page
|
||||
logic ptw_active; // PTW is currently walking a page table
|
||||
logic walking_instr; // PTW is walking because of an ITLB miss
|
||||
logic ptw_error; // PTW threw an exception
|
||||
logic ptw_access_exception; // PTW threw an access exception (PMPs)
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_bad_paddr; // PTW PMP exception bad physical addr
|
||||
|
||||
logic [CVA6Cfg.VLEN-1:0] update_vaddr;
|
||||
tlb_update_t update_ptw_itlb, update_ptw_dtlb;
|
||||
|
||||
logic itlb_lu_access;
|
||||
riscv::pte_t itlb_content;
|
||||
logic itlb_is_2M;
|
||||
logic itlb_is_1G;
|
||||
logic itlb_lu_hit;
|
||||
|
||||
logic dtlb_lu_access;
|
||||
riscv::pte_t dtlb_content;
|
||||
logic dtlb_is_2M;
|
||||
logic dtlb_is_1G;
|
||||
logic dtlb_lu_hit;
|
||||
|
||||
|
||||
// Assignments
|
||||
assign itlb_lu_access = icache_areq_i.fetch_req;
|
||||
assign dtlb_lu_access = lsu_req_i;
|
||||
|
||||
|
||||
tlb #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.tlb_update_t(tlb_update_t),
|
||||
.TLB_ENTRIES (INSTR_TLB_ENTRIES)
|
||||
) i_itlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i(flush_tlb_i),
|
||||
|
||||
.update_i(update_ptw_itlb),
|
||||
|
||||
.lu_access_i (itlb_lu_access),
|
||||
.lu_asid_i (asid_i),
|
||||
.asid_to_be_flushed_i (asid_to_be_flushed_i),
|
||||
.vaddr_to_be_flushed_i(vaddr_to_be_flushed_i),
|
||||
.lu_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
.lu_content_o (itlb_content),
|
||||
|
||||
.lu_is_2M_o(itlb_is_2M),
|
||||
.lu_is_1G_o(itlb_is_1G),
|
||||
.lu_hit_o (itlb_lu_hit)
|
||||
);
|
||||
|
||||
tlb #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.tlb_update_t(tlb_update_t),
|
||||
.TLB_ENTRIES (DATA_TLB_ENTRIES)
|
||||
) i_dtlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i(flush_tlb_i),
|
||||
|
||||
.update_i(update_ptw_dtlb),
|
||||
|
||||
.lu_access_i (dtlb_lu_access),
|
||||
.lu_asid_i (asid_i),
|
||||
.asid_to_be_flushed_i (asid_to_be_flushed_i),
|
||||
.vaddr_to_be_flushed_i(vaddr_to_be_flushed_i),
|
||||
.lu_vaddr_i (lsu_vaddr_i),
|
||||
.lu_content_o (dtlb_content),
|
||||
|
||||
.lu_is_2M_o(dtlb_is_2M),
|
||||
.lu_is_1G_o(dtlb_is_1G),
|
||||
.lu_hit_o (dtlb_lu_hit)
|
||||
);
|
||||
|
||||
|
||||
ptw #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.dcache_req_i_t(dcache_req_i_t),
|
||||
.dcache_req_o_t(dcache_req_o_t),
|
||||
.tlb_update_t(tlb_update_t)
|
||||
) i_ptw (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.ptw_active_o (ptw_active),
|
||||
.walking_instr_o (walking_instr),
|
||||
.ptw_error_o (ptw_error),
|
||||
.ptw_access_exception_o(ptw_access_exception),
|
||||
.enable_translation_i (enable_translation_i),
|
||||
|
||||
.update_vaddr_o(update_vaddr),
|
||||
.itlb_update_o (update_ptw_itlb),
|
||||
.dtlb_update_o (update_ptw_dtlb),
|
||||
|
||||
.itlb_access_i(itlb_lu_access),
|
||||
.itlb_hit_i (itlb_lu_hit),
|
||||
.itlb_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
|
||||
.dtlb_access_i(dtlb_lu_access),
|
||||
.dtlb_hit_i (dtlb_lu_hit),
|
||||
.dtlb_vaddr_i (lsu_vaddr_i),
|
||||
|
||||
.req_port_i (req_port_i),
|
||||
.req_port_o (req_port_o),
|
||||
.pmpcfg_i,
|
||||
.pmpaddr_i,
|
||||
.bad_paddr_o(ptw_bad_paddr),
|
||||
.*
|
||||
);
|
||||
|
||||
// ila_1 i_ila_1 (
|
||||
// .clk(clk_i), // input wire clk
|
||||
// .probe0({req_port_o.address_tag, req_port_o.address_index}),
|
||||
// .probe1(req_port_o.data_req), // input wire [63:0] probe1
|
||||
// .probe2(req_port_i.data_gnt), // input wire [0:0] probe2
|
||||
// .probe3(req_port_i.data_rdata), // input wire [0:0] probe3
|
||||
// .probe4(req_port_i.data_rvalid), // input wire [0:0] probe4
|
||||
// .probe5(ptw_error), // input wire [1:0] probe5
|
||||
// .probe6(update_vaddr), // input wire [0:0] probe6
|
||||
// .probe7(update_ptw_itlb.valid), // input wire [0:0] probe7
|
||||
// .probe8(update_ptw_dtlb.valid), // input wire [0:0] probe8
|
||||
// .probe9(dtlb_lu_access), // input wire [0:0] probe9
|
||||
// .probe10(lsu_vaddr_i), // input wire [0:0] probe10
|
||||
// .probe11(dtlb_lu_hit), // input wire [0:0] probe11
|
||||
// .probe12(itlb_lu_access), // input wire [0:0] probe12
|
||||
// .probe13(icache_areq_i.fetch_vaddr), // input wire [0:0] probe13
|
||||
// .probe14(itlb_lu_hit) // input wire [0:0] probe13
|
||||
// );
|
||||
|
||||
//-----------------------
|
||||
// Instruction Interface
|
||||
//-----------------------
|
||||
logic match_any_execute_region;
|
||||
logic pmp_instr_allow;
|
||||
|
||||
// The instruction interface is a simple request response interface
|
||||
always_comb begin : instr_interface
|
||||
// MMU disabled: just pass through
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
icache_areq_o.fetch_paddr = icache_areq_i.fetch_vaddr[CVA6Cfg.PLEN-1:0]; // play through in case we disabled address translation
|
||||
// two potential exception sources:
|
||||
// 1. HPTW threw an exception -> signal with a page fault exception
|
||||
// 2. We got an access error because of insufficient permissions -> throw an access exception
|
||||
icache_areq_o.fetch_exception = '0;
|
||||
// Check whether we are allowed to access this memory region from a fetch perspective
|
||||
iaccess_err = icache_areq_i.fetch_req && enable_translation_i
|
||||
&& (((priv_lvl_i == riscv::PRIV_LVL_U) && ~itlb_content.u)
|
||||
|| ((priv_lvl_i == riscv::PRIV_LVL_S) && itlb_content.u));
|
||||
|
||||
// MMU enabled: address from TLB, request delayed until hit. Error when TLB
|
||||
// hit and no access right or TLB hit and translated address not valid (e.g.
|
||||
// AXI decode error), or when PTW performs walk due to ITLB miss and raises
|
||||
// an error.
|
||||
if (enable_translation_i) begin
|
||||
// we work with SV39 or SV32, so if VM is enabled, check that all bits [CVA6Cfg.VLEN-1:CVA6Cfg.SV-1] are equal
|
||||
if (icache_areq_i.fetch_req && !((&icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b1 || (|icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b0)) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr
|
||||
};
|
||||
end
|
||||
|
||||
icache_areq_o.fetch_valid = 1'b0;
|
||||
|
||||
// 4K page
|
||||
icache_areq_o.fetch_paddr = {itlb_content.ppn, icache_areq_i.fetch_vaddr[11:0]};
|
||||
// Mega page
|
||||
if (itlb_is_2M) begin
|
||||
icache_areq_o.fetch_paddr[20:12] = icache_areq_i.fetch_vaddr[20:12];
|
||||
end
|
||||
// Giga page
|
||||
if (itlb_is_1G) begin
|
||||
icache_areq_o.fetch_paddr[29:12] = icache_areq_i.fetch_vaddr[29:12];
|
||||
end
|
||||
|
||||
// ---------
|
||||
// ITLB Hit
|
||||
// --------
|
||||
// if we hit the ITLB output the request signal immediately
|
||||
if (itlb_lu_hit) begin
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
// we got an access error
|
||||
if (iaccess_err) begin
|
||||
// throw a page fault
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr
|
||||
};
|
||||
end else if (!pmp_instr_allow) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr
|
||||
};
|
||||
end
|
||||
end else
|
||||
// ---------
|
||||
// ITLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions happening during walking the page table
|
||||
if (ptw_active && walking_instr) begin
|
||||
icache_areq_o.fetch_valid = ptw_error | ptw_access_exception;
|
||||
if (ptw_error) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_PAGE_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, update_vaddr
|
||||
};
|
||||
end else begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, update_vaddr
|
||||
};
|
||||
end
|
||||
end
|
||||
end
|
||||
// if it didn't match any execute region throw an `Instruction Access Fault`
|
||||
// or: if we are not translating, check PMPs immediately on the paddr
|
||||
if ((!match_any_execute_region && !ptw_error) || (!enable_translation_i && !pmp_instr_allow)) begin
|
||||
icache_areq_o.fetch_exception.cause = riscv::INSTR_ACCESS_FAULT;
|
||||
icache_areq_o.fetch_exception.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
icache_areq_o.fetch_exception.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.PLEN{1'b0}}, icache_areq_o.fetch_paddr
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
// check for execute flag on memory
|
||||
assign match_any_execute_region = config_pkg::is_inside_execute_regions(
|
||||
CVA6Cfg, {{64 - CVA6Cfg.PLEN{1'b0}}, icache_areq_o.fetch_paddr}
|
||||
);
|
||||
|
||||
// Instruction fetch
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_if (
|
||||
.addr_i (icache_areq_o.fetch_paddr),
|
||||
.priv_lvl_i,
|
||||
// we will always execute on the instruction fetch port
|
||||
.access_type_i(riscv::ACCESS_EXEC),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_instr_allow)
|
||||
);
|
||||
|
||||
//-----------------------
|
||||
// Data Interface
|
||||
//-----------------------
|
||||
logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_n, lsu_vaddr_q;
|
||||
riscv::pte_t dtlb_pte_n, dtlb_pte_q;
|
||||
exception_t misaligned_ex_n, misaligned_ex_q;
|
||||
logic lsu_req_n, lsu_req_q;
|
||||
logic lsu_is_store_n, lsu_is_store_q;
|
||||
logic dtlb_hit_n, dtlb_hit_q;
|
||||
logic dtlb_is_2M_n, dtlb_is_2M_q;
|
||||
logic dtlb_is_1G_n, dtlb_is_1G_q;
|
||||
|
||||
// check if we need to do translation or if we are always ready (e.g.: we are not translating anything)
|
||||
assign lsu_dtlb_hit_o = (en_ld_st_translation_i) ? dtlb_lu_hit : 1'b1;
|
||||
|
||||
// Wires to PMP checks
|
||||
riscv::pmp_access_t pmp_access_type;
|
||||
logic pmp_data_allow;
|
||||
localparam PPNWMin = (CVA6Cfg.PPNW - 1 > 29) ? 29 : CVA6Cfg.PPNW - 1;
|
||||
// The data interface is simpler and only consists of a request/response interface
|
||||
always_comb begin : data_interface
|
||||
// save request and DTLB response
|
||||
lsu_vaddr_n = lsu_vaddr_i;
|
||||
lsu_req_n = lsu_req_i;
|
||||
misaligned_ex_n = misaligned_ex_i;
|
||||
dtlb_pte_n = dtlb_content;
|
||||
dtlb_hit_n = dtlb_lu_hit;
|
||||
lsu_is_store_n = lsu_is_store_i;
|
||||
dtlb_is_2M_n = dtlb_is_2M;
|
||||
dtlb_is_1G_n = dtlb_is_1G;
|
||||
|
||||
lsu_paddr_o = lsu_vaddr_q[CVA6Cfg.PLEN-1:0];
|
||||
lsu_dtlb_ppn_o = lsu_vaddr_n[CVA6Cfg.PLEN-1:12];
|
||||
lsu_valid_o = lsu_req_q;
|
||||
lsu_exception_o = misaligned_ex_q;
|
||||
pmp_access_type = lsu_is_store_q ? riscv::ACCESS_WRITE : riscv::ACCESS_READ;
|
||||
|
||||
// mute misaligned exceptions if there is no request otherwise they will throw accidental exceptions
|
||||
misaligned_ex_n.valid = misaligned_ex_i.valid & lsu_req_i;
|
||||
|
||||
// Check if the User flag is set, then we may only access it in supervisor mode
|
||||
// if SUM is enabled
|
||||
daccess_err = en_ld_st_translation_i && ((ld_st_priv_lvl_i == riscv::PRIV_LVL_S && !sum_i && dtlb_pte_q.u) || // SUM is not set and we are trying to access a user page in supervisor mode
|
||||
(ld_st_priv_lvl_i == riscv::PRIV_LVL_U && !dtlb_pte_q.u)); // this is not a user page but we are in user mode and trying to access it
|
||||
// translation is enabled and no misaligned exception occurred
|
||||
if (en_ld_st_translation_i && !misaligned_ex_q.valid) begin
|
||||
lsu_valid_o = 1'b0;
|
||||
// 4K page
|
||||
lsu_paddr_o = {dtlb_pte_q.ppn, lsu_vaddr_q[11:0]};
|
||||
lsu_dtlb_ppn_o = dtlb_content.ppn;
|
||||
// Mega page
|
||||
if (dtlb_is_2M_q) begin
|
||||
lsu_paddr_o[20:12] = lsu_vaddr_q[20:12];
|
||||
lsu_dtlb_ppn_o[20:12] = lsu_vaddr_n[20:12];
|
||||
end
|
||||
// Giga page
|
||||
if (dtlb_is_1G_q) begin
|
||||
lsu_paddr_o[PPNWMin:12] = lsu_vaddr_q[PPNWMin:12];
|
||||
lsu_dtlb_ppn_o[PPNWMin:12] = lsu_vaddr_n[PPNWMin:12];
|
||||
end
|
||||
// ---------
|
||||
// DTLB Hit
|
||||
// --------
|
||||
if (dtlb_hit_q && lsu_req_q) begin
|
||||
lsu_valid_o = 1'b1;
|
||||
// exception priority:
|
||||
// PAGE_FAULTS have higher priority than ACCESS_FAULTS
|
||||
// virtual memory based exceptions are PAGE_FAULTS
|
||||
// physical memory based exceptions are ACCESS_FAULTS (PMA/PMP)
|
||||
|
||||
// this is a store
|
||||
if (lsu_is_store_q) begin
|
||||
// check if the page is write-able and we are not violating privileges
|
||||
// also check if the dirty flag is set
|
||||
if (!dtlb_pte_q.w || daccess_err || !dtlb_pte_q.d) begin
|
||||
lsu_exception_o.cause = riscv::STORE_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
end
|
||||
|
||||
// this is a load
|
||||
end else begin
|
||||
// check for sufficient access privileges - throw a page fault if necessary
|
||||
if (daccess_err) begin
|
||||
lsu_exception_o.cause = riscv::LOAD_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q
|
||||
};
|
||||
end
|
||||
end
|
||||
end else
|
||||
|
||||
// ---------
|
||||
// DTLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions
|
||||
if (ptw_active && !walking_instr) begin
|
||||
// page table walker threw an exception
|
||||
if (ptw_error) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// the page table walker can only throw page faults
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o.cause = riscv::STORE_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LOAD_PAGE_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {
|
||||
{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
if (ptw_access_exception) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// Any fault of the page table walk should be based of the original access type
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, lsu_vaddr_n};
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, lsu_vaddr_n};
|
||||
end
|
||||
end
|
||||
end
|
||||
end // If translation is not enabled, check the paddr immediately against PMPs
|
||||
else if (lsu_req_q && !misaligned_ex_q.valid && !pmp_data_allow) begin
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o.cause = riscv::ST_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {{CVA6Cfg.XLEN - CVA6Cfg.PLEN{1'b0}}, lsu_paddr_o};
|
||||
end else begin
|
||||
lsu_exception_o.cause = riscv::LD_ACCESS_FAULT;
|
||||
lsu_exception_o.valid = 1'b1;
|
||||
if (CVA6Cfg.TvalEn)
|
||||
lsu_exception_o.tval = {{CVA6Cfg.XLEN - CVA6Cfg.PLEN{1'b0}}, lsu_paddr_o};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Load/store PMP check
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_data (
|
||||
.addr_i (lsu_paddr_o),
|
||||
.priv_lvl_i (ld_st_priv_lvl_i),
|
||||
.access_type_i(pmp_access_type),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_data_allow)
|
||||
);
|
||||
|
||||
// ----------
|
||||
// Registers
|
||||
// ----------
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
lsu_vaddr_q <= '0;
|
||||
lsu_req_q <= '0;
|
||||
misaligned_ex_q <= '0;
|
||||
dtlb_pte_q <= '0;
|
||||
dtlb_hit_q <= '0;
|
||||
lsu_is_store_q <= '0;
|
||||
dtlb_is_2M_q <= '0;
|
||||
dtlb_is_1G_q <= '0;
|
||||
end else begin
|
||||
lsu_vaddr_q <= lsu_vaddr_n;
|
||||
lsu_req_q <= lsu_req_n;
|
||||
misaligned_ex_q <= misaligned_ex_n;
|
||||
dtlb_pte_q <= dtlb_pte_n;
|
||||
dtlb_hit_q <= dtlb_hit_n;
|
||||
lsu_is_store_q <= lsu_is_store_n;
|
||||
dtlb_is_2M_q <= dtlb_is_2M_n;
|
||||
dtlb_is_1G_q <= dtlb_is_1G_n;
|
||||
end
|
||||
end
|
||||
endmodule
|
|
@ -1,411 +0,0 @@
|
|||
// Copyright 2018 ETH Zurich and University of Bologna.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: David Schaffenrath, TU Graz
|
||||
// Author: Florian Zaruba, ETH Zurich
|
||||
// Date: 24.4.2017
|
||||
// Description: Hardware-PTW
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
|
||||
module ptw
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter type tlb_update_t = logic
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // flush everything, we need to do this because
|
||||
// actually everything we do is speculative at this stage
|
||||
// e.g.: there could be a CSR instruction that changes everything
|
||||
output logic ptw_active_o,
|
||||
output logic walking_instr_o, // set when walking for TLB
|
||||
output logic ptw_error_o, // set when an error occurred
|
||||
output logic ptw_access_exception_o, // set when an PMP access exception occured
|
||||
input logic enable_translation_i, // CSRs indicate to enable SV39
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
|
||||
input logic lsu_is_store_i, // this translation was triggered by a store
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
|
||||
|
||||
// to TLBs, update logic
|
||||
output tlb_update_t itlb_update_o,
|
||||
output tlb_update_t dtlb_update_o,
|
||||
|
||||
output logic [CVA6Cfg.VLEN-1:0] update_vaddr_o,
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
input logic itlb_access_i,
|
||||
input logic itlb_hit_i,
|
||||
input logic [ CVA6Cfg.VLEN-1:0] itlb_vaddr_i,
|
||||
|
||||
input logic dtlb_access_i,
|
||||
input logic dtlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] dtlb_vaddr_i,
|
||||
// from CSR file
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i, // ppn from satp
|
||||
input logic mxr_i,
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
// PMP
|
||||
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i,
|
||||
output logic [CVA6Cfg.PLEN-1:0] bad_paddr_o
|
||||
|
||||
);
|
||||
|
||||
// input registers
|
||||
logic data_rvalid_q;
|
||||
logic [63:0] data_rdata_q;
|
||||
|
||||
riscv::pte_t pte;
|
||||
assign pte = riscv::pte_t'(data_rdata_q);
|
||||
|
||||
enum logic [2:0] {
|
||||
IDLE,
|
||||
WAIT_GRANT,
|
||||
PTE_LOOKUP,
|
||||
WAIT_RVALID,
|
||||
PROPAGATE_ERROR,
|
||||
PROPAGATE_ACCESS_ERROR
|
||||
}
|
||||
state_q, state_d;
|
||||
|
||||
// SV39 defines three levels of page tables
|
||||
enum logic [1:0] {
|
||||
LVL1,
|
||||
LVL2,
|
||||
LVL3
|
||||
}
|
||||
ptw_lvl_q, ptw_lvl_n;
|
||||
|
||||
// is this an instruction page table walk?
|
||||
logic is_instr_ptw_q, is_instr_ptw_n;
|
||||
logic global_mapping_q, global_mapping_n;
|
||||
// latched tag signal
|
||||
logic tag_valid_n, tag_valid_q;
|
||||
// register the ASID
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] tlb_update_asid_q, tlb_update_asid_n;
|
||||
// register the VPN we need to walk, SV39 defines a 39 bit virtual address
|
||||
logic [CVA6Cfg.VLEN-1:0] vaddr_q, vaddr_n;
|
||||
// 4 byte aligned physical pointer
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_pptr_q, ptw_pptr_n;
|
||||
|
||||
// Assignments
|
||||
assign update_vaddr_o = vaddr_q;
|
||||
|
||||
assign ptw_active_o = (state_q != IDLE);
|
||||
assign walking_instr_o = is_instr_ptw_q;
|
||||
// directly output the correct physical address
|
||||
assign req_port_o.address_index = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH-1:0];
|
||||
assign req_port_o.address_tag = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH+CVA6Cfg.DCACHE_TAG_WIDTH-1:CVA6Cfg.DCACHE_INDEX_WIDTH];
|
||||
// we are never going to kill this request
|
||||
assign req_port_o.kill_req = '0;
|
||||
// we are never going to write with the HPTW
|
||||
assign req_port_o.data_wdata = 64'b0;
|
||||
// we only issue one single request at a time
|
||||
assign req_port_o.data_id = '0;
|
||||
// -----------
|
||||
// TLB Update
|
||||
// -----------
|
||||
assign itlb_update_o.vpn = {{39 - CVA6Cfg.SV{1'b0}}, vaddr_q[CVA6Cfg.SV-1:12]};
|
||||
assign dtlb_update_o.vpn = {{39 - CVA6Cfg.SV{1'b0}}, vaddr_q[CVA6Cfg.SV-1:12]};
|
||||
// update the correct page table level
|
||||
assign itlb_update_o.is_2M = (ptw_lvl_q == LVL2);
|
||||
assign itlb_update_o.is_1G = (ptw_lvl_q == LVL1);
|
||||
assign dtlb_update_o.is_2M = (ptw_lvl_q == LVL2);
|
||||
assign dtlb_update_o.is_1G = (ptw_lvl_q == LVL1);
|
||||
// output the correct ASID
|
||||
assign itlb_update_o.asid = tlb_update_asid_q;
|
||||
assign dtlb_update_o.asid = tlb_update_asid_q;
|
||||
// set the global mapping bit
|
||||
assign itlb_update_o.content = pte | (global_mapping_q << 5);
|
||||
assign dtlb_update_o.content = pte | (global_mapping_q << 5);
|
||||
|
||||
assign req_port_o.tag_valid = tag_valid_q;
|
||||
|
||||
logic allow_access;
|
||||
|
||||
assign bad_paddr_o = ptw_access_exception_o ? ptw_pptr_q : 'b0;
|
||||
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_ptw (
|
||||
.addr_i (ptw_pptr_q),
|
||||
// PTW access are always checked as if in S-Mode...
|
||||
.priv_lvl_i (riscv::PRIV_LVL_S),
|
||||
// ...and they are always loads
|
||||
.access_type_i(riscv::ACCESS_READ),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (allow_access)
|
||||
);
|
||||
|
||||
//-------------------
|
||||
// Page table walker
|
||||
//-------------------
|
||||
// A virtual address va is translated into a physical address pa as follows:
|
||||
// 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39,
|
||||
// PAGESIZE=2^12 and LEVELS=3.)
|
||||
// 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. (For
|
||||
// Sv32, PTESIZE=4.)
|
||||
// 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise an access
|
||||
// exception.
|
||||
// 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to step 5.
|
||||
// Otherwise, this PTE is a pointer to the next level of the page table.
|
||||
// Let i=i-1. If i < 0, stop and raise an access exception. Otherwise, let
|
||||
// a = pte.ppn × PAGESIZE and go to step 2.
|
||||
// 5. A leaf PTE has been found. Determine if the requested memory access
|
||||
// is allowed by the pte.r, pte.w, and pte.x bits. If not, stop and
|
||||
// raise an access exception. Otherwise, the translation is successful.
|
||||
// Set pte.a to 1, and, if the memory access is a store, set pte.d to 1.
|
||||
// The translated physical address is given as follows:
|
||||
// - pa.pgoff = va.pgoff.
|
||||
// - If i > 0, then this is a superpage translation and
|
||||
// pa.ppn[i-1:0] = va.vpn[i-1:0].
|
||||
// - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i].
|
||||
always_comb begin : ptw
|
||||
// default assignments
|
||||
// PTW memory interface
|
||||
tag_valid_n = 1'b0;
|
||||
req_port_o.data_req = 1'b0;
|
||||
req_port_o.data_be = 8'hFF;
|
||||
req_port_o.data_size = 2'b11;
|
||||
req_port_o.data_we = 1'b0;
|
||||
ptw_error_o = 1'b0;
|
||||
ptw_access_exception_o = 1'b0;
|
||||
itlb_update_o.valid = 1'b0;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
is_instr_ptw_n = is_instr_ptw_q;
|
||||
ptw_lvl_n = ptw_lvl_q;
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
state_d = state_q;
|
||||
global_mapping_n = global_mapping_q;
|
||||
// input registers
|
||||
tlb_update_asid_n = tlb_update_asid_q;
|
||||
vaddr_n = vaddr_q;
|
||||
|
||||
itlb_miss_o = 1'b0;
|
||||
dtlb_miss_o = 1'b0;
|
||||
|
||||
case (state_q)
|
||||
|
||||
IDLE: begin
|
||||
// by default we start with the top-most page table
|
||||
ptw_lvl_n = LVL1;
|
||||
global_mapping_n = 1'b0;
|
||||
is_instr_ptw_n = 1'b0;
|
||||
// if we got an ITLB miss
|
||||
if (enable_translation_i & itlb_access_i & ~itlb_hit_i & ~dtlb_access_i) begin
|
||||
ptw_pptr_n = {satp_ppn_i, itlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
is_instr_ptw_n = 1'b1;
|
||||
tlb_update_asid_n = asid_i;
|
||||
vaddr_n = itlb_vaddr_i;
|
||||
state_d = WAIT_GRANT;
|
||||
itlb_miss_o = 1'b1;
|
||||
// we got an DTLB miss
|
||||
end else if (en_ld_st_translation_i & dtlb_access_i & ~dtlb_hit_i) begin
|
||||
ptw_pptr_n = {satp_ppn_i, dtlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
tlb_update_asid_n = asid_i;
|
||||
vaddr_n = dtlb_vaddr_i;
|
||||
state_d = WAIT_GRANT;
|
||||
dtlb_miss_o = 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
WAIT_GRANT: begin
|
||||
// send a request out
|
||||
req_port_o.data_req = 1'b1;
|
||||
// wait for the WAIT_GRANT
|
||||
if (req_port_i.data_gnt) begin
|
||||
// send the tag valid signal one cycle later
|
||||
tag_valid_n = 1'b1;
|
||||
state_d = PTE_LOOKUP;
|
||||
end
|
||||
end
|
||||
|
||||
PTE_LOOKUP: begin
|
||||
// we wait for the valid signal
|
||||
if (data_rvalid_q) begin
|
||||
|
||||
// check if the global mapping bit is set
|
||||
if (pte.g) global_mapping_n = 1'b1;
|
||||
|
||||
// -------------
|
||||
// Invalid PTE
|
||||
// -------------
|
||||
// If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise a page-fault exception.
|
||||
if (!pte.v || (!pte.r && pte.w)) state_d = PROPAGATE_ERROR;
|
||||
// -----------
|
||||
// Valid PTE
|
||||
// -----------
|
||||
else begin
|
||||
state_d = IDLE;
|
||||
// it is a valid PTE
|
||||
// if pte.r = 1 or pte.x = 1 it is a valid PTE
|
||||
if (pte.r || pte.x) begin
|
||||
// Valid translation found (either 1G, 2M or 4K entry)
|
||||
if (is_instr_ptw_q) begin
|
||||
// ------------
|
||||
// Update ITLB
|
||||
// ------------
|
||||
// If page is not executable, we can directly raise an error. This
|
||||
// doesn't put a useless entry into the TLB. The same idea applies
|
||||
// to the access flag since we let the access flag be managed by SW.
|
||||
if (!pte.x || !pte.a) state_d = PROPAGATE_ERROR;
|
||||
else itlb_update_o.valid = 1'b1;
|
||||
|
||||
end else begin
|
||||
// ------------
|
||||
// Update DTLB
|
||||
// ------------
|
||||
// Check if the access flag has been set, otherwise throw a page-fault
|
||||
// and let the software handle those bits.
|
||||
// If page is not readable (there are no write-only pages)
|
||||
// we can directly raise an error. This doesn't put a useless
|
||||
// entry into the TLB.
|
||||
if (pte.a && (pte.r || (pte.x && mxr_i))) begin
|
||||
dtlb_update_o.valid = 1'b1;
|
||||
end else begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
end
|
||||
// Request is a store: perform some additional checks
|
||||
// If the request was a store and the page is not write-able, raise an error
|
||||
// the same applies if the dirty flag is not set
|
||||
if (lsu_is_store_i && (!pte.w || !pte.d)) begin
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
end
|
||||
end
|
||||
// check if the ppn is correctly aligned:
|
||||
// 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned superpage; stop and raise a page-fault
|
||||
// exception.
|
||||
if (ptw_lvl_q == LVL1 && pte.ppn[17:0] != '0) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
itlb_update_o.valid = 1'b0;
|
||||
end else if (ptw_lvl_q == LVL2 && pte.ppn[8:0] != '0) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
itlb_update_o.valid = 1'b0;
|
||||
end
|
||||
// this is a pointer to the next TLB level
|
||||
end else begin
|
||||
// pointer to next level of page table
|
||||
if (ptw_lvl_q == LVL1) begin
|
||||
// we are in the second level now
|
||||
ptw_lvl_n = LVL2;
|
||||
ptw_pptr_n = {pte.ppn, vaddr_q[29:21], 3'b0};
|
||||
end
|
||||
|
||||
if (ptw_lvl_q == LVL2) begin
|
||||
// here we received a pointer to the third level
|
||||
ptw_lvl_n = LVL3;
|
||||
ptw_pptr_n = {pte.ppn, vaddr_q[20:12], 3'b0};
|
||||
end
|
||||
|
||||
state_d = WAIT_GRANT;
|
||||
|
||||
if (ptw_lvl_q == LVL3) begin
|
||||
// Should already be the last level page table => Error
|
||||
ptw_lvl_n = LVL3;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Check if this access was actually allowed from a PMP perspective
|
||||
if (!allow_access) begin
|
||||
itlb_update_o.valid = 1'b0;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
// we have to return the failed address in bad_addr
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
state_d = PROPAGATE_ACCESS_ERROR;
|
||||
end
|
||||
end
|
||||
// we've got a data WAIT_GRANT so tell the cache that the tag is valid
|
||||
end
|
||||
// Propagate error to MMU/LSU
|
||||
PROPAGATE_ERROR: begin
|
||||
state_d = IDLE;
|
||||
ptw_error_o = 1'b1;
|
||||
end
|
||||
PROPAGATE_ACCESS_ERROR: begin
|
||||
state_d = IDLE;
|
||||
ptw_access_exception_o = 1'b1;
|
||||
end
|
||||
// wait for the rvalid before going back to IDLE
|
||||
WAIT_RVALID: begin
|
||||
if (data_rvalid_q) state_d = IDLE;
|
||||
end
|
||||
default: begin
|
||||
state_d = IDLE;
|
||||
end
|
||||
endcase
|
||||
|
||||
// -------
|
||||
// Flush
|
||||
// -------
|
||||
// should we have flushed before we got an rvalid, wait for it until going back to IDLE
|
||||
if (flush_i) begin
|
||||
// on a flush check whether we are
|
||||
// 1. in the PTE Lookup check whether we still need to wait for an rvalid
|
||||
// 2. waiting for a grant, if so: wait for it
|
||||
// if not, go back to idle
|
||||
if (((state_q inside {PTE_LOOKUP, WAIT_RVALID}) && !data_rvalid_q) ||
|
||||
((state_q == WAIT_GRANT) && req_port_i.data_gnt))
|
||||
state_d = WAIT_RVALID;
|
||||
else state_d = IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
state_q <= IDLE;
|
||||
is_instr_ptw_q <= 1'b0;
|
||||
ptw_lvl_q <= LVL1;
|
||||
tag_valid_q <= 1'b0;
|
||||
tlb_update_asid_q <= '0;
|
||||
vaddr_q <= '0;
|
||||
ptw_pptr_q <= '0;
|
||||
global_mapping_q <= 1'b0;
|
||||
data_rdata_q <= '0;
|
||||
data_rvalid_q <= 1'b0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
ptw_pptr_q <= ptw_pptr_n;
|
||||
is_instr_ptw_q <= is_instr_ptw_n;
|
||||
ptw_lvl_q <= ptw_lvl_n;
|
||||
tag_valid_q <= tag_valid_n;
|
||||
tlb_update_asid_q <= tlb_update_asid_n;
|
||||
vaddr_q <= vaddr_n;
|
||||
global_mapping_q <= global_mapping_n;
|
||||
data_rdata_q <= req_port_i.data_rdata;
|
||||
data_rvalid_q <= req_port_i.data_rvalid;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
/* verilator lint_on WIDTH */
|
|
@ -1,292 +0,0 @@
|
|||
// Copyright 2018 ETH Zurich and University of Bologna.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: David Schaffenrath, TU Graz
|
||||
// Author: Florian Zaruba, ETH Zurich
|
||||
// Date: 21.4.2017
|
||||
// Description: Translation Lookaside Buffer, SV39
|
||||
// fully set-associative
|
||||
|
||||
|
||||
module tlb
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type tlb_update_t = logic,
|
||||
parameter int unsigned TLB_ENTRIES = 4
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // Flush signal
|
||||
// Update TLB
|
||||
input tlb_update_t update_i,
|
||||
// Lookup signals
|
||||
input logic lu_access_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] lu_asid_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] lu_vaddr_i,
|
||||
output riscv::pte_t lu_content_o,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
output logic lu_is_2M_o,
|
||||
output logic lu_is_1G_o,
|
||||
output logic lu_hit_o
|
||||
);
|
||||
|
||||
localparam VPN2 = (CVA6Cfg.VLEN - 31 < 8) ? CVA6Cfg.VLEN - 31 : 8;
|
||||
|
||||
// SV39 defines three levels of page tables
|
||||
struct packed {
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||
logic [VPN2:0] vpn2;
|
||||
logic [8:0] vpn1;
|
||||
logic [8:0] vpn0;
|
||||
logic is_2M;
|
||||
logic is_1G;
|
||||
logic valid;
|
||||
} [TLB_ENTRIES-1:0]
|
||||
tags_q, tags_n;
|
||||
|
||||
riscv::pte_t [TLB_ENTRIES-1:0] content_q, content_n;
|
||||
logic [8:0] vpn0, vpn1;
|
||||
logic [VPN2:0] vpn2;
|
||||
logic [TLB_ENTRIES-1:0] lu_hit; // to replacement logic
|
||||
logic [TLB_ENTRIES-1:0] replace_en; // replace the following entry, set by replacement strategy
|
||||
//-------------
|
||||
// Translation
|
||||
//-------------
|
||||
always_comb begin : translation
|
||||
vpn0 = lu_vaddr_i[20:12];
|
||||
vpn1 = lu_vaddr_i[29:21];
|
||||
vpn2 = lu_vaddr_i[30+VPN2:30];
|
||||
|
||||
// default assignment
|
||||
lu_hit = '{default: 0};
|
||||
lu_hit_o = 1'b0;
|
||||
lu_content_o = '{default: 0};
|
||||
lu_is_1G_o = 1'b0;
|
||||
lu_is_2M_o = 1'b0;
|
||||
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
||||
// first level match, this may be a giga page, check the ASID flags as well
|
||||
// if the entry is associated to a global address, don't match the ASID (ASID is don't care)
|
||||
if (tags_q[i].valid && ((lu_asid_i == tags_q[i].asid) || content_q[i].g) && vpn2 == tags_q[i].vpn2) begin
|
||||
// second level
|
||||
if (tags_q[i].is_1G) begin
|
||||
lu_is_1G_o = 1'b1;
|
||||
lu_content_o = content_q[i];
|
||||
lu_hit_o = 1'b1;
|
||||
lu_hit[i] = 1'b1;
|
||||
// not a giga page hit so check further
|
||||
end else if (vpn1 == tags_q[i].vpn1) begin
|
||||
// this could be a 2 mega page hit or a 4 kB hit
|
||||
// output accordingly
|
||||
if (tags_q[i].is_2M || vpn0 == tags_q[i].vpn0) begin
|
||||
lu_is_2M_o = tags_q[i].is_2M;
|
||||
lu_content_o = content_q[i];
|
||||
lu_hit_o = 1'b1;
|
||||
lu_hit[i] = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
logic asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
|
||||
logic vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn0_match;
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn1_match;
|
||||
logic [TLB_ENTRIES-1:0] vaddr_vpn2_match;
|
||||
|
||||
assign asid_to_be_flushed_is0 = ~(|asid_to_be_flushed_i);
|
||||
assign vaddr_to_be_flushed_is0 = ~(|vaddr_to_be_flushed_i);
|
||||
|
||||
// ------------------
|
||||
// Update and Flush
|
||||
// ------------------
|
||||
always_comb begin : update_flush
|
||||
tags_n = tags_q;
|
||||
content_n = content_q;
|
||||
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
||||
|
||||
vaddr_vpn0_match[i] = (vaddr_to_be_flushed_i[20:12] == tags_q[i].vpn0);
|
||||
vaddr_vpn1_match[i] = (vaddr_to_be_flushed_i[29:21] == tags_q[i].vpn1);
|
||||
vaddr_vpn2_match[i] = (vaddr_to_be_flushed_i[30+VPN2:30] == tags_q[i].vpn2);
|
||||
|
||||
if (flush_i) begin
|
||||
// invalidate logic
|
||||
// flush everything if ASID is 0 and vaddr is 0 ("SFENCE.VMA x0 x0" case)
|
||||
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0) tags_n[i].valid = 1'b0;
|
||||
// flush vaddr in all addressing space ("SFENCE.VMA vaddr x0" case), it should happen only for leaf pages
|
||||
else if (asid_to_be_flushed_is0 && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_2M) ) && (~vaddr_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global and asid and vaddr both matches with the entry to be flushed ("SFENCE.VMA vaddr asid" case)
|
||||
else if ((!content_q[i].g) && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_2M)) && (asid_to_be_flushed_i == tags_q[i].asid) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// the entry is flushed if it's not global, and the asid matches and vaddr is 0. ("SFENCE.VMA 0 asid" case)
|
||||
else if ((!content_q[i].g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid) && (!asid_to_be_flushed_is0))
|
||||
tags_n[i].valid = 1'b0;
|
||||
// normal replacement
|
||||
end else if (update_i.valid & replace_en[i]) begin
|
||||
// update tag array
|
||||
tags_n[i] = '{
|
||||
asid: update_i.asid,
|
||||
vpn2: update_i.vpn[18+VPN2:18],
|
||||
vpn1: update_i.vpn[17:9],
|
||||
vpn0: update_i.vpn[8:0],
|
||||
is_1G: update_i.is_1G,
|
||||
is_2M: update_i.is_2M,
|
||||
valid: 1'b1
|
||||
};
|
||||
// and content as well
|
||||
content_n[i] = update_i.content;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// -----------------------------------------------
|
||||
// PLRU - Pseudo Least Recently Used Replacement
|
||||
// -----------------------------------------------
|
||||
logic [2*(TLB_ENTRIES-1)-1:0] plru_tree_q, plru_tree_n;
|
||||
always_comb begin : plru_replacement
|
||||
plru_tree_n = plru_tree_q;
|
||||
// The PLRU-tree indexing:
|
||||
// lvl0 0
|
||||
// / \
|
||||
// / \
|
||||
// lvl1 1 2
|
||||
// / \ / \
|
||||
// lvl2 3 4 5 6
|
||||
// / \ /\/\ /\
|
||||
// ... ... ... ...
|
||||
// Just predefine which nodes will be set/cleared
|
||||
// E.g. for a TLB with 8 entries, the for-loop is semantically
|
||||
// equivalent to the following pseudo-code:
|
||||
// unique case (1'b1)
|
||||
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
|
||||
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
|
||||
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
|
||||
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
|
||||
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
|
||||
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
|
||||
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
|
||||
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
|
||||
// default: begin /* No hit */ end
|
||||
// endcase
|
||||
for (
|
||||
int unsigned i = 0; i < TLB_ENTRIES; i++
|
||||
) begin
|
||||
automatic int unsigned idx_base, shift, new_index;
|
||||
// we got a hit so update the pointer as it was least recently used
|
||||
if (lu_hit[i] & lu_access_i) begin
|
||||
// Set the nodes to the values we would expect
|
||||
for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin
|
||||
idx_base = $unsigned((2 ** lvl) - 1);
|
||||
// lvl0 <=> MSB, lvl1 <=> MSB-1, ...
|
||||
shift = $clog2(TLB_ENTRIES) - lvl;
|
||||
// to circumvent the 32 bit integer arithmetic assignment
|
||||
new_index = ~((i >> (shift - 1)) & 32'b1);
|
||||
plru_tree_n[idx_base+(i>>shift)] = new_index[0];
|
||||
end
|
||||
end
|
||||
end
|
||||
// Decode tree to write enable signals
|
||||
// Next for-loop basically creates the following logic for e.g. an 8 entry
|
||||
// TLB (note: pseudo-code obviously):
|
||||
// replace_en[7] = &plru_tree_q[ 6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,1}
|
||||
// replace_en[6] = &plru_tree_q[~6, 2, 0]; //plru_tree_q[0,2,6]=={1,1,0}
|
||||
// replace_en[5] = &plru_tree_q[ 5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,1}
|
||||
// replace_en[4] = &plru_tree_q[~5,~2, 0]; //plru_tree_q[0,2,5]=={1,0,0}
|
||||
// replace_en[3] = &plru_tree_q[ 4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,1}
|
||||
// replace_en[2] = &plru_tree_q[~4, 1,~0]; //plru_tree_q[0,1,4]=={0,1,0}
|
||||
// replace_en[1] = &plru_tree_q[ 3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,1}
|
||||
// replace_en[0] = &plru_tree_q[~3,~1,~0]; //plru_tree_q[0,1,3]=={0,0,0}
|
||||
// For each entry traverse the tree. If every tree-node matches,
|
||||
// the corresponding bit of the entry's index, this is
|
||||
// the next entry to replace.
|
||||
for (int unsigned i = 0; i < TLB_ENTRIES; i += 1) begin
|
||||
automatic logic en;
|
||||
automatic int unsigned idx_base, shift, new_index;
|
||||
en = 1'b1;
|
||||
for (int unsigned lvl = 0; lvl < $clog2(TLB_ENTRIES); lvl++) begin
|
||||
idx_base = $unsigned((2 ** lvl) - 1);
|
||||
// lvl0 <=> MSB, lvl1 <=> MSB-1, ...
|
||||
shift = $clog2(TLB_ENTRIES) - lvl;
|
||||
|
||||
// en &= plru_tree_q[idx_base + (i>>shift)] == ((i >> (shift-1)) & 1'b1);
|
||||
new_index = (i >> (shift - 1)) & 32'b1;
|
||||
if (new_index[0]) begin
|
||||
en &= plru_tree_q[idx_base+(i>>shift)];
|
||||
end else begin
|
||||
en &= ~plru_tree_q[idx_base+(i>>shift)];
|
||||
end
|
||||
end
|
||||
replace_en[i] = en;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
tags_q <= '{default: 0};
|
||||
content_q <= '{default: 0};
|
||||
plru_tree_q <= '{default: 0};
|
||||
end else begin
|
||||
tags_q <= tags_n;
|
||||
content_q <= content_n;
|
||||
plru_tree_q <= plru_tree_n;
|
||||
end
|
||||
end
|
||||
//--------------
|
||||
// Sanity checks
|
||||
//--------------
|
||||
|
||||
//pragma translate_off
|
||||
`ifndef VERILATOR
|
||||
|
||||
initial begin : p_assertions
|
||||
assert ((TLB_ENTRIES % 2 == 0) && (TLB_ENTRIES > 1))
|
||||
else begin
|
||||
$error("TLB size must be a multiple of 2 and greater than 1");
|
||||
$stop();
|
||||
end
|
||||
assert (CVA6Cfg.ASID_WIDTH >= 1)
|
||||
else begin
|
||||
$error("ASID width must be at least 1");
|
||||
$stop();
|
||||
end
|
||||
end
|
||||
|
||||
// Just for checking
|
||||
function int countSetBits(logic [TLB_ENTRIES-1:0] vector);
|
||||
automatic int count = 0;
|
||||
foreach (vector[idx]) begin
|
||||
count += vector[idx];
|
||||
end
|
||||
return count;
|
||||
endfunction
|
||||
|
||||
assert property (@(posedge clk_i) (countSetBits(lu_hit) <= 1))
|
||||
else begin
|
||||
$error("More then one hit in TLB!");
|
||||
$stop();
|
||||
end
|
||||
assert property (@(posedge clk_i) (countSetBits(replace_en) <= 1))
|
||||
else begin
|
||||
$error("More then one TLB entry selected for next replace!");
|
||||
$stop();
|
||||
end
|
||||
|
||||
`endif
|
||||
//pragma translate_on
|
||||
|
||||
endmodule
|
|
@ -1,731 +0,0 @@
|
|||
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Bruno Sá
|
||||
// Date: 14/08/2022
|
||||
// Acknowledges: Technology Innovation Institute (TII)
|
||||
//
|
||||
// Description: Memory Management Unit for CV32A6, contains TLB and
|
||||
// address translation unit. Sv39x4 as defined in RISC-V
|
||||
// privilege specification 1.12.
|
||||
// This module is an adaptation of the MMU Sv39x4 developed
|
||||
// by Florian Zaruba to the Sv39x4 standard.
|
||||
|
||||
|
||||
module cva6_mmu_sv39x4
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type icache_areq_t = logic,
|
||||
parameter type icache_arsp_t = logic,
|
||||
parameter type icache_dreq_t = logic,
|
||||
parameter type icache_drsp_t = logic,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter type exception_t = logic,
|
||||
parameter int unsigned INSTR_TLB_ENTRIES = 4,
|
||||
parameter int unsigned DATA_TLB_ENTRIES = 4
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_ni,
|
||||
input logic flush_i,
|
||||
input logic enable_translation_i,
|
||||
input logic enable_g_translation_i,
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
input logic en_ld_st_g_translation_i, // enable G-Stage translation for load/stores
|
||||
// IF interface
|
||||
input icache_arsp_t icache_areq_i,
|
||||
output icache_areq_t icache_areq_o,
|
||||
// LSU interface
|
||||
// this is a more minimalistic interface because the actual addressing logic is handled
|
||||
// in the LSU as we distinguish load and stores, what we do here is simple address translation
|
||||
input exception_t misaligned_ex_i,
|
||||
input logic lsu_req_i, // request address translation
|
||||
input logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_i, // virtual address in
|
||||
input logic [31:0] lsu_tinst_i, // transformed instruction in
|
||||
input logic lsu_is_store_i, // the translation is requested by a store
|
||||
output logic csr_hs_ld_st_inst_o, // hyp load store instruction
|
||||
// if we need to walk the page table we can't grant in the same cycle
|
||||
// Cycle 0
|
||||
output logic lsu_dtlb_hit_o, // sent in the same cycle as the request if translation hits in the DTLB
|
||||
output logic [CVA6Cfg.PPNW-1:0] lsu_dtlb_ppn_o, // ppn (send same cycle as hit)
|
||||
// Cycle 1
|
||||
output logic lsu_valid_o, // translation is valid
|
||||
output logic [riscv::PLEN-1:0] lsu_paddr_o, // translated address
|
||||
output exception_t lsu_exception_o, // address translation threw an exception
|
||||
// General control signals
|
||||
input riscv::priv_lvl_t priv_lvl_i,
|
||||
input logic v_i,
|
||||
input riscv::priv_lvl_t ld_st_priv_lvl_i,
|
||||
input logic ld_st_v_i,
|
||||
input logic sum_i,
|
||||
input logic vs_sum_i,
|
||||
input logic mxr_i,
|
||||
input logic vmxr_i,
|
||||
input logic hlvx_inst_i,
|
||||
input logic hs_ld_st_inst_i,
|
||||
// input logic flag_mprv_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] vsatp_ppn_i,
|
||||
input logic [CVA6Cfg.PPNW-1:0] hgatp_ppn_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] vs_asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||
input logic [CVA6Cfg.GPLEN-1:0] gpaddr_to_be_flushed_i,
|
||||
input logic flush_tlb_i,
|
||||
input logic flush_tlb_vvma_i,
|
||||
input logic flush_tlb_gvma_i,
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
// PMP
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][riscv::PLEN-3:0] pmpaddr_i
|
||||
);
|
||||
localparam type tlb_update_t = struct packed {
|
||||
logic valid; // valid flag
|
||||
logic is_s_2M;
|
||||
logic is_s_1G;
|
||||
logic is_g_2M;
|
||||
logic is_g_1G;
|
||||
logic [28:0] vpn;
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
|
||||
riscv::pte_t content;
|
||||
riscv::pte_t g_content;
|
||||
};
|
||||
|
||||
logic iaccess_err; // insufficient privilege to access this instruction page
|
||||
logic i_g_st_access_err; // insufficient privilege at g stage to access this instruction page
|
||||
logic daccess_err; // insufficient privilege to access this data page
|
||||
logic d_g_st_access_err; // insufficient privilege to access this data page
|
||||
logic ptw_active; // PTW is currently walking a page table
|
||||
logic walking_instr; // PTW is walking because of an ITLB miss
|
||||
logic ptw_error; // PTW threw an exception
|
||||
logic ptw_error_at_g_st; // PTW threw an exception at the G-Stage
|
||||
logic ptw_err_at_g_int_st; // PTW threw an exception at the G-Stage during S-Stage translation
|
||||
logic ptw_access_exception; // PTW threw an access exception (PMPs)
|
||||
logic [CVA6Cfg.GPLEN-1:0] ptw_bad_gpaddr; // PTW guest page fault bad guest physical addr
|
||||
|
||||
logic [CVA6Cfg.VLEN-1:0] update_vaddr;
|
||||
tlb_update_t update_ptw_itlb, update_ptw_dtlb;
|
||||
|
||||
logic itlb_lu_access;
|
||||
riscv::pte_t itlb_content;
|
||||
logic itlb_is_2M;
|
||||
logic itlb_is_1G;
|
||||
// data from G-stage translation
|
||||
riscv::pte_t itlb_g_content;
|
||||
logic itlb_lu_hit;
|
||||
logic [ CVA6Cfg.GPLEN-1:0] itlb_gpaddr;
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] itlb_lu_asid;
|
||||
|
||||
logic dtlb_lu_access;
|
||||
riscv::pte_t dtlb_content;
|
||||
logic dtlb_is_2M;
|
||||
logic dtlb_is_1G;
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] dtlb_lu_asid;
|
||||
// data from G-stage translation
|
||||
riscv::pte_t dtlb_g_content;
|
||||
logic dtlb_lu_hit;
|
||||
logic [ CVA6Cfg.GPLEN-1:0] dtlb_gpaddr;
|
||||
|
||||
|
||||
// Assignments
|
||||
assign itlb_lu_access = icache_areq_i.fetch_req;
|
||||
assign dtlb_lu_access = lsu_req_i;
|
||||
assign itlb_lu_asid = v_i ? vs_asid_i : asid_i;
|
||||
assign dtlb_lu_asid = (ld_st_v_i || flush_tlb_vvma_i) ? vs_asid_i : asid_i;
|
||||
|
||||
|
||||
cva6_tlb_sv39x4 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.tlb_update_t(tlb_update_t),
|
||||
.TLB_ENTRIES (INSTR_TLB_ENTRIES)
|
||||
) i_itlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i (flush_tlb_i),
|
||||
.flush_vvma_i(flush_tlb_vvma_i),
|
||||
.flush_gvma_i(flush_tlb_gvma_i),
|
||||
.s_st_enbl_i (enable_translation_i),
|
||||
.g_st_enbl_i (enable_g_translation_i),
|
||||
.v_i (v_i),
|
||||
|
||||
.update_i(update_ptw_itlb),
|
||||
|
||||
.lu_access_i (itlb_lu_access),
|
||||
.lu_asid_i (itlb_lu_asid),
|
||||
.lu_vmid_i (vmid_i),
|
||||
.asid_to_be_flushed_i (asid_to_be_flushed_i),
|
||||
.vmid_to_be_flushed_i (vmid_to_be_flushed_i),
|
||||
.vaddr_to_be_flushed_i (vaddr_to_be_flushed_i),
|
||||
.gpaddr_to_be_flushed_i(gpaddr_to_be_flushed_i),
|
||||
.lu_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
.lu_content_o (itlb_content),
|
||||
.lu_g_content_o (itlb_g_content),
|
||||
.lu_gpaddr_o (itlb_gpaddr),
|
||||
|
||||
.lu_is_2M_o(itlb_is_2M),
|
||||
.lu_is_1G_o(itlb_is_1G),
|
||||
.lu_hit_o (itlb_lu_hit)
|
||||
);
|
||||
|
||||
cva6_tlb_sv39x4 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.tlb_update_t(tlb_update_t),
|
||||
.TLB_ENTRIES (DATA_TLB_ENTRIES)
|
||||
) i_dtlb (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.flush_i (flush_tlb_i),
|
||||
.flush_vvma_i(flush_tlb_vvma_i),
|
||||
.flush_gvma_i(flush_tlb_gvma_i),
|
||||
.s_st_enbl_i (en_ld_st_translation_i),
|
||||
.g_st_enbl_i (en_ld_st_g_translation_i),
|
||||
.v_i (ld_st_v_i),
|
||||
|
||||
.update_i(update_ptw_dtlb),
|
||||
|
||||
.lu_access_i (dtlb_lu_access),
|
||||
.lu_asid_i (dtlb_lu_asid),
|
||||
.lu_vmid_i (vmid_i),
|
||||
.asid_to_be_flushed_i (asid_to_be_flushed_i),
|
||||
.vmid_to_be_flushed_i (vmid_to_be_flushed_i),
|
||||
.vaddr_to_be_flushed_i (vaddr_to_be_flushed_i),
|
||||
.gpaddr_to_be_flushed_i(gpaddr_to_be_flushed_i),
|
||||
.lu_vaddr_i (lsu_vaddr_i),
|
||||
.lu_content_o (dtlb_content),
|
||||
.lu_g_content_o (dtlb_g_content),
|
||||
.lu_gpaddr_o (dtlb_gpaddr),
|
||||
|
||||
.lu_is_2M_o(dtlb_is_2M),
|
||||
.lu_is_1G_o(dtlb_is_1G),
|
||||
.lu_hit_o (dtlb_lu_hit)
|
||||
);
|
||||
|
||||
|
||||
cva6_ptw_sv39x4 #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.dcache_req_i_t(dcache_req_i_t),
|
||||
.dcache_req_o_t(dcache_req_o_t),
|
||||
.tlb_update_t(tlb_update_t)
|
||||
) i_ptw (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.ptw_active_o (ptw_active),
|
||||
.walking_instr_o (walking_instr),
|
||||
.ptw_error_o (ptw_error),
|
||||
.ptw_error_at_g_st_o (ptw_error_at_g_st),
|
||||
.ptw_err_at_g_int_st_o (ptw_err_at_g_int_st),
|
||||
.ptw_access_exception_o(ptw_access_exception),
|
||||
.enable_translation_i (enable_translation_i),
|
||||
.enable_g_translation_i(enable_g_translation_i),
|
||||
|
||||
.update_vaddr_o(update_vaddr),
|
||||
.itlb_update_o (update_ptw_itlb),
|
||||
.dtlb_update_o (update_ptw_dtlb),
|
||||
|
||||
.itlb_access_i(itlb_lu_access),
|
||||
.itlb_hit_i (itlb_lu_hit),
|
||||
.itlb_vaddr_i (icache_areq_i.fetch_vaddr),
|
||||
|
||||
.dtlb_access_i(dtlb_lu_access),
|
||||
.dtlb_hit_i (dtlb_lu_hit),
|
||||
.dtlb_vaddr_i (lsu_vaddr_i),
|
||||
.hlvx_inst_i (hlvx_inst_i),
|
||||
|
||||
.req_port_i (req_port_i),
|
||||
.req_port_o (req_port_o),
|
||||
.pmpcfg_i,
|
||||
.pmpaddr_i,
|
||||
.bad_gpaddr_o(ptw_bad_gpaddr),
|
||||
.*
|
||||
);
|
||||
|
||||
// ila_1 i_ila_1 (
|
||||
// .clk(clk_i), // input wire clk
|
||||
// .probe0({req_port_o.address_tag, req_port_o.address_index}),
|
||||
// .probe1(req_port_o.data_req), // input wire [63:0] probe1
|
||||
// .probe2(req_port_i.data_gnt), // input wire [0:0] probe2
|
||||
// .probe3(req_port_i.data_rdata), // input wire [0:0] probe3
|
||||
// .probe4(req_port_i.data_rvalid), // input wire [0:0] probe4
|
||||
// .probe5(ptw_error), // input wire [1:0] probe5
|
||||
// .probe6(update_vaddr), // input wire [0:0] probe6
|
||||
// .probe7(update_ptw_itlb.valid), // input wire [0:0] probe7
|
||||
// .probe8(update_ptw_dtlb.valid), // input wire [0:0] probe8
|
||||
// .probe9(dtlb_lu_access), // input wire [0:0] probe9
|
||||
// .probe10(lsu_vaddr_i), // input wire [0:0] probe10
|
||||
// .probe11(dtlb_lu_hit), // input wire [0:0] probe11
|
||||
// .probe12(itlb_lu_access), // input wire [0:0] probe12
|
||||
// .probe13(icache_areq_i.fetch_vaddr), // input wire [0:0] probe13
|
||||
// .probe14(itlb_lu_hit) // input wire [0:0] probe13
|
||||
// );
|
||||
|
||||
//-----------------------
|
||||
// Instruction Interface
|
||||
//-----------------------
|
||||
logic match_any_execute_region;
|
||||
logic pmp_instr_allow;
|
||||
// The instruction interface is a simple request response interface
|
||||
always_comb begin : instr_interface
|
||||
// MMU disabled: just pass through
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
icache_areq_o.fetch_paddr = icache_areq_i.fetch_vaddr[CVA6Cfg.PLEN-1:0]; // play through in case we disabled address translation
|
||||
// two potential exception sources:
|
||||
// 1. HPTW threw an exception -> signal with a page fault exception
|
||||
// 2. We got an access error because of insufficient permissions -> throw an access exception
|
||||
icache_areq_o.fetch_exception = '0;
|
||||
// Check whether we are allowed to access this memory region from a fetch perspective
|
||||
iaccess_err = icache_areq_i.fetch_req && enable_translation_i && (((priv_lvl_i == riscv::PRIV_LVL_U) && ~itlb_content.u)
|
||||
|| ((priv_lvl_i == riscv::PRIV_LVL_S) && itlb_content.u));
|
||||
|
||||
i_g_st_access_err = icache_areq_i.fetch_req && enable_g_translation_i && !itlb_g_content.u;
|
||||
// MMU enabled: address from TLB, request delayed until hit. Error when TLB
|
||||
// hit and no access right or TLB hit and translated address not valid (e.g.
|
||||
// AXI decode error), or when PTW performs walk due to ITLB miss and raises
|
||||
// an error.
|
||||
if ((enable_translation_i || enable_g_translation_i)) begin
|
||||
// we work with SV39 or SV32, so if VM is enabled, check that all bits [CVA6Cfg.VLEN-1:CVA6Cfg.SV-1] are equal
|
||||
if (icache_areq_i.fetch_req && !((&icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b1 || (|icache_areq_i.fetch_vaddr[CVA6Cfg.VLEN-1:CVA6Cfg.SV-1]) == 1'b0)) begin
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
{{32{1'b0}}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
|
||||
icache_areq_o.fetch_valid = 1'b0;
|
||||
|
||||
// 4K page
|
||||
icache_areq_o.fetch_paddr = {
|
||||
enable_g_translation_i ? itlb_g_content.ppn : itlb_content.ppn,
|
||||
icache_areq_i.fetch_vaddr[11:0]
|
||||
};
|
||||
// Mega page
|
||||
if (itlb_is_2M) begin
|
||||
icache_areq_o.fetch_paddr[20:12] = icache_areq_i.fetch_vaddr[20:12];
|
||||
end
|
||||
// Giga page
|
||||
if (itlb_is_1G) begin
|
||||
icache_areq_o.fetch_paddr[29:12] = icache_areq_i.fetch_vaddr[29:12];
|
||||
end
|
||||
// ---------
|
||||
// ITLB Hit
|
||||
// --------
|
||||
// if we hit the ITLB output the request signal immediately
|
||||
if (itlb_lu_hit) begin
|
||||
icache_areq_o.fetch_valid = icache_areq_i.fetch_req;
|
||||
if (i_g_st_access_err) begin
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_GUEST_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr},
|
||||
itlb_gpaddr[CVA6Cfg.GPLEN-1:0],
|
||||
{{32{1'b0}}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
// we got an access error
|
||||
end else if (iaccess_err) begin
|
||||
// throw a page fault
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
{{32{1'b0}}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end else if (!pmp_instr_allow) begin
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, icache_areq_i.fetch_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
{{32{1'b0}}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end else
|
||||
// ---------
|
||||
// ITLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions happening during walking the page table
|
||||
if (ptw_active && walking_instr) begin
|
||||
icache_areq_o.fetch_valid = ptw_error | ptw_access_exception;
|
||||
if (ptw_error) begin
|
||||
if (ptw_error_at_g_st) begin
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_GUEST_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, update_vaddr},
|
||||
ptw_bad_gpaddr,
|
||||
(ptw_err_at_g_int_st ? (CVA6Cfg.IS_XLEN64 ? riscv::READ_64_PSEUDOINSTRUCTION : riscv::READ_32_PSEUDOINSTRUCTION) : {{32{1'b0}}}),
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end else begin
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
{{32{1'b0}}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end // TODO(moschn,zarubaf): What should the value of tval be in this case?
|
||||
else
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{1'b0}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
{32{1'b0}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end
|
||||
// if it didn't match any execute region throw an `Instruction Access Fault`
|
||||
// or: if we are not translating, check PMPs immediately on the paddr
|
||||
if ((!match_any_execute_region && !ptw_error) || (!(enable_translation_i || enable_g_translation_i) && !pmp_instr_allow)) begin
|
||||
icache_areq_o.fetch_exception = {
|
||||
riscv::INSTR_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.PLEN{1'b0}}, icache_areq_o.fetch_paddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
{{32{1'b0}}},
|
||||
v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
// check for execute flag on memory
|
||||
assign match_any_execute_region = config_pkg::is_inside_execute_regions(
|
||||
CVA6Cfg, {{64 - CVA6Cfg.PLEN{1'b0}}, icache_areq_o.fetch_paddr}
|
||||
);
|
||||
|
||||
// Instruction fetch
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_if (
|
||||
.addr_i (icache_areq_o.fetch_paddr),
|
||||
.priv_lvl_i,
|
||||
// we will always execute on the instruction fetch port
|
||||
.access_type_i(riscv::ACCESS_EXEC),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_instr_allow)
|
||||
);
|
||||
|
||||
//-----------------------
|
||||
// Data Interface
|
||||
//-----------------------
|
||||
logic [CVA6Cfg.VLEN-1:0] lsu_vaddr_n, lsu_vaddr_q;
|
||||
logic [CVA6Cfg.GPLEN-1:0] lsu_gpaddr_n, lsu_gpaddr_q;
|
||||
logic [31:0] lsu_tinst_n, lsu_tinst_q;
|
||||
logic hs_ld_st_inst_n, hs_ld_st_inst_q;
|
||||
riscv::pte_t dtlb_pte_n, dtlb_pte_q;
|
||||
riscv::pte_t dtlb_gpte_n, dtlb_gpte_q;
|
||||
exception_t misaligned_ex_n, misaligned_ex_q;
|
||||
logic lsu_req_n, lsu_req_q;
|
||||
logic lsu_is_store_n, lsu_is_store_q;
|
||||
logic dtlb_hit_n, dtlb_hit_q;
|
||||
logic dtlb_is_2M_n, dtlb_is_2M_q;
|
||||
logic dtlb_is_1G_n, dtlb_is_1G_q;
|
||||
|
||||
// check if we need to do translation or if we are always ready (e.g.: we are not translating anything)
|
||||
assign lsu_dtlb_hit_o = (en_ld_st_translation_i || en_ld_st_g_translation_i) ? dtlb_lu_hit : 1'b1;
|
||||
|
||||
// Wires to PMP checks
|
||||
riscv::pmp_access_t pmp_access_type;
|
||||
logic pmp_data_allow;
|
||||
localparam PPNWMin = (CVA6Cfg.PPNW - 1 > 29) ? 29 : CVA6Cfg.PPNW - 1;
|
||||
// The data interface is simpler and only consists of a request/response interface
|
||||
always_comb begin : data_interface
|
||||
// save request and DTLB response
|
||||
lsu_vaddr_n = lsu_vaddr_i;
|
||||
lsu_tinst_n = lsu_tinst_i;
|
||||
lsu_gpaddr_n = dtlb_gpaddr;
|
||||
lsu_req_n = lsu_req_i;
|
||||
hs_ld_st_inst_n = hs_ld_st_inst_i;
|
||||
misaligned_ex_n = misaligned_ex_i;
|
||||
dtlb_pte_n = dtlb_content;
|
||||
dtlb_gpte_n = dtlb_g_content;
|
||||
dtlb_hit_n = dtlb_lu_hit;
|
||||
lsu_is_store_n = lsu_is_store_i;
|
||||
dtlb_is_2M_n = dtlb_is_2M;
|
||||
dtlb_is_1G_n = dtlb_is_1G;
|
||||
|
||||
lsu_paddr_o = lsu_vaddr_q[CVA6Cfg.PLEN-1:0];
|
||||
lsu_dtlb_ppn_o = lsu_vaddr_n[CVA6Cfg.PLEN-1:12];
|
||||
lsu_valid_o = lsu_req_q;
|
||||
lsu_exception_o = misaligned_ex_q;
|
||||
csr_hs_ld_st_inst_o = hs_ld_st_inst_i || hs_ld_st_inst_q;
|
||||
pmp_access_type = lsu_is_store_q ? riscv::ACCESS_WRITE : riscv::ACCESS_READ;
|
||||
|
||||
// mute misaligned exceptions if there is no request otherwise they will throw accidental exceptions
|
||||
misaligned_ex_n.valid = misaligned_ex_i.valid & lsu_req_i;
|
||||
|
||||
// Check if the User flag is set, then we may only access it in supervisor mode
|
||||
// if SUM is enabled
|
||||
daccess_err = en_ld_st_translation_i &&
|
||||
((ld_st_priv_lvl_i == riscv::PRIV_LVL_S && (ld_st_v_i ? !vs_sum_i : !sum_i ) && dtlb_pte_q.u) || // SUM is not set and we are trying to access a user page in supervisor mode
|
||||
(ld_st_priv_lvl_i == riscv::PRIV_LVL_U && !dtlb_pte_q.u));
|
||||
d_g_st_access_err = en_ld_st_g_translation_i && !dtlb_gpte_q.u;
|
||||
// translation is enabled and no misaligned exception occurred
|
||||
if ((en_ld_st_translation_i || en_ld_st_g_translation_i) && !misaligned_ex_q.valid) begin
|
||||
lsu_valid_o = 1'b0;
|
||||
// 4K page
|
||||
lsu_paddr_o = {
|
||||
(en_ld_st_g_translation_i) ? dtlb_gpte_q.ppn : dtlb_pte_q.ppn, lsu_vaddr_q[11:0]
|
||||
};
|
||||
lsu_dtlb_ppn_o = (en_ld_st_g_translation_i) ? dtlb_g_content.ppn : dtlb_content.ppn;
|
||||
// Mega page
|
||||
if (dtlb_is_2M_q) begin
|
||||
lsu_paddr_o[20:12] = lsu_vaddr_q[20:12];
|
||||
lsu_dtlb_ppn_o[20:12] = lsu_vaddr_n[20:12];
|
||||
end
|
||||
// Giga page
|
||||
if (dtlb_is_1G_q) begin
|
||||
lsu_paddr_o[PPNWMin:12] = lsu_vaddr_q[PPNWMin:12];
|
||||
lsu_dtlb_ppn_o[PPNWMin:12] = lsu_vaddr_n[PPNWMin:12];
|
||||
end
|
||||
// ---------
|
||||
// DTLB Hit
|
||||
// --------
|
||||
if (dtlb_hit_q && lsu_req_q) begin
|
||||
lsu_valid_o = 1'b1;
|
||||
// exception priority:
|
||||
// PAGE_FAULTS have higher priority than ACCESS_FAULTS
|
||||
// virtual memory based exceptions are PAGE_FAULTS
|
||||
// physical memory based exceptions are ACCESS_FAULTS (PMA/PMP)
|
||||
|
||||
// this is a store
|
||||
if (lsu_is_store_q) begin
|
||||
// check if the page is write-able and we are not violating privileges
|
||||
// also check if the dirty flag is set
|
||||
if(en_ld_st_g_translation_i && (!dtlb_gpte_q.w || d_g_st_access_err || !dtlb_gpte_q.d)) begin
|
||||
lsu_exception_o = {
|
||||
riscv::STORE_GUEST_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q},
|
||||
lsu_gpaddr_q,
|
||||
{32{1'b0}},
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end else if (en_ld_st_translation_i && (!dtlb_pte_q.w || daccess_err || !dtlb_pte_q.d)) begin
|
||||
lsu_exception_o = {
|
||||
riscv::STORE_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o = {
|
||||
riscv::ST_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.PLEN{1'b0}}, lsu_paddr_o},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
|
||||
// this is a load
|
||||
end else begin
|
||||
if (d_g_st_access_err) begin
|
||||
lsu_exception_o = {
|
||||
riscv::LOAD_GUEST_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q},
|
||||
lsu_gpaddr_q,
|
||||
{{32{1'b0}}},
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
// check for sufficient access privileges - throw a page fault if necessary
|
||||
end else if (daccess_err) begin
|
||||
lsu_exception_o = {
|
||||
riscv::LOAD_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
// Check if any PMPs are violated
|
||||
end else if (!pmp_data_allow) begin
|
||||
lsu_exception_o = {
|
||||
riscv::LD_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, lsu_vaddr_q},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end
|
||||
end else
|
||||
|
||||
// ---------
|
||||
// DTLB Miss
|
||||
// ---------
|
||||
// watch out for exceptions
|
||||
if (ptw_active && !walking_instr) begin
|
||||
// page table walker threw an exception
|
||||
if (ptw_error) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// the page table walker can only throw page faults
|
||||
if (lsu_is_store_q) begin
|
||||
if (ptw_error_at_g_st) begin
|
||||
lsu_exception_o = {
|
||||
riscv::STORE_GUEST_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
ptw_bad_gpaddr,
|
||||
(ptw_err_at_g_int_st ? (CVA6Cfg.IS_XLEN64 ? riscv::READ_64_PSEUDOINSTRUCTION : riscv::READ_32_PSEUDOINSTRUCTION) : {{32{1'b0}}}),
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end else begin
|
||||
lsu_exception_o = {
|
||||
riscv::STORE_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end else begin
|
||||
if (ptw_error_at_g_st) begin
|
||||
lsu_exception_o = {
|
||||
riscv::LOAD_GUEST_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
ptw_bad_gpaddr,
|
||||
(ptw_err_at_g_int_st ? (CVA6Cfg.IS_XLEN64 ? riscv::READ_64_PSEUDOINSTRUCTION : riscv::READ_32_PSEUDOINSTRUCTION) : {{32{1'b0}}}),
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end else begin
|
||||
lsu_exception_o = {
|
||||
riscv::LOAD_PAGE_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (ptw_access_exception) begin
|
||||
// an error makes the translation valid
|
||||
lsu_valid_o = 1'b1;
|
||||
// the page table walker can only throw page faults
|
||||
lsu_exception_o = {
|
||||
riscv::LD_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end
|
||||
end // If translation is not enabled, check the paddr immediately against PMPs
|
||||
else if (lsu_req_q && !misaligned_ex_q.valid && !pmp_data_allow) begin
|
||||
if (lsu_is_store_q) begin
|
||||
lsu_exception_o = {
|
||||
riscv::ST_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end else begin
|
||||
lsu_exception_o = {
|
||||
riscv::LD_ACCESS_FAULT,
|
||||
{{CVA6Cfg.XLEN - CVA6Cfg.VLEN{lsu_vaddr_q[CVA6Cfg.VLEN-1]}}, update_vaddr},
|
||||
{CVA6Cfg.GPLEN{1'b0}},
|
||||
lsu_tinst_q,
|
||||
ld_st_v_i,
|
||||
1'b1
|
||||
};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Load/store PMP check
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_data (
|
||||
.addr_i (lsu_paddr_o),
|
||||
.priv_lvl_i (ld_st_priv_lvl_i),
|
||||
.access_type_i(pmp_access_type),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (pmp_data_allow)
|
||||
);
|
||||
|
||||
// ----------
|
||||
// Registers
|
||||
// ----------
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
lsu_vaddr_q <= '0;
|
||||
lsu_gpaddr_q <= '0;
|
||||
lsu_tinst_q <= '0;
|
||||
hs_ld_st_inst_q <= '0;
|
||||
lsu_req_q <= '0;
|
||||
misaligned_ex_q <= '0;
|
||||
dtlb_pte_q <= '0;
|
||||
dtlb_gpte_q <= '0;
|
||||
dtlb_hit_q <= '0;
|
||||
lsu_is_store_q <= '0;
|
||||
dtlb_is_2M_q <= '0;
|
||||
dtlb_is_1G_q <= '0;
|
||||
end else begin
|
||||
lsu_vaddr_q <= lsu_vaddr_n;
|
||||
lsu_gpaddr_q <= lsu_gpaddr_n;
|
||||
lsu_tinst_q <= lsu_tinst_n;
|
||||
hs_ld_st_inst_q <= hs_ld_st_inst_n;
|
||||
lsu_req_q <= lsu_req_n;
|
||||
misaligned_ex_q <= misaligned_ex_n;
|
||||
dtlb_pte_q <= dtlb_pte_n;
|
||||
dtlb_gpte_q <= dtlb_gpte_n;
|
||||
dtlb_hit_q <= dtlb_hit_n;
|
||||
lsu_is_store_q <= lsu_is_store_n;
|
||||
dtlb_is_2M_q <= dtlb_is_2M_n;
|
||||
dtlb_is_1G_q <= dtlb_is_1G_n;
|
||||
end
|
||||
end
|
||||
endmodule
|
|
@ -1,641 +0,0 @@
|
|||
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
|
||||
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
||||
// or agreed to in writing, software, hardware and materials distributed under
|
||||
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations under the License.
|
||||
//
|
||||
// Author: Bruno Sá
|
||||
// Date: 14/08/2022
|
||||
// Acknowledges: Technology Innovation Institute (TII)
|
||||
//
|
||||
// Description: Hardware-PTW (Page-Table-Walker) for MMU Sv39x4.
|
||||
// This module is an adaptation of the Sv39 PTW developed
|
||||
// by Florian Zaruba and David Schaffenrath to the Sv39x4 standard.
|
||||
//
|
||||
|
||||
/* verilator lint_off WIDTH */
|
||||
|
||||
module cva6_ptw_sv39x4
|
||||
import ariane_pkg::*;
|
||||
#(
|
||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||
parameter type dcache_req_i_t = logic,
|
||||
parameter type dcache_req_o_t = logic,
|
||||
parameter type tlb_update_t = logic
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // flush everything, we need to do this because
|
||||
// actually everything we do is speculative at this stage
|
||||
// e.g.: there could be a CSR instruction that changes everything
|
||||
output logic ptw_active_o,
|
||||
output logic walking_instr_o, // set when walking for TLB
|
||||
output logic ptw_error_o, // set when an error occurred
|
||||
output logic ptw_error_at_g_st_o, // set when an error occurred at the G-Stage
|
||||
output logic ptw_err_at_g_int_st_o, // set when an error occurred at the G-Stage during S-Stage translation
|
||||
output logic ptw_access_exception_o, // set when an PMP access exception occured
|
||||
input logic enable_translation_i, // CSRs indicate to enable SV39 VS-Stage translation
|
||||
input logic enable_g_translation_i, // CSRs indicate to enable SV39 G-Stage translation
|
||||
input logic en_ld_st_translation_i, // enable virtual memory translation for load/stores
|
||||
input logic en_ld_st_g_translation_i, // enable G-Stage translation for load/stores
|
||||
input logic v_i, // current virtualization mode bit
|
||||
input logic ld_st_v_i, // load/store virtualization mode bit
|
||||
input logic hlvx_inst_i, // is a HLVX load/store instruction
|
||||
|
||||
input logic lsu_is_store_i, // this translation was triggered by a store
|
||||
// PTW memory interface
|
||||
input dcache_req_o_t req_port_i,
|
||||
output dcache_req_i_t req_port_o,
|
||||
|
||||
|
||||
// to TLBs, update logic
|
||||
output tlb_update_t itlb_update_o,
|
||||
output tlb_update_t dtlb_update_o,
|
||||
|
||||
output logic [CVA6Cfg.VLEN-1:0] update_vaddr_o,
|
||||
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_i,
|
||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] vs_asid_i,
|
||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_i,
|
||||
// from TLBs
|
||||
// did we miss?
|
||||
input logic itlb_access_i,
|
||||
input logic itlb_hit_i,
|
||||
input logic [ CVA6Cfg.VLEN-1:0] itlb_vaddr_i,
|
||||
|
||||
input logic dtlb_access_i,
|
||||
input logic dtlb_hit_i,
|
||||
input logic [CVA6Cfg.VLEN-1:0] dtlb_vaddr_i,
|
||||
// from CSR file
|
||||
input logic [CVA6Cfg.PPNW-1:0] satp_ppn_i, // ppn from satp
|
||||
input logic [CVA6Cfg.PPNW-1:0] vsatp_ppn_i, // ppn from satp
|
||||
input logic [CVA6Cfg.PPNW-1:0] hgatp_ppn_i, // ppn from hgatp
|
||||
input logic mxr_i,
|
||||
input logic vmxr_i,
|
||||
// Performance counters
|
||||
output logic itlb_miss_o,
|
||||
output logic dtlb_miss_o,
|
||||
// PMP
|
||||
|
||||
input riscv::pmpcfg_t [15:0] pmpcfg_i,
|
||||
input logic [15:0][CVA6Cfg.PLEN-3:0] pmpaddr_i,
|
||||
output logic [CVA6Cfg.GPLEN-1:0] bad_gpaddr_o
|
||||
|
||||
);
|
||||
|
||||
// input registers
|
||||
logic data_rvalid_q;
|
||||
logic [63:0] data_rdata_q;
|
||||
|
||||
riscv::pte_t pte;
|
||||
// register to perform context switch between stages
|
||||
riscv::pte_t gpte_q, gpte_d;
|
||||
assign pte = riscv::pte_t'(data_rdata_q);
|
||||
|
||||
enum logic [2:0] {
|
||||
IDLE,
|
||||
WAIT_GRANT,
|
||||
PTE_LOOKUP,
|
||||
WAIT_RVALID,
|
||||
PROPAGATE_ERROR,
|
||||
PROPAGATE_ACCESS_ERROR
|
||||
}
|
||||
state_q, state_d;
|
||||
|
||||
// SV39 defines three levels of page tables
|
||||
enum logic [1:0] {
|
||||
LVL1,
|
||||
LVL2,
|
||||
LVL3
|
||||
}
|
||||
ptw_lvl_q, ptw_lvl_n, gptw_lvl_n, gptw_lvl_q;
|
||||
|
||||
// define 3 PTW stages
|
||||
// S_STAGE -> S/VS-stage normal translation controlled by the satp/vsatp CSRs
|
||||
// G_INTERMED_STAGE -> Converts the S/VS-stage non-leaf GPA pointers to HPA (controlled by hgatp)
|
||||
// G_FINAL_STAGE -> Converts the S/VS-stage final GPA to HPA (controlled by hgatp)
|
||||
enum logic [1:0] {
|
||||
S_STAGE,
|
||||
G_INTERMED_STAGE,
|
||||
G_FINAL_STAGE
|
||||
}
|
||||
ptw_stage_q, ptw_stage_d;
|
||||
|
||||
// is this an instruction page table walk?
|
||||
logic is_instr_ptw_q, is_instr_ptw_n;
|
||||
logic global_mapping_q, global_mapping_n;
|
||||
// latched tag signal
|
||||
logic tag_valid_n, tag_valid_q;
|
||||
// register the ASID
|
||||
logic [CVA6Cfg.ASID_WIDTH-1:0] tlb_update_asid_q, tlb_update_asid_n;
|
||||
// register the VMID
|
||||
logic [CVA6Cfg.VMID_WIDTH-1:0] tlb_update_vmid_q, tlb_update_vmid_n;
|
||||
// register the VPN we need to walk, SV39 defines a 39 bit virtual address
|
||||
logic [CVA6Cfg.VLEN-1:0] vaddr_q, vaddr_n;
|
||||
// register the VPN we need to walk, SV39x4 defines a 41 bit virtual address for the G-Stage
|
||||
logic [CVA6Cfg.GPLEN-1:0] gpaddr_q, gpaddr_n;
|
||||
// 4 byte aligned physical pointer
|
||||
logic [CVA6Cfg.PLEN-1:0] ptw_pptr_q, ptw_pptr_n;
|
||||
logic [CVA6Cfg.PLEN-1:0] gptw_pptr_q, gptw_pptr_n;
|
||||
|
||||
// Assignments
|
||||
assign update_vaddr_o = vaddr_q;
|
||||
|
||||
assign ptw_active_o = (state_q != IDLE);
|
||||
assign walking_instr_o = is_instr_ptw_q;
|
||||
// directly output the correct physical address
|
||||
assign req_port_o.address_index = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH-1:0];
|
||||
assign req_port_o.address_tag = ptw_pptr_q[CVA6Cfg.DCACHE_INDEX_WIDTH+CVA6Cfg.DCACHE_TAG_WIDTH-1:CVA6Cfg.DCACHE_INDEX_WIDTH];
|
||||
// we are never going to kill this request
|
||||
assign req_port_o.kill_req = '0;
|
||||
// we are never going to write with the HPTW
|
||||
assign req_port_o.data_wdata = 64'b0;
|
||||
|
||||
// -----------
|
||||
// TLB Update
|
||||
// -----------
|
||||
always_comb begin : tlb_update
|
||||
|
||||
itlb_update_o.vpn = {{41 - CVA6Cfg.SVX{1'b0}}, vaddr_q[CVA6Cfg.SVX-1:12]};
|
||||
dtlb_update_o.vpn = {{41 - CVA6Cfg.SVX{1'b0}}, vaddr_q[CVA6Cfg.SVX-1:12]};
|
||||
// update the correct page table level
|
||||
if (enable_g_translation_i && enable_translation_i) begin
|
||||
itlb_update_o.is_s_2M = (gptw_lvl_q == LVL2);
|
||||
itlb_update_o.is_s_1G = (gptw_lvl_q == LVL1);
|
||||
itlb_update_o.is_g_2M = (ptw_lvl_q == LVL2);
|
||||
itlb_update_o.is_g_1G = (ptw_lvl_q == LVL1);
|
||||
end else if (enable_translation_i) begin
|
||||
itlb_update_o.is_s_2M = (ptw_lvl_q == LVL2);
|
||||
itlb_update_o.is_s_1G = (ptw_lvl_q == LVL1);
|
||||
itlb_update_o.is_g_2M = 1'b0;
|
||||
itlb_update_o.is_g_1G = 1'b0;
|
||||
end else begin
|
||||
itlb_update_o.is_s_2M = 1'b0;
|
||||
itlb_update_o.is_s_1G = 1'b0;
|
||||
itlb_update_o.is_g_2M = (ptw_lvl_q == LVL2);
|
||||
itlb_update_o.is_g_1G = (ptw_lvl_q == LVL1);
|
||||
end
|
||||
|
||||
if (en_ld_st_g_translation_i && en_ld_st_translation_i) begin
|
||||
dtlb_update_o.is_s_2M = (gptw_lvl_q == LVL2);
|
||||
dtlb_update_o.is_s_1G = (gptw_lvl_q == LVL1);
|
||||
dtlb_update_o.is_g_2M = (ptw_lvl_q == LVL2);
|
||||
dtlb_update_o.is_g_1G = (ptw_lvl_q == LVL1);
|
||||
end else if (en_ld_st_translation_i) begin
|
||||
dtlb_update_o.is_s_2M = (ptw_lvl_q == LVL2);
|
||||
dtlb_update_o.is_s_1G = (ptw_lvl_q == LVL1);
|
||||
dtlb_update_o.is_g_2M = 1'b0;
|
||||
dtlb_update_o.is_g_1G = 1'b0;
|
||||
end else begin
|
||||
dtlb_update_o.is_s_2M = 1'b0;
|
||||
dtlb_update_o.is_s_1G = 1'b0;
|
||||
dtlb_update_o.is_g_2M = (ptw_lvl_q == LVL2);
|
||||
dtlb_update_o.is_g_1G = (ptw_lvl_q == LVL1);
|
||||
end
|
||||
// output the correct ASID
|
||||
itlb_update_o.asid = tlb_update_asid_q;
|
||||
dtlb_update_o.asid = tlb_update_asid_q;
|
||||
// output the correct VMID
|
||||
itlb_update_o.vmid = tlb_update_vmid_q;
|
||||
dtlb_update_o.vmid = tlb_update_vmid_q;
|
||||
// set the global mapping bit
|
||||
if (enable_g_translation_i) begin
|
||||
itlb_update_o.content = gpte_q | (global_mapping_q << 5);
|
||||
itlb_update_o.g_content = pte;
|
||||
end else begin
|
||||
itlb_update_o.content = pte | (global_mapping_q << 5);
|
||||
itlb_update_o.g_content = '0;
|
||||
end
|
||||
if (en_ld_st_g_translation_i) begin
|
||||
dtlb_update_o.content = gpte_q | (global_mapping_q << 5);
|
||||
dtlb_update_o.g_content = pte;
|
||||
end else begin
|
||||
dtlb_update_o.content = pte | (global_mapping_q << 5);
|
||||
dtlb_update_o.g_content = '0;
|
||||
end
|
||||
end
|
||||
|
||||
assign req_port_o.tag_valid = tag_valid_q;
|
||||
|
||||
logic allow_access;
|
||||
|
||||
assign bad_gpaddr_o = ptw_error_at_g_st_o ? ((ptw_stage_q == G_INTERMED_STAGE) ? gptw_pptr_q[CVA6Cfg.GPLEN:0] : gpaddr_q) : 'b0;
|
||||
|
||||
pmp #(
|
||||
.CVA6Cfg (CVA6Cfg),
|
||||
.PLEN (CVA6Cfg.PLEN),
|
||||
.PMP_LEN (CVA6Cfg.PLEN - 2),
|
||||
.NR_ENTRIES(CVA6Cfg.NrPMPEntries)
|
||||
) i_pmp_ptw (
|
||||
.addr_i (ptw_pptr_q),
|
||||
// PTW access are always checked as if in S-Mode...
|
||||
.priv_lvl_i (riscv::PRIV_LVL_S),
|
||||
// ...and they are always loads
|
||||
.access_type_i(riscv::ACCESS_READ),
|
||||
// Configuration
|
||||
.conf_addr_i (pmpaddr_i),
|
||||
.conf_i (pmpcfg_i),
|
||||
.allow_o (allow_access)
|
||||
);
|
||||
|
||||
//-------------------
|
||||
// Page table walker
|
||||
//-------------------
|
||||
// A virtual address va is translated into a physical address pa as follows:
|
||||
// 1. Let a be sptbr.ppn × PAGESIZE, and let i = LEVELS-1. (For Sv39,
|
||||
// PAGESIZE=2^12 and LEVELS=3.)
|
||||
// 2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. (For
|
||||
// Sv32, PTESIZE=4.)
|
||||
// 3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, or if any bits or encodings
|
||||
// that are reserved for future standard use are set within pte, stop and raise
|
||||
// a page-fault exception corresponding to the original access type.
|
||||
// 4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to step 5.
|
||||
// Otherwise, this PTE is a pointer to the next level of the page table.
|
||||
// Let i=i-1. If i < 0, stop and raise an access exception. Otherwise, let
|
||||
// a = pte.ppn × PAGESIZE and go to step 2.
|
||||
// 5. A leaf PTE has been found. Determine if the requested memory access
|
||||
// is allowed by the pte.r, pte.w, and pte.x bits. If not, stop and
|
||||
// raise an access exception. Otherwise, the translation is successful.
|
||||
// Set pte.a to 1, and, if the memory access is a store, set pte.d to 1.
|
||||
// The translated physical address is given as follows:
|
||||
// - pa.pgoff = va.pgoff.
|
||||
// - If i > 0, then this is a superpage translation and
|
||||
// pa.ppn[i-1:0] = va.vpn[i-1:0].
|
||||
// - pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i].
|
||||
always_comb begin : ptw
|
||||
automatic logic [ CVA6Cfg.PLEN-1:0] pptr;
|
||||
automatic logic [CVA6Cfg.GPLEN-1:0] gpaddr;
|
||||
// default assignments
|
||||
// PTW memory interface
|
||||
tag_valid_n = 1'b0;
|
||||
req_port_o.data_req = 1'b0;
|
||||
req_port_o.data_be = 8'hFF;
|
||||
req_port_o.data_size = 2'b11;
|
||||
req_port_o.data_we = 1'b0;
|
||||
ptw_error_o = 1'b0;
|
||||
ptw_error_at_g_st_o = 1'b0;
|
||||
ptw_err_at_g_int_st_o = 1'b0;
|
||||
ptw_access_exception_o = 1'b0;
|
||||
itlb_update_o.valid = 1'b0;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
is_instr_ptw_n = is_instr_ptw_q;
|
||||
ptw_lvl_n = ptw_lvl_q;
|
||||
gptw_lvl_n = gptw_lvl_q;
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
gptw_pptr_n = gptw_pptr_q;
|
||||
state_d = state_q;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
gpte_d = gpte_q;
|
||||
global_mapping_n = global_mapping_q;
|
||||
// input registers
|
||||
tlb_update_asid_n = tlb_update_asid_q;
|
||||
tlb_update_vmid_n = tlb_update_vmid_q;
|
||||
vaddr_n = vaddr_q;
|
||||
gpaddr_n = gpaddr_q;
|
||||
pptr = ptw_pptr_q;
|
||||
gpaddr = gpaddr_q;
|
||||
|
||||
itlb_miss_o = 1'b0;
|
||||
dtlb_miss_o = 1'b0;
|
||||
|
||||
case (state_q)
|
||||
|
||||
IDLE: begin
|
||||
// by default we start with the top-most page table
|
||||
ptw_lvl_n = LVL1;
|
||||
gptw_lvl_n = LVL1;
|
||||
global_mapping_n = 1'b0;
|
||||
is_instr_ptw_n = 1'b0;
|
||||
gpaddr_n = '0;
|
||||
gpte_d = '0;
|
||||
// if we got an ITLB miss
|
||||
if ((enable_translation_i | enable_g_translation_i) & itlb_access_i & ~itlb_hit_i & ~dtlb_access_i) begin
|
||||
if (enable_translation_i && enable_g_translation_i) begin
|
||||
ptw_stage_d = G_INTERMED_STAGE;
|
||||
pptr = {vsatp_ppn_i, itlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
gptw_pptr_n = pptr;
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], pptr[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
end else if (!enable_translation_i && enable_g_translation_i) begin
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
gpaddr_n = itlb_vaddr_i[CVA6Cfg.SVX-1:0];
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], itlb_vaddr_i[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
end else begin
|
||||
ptw_stage_d = S_STAGE;
|
||||
if (v_i) ptw_pptr_n = {vsatp_ppn_i, itlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
else ptw_pptr_n = {satp_ppn_i, itlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
end
|
||||
is_instr_ptw_n = 1'b1;
|
||||
tlb_update_asid_n = v_i ? vs_asid_i : asid_i;
|
||||
tlb_update_vmid_n = vmid_i;
|
||||
vaddr_n = itlb_vaddr_i;
|
||||
state_d = WAIT_GRANT;
|
||||
itlb_miss_o = 1'b1;
|
||||
// we got an DTLB miss
|
||||
end else if ((en_ld_st_translation_i || en_ld_st_g_translation_i) & dtlb_access_i & ~dtlb_hit_i) begin
|
||||
if (en_ld_st_translation_i && en_ld_st_g_translation_i) begin
|
||||
ptw_stage_d = G_INTERMED_STAGE;
|
||||
pptr = {vsatp_ppn_i, dtlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
gptw_pptr_n = pptr;
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], pptr[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
end else if (!en_ld_st_translation_i && en_ld_st_g_translation_i) begin
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
gpaddr_n = dtlb_vaddr_i[CVA6Cfg.SVX-1:0];
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], dtlb_vaddr_i[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
end else begin
|
||||
ptw_stage_d = S_STAGE;
|
||||
if (ld_st_v_i) ptw_pptr_n = {vsatp_ppn_i, dtlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
else ptw_pptr_n = {satp_ppn_i, dtlb_vaddr_i[CVA6Cfg.SV-1:30], 3'b0};
|
||||
end
|
||||
tlb_update_asid_n = ld_st_v_i ? vs_asid_i : asid_i;
|
||||
tlb_update_vmid_n = vmid_i;
|
||||
vaddr_n = dtlb_vaddr_i;
|
||||
state_d = WAIT_GRANT;
|
||||
dtlb_miss_o = 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
WAIT_GRANT: begin
|
||||
// send a request out
|
||||
req_port_o.data_req = 1'b1;
|
||||
// wait for the WAIT_GRANT
|
||||
if (req_port_i.data_gnt) begin
|
||||
// send the tag valid signal one cycle later
|
||||
tag_valid_n = 1'b1;
|
||||
state_d = PTE_LOOKUP;
|
||||
end
|
||||
end
|
||||
|
||||
PTE_LOOKUP: begin
|
||||
// we wait for the valid signal
|
||||
if (data_rvalid_q) begin
|
||||
|
||||
// check if the global mapping bit is set
|
||||
if (pte.g && ptw_stage_q == S_STAGE) global_mapping_n = 1'b1;
|
||||
|
||||
// -------------
|
||||
// Invalid PTE
|
||||
// -------------
|
||||
// If pte.v = 0, or if pte.r = 0 and pte.w = 1, stop and raise a page-fault exception.
|
||||
if (!pte.v || (!pte.r && pte.w) || (|pte.reserved)) state_d = PROPAGATE_ERROR;
|
||||
// -----------
|
||||
// Valid PTE
|
||||
// -----------
|
||||
else begin
|
||||
state_d = IDLE;
|
||||
// it is a valid PTE
|
||||
// if pte.r = 1 or pte.x = 1 it is a valid PTE
|
||||
if (pte.r || pte.x) begin
|
||||
case (ptw_stage_q)
|
||||
S_STAGE: begin
|
||||
if ((is_instr_ptw_q && enable_g_translation_i) || (!is_instr_ptw_q && en_ld_st_g_translation_i)) begin
|
||||
state_d = WAIT_GRANT;
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
gpte_d = pte;
|
||||
gptw_lvl_n = ptw_lvl_q;
|
||||
gpaddr = {pte.ppn[CVA6Cfg.GPPNW-1:0], vaddr_q[11:0]};
|
||||
if (ptw_lvl_q == LVL2) gpaddr[20:0] = vaddr_q[20:0];
|
||||
if (ptw_lvl_q == LVL1) gpaddr[29:0] = vaddr_q[29:0];
|
||||
gpaddr_n = gpaddr;
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], gpaddr[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
ptw_lvl_n = LVL1;
|
||||
end
|
||||
end
|
||||
G_INTERMED_STAGE: begin
|
||||
state_d = WAIT_GRANT;
|
||||
ptw_stage_d = S_STAGE;
|
||||
ptw_lvl_n = gptw_lvl_q;
|
||||
pptr = {pte.ppn[CVA6Cfg.GPPNW-1:0], gptw_pptr_q[11:0]};
|
||||
if (ptw_lvl_q == LVL2) pptr[20:0] = gptw_pptr_q[20:0];
|
||||
if (ptw_lvl_q == LVL1) pptr[29:0] = gptw_pptr_q[29:0];
|
||||
ptw_pptr_n = pptr;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
// Valid translation found (either 1G, 2M or 4K entry)
|
||||
if (is_instr_ptw_q) begin
|
||||
// ------------
|
||||
// Update ITLB
|
||||
// ------------
|
||||
// If page is not executable, we can directly raise an error. This
|
||||
// doesn't put a useless entry into the TLB. The same idea applies
|
||||
// to the access flag since we let the access flag be managed by SW.
|
||||
if (!pte.x || !pte.a) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end else if ((ptw_stage_q == G_FINAL_STAGE) || !enable_g_translation_i)
|
||||
itlb_update_o.valid = 1'b1;
|
||||
|
||||
end else begin
|
||||
// ------------
|
||||
// Update DTLB
|
||||
// ------------
|
||||
// Check if the access flag has been set, otherwise throw a page-fault
|
||||
// and let the software handle those bits.
|
||||
// If page is not readable (there are no write-only pages)
|
||||
// we can directly raise an error. This doesn't put a useless
|
||||
// entry into the TLB.
|
||||
if (pte.a && ((pte.r && !hlvx_inst_i) || (pte.x && (mxr_i || hlvx_inst_i || (ptw_stage_q == S_STAGE && vmxr_i && ld_st_v_i))))) begin
|
||||
if ((ptw_stage_q == G_FINAL_STAGE) || !en_ld_st_g_translation_i)
|
||||
dtlb_update_o.valid = 1'b1;
|
||||
end else begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
// Request is a store: perform some additional checks
|
||||
// If the request was a store and the page is not write-able, raise an error
|
||||
// the same applies if the dirty flag is not set
|
||||
if (lsu_is_store_i && (!pte.w || !pte.d)) begin
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
end
|
||||
// check if the ppn is correctly aligned:
|
||||
// 6. If i > 0 and pa.ppn[i − 1 : 0] != 0, this is a misaligned superpage; stop and raise a page-fault
|
||||
// exception.
|
||||
if (ptw_lvl_q == LVL1 && pte.ppn[17:0] != '0) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
itlb_update_o.valid = 1'b0;
|
||||
end else if (ptw_lvl_q == LVL2 && pte.ppn[8:0] != '0) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
itlb_update_o.valid = 1'b0;
|
||||
end
|
||||
// check if 63:41 are all zeros
|
||||
if (((v_i && is_instr_ptw_q) || (ld_st_v_i && !is_instr_ptw_q)) && ptw_stage_q == S_STAGE && !((|pte.ppn[CVA6Cfg.PPNW-1:CVA6Cfg.GPPNW]) == 1'b0)) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = G_FINAL_STAGE;
|
||||
end
|
||||
// this is a pointer to the next TLB level
|
||||
end else begin
|
||||
// pointer to next level of page table
|
||||
if (ptw_lvl_q == LVL1) begin
|
||||
// we are in the second level now
|
||||
ptw_lvl_n = LVL2;
|
||||
case (ptw_stage_q)
|
||||
S_STAGE: begin
|
||||
if ((is_instr_ptw_q && enable_g_translation_i) || (!is_instr_ptw_q && en_ld_st_g_translation_i)) begin
|
||||
ptw_stage_d = G_INTERMED_STAGE;
|
||||
gpte_d = pte;
|
||||
gptw_lvl_n = LVL2;
|
||||
pptr = {pte.ppn, vaddr_q[29:21], 3'b0};
|
||||
gptw_pptr_n = pptr;
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], pptr[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
ptw_lvl_n = LVL1;
|
||||
end else begin
|
||||
ptw_pptr_n = {pte.ppn, vaddr_q[29:21], 3'b0};
|
||||
end
|
||||
end
|
||||
G_INTERMED_STAGE: begin
|
||||
ptw_pptr_n = {pte.ppn, gptw_pptr_q[29:21], 3'b0};
|
||||
end
|
||||
G_FINAL_STAGE: begin
|
||||
ptw_pptr_n = {pte.ppn, gpaddr_q[29:21], 3'b0};
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
if (ptw_lvl_q == LVL2) begin
|
||||
// here we received a pointer to the third level
|
||||
ptw_lvl_n = LVL3;
|
||||
unique case (ptw_stage_q)
|
||||
S_STAGE: begin
|
||||
if ((is_instr_ptw_q && enable_g_translation_i) || (!is_instr_ptw_q && en_ld_st_g_translation_i)) begin
|
||||
ptw_stage_d = G_INTERMED_STAGE;
|
||||
gpte_d = pte;
|
||||
gptw_lvl_n = LVL3;
|
||||
pptr = {pte.ppn, vaddr_q[20:12], 3'b0};
|
||||
gptw_pptr_n = pptr;
|
||||
ptw_pptr_n = {hgatp_ppn_i[CVA6Cfg.PPNW-1:2], pptr[CVA6Cfg.SVX-1:30], 3'b0};
|
||||
ptw_lvl_n = LVL1;
|
||||
end else begin
|
||||
ptw_pptr_n = {pte.ppn, vaddr_q[20:12], 3'b0};
|
||||
end
|
||||
end
|
||||
G_INTERMED_STAGE: begin
|
||||
ptw_pptr_n = {pte.ppn, gptw_pptr_q[20:12], 3'b0};
|
||||
end
|
||||
G_FINAL_STAGE: begin
|
||||
ptw_pptr_n = {pte.ppn, gpaddr_q[20:12], 3'b0};
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
state_d = WAIT_GRANT;
|
||||
// check if reserved bits are cleared for non-leaf entries
|
||||
if (pte.a || pte.d || pte.u) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
if (ptw_lvl_q == LVL3) begin
|
||||
// Should already be the last level page table => Error
|
||||
ptw_lvl_n = LVL3;
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
// check if 63:41 are all zeros
|
||||
if (((v_i && is_instr_ptw_q) || (ld_st_v_i && !is_instr_ptw_q)) && ptw_stage_q == S_STAGE && !((|pte.ppn[CVA6Cfg.PPNW-1:CVA6Cfg.GPPNW]) == 1'b0)) begin
|
||||
state_d = PROPAGATE_ERROR;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Check if this access was actually allowed from a PMP perspective
|
||||
if (!allow_access) begin
|
||||
itlb_update_o.valid = 1'b0;
|
||||
dtlb_update_o.valid = 1'b0;
|
||||
// we have to return the failed address in bad_addr
|
||||
ptw_pptr_n = ptw_pptr_q;
|
||||
ptw_stage_d = ptw_stage_q;
|
||||
state_d = PROPAGATE_ACCESS_ERROR;
|
||||
end
|
||||
end
|
||||
// we've got a data WAIT_GRANT so tell the cache that the tag is valid
|
||||
end
|
||||
// Propagate error to MMU/LSU
|
||||
PROPAGATE_ERROR: begin
|
||||
state_d = IDLE;
|
||||
ptw_error_o = 1'b1;
|
||||
ptw_error_at_g_st_o = (ptw_stage_q != S_STAGE) ? 1'b1 : 1'b0;
|
||||
ptw_err_at_g_int_st_o = (ptw_stage_q == G_INTERMED_STAGE) ? 1'b1 : 1'b0;
|
||||
end
|
||||
PROPAGATE_ACCESS_ERROR: begin
|
||||
state_d = IDLE;
|
||||
ptw_access_exception_o = 1'b1;
|
||||
end
|
||||
// wait for the rvalid before going back to IDLE
|
||||
WAIT_RVALID: begin
|
||||
if (data_rvalid_q) state_d = IDLE;
|
||||
end
|
||||
default: begin
|
||||
state_d = IDLE;
|
||||
end
|
||||
endcase
|
||||
|
||||
// -------
|
||||
// Flush
|
||||
// -------
|
||||
// should we have flushed before we got an rvalid, wait for it until going back to IDLE
|
||||
if (flush_i) begin
|
||||
// on a flush check whether we are
|
||||
// 1. in the PTE Lookup check whether we still need to wait for an rvalid
|
||||
// 2. waiting for a grant, if so: wait for it
|
||||
// if not, go back to idle
|
||||
if (((state_q inside {PTE_LOOKUP, WAIT_RVALID}) && !data_rvalid_q) ||
|
||||
((state_q == WAIT_GRANT) && req_port_i.data_gnt))
|
||||
state_d = WAIT_RVALID;
|
||||
else state_d = IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (~rst_ni) begin
|
||||
state_q <= IDLE;
|
||||
ptw_stage_q <= S_STAGE;
|
||||
is_instr_ptw_q <= 1'b0;
|
||||
ptw_lvl_q <= LVL1;
|
||||
gptw_lvl_q <= LVL1;
|
||||
tag_valid_q <= 1'b0;
|
||||
tlb_update_asid_q <= '0;
|
||||
tlb_update_vmid_q <= '0;
|
||||
vaddr_q <= '0;
|
||||
gpaddr_q <= '0;
|
||||
ptw_pptr_q <= '0;
|
||||
gptw_pptr_q <= '0;
|
||||
global_mapping_q <= 1'b0;
|
||||
data_rdata_q <= '0;
|
||||
gpte_q <= '0;
|
||||
data_rvalid_q <= 1'b0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
ptw_stage_q <= ptw_stage_d;
|
||||
ptw_pptr_q <= ptw_pptr_n;
|
||||
gptw_pptr_q <= gptw_pptr_n;
|
||||
is_instr_ptw_q <= is_instr_ptw_n;
|
||||
ptw_lvl_q <= ptw_lvl_n;
|
||||
gptw_lvl_q <= gptw_lvl_n;
|
||||
tag_valid_q <= tag_valid_n;
|
||||
tlb_update_asid_q <= tlb_update_asid_n;
|
||||
tlb_update_vmid_q <= tlb_update_vmid_n;
|
||||
vaddr_q <= vaddr_n;
|
||||
gpaddr_q <= gpaddr_n;
|
||||
global_mapping_q <= global_mapping_n;
|
||||
data_rdata_q <= req_port_i.data_rdata;
|
||||
gpte_q <= gpte_d;
|
||||
data_rvalid_q <= req_port_i.data_rvalid;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
/* verilator lint_on WIDTH */
|
|
@ -34,8 +34,7 @@ ariane:
|
|||
src/load_unit.sv,
|
||||
src/load_store_unit.sv,
|
||||
src/miss_handler.sv,
|
||||
src/mmu_sv39/mmu.sv,
|
||||
src/mmu_sv32/cva6_mmu_sv32.sv,
|
||||
src/cva6_mmu/cva6_mmu.sv,
|
||||
src/mult.sv,
|
||||
src/nbdcache.sv,
|
||||
src/vdregs.sv,
|
||||
|
@ -43,14 +42,12 @@ ariane:
|
|||
src/sram_wrapper.sv,
|
||||
src/pcgen_stage.sv,
|
||||
src/perf_counters.sv,
|
||||
src/mmu_sv39/ptw.sv,
|
||||
src/mmu_sv32/cva6_ptw_sv32.sv,
|
||||
src/cva6_mmu/cva6_ptw.sv,
|
||||
src/re_name.sv,
|
||||
src/scoreboard.sv,
|
||||
src/store_buffer.sv,
|
||||
src/store_unit.sv,
|
||||
src/mmu_sv39/tlb.sv,
|
||||
src/mmu_sv32/cva6_tlb_sv32.sv,
|
||||
src/cva6_mmu/cva6_tlb.sv,
|
||||
src/acc_dispatcher.sv,
|
||||
src/debug/dm_csrs.sv,
|
||||
src/debug/dm_mem.sv,
|
||||
|
|
Loading…
Add table
Reference in a new issue