mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-19 03:44:46 -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
|
- core/cva6_accel_first_pass_decoder_stub.sv
|
||||||
|
|
||||||
# MMU
|
# 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:
|
files:
|
||||||
- core/mmu_sv39/tlb.sv
|
- core/cva6_mmu/cva6_tlb.sv
|
||||||
- core/mmu_sv39/mmu.sv
|
- core/cva6_mmu/cva6_mmu.sv
|
||||||
- core/mmu_sv39/ptw.sv
|
- core/cva6_mmu/cva6_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
|
|
||||||
|
|
||||||
# Packages
|
# Packages
|
||||||
- core/include/wt_cache_pkg.sv
|
- core/include/wt_cache_pkg.sv
|
||||||
|
|
|
@ -33,20 +33,17 @@ filesets:
|
||||||
- src/lsu_arbiter.sv
|
- src/lsu_arbiter.sv
|
||||||
- src/lsu.sv
|
- src/lsu.sv
|
||||||
- src/miss_handler.sv
|
- src/miss_handler.sv
|
||||||
- src/mmu_sv39/mmu.sv
|
- src/cva6_mmu/cva6_mmu.sv
|
||||||
- src/mmu_sv32/cva6_mmu_sv32.sv
|
|
||||||
- src/mult.sv
|
- src/mult.sv
|
||||||
- src/nbdcache.sv
|
- src/nbdcache.sv
|
||||||
- src/pcgen_stage.sv
|
- src/pcgen_stage.sv
|
||||||
- src/perf_counters.sv
|
- src/perf_counters.sv
|
||||||
- src/mmu_sv39/ptw.sv
|
- src/cva6_mmu/cva6_ptw.sv
|
||||||
- src/mmu_sv32/cva6_ptw_sv32.sv
|
|
||||||
- src/regfile_ff.sv
|
- src/regfile_ff.sv
|
||||||
- src/scoreboard.sv
|
- src/scoreboard.sv
|
||||||
- src/store_buffer.sv
|
- src/store_buffer.sv
|
||||||
- src/store_unit.sv
|
- src/store_unit.sv
|
||||||
- src/mmu_sv39/tlb.sv
|
- src/cva6_mmu/cva6_tlb.sv
|
||||||
- src/mmu_sv32/cva6_tlb_sv32.sv
|
|
||||||
file_type : systemVerilogSource
|
file_type : systemVerilogSource
|
||||||
depend :
|
depend :
|
||||||
- pulp-platform.org::axi_mem_if
|
- 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}/vendor/pulp-platform/tech_cells_generic/src/rtl/tc_sram.sv
|
||||||
${CVA6_REPO_DIR}/common/local/util/sram.sv
|
${CVA6_REPO_DIR}/common/local/util/sram.sv
|
||||||
|
|
||||||
// MMU Sv39
|
// MMU
|
||||||
${CVA6_REPO_DIR}/core/mmu_sv39/mmu.sv
|
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_mmu.sv
|
||||||
${CVA6_REPO_DIR}/core/mmu_sv39/ptw.sv
|
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_ptw.sv
|
||||||
${CVA6_REPO_DIR}/core/mmu_sv39/tlb.sv
|
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_tlb.sv
|
||||||
|
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_shared_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
|
|
||||||
|
|
||||||
// end of manifest
|
// 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
|
// Copyright and related rights are licensed under the Solderpad Hardware
|
||||||
// License, Version 0.51 (the "License"); you may not use this file except in
|
// 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
|
// 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
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
//
|
//
|
||||||
// Author: Bruno Sá
|
// Author: Angela Gonzalez PlanV Technology
|
||||||
// Date: 14/08/2022
|
// Date: 26/02/2024
|
||||||
// Acknowledges: Technology Innovation Institute (TII)
|
|
||||||
//
|
//
|
||||||
// Description: Translation Lookaside Buffer, Sv39x4 , fully set-associative
|
// Description: Translation Lookaside Buffer, parameterizable to Sv32 or Sv39 ,
|
||||||
// This module is an adaptation of the Sv39 TLB developed
|
// or sv39x4 fully set-associative
|
||||||
// by Florian Zaruba and David Schaffenrath to the Sv39x4 standard.
|
// 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
|
||||||
module cva6_tlb_sv39x4
|
|
||||||
import ariane_pkg::*;
|
import ariane_pkg::*;
|
||||||
#(
|
#(
|
||||||
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
|
||||||
parameter type tlb_update_t = logic,
|
parameter type pte_cva6_t = logic,
|
||||||
parameter int unsigned TLB_ENTRIES = 4
|
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 clk_i, // Clock
|
||||||
input logic rst_ni, // Asynchronous reset active low
|
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 g_st_enbl_i, // g-stage enabled
|
||||||
input logic v_i, // virtualization mode
|
input logic v_i, // virtualization mode
|
||||||
// Update TLB
|
// Update TLB
|
||||||
input tlb_update_t update_i,
|
input tlb_update_cva6_t update_i,
|
||||||
// Lookup signals
|
// Lookup signals
|
||||||
input logic lu_access_i,
|
input logic lu_access_i,
|
||||||
input logic [CVA6Cfg.ASID_WIDTH-1:0] lu_asid_i,
|
input logic [CVA6Cfg.ASID_WIDTH-1:0] lu_asid_i,
|
||||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] lu_vmid_i,
|
input logic [CVA6Cfg.VMID_WIDTH-1:0] lu_vmid_i,
|
||||||
input logic [CVA6Cfg.VLEN-1:0] lu_vaddr_i,
|
input logic [CVA6Cfg.VLEN-1:0] lu_vaddr_i,
|
||||||
output logic [CVA6Cfg.GPLEN-1:0] lu_gpaddr_o,
|
output logic [CVA6Cfg.GPLEN-1:0] lu_gpaddr_o,
|
||||||
output riscv::pte_t lu_content_o,
|
output pte_cva6_t lu_content_o,
|
||||||
output riscv::pte_t lu_g_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.ASID_WIDTH-1:0] asid_to_be_flushed_i,
|
||||||
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_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.VLEN-1:0] vaddr_to_be_flushed_i,
|
||||||
input logic [CVA6Cfg.GPLEN-1:0] gpaddr_to_be_flushed_i,
|
input logic [CVA6Cfg.GPLEN-1:0] gpaddr_to_be_flushed_i,
|
||||||
output logic lu_is_2M_o,
|
output logic [CVA6Cfg.PtLevels-2:0] lu_is_page_o,
|
||||||
output logic lu_is_1G_o,
|
|
||||||
output logic lu_hit_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;
|
localparam GPPN2 = (CVA6Cfg.XLEN == 32) ? CVA6Cfg.VLEN - 33 : 10;
|
||||||
|
|
||||||
// SV39 defines three levels of page tables
|
// SV39 defines three levels of page tables
|
||||||
struct packed {
|
struct packed {
|
||||||
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
|
||||||
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
|
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
|
||||||
logic [GPPN2:0] vpn2;
|
logic [CVA6Cfg.PtLevels+HYP_EXT-1:0][(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] vpn;
|
||||||
logic [8:0] vpn1;
|
logic [CVA6Cfg.PtLevels-2:0][HYP_EXT:0] is_page;
|
||||||
logic [8:0] vpn0;
|
logic [HYP_EXT*2:0] v_st_enbl; // v_i,g-stage enabled, s-stage enabled
|
||||||
logic is_s_2M;
|
logic valid;
|
||||||
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;
|
|
||||||
} [TLB_ENTRIES-1:0]
|
} [TLB_ENTRIES-1:0]
|
||||||
tags_q, tags_n;
|
tags_q, tags_n;
|
||||||
|
|
||||||
struct packed {
|
struct packed {
|
||||||
riscv::pte_t pte;
|
pte_cva6_t pte;
|
||||||
riscv::pte_t gpte;
|
pte_cva6_t gpte;
|
||||||
} [TLB_ENTRIES-1:0]
|
} [TLB_ENTRIES-1:0]
|
||||||
content_q, content_n;
|
content_q, content_n;
|
||||||
|
|
||||||
logic [8:0] vpn0, vpn1;
|
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] vpn_match;
|
||||||
logic [GPPN2:0] vpn2;
|
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] 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] 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] match_asid;
|
||||||
logic [TLB_ENTRIES-1:0] is_1G;
|
logic [TLB_ENTRIES-1:0] match_vmid;
|
||||||
logic [TLB_ENTRIES-1:0] is_2M;
|
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;
|
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
|
// 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
|
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
|
// default assignment
|
||||||
lu_hit = '{default: 0};
|
lu_hit = '{default: 0};
|
||||||
lu_hit_o = 1'b0;
|
lu_hit_o = 1'b0;
|
||||||
lu_content_o = '{default: 0};
|
lu_content_o = '{default: 0};
|
||||||
lu_g_content_o = '{default: 0};
|
lu_g_content_o = '{default: 0};
|
||||||
lu_is_1G_o = 1'b0;
|
lu_is_page_o = '{default: 0};
|
||||||
lu_is_2M_o = 1'b0;
|
|
||||||
match_asid = '{default: 0};
|
match_asid = '{default: 0};
|
||||||
match_vmid = '{default: 0};
|
match_vmid = CVA6Cfg.RVH ? '{default: 0} : '{default: 1};
|
||||||
match_stage = '{default: 0};
|
match_stage = '{default: 0};
|
||||||
is_1G = '{default: 0};
|
|
||||||
is_2M = '{default: 0};
|
|
||||||
g_content = '{default: 0};
|
g_content = '{default: 0};
|
||||||
lu_gpaddr_o = '{default: 0};
|
lu_gpaddr_o = '{default: 0};
|
||||||
|
|
||||||
|
|
||||||
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
|
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
|
// 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 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_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);
|
if (CVA6Cfg.RVH) begin
|
||||||
is_2M[i] = is_trans_2M(
|
match_vmid[i] = (lu_vmid_i == tags_q[i].vmid && g_st_enbl_i) || !g_st_enbl_i;
|
||||||
s_st_enbl_i,
|
end
|
||||||
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
|
|
||||||
);
|
|
||||||
// check if translation is a: S-Stage and G-Stage, S-Stage only or G-Stage only translation and virtualization mode is on/off
|
// 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);
|
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] && (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,
|
if (tags_q[i].valid && match_asid[i] && match_vmid[i] && match_stage[i]) begin
|
||||||
content_q[i].pte);
|
|
||||||
if (is_1G[i]) begin
|
if (CVA6Cfg.RVH && vpn_match[i][HYP_EXT*2]) begin
|
||||||
lu_is_1G_o = is_1G[i];
|
if (s_st_enbl_i) begin
|
||||||
lu_content_o = content_q[i].pte;
|
lu_gpaddr_o = {content_q[i].pte.ppn[(CVA6Cfg.GPPNW-1):0], lu_vaddr_i[11:0]};
|
||||||
lu_g_content_o = content_q[i].gpte;
|
// Giga page
|
||||||
lu_hit_o = 1'b1;
|
if (tags_q[i].is_page[0][0])
|
||||||
lu_hit[i] = 1'b1;
|
lu_gpaddr_o[12+2*CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12] = lu_vaddr_i[12+2*CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12];
|
||||||
// not a giga page hit so check further
|
// Mega page
|
||||||
end else if (vpn1 == tags_q[i].vpn1) begin
|
if (tags_q[i].is_page[HYP_EXT][0])
|
||||||
// this could be a 2 mega page hit or a 4 kB hit
|
lu_gpaddr_o[12+CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12] = lu_vaddr_i[12+CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12];
|
||||||
// output accordingly
|
end else begin
|
||||||
if (is_2M[i] || vpn0 == tags_q[i].vpn0) begin
|
lu_gpaddr_o =CVA6Cfg.GPLEN'(lu_vaddr_i[(CVA6Cfg.XLEN == 32 ? CVA6Cfg.VLEN: CVA6Cfg.GPLEN)-1:0]);
|
||||||
lu_is_2M_o = is_2M[i];
|
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
|
// Compute G-Stage PPN based on the gpaddr
|
||||||
g_content = content_q[i].gpte;
|
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_page[HYP_EXT][HYP_EXT]) 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];
|
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
|
// Output G-stage and S-stage content
|
||||||
lu_g_content_o = g_content;
|
lu_g_content_o = level_match[i][CVA6Cfg.PtLevels-1] ? content_q[i].gpte : g_content;
|
||||||
lu_content_o = content_q[i].pte;
|
|
||||||
lu_hit_o = 1'b1;
|
|
||||||
lu_hit[i] = 1'b1;
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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 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 vmid_to_be_flushed_is0; // indicates that the VMID provided 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 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 asid_to_be_flushed_is0 = ~(|asid_to_be_flushed_i);
|
||||||
assign vaddr_to_be_flushed_is0 = ~(|vaddr_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
|
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(
|
if (CVA6Cfg.RVH) begin
|
||||||
tags_q[i].s_st_enbl,
|
|
||||||
tags_q[i].is_s_1G,
|
if (tags_q[i].v_st_enbl[0]) begin
|
||||||
tags_q[i].is_s_2M,
|
gppn[i] = content_q[i].pte.ppn[(CVA6Cfg.GPPNW-1):0];
|
||||||
{
|
if (tags_q[i].is_page[HYP_EXT][0])
|
||||||
tags_q[i].vpn2, tags_q[i].vpn1, tags_q[i].vpn0
|
gppn[i][CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:0] = tags_q[i].vpn[0];
|
||||||
},
|
if (tags_q[i].is_page[0][0])
|
||||||
content_q[i].pte
|
gppn[i][2*(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] = {
|
||||||
);
|
tags_q[i].vpn[HYP_EXT], tags_q[i].vpn[0]
|
||||||
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]);
|
end else begin
|
||||||
gpaddr_gppn2_match[i] = (gpaddr_to_be_flushed_i[30+GPPN2:30] == gppn[i][18+GPPN2:18]);
|
gppn[i][CVA6Cfg.VpnLen-1:0] = CVA6Cfg.VpnLen'(tags_q[i].vpn);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
if (flush_i) begin
|
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
|
// invalidate logic
|
||||||
// flush everything if ASID is 0 and vaddr is 0 ("SFENCE.VMA x0 x0" case)
|
// 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;
|
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
|
// 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;
|
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)
|
// 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;
|
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)
|
// 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;
|
tags_n[i].valid = 1'b0;
|
||||||
end
|
end
|
||||||
end else if (flush_vvma_i) begin
|
end else if (flush_vvma_i && CVA6Cfg.RVH) begin
|
||||||
if (tags_q[i].v && tags_q[i].s_st_enbl) begin
|
if (tags_q[i].v_st_enbl[HYP_EXT*2] && tags_q[i].v_st_enbl[0]) begin
|
||||||
// invalidate logic
|
// invalidate logic
|
||||||
// flush everything if current VMID matches and ASID is 0 and vaddr is 0 ("SFENCE.VMA/HFENCE.VVMA x0 x0" case)
|
// 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;
|
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
|
// 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;
|
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)
|
// 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;
|
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)
|
// 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;
|
tags_n[i].valid = 1'b0;
|
||||||
end
|
end
|
||||||
end else if (flush_gvma_i) begin
|
end else if (flush_gvma_i && CVA6Cfg.RVH) begin
|
||||||
if (tags_q[i].g_st_enbl) begin
|
if (tags_q[i].v_st_enbl[HYP_EXT]) begin
|
||||||
// invalidate logic
|
// invalidate logic
|
||||||
// flush everything if vmid is 0 and addr is 0 ("HFENCE.GVMA x0 x0" case)
|
// 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;
|
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
|
// 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;
|
tags_n[i].valid = 1'b0;
|
||||||
// the entry vmid and gpaddr both matches with the entry to be flushed ("HFENCE.GVMA gpaddr vmid" case)
|
// 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;
|
tags_n[i].valid = 1'b0;
|
||||||
// the entry is flushed if the vmid matches and gpaddr is 0. ("HFENCE.GVMA 0 vmid" case)
|
// 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))
|
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
|
end
|
||||||
// normal replacement
|
// normal replacement
|
||||||
end else if (update_i.valid & replace_en[i]) begin
|
end else if (update_i.valid & replace_en[i]) begin
|
||||||
// update tag array
|
// end else if (update_i.valid & replace_en[i] && !lu_hit_o) begin //to add this fix
|
||||||
tags_n[i] = '{
|
//update tag
|
||||||
asid: update_i.asid,
|
tags_n[i] = {
|
||||||
vmid: update_i.vmid,
|
update_i.asid,
|
||||||
vpn2: update_i.vpn[18+GPPN2:18],
|
update_i.vmid,
|
||||||
vpn1: update_i.vpn[17:9],
|
((CVA6Cfg.PtLevels + HYP_EXT) * (CVA6Cfg.VpnLen / CVA6Cfg.PtLevels))'(update_i.vpn),
|
||||||
vpn0: update_i.vpn[8:0],
|
update_i.is_page,
|
||||||
s_st_enbl: s_st_enbl_i,
|
update_i.v_st_enbl,
|
||||||
g_st_enbl: g_st_enbl_i,
|
1'b1
|
||||||
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
|
|
||||||
};
|
};
|
||||||
// and content as well
|
// update content as well
|
||||||
content_n[i].pte = update_i.content;
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -285,27 +312,27 @@ module cva6_tlb_sv39x4
|
||||||
// The PLRU-tree indexing:
|
// The PLRU-tree indexing:
|
||||||
// lvl0 0
|
// lvl0 0
|
||||||
// / \
|
// / \
|
||||||
// / \
|
// / \
|
||||||
// lvl1 1 2
|
// lvl1 1 2
|
||||||
// / \ / \
|
// / \ / \
|
||||||
// lvl2 3 4 5 6
|
// lvl2 3 4 5 6
|
||||||
// / \ /\/\ /\
|
// / \ /\/\ /\
|
||||||
// ... ... ... ...
|
// ... ... ... ...
|
||||||
// Just predefine which nodes will be set/cleared
|
// Just predefine which nodes will be set/cleared
|
||||||
// E.g. for a TLB with 8 entries, the for-loop is semantically
|
// E.g. for a TLB with 8 entries, the for-loop is semantically
|
||||||
// equivalent to the following pseudo-code:
|
// equivalent to the following pseudo-code:
|
||||||
// unique case (1'b1)
|
// unique case (1'b1)
|
||||||
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
|
// 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[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
|
||||||
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
|
// 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[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
|
||||||
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
|
// 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[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
|
||||||
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
|
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
|
||||||
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
|
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
|
||||||
// default: begin /* No hit */ end
|
// default: begin /* No hit */ end
|
||||||
// endcase
|
// endcase
|
||||||
for (
|
for (
|
||||||
int unsigned i = 0; i < TLB_ENTRIES; i++
|
int unsigned i = 0; i < TLB_ENTRIES; i++
|
||||||
) begin
|
) begin
|
||||||
automatic int unsigned idx_base, shift, new_index;
|
automatic int unsigned idx_base, shift, new_index;
|
|
@ -571,12 +571,6 @@ package ariane_pkg;
|
||||||
endcase
|
endcase
|
||||||
endfunction
|
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
|
// Performance counter
|
||||||
// -------------------
|
// -------------------
|
||||||
|
|
|
@ -25,6 +25,10 @@ package build_config_pkg;
|
||||||
int unsigned DCACHE_INDEX_WIDTH = $clog2(CVA6Cfg.DcacheByteSize / CVA6Cfg.DcacheSetAssoc);
|
int unsigned DCACHE_INDEX_WIDTH = $clog2(CVA6Cfg.DcacheByteSize / CVA6Cfg.DcacheSetAssoc);
|
||||||
int unsigned DCACHE_OFFSET_WIDTH = $clog2(CVA6Cfg.DcacheLineWidth / 8);
|
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;
|
config_pkg::cva6_cfg_t cfg;
|
||||||
|
|
||||||
cfg.XLEN = CVA6Cfg.XLEN;
|
cfg.XLEN = CVA6Cfg.XLEN;
|
||||||
|
@ -147,6 +151,10 @@ package build_config_pkg;
|
||||||
cfg.SVX = (cfg.MODE_SV == config_pkg::ModeSv32) ? 34 : 41;
|
cfg.SVX = (cfg.MODE_SV == config_pkg::ModeSv32) ? 34 : 41;
|
||||||
cfg.InstrTlbEntries = CVA6Cfg.InstrTlbEntries;
|
cfg.InstrTlbEntries = CVA6Cfg.InstrTlbEntries;
|
||||||
cfg.DataTlbEntries = CVA6Cfg.DataTlbEntries;
|
cfg.DataTlbEntries = CVA6Cfg.DataTlbEntries;
|
||||||
|
cfg.UseSharedTlb = CVA6Cfg.UseSharedTlb;
|
||||||
|
cfg.SharedTlbDepth = CVA6Cfg.SharedTlbDepth;
|
||||||
|
cfg.VpnLen = VpnLen;
|
||||||
|
cfg.PtLevels = PtLevels;
|
||||||
|
|
||||||
return cfg;
|
return cfg;
|
||||||
endfunction
|
endfunction
|
||||||
|
|
|
@ -184,6 +184,10 @@ package config_pkg;
|
||||||
int unsigned InstrTlbEntries;
|
int unsigned InstrTlbEntries;
|
||||||
// MMU data TLB entries
|
// MMU data TLB entries
|
||||||
int unsigned DataTlbEntries;
|
int unsigned DataTlbEntries;
|
||||||
|
// MMU option to use shared TLB
|
||||||
|
bit unsigned UseSharedTlb;
|
||||||
|
// MMU depth of shared TLB
|
||||||
|
int unsigned SharedTlbDepth;
|
||||||
} cva6_user_cfg_t;
|
} cva6_user_cfg_t;
|
||||||
|
|
||||||
typedef struct packed {
|
typedef struct packed {
|
||||||
|
@ -253,6 +257,10 @@ package config_pkg;
|
||||||
int unsigned BHTEntries;
|
int unsigned BHTEntries;
|
||||||
int unsigned InstrTlbEntries;
|
int unsigned InstrTlbEntries;
|
||||||
int unsigned DataTlbEntries;
|
int unsigned DataTlbEntries;
|
||||||
|
bit unsigned UseSharedTlb;
|
||||||
|
int unsigned SharedTlbDepth;
|
||||||
|
int unsigned VpnLen;
|
||||||
|
int unsigned PtLevels;
|
||||||
|
|
||||||
logic [63:0] DmBaseAddress;
|
logic [63:0] DmBaseAddress;
|
||||||
bit TvalEn;
|
bit TvalEn;
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 0;
|
localparam CVA6ConfigRASDepth = 0;
|
||||||
localparam CVA6ConfigBTBEntries = 0;
|
localparam CVA6ConfigBTBEntries = 0;
|
||||||
localparam CVA6ConfigBHTEntries = 0;
|
localparam CVA6ConfigBHTEntries = 0;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -26,9 +26,6 @@ package cva6_config_pkg;
|
||||||
|
|
||||||
localparam CVA6ConfigNrScoreboardEntries = 4; // cvxif_pkg.sv
|
localparam CVA6ConfigNrScoreboardEntries = 4; // cvxif_pkg.sv
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2; // MMU
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2; // MMU
|
|
||||||
|
|
||||||
localparam config_pkg::cva6_user_cfg_t cva6_cfg = '{
|
localparam config_pkg::cva6_user_cfg_t cva6_cfg = '{
|
||||||
XLEN: unsigned'(CVA6ConfigXlen),
|
XLEN: unsigned'(CVA6ConfigXlen),
|
||||||
FpgaEn: bit'(0),
|
FpgaEn: bit'(0),
|
||||||
|
@ -94,8 +91,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(2),
|
WtDcacheWbufDepth: int'(2),
|
||||||
FetchUserWidth: unsigned'(32),
|
FetchUserWidth: unsigned'(32),
|
||||||
FetchUserEn: unsigned'(0),
|
FetchUserEn: unsigned'(0),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(0),
|
NrLoadPipeRegs: int'(0),
|
||||||
NrStorePipeRegs: int'(0),
|
NrStorePipeRegs: int'(0),
|
||||||
DcacheIdWidth: int'(1)
|
DcacheIdWidth: int'(1)
|
||||||
|
|
|
@ -57,9 +57,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 1;
|
localparam CVA6ConfigNrLoadBufEntries = 1;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 0;
|
localparam CVA6ConfigBTBEntries = 0;
|
||||||
localparam CVA6ConfigBHTEntries = 32;
|
localparam CVA6ConfigBHTEntries = 32;
|
||||||
|
@ -141,8 +138,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrLoadPipeRegs = 1;
|
localparam CVA6ConfigNrLoadPipeRegs = 1;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 2;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 2;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(2),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(2),
|
||||||
|
UseSharedTlb: bit'(1),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -65,9 +65,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 8;
|
localparam CVA6ConfigNrLoadBufEntries = 8;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -149,8 +146,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -58,9 +58,6 @@ package cva6_config_pkg;
|
||||||
localparam CVA6ConfigNrStorePipeRegs = 0;
|
localparam CVA6ConfigNrStorePipeRegs = 0;
|
||||||
localparam CVA6ConfigNrLoadBufEntries = 2;
|
localparam CVA6ConfigNrLoadBufEntries = 2;
|
||||||
|
|
||||||
localparam CVA6ConfigInstrTlbEntries = 16;
|
|
||||||
localparam CVA6ConfigDataTlbEntries = 16;
|
|
||||||
|
|
||||||
localparam CVA6ConfigRASDepth = 2;
|
localparam CVA6ConfigRASDepth = 2;
|
||||||
localparam CVA6ConfigBTBEntries = 32;
|
localparam CVA6ConfigBTBEntries = 32;
|
||||||
localparam CVA6ConfigBHTEntries = 128;
|
localparam CVA6ConfigBHTEntries = 128;
|
||||||
|
@ -142,8 +139,10 @@ package cva6_config_pkg;
|
||||||
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
|
||||||
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
|
||||||
DCacheType: CVA6ConfigDcacheType,
|
DCacheType: CVA6ConfigDcacheType,
|
||||||
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
|
InstrTlbEntries: int'(16),
|
||||||
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
|
DataTlbEntries: int'(16),
|
||||||
|
UseSharedTlb: bit'(0),
|
||||||
|
SharedTlbDepth: int'(64),
|
||||||
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
|
||||||
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
|
||||||
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)
|
||||||
|
|
|
@ -228,120 +228,65 @@ module load_store_unit
|
||||||
|
|
||||||
logic hs_ld_st_inst;
|
logic hs_ld_st_inst;
|
||||||
logic hlvx_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
|
// MMU e.g.: TLBs/PTW
|
||||||
// -------------------
|
// -------------------
|
||||||
if (CVA6Cfg.MmuPresent && CVA6Cfg.RVH && (CVA6Cfg.XLEN == 64)) begin : gen_mmu_sv39x4
|
|
||||||
cva6_mmu_sv39x4 #(
|
if (CVA6Cfg.MmuPresent) begin : gen_mmu
|
||||||
.CVA6Cfg (CVA6Cfg),
|
localparam HYP_EXT = CVA6Cfg.RVH ? 1 : 0;
|
||||||
.exception_t (exception_t),
|
|
||||||
.icache_areq_t (icache_areq_t),
|
cva6_mmu #(
|
||||||
.icache_arsp_t (icache_arsp_t),
|
.CVA6Cfg (CVA6Cfg),
|
||||||
.icache_dreq_t (icache_dreq_t),
|
.exception_t (exception_t),
|
||||||
.icache_drsp_t (icache_drsp_t),
|
.icache_areq_t (icache_areq_t),
|
||||||
.dcache_req_i_t (dcache_req_i_t),
|
.icache_arsp_t (icache_arsp_t),
|
||||||
.dcache_req_o_t (dcache_req_o_t),
|
.icache_dreq_t (icache_dreq_t),
|
||||||
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
|
.icache_drsp_t (icache_drsp_t),
|
||||||
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
|
.dcache_req_i_t(dcache_req_i_t),
|
||||||
|
.dcache_req_o_t(dcache_req_o_t),
|
||||||
|
.HYP_EXT (HYP_EXT)
|
||||||
) i_cva6_mmu (
|
) 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 bypass
|
||||||
.misaligned_ex_i(misaligned_exception),
|
.misaligned_ex_i(misaligned_exception),
|
||||||
.lsu_is_store_i (st_translation_req),
|
.lsu_req_i(translation_req),
|
||||||
.lsu_req_i (translation_req),
|
.lsu_vaddr_i(mmu_vaddr),
|
||||||
.lsu_vaddr_i (mmu_vaddr),
|
.lsu_tinst_i(mmu_tinst),
|
||||||
.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_valid_o (translation_valid),
|
||||||
.lsu_paddr_o (mmu_paddr),
|
.lsu_paddr_o (mmu_paddr),
|
||||||
.lsu_exception_o(mmu_exception),
|
.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
|
.priv_lvl_i (priv_lvl_i),
|
||||||
// connecting PTW to D$ IF
|
.ld_st_priv_lvl_i(ld_st_priv_lvl_i),
|
||||||
.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
|
|
||||||
.hlvx_inst_i (mmu_hlvx_inst),
|
.hlvx_inst_i (mmu_hlvx_inst),
|
||||||
.hs_ld_st_inst_i(mmu_hs_ld_st_inst),
|
.hs_ld_st_inst_i(mmu_hs_ld_st_inst),
|
||||||
.*
|
|
||||||
);
|
.itlb_miss_o(itlb_miss_o),
|
||||||
end else if (CVA6Cfg.MmuPresent && (CVA6Cfg.XLEN == 64)) begin : gen_mmu_sv39
|
.dtlb_miss_o(dtlb_miss_o),
|
||||||
mmu #(
|
|
||||||
.CVA6Cfg (CVA6Cfg),
|
.req_port_i(dcache_req_ports_i[0]),
|
||||||
.exception_t (exception_t),
|
.req_port_o(dcache_req_ports_o[0]),
|
||||||
.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),
|
|
||||||
.pmpcfg_i,
|
.pmpcfg_i,
|
||||||
.pmpaddr_i,
|
.pmpaddr_i,
|
||||||
.*
|
.*
|
||||||
);
|
);
|
||||||
|
|
||||||
end else begin : gen_no_mmu
|
end else begin : gen_no_mmu
|
||||||
|
|
||||||
if (CVA6Cfg.VLEN > CVA6Cfg.PLEN) begin
|
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_unit.sv,
|
||||||
src/load_store_unit.sv,
|
src/load_store_unit.sv,
|
||||||
src/miss_handler.sv,
|
src/miss_handler.sv,
|
||||||
src/mmu_sv39/mmu.sv,
|
src/cva6_mmu/cva6_mmu.sv,
|
||||||
src/mmu_sv32/cva6_mmu_sv32.sv,
|
|
||||||
src/mult.sv,
|
src/mult.sv,
|
||||||
src/nbdcache.sv,
|
src/nbdcache.sv,
|
||||||
src/vdregs.sv,
|
src/vdregs.sv,
|
||||||
|
@ -43,14 +42,12 @@ ariane:
|
||||||
src/sram_wrapper.sv,
|
src/sram_wrapper.sv,
|
||||||
src/pcgen_stage.sv,
|
src/pcgen_stage.sv,
|
||||||
src/perf_counters.sv,
|
src/perf_counters.sv,
|
||||||
src/mmu_sv39/ptw.sv,
|
src/cva6_mmu/cva6_ptw.sv,
|
||||||
src/mmu_sv32/cva6_ptw_sv32.sv,
|
|
||||||
src/re_name.sv,
|
src/re_name.sv,
|
||||||
src/scoreboard.sv,
|
src/scoreboard.sv,
|
||||||
src/store_buffer.sv,
|
src/store_buffer.sv,
|
||||||
src/store_unit.sv,
|
src/store_unit.sv,
|
||||||
src/mmu_sv39/tlb.sv,
|
src/cva6_mmu/cva6_tlb.sv,
|
||||||
src/mmu_sv32/cva6_tlb_sv32.sv,
|
|
||||||
src/acc_dispatcher.sv,
|
src/acc_dispatcher.sv,
|
||||||
src/debug/dm_csrs.sv,
|
src/debug/dm_csrs.sv,
|
||||||
src/debug/dm_mem.sv,
|
src/debug/dm_mem.sv,
|
||||||
|
|
Loading…
Add table
Reference in a new issue