integrate unified mmu with H extension (#1958)

This commit is contained in:
AngelaGonzalezMarino 2024-05-16 00:24:50 +02:00 committed by GitHub
parent 821e2ebc3f
commit 9142fdd03a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 2344 additions and 4655 deletions

View file

@ -65,23 +65,11 @@ sources:
- core/cva6_accel_first_pass_decoder_stub.sv
# MMU
- target: any(cv64a6_imafdcv_sv39, cv64a6_imafdc_sv39, cv64a6_imafdc_sv39_wb)
- target: any(cv64a6_imafdcv_sv39, cv64a6_imafdc_sv39, cv64a6_imafdc_sv39_wb, cv64a6_imafdch_sv39, cv64a6_imafdch_sv39_wb, cv32a6_imac_sv0, cv32a6_imac_sv32, cv32a6_imafc_sv32)
files:
- core/mmu_sv39/tlb.sv
- core/mmu_sv39/mmu.sv
- core/mmu_sv39/ptw.sv
- target: any(cv64a6_imafdch_sv39, cv64a6_imafdch_sv39_wb)
files:
- core/mmu_sv39x4/cva6_tlb_sv39x4.sv
- core/mmu_sv39x4/cva6_mmu_sv39x4.sv
- core/mmu_sv39x4/cva6_ptw_sv39x4.sv
- target: any(cv32a6_imac_sv0, cv32a6_imac_sv32, cv32a6_imafc_sv32)
files:
- core/mmu_sv32/cva6_tlb_sv32.sv
- core/mmu_sv32/cva6_mmu_sv32.sv
- core/mmu_sv32/cva6_ptw_sv32.sv
- core/cva6_mmu/cva6_tlb.sv
- core/cva6_mmu/cva6_mmu.sv
- core/cva6_mmu/cva6_ptw.sv
# Packages
- core/include/wt_cache_pkg.sv

View file

@ -33,20 +33,17 @@ filesets:
- src/lsu_arbiter.sv
- src/lsu.sv
- src/miss_handler.sv
- src/mmu_sv39/mmu.sv
- src/mmu_sv32/cva6_mmu_sv32.sv
- src/cva6_mmu/cva6_mmu.sv
- src/mult.sv
- src/nbdcache.sv
- src/pcgen_stage.sv
- src/perf_counters.sv
- src/mmu_sv39/ptw.sv
- src/mmu_sv32/cva6_ptw_sv32.sv
- src/cva6_mmu/cva6_ptw.sv
- src/regfile_ff.sv
- src/scoreboard.sv
- src/store_buffer.sv
- src/store_unit.sv
- src/mmu_sv39/tlb.sv
- src/mmu_sv32/cva6_tlb_sv32.sv
- src/cva6_mmu/cva6_tlb.sv
file_type : systemVerilogSource
depend :
- pulp-platform.org::axi_mem_if

View file

@ -183,20 +183,10 @@ ${CVA6_REPO_DIR}/common/local/util/tc_sram_wrapper.sv
${CVA6_REPO_DIR}/vendor/pulp-platform/tech_cells_generic/src/rtl/tc_sram.sv
${CVA6_REPO_DIR}/common/local/util/sram.sv
// MMU Sv39
${CVA6_REPO_DIR}/core/mmu_sv39/mmu.sv
${CVA6_REPO_DIR}/core/mmu_sv39/ptw.sv
${CVA6_REPO_DIR}/core/mmu_sv39/tlb.sv
// MMU Sv39x4
${CVA6_REPO_DIR}/core/mmu_sv39x4/cva6_mmu_sv39x4.sv
${CVA6_REPO_DIR}/core/mmu_sv39x4/cva6_ptw_sv39x4.sv
${CVA6_REPO_DIR}/core/mmu_sv39x4/cva6_tlb_sv39x4.sv
// MMU Sv32
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_mmu_sv32.sv
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_ptw_sv32.sv
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_tlb_sv32.sv
${CVA6_REPO_DIR}/core/mmu_sv32/cva6_shared_tlb_sv32.sv
// MMU
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_mmu.sv
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_ptw.sv
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_tlb.sv
${CVA6_REPO_DIR}/core/cva6_mmu/cva6_shared_tlb.sv
// end of manifest

860
core/cva6_mmu/cva6_mmu.sv Normal file
View 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
View 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 */

View 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 */

View file

@ -1,4 +1,8 @@
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
// Copyright (c) 2018 ETH Zurich and University of Bologna.
// Copyright (c) 2021 Thales.
// Copyright (c) 2022 Bruno Sá and Zero-Day Labs.
// Copyright (c) 2024 PlanV Technology
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
@ -8,21 +12,23 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//
// Author: Bruno Sá
// Date: 14/08/2022
// Acknowledges: Technology Innovation Institute (TII)
// Author: Angela Gonzalez PlanV Technology
// Date: 26/02/2024
//
// Description: Translation Lookaside Buffer, Sv39x4 , fully set-associative
// This module is an adaptation of the Sv39 TLB developed
// by Florian Zaruba and David Schaffenrath to the Sv39x4 standard.
// Description: Translation Lookaside Buffer, parameterizable to Sv32 or Sv39 ,
// or sv39x4 fully set-associative
// This module is an merge of the Sv32 TLB developed by Sebastien
// Jacq (Thales Research & Technology), the Sv39 TLB developed
// by Florian Zaruba and David Schaffenrath and the Sv39x4 by Bruno Sá.
module cva6_tlb_sv39x4
module cva6_tlb
import ariane_pkg::*;
#(
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
parameter type tlb_update_t = logic,
parameter int unsigned TLB_ENTRIES = 4
parameter type pte_cva6_t = logic,
parameter type tlb_update_cva6_t = logic,
parameter int unsigned TLB_ENTRIES = 4,
parameter int unsigned HYP_EXT = 0
) (
input logic clk_i, // Clock
input logic rst_ni, // Asynchronous reset active low
@ -33,146 +39,173 @@ module cva6_tlb_sv39x4
input logic g_st_enbl_i, // g-stage enabled
input logic v_i, // virtualization mode
// Update TLB
input tlb_update_t update_i,
input tlb_update_cva6_t update_i,
// Lookup signals
input logic lu_access_i,
input logic [CVA6Cfg.ASID_WIDTH-1:0] lu_asid_i,
input logic [CVA6Cfg.VMID_WIDTH-1:0] lu_vmid_i,
input logic [CVA6Cfg.VLEN-1:0] lu_vaddr_i,
output logic [CVA6Cfg.GPLEN-1:0] lu_gpaddr_o,
output riscv::pte_t lu_content_o,
output riscv::pte_t lu_g_content_o,
output pte_cva6_t lu_content_o,
output pte_cva6_t lu_g_content_o,
input logic [CVA6Cfg.ASID_WIDTH-1:0] asid_to_be_flushed_i,
input logic [CVA6Cfg.VMID_WIDTH-1:0] vmid_to_be_flushed_i,
input logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed_i,
input logic [CVA6Cfg.GPLEN-1:0] gpaddr_to_be_flushed_i,
output logic lu_is_2M_o,
output logic lu_is_1G_o,
output logic [CVA6Cfg.PtLevels-2:0] lu_is_page_o,
output logic lu_hit_o
);
localparam VPN2 = (CVA6Cfg.VLEN - 31 < 8) ? CVA6Cfg.VLEN - 31 : 8;
localparam GPPN2 = (CVA6Cfg.XLEN == 32) ? CVA6Cfg.VLEN - 33 : 10;
// SV39 defines three levels of page tables
struct packed {
logic [CVA6Cfg.ASID_WIDTH-1:0] asid;
logic [CVA6Cfg.VMID_WIDTH-1:0] vmid;
logic [GPPN2:0] vpn2;
logic [8:0] vpn1;
logic [8:0] vpn0;
logic is_s_2M;
logic is_s_1G;
logic is_g_2M;
logic is_g_1G;
logic s_st_enbl; // s-stage translation
logic g_st_enbl; // g-stage translation
logic v; // virtualization mode
logic valid;
logic [CVA6Cfg.PtLevels+HYP_EXT-1:0][(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] vpn;
logic [CVA6Cfg.PtLevels-2:0][HYP_EXT:0] is_page;
logic [HYP_EXT*2:0] v_st_enbl; // v_i,g-stage enabled, s-stage enabled
logic valid;
} [TLB_ENTRIES-1:0]
tags_q, tags_n;
struct packed {
riscv::pte_t pte;
riscv::pte_t gpte;
pte_cva6_t pte;
pte_cva6_t gpte;
} [TLB_ENTRIES-1:0]
content_q, content_n;
logic [8:0] vpn0, vpn1;
logic [GPPN2:0] vpn2;
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] vpn_match;
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] level_match;
logic [TLB_ENTRIES-1:0][HYP_EXT:0][CVA6Cfg.PtLevels-1:0] vaddr_vpn_match;
logic [TLB_ENTRIES-1:0][HYP_EXT:0][CVA6Cfg.PtLevels-1:0] vaddr_level_match;
logic [TLB_ENTRIES-1:0] lu_hit; // to replacement logic
logic [TLB_ENTRIES-1:0] replace_en; // replace the following entry, set by replacement strategy
logic [TLB_ENTRIES-1:0] match_vmid;
logic [TLB_ENTRIES-1:0] match_asid;
logic [TLB_ENTRIES-1:0] is_1G;
logic [TLB_ENTRIES-1:0] is_2M;
logic [TLB_ENTRIES-1:0] match_vmid;
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-1:0] page_match;
logic [TLB_ENTRIES-1:0][HYP_EXT:0][CVA6Cfg.PtLevels-1:0] vpage_match;
logic [TLB_ENTRIES-1:0][CVA6Cfg.PtLevels-2:0] is_page_o;
logic [TLB_ENTRIES-1:0] match_stage;
riscv::pte_t g_content;
pte_cva6_t g_content;
logic [TLB_ENTRIES-1:0][(CVA6Cfg.GPPNW-1):0] gppn;
logic [2:0] v_st_enbl;
assign v_st_enbl = (CVA6Cfg.RVH) ? {v_i, g_st_enbl_i, s_st_enbl_i} : '1;
//-------------
// Translation
//-------------
genvar i, x, z, w;
generate
for (i = 0; i < TLB_ENTRIES; i++) begin
for (x = 0; x < CVA6Cfg.PtLevels; x++) begin
//identify page_match for all TLB Entries
assign page_match[i][x] = x==0 ? 1 :((HYP_EXT==0 || x==(CVA6Cfg.PtLevels-1)) ? // PAGE_MATCH CONTAINS THE MATCH INFORMATION FOR EACH TAG OF is_1G and is_2M in sv39x4. HIGHER LEVEL (Giga page), THEN THERE IS THE Mega page AND AT THE LOWER LEVEL IS ALWAYS 1
&(tags_q[i].is_page[CVA6Cfg.PtLevels-1-x] | (~v_st_enbl[HYP_EXT:0])):
((&v_st_enbl[HYP_EXT:0]) ?
((tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][0] && (tags_q[i].is_page[CVA6Cfg.PtLevels-2-x][HYP_EXT] || tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT]))
|| (tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT] && (tags_q[i].is_page[CVA6Cfg.PtLevels-2-x][0] || tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][0]))):
tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][0] && s_st_enbl_i || tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][HYP_EXT] && g_st_enbl_i));
//identify if vpn matches at all PT levels for all TLB entries
assign vpn_match[i][x] = (CVA6Cfg.RVH && x == (CVA6Cfg.PtLevels - 1) && ~s_st_enbl_i) ? //
lu_vaddr_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(x+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*x)] == tags_q[i].vpn[x] && lu_vaddr_i[12+HYP_EXT*(CVA6Cfg.VpnLen-1): 12+HYP_EXT*(CVA6Cfg.VpnLen-(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels))] == tags_q[i].vpn[x+HYP_EXT][(CVA6Cfg.VpnLen%CVA6Cfg.PtLevels)-HYP_EXT:0]: //
lu_vaddr_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(x+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*x)] == tags_q[i].vpn[x];
//identify if there is a hit at each PT level for all TLB entries
assign level_match[i][x] = &vpn_match[i][CVA6Cfg.PtLevels-1:x] && page_match[i][x];
//identify vpage_match for all TLB Entries and vaddr_level match (if there is a hit at each PT level for all TLB entries on the vaddr)
for (z = 0; z < HYP_EXT + 1; z++) begin
assign vpage_match[i][z][x] = x == 0 ? 1 : tags_q[i].is_page[CVA6Cfg.PtLevels-1-x][z];
assign vaddr_level_match[i][z][x]= &vaddr_vpn_match[i][z][CVA6Cfg.PtLevels-1:x] && vpage_match[i][z][x];
end
//identify if virtual address vpn matches at all PT levels for all TLB entries
assign vaddr_vpn_match[i][0][x] = vaddr_to_be_flushed_i[12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*(x+1))-1:12+((CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)*x)] == tags_q[i].vpn[x];
end
if (CVA6Cfg.RVH) begin
//identify if GPADDR matches the GPPN
assign vaddr_vpn_match[i][HYP_EXT][0] = (gpaddr_to_be_flushed_i[20:12] == gppn[i][8:0]);
assign vaddr_vpn_match[i][HYP_EXT][HYP_EXT] = (gpaddr_to_be_flushed_i[29:21] == gppn[i][17:9]);
assign vaddr_vpn_match[i][HYP_EXT][HYP_EXT*2] = (gpaddr_to_be_flushed_i[30+GPPN2:30] == gppn[i][18+GPPN2:18]);
end
for (w = 0; w < CVA6Cfg.PtLevels - 1; w++) begin
assign is_page_o[i][w] = page_match[i][CVA6Cfg.PtLevels - 1 - w]; //THIS REORGANIZES THE PAGES TO MATCH THE OUTPUT STRUCTURE (2M,1G)
end
end
endgenerate
always_comb begin : translation
automatic logic [GPPN2:0] mask_pn2;
mask_pn2 = s_st_enbl_i ? ((2 ** (VPN2 + 1)) - 1) : ((2 ** (GPPN2 + 1)) - 1);
vpn0 = lu_vaddr_i[20:12];
vpn1 = lu_vaddr_i[29:21];
vpn2 = lu_vaddr_i[30+GPPN2:30] & mask_pn2;
// default assignment
lu_hit = '{default: 0};
lu_hit_o = 1'b0;
lu_content_o = '{default: 0};
lu_g_content_o = '{default: 0};
lu_is_1G_o = 1'b0;
lu_is_2M_o = 1'b0;
lu_is_page_o = '{default: 0};
match_asid = '{default: 0};
match_vmid = '{default: 0};
match_vmid = CVA6Cfg.RVH ? '{default: 0} : '{default: 1};
match_stage = '{default: 0};
is_1G = '{default: 0};
is_2M = '{default: 0};
g_content = '{default: 0};
lu_gpaddr_o = '{default: 0};
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
// first level match, this may be a giga page, check the ASID flags as well
// if the entry is associated to a global address, don't match the ASID (ASID is don't care)
match_asid[i] = (((lu_asid_i == tags_q[i].asid) || content_q[i].pte.g) && s_st_enbl_i) || !s_st_enbl_i;
match_vmid[i] = (lu_vmid_i == tags_q[i].vmid && g_st_enbl_i) || !g_st_enbl_i;
is_1G[i] = is_trans_1G(s_st_enbl_i, g_st_enbl_i, tags_q[i].is_s_1G, tags_q[i].is_g_1G);
is_2M[i] = is_trans_2M(
s_st_enbl_i,
g_st_enbl_i,
tags_q[i].is_s_1G,
tags_q[i].is_s_2M,
tags_q[i].is_g_1G,
tags_q[i].is_g_2M
);
match_asid[i] = ((lu_asid_i == tags_q[i].asid || content_q[i].pte.g) && s_st_enbl_i) || !s_st_enbl_i;
if (CVA6Cfg.RVH) begin
match_vmid[i] = (lu_vmid_i == tags_q[i].vmid && g_st_enbl_i) || !g_st_enbl_i;
end
// check if translation is a: S-Stage and G-Stage, S-Stage only or G-Stage only translation and virtualization mode is on/off
match_stage[i] = (tags_q[i].v == v_i) && (tags_q[i].g_st_enbl == g_st_enbl_i) && (tags_q[i].s_st_enbl == s_st_enbl_i);
if (tags_q[i].valid && match_asid[i] && match_vmid[i] && match_stage[i] && (vpn2 == (tags_q[i].vpn2 & mask_pn2))) begin
lu_gpaddr_o = make_gpaddr(s_st_enbl_i, tags_q[i].is_s_1G, tags_q[i].is_s_2M, lu_vaddr_i,
content_q[i].pte);
if (is_1G[i]) begin
lu_is_1G_o = is_1G[i];
lu_content_o = content_q[i].pte;
lu_g_content_o = content_q[i].gpte;
lu_hit_o = 1'b1;
lu_hit[i] = 1'b1;
// not a giga page hit so check further
end else if (vpn1 == tags_q[i].vpn1) begin
// this could be a 2 mega page hit or a 4 kB hit
// output accordingly
if (is_2M[i] || vpn0 == tags_q[i].vpn0) begin
lu_is_2M_o = is_2M[i];
match_stage[i] = tags_q[i].v_st_enbl[HYP_EXT*2:0] == v_st_enbl[HYP_EXT*2:0];
if (tags_q[i].valid && match_asid[i] && match_vmid[i] && match_stage[i]) begin
if (CVA6Cfg.RVH && vpn_match[i][HYP_EXT*2]) begin
if (s_st_enbl_i) begin
lu_gpaddr_o = {content_q[i].pte.ppn[(CVA6Cfg.GPPNW-1):0], lu_vaddr_i[11:0]};
// Giga page
if (tags_q[i].is_page[0][0])
lu_gpaddr_o[12+2*CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12] = lu_vaddr_i[12+2*CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12];
// Mega page
if (tags_q[i].is_page[HYP_EXT][0])
lu_gpaddr_o[12+CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12] = lu_vaddr_i[12+CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:12];
end else begin
lu_gpaddr_o =CVA6Cfg.GPLEN'(lu_vaddr_i[(CVA6Cfg.XLEN == 32 ? CVA6Cfg.VLEN: CVA6Cfg.GPLEN)-1:0]);
end
end
if (|level_match[i]) begin
lu_is_page_o = is_page_o[i];
lu_content_o = content_q[i].pte;
lu_hit_o = 1'b1;
lu_hit[i] = 1'b1;
if (CVA6Cfg.RVH) begin
// Compute G-Stage PPN based on the gpaddr
g_content = content_q[i].gpte;
if (tags_q[i].is_g_2M) g_content.ppn[8:0] = lu_gpaddr_o[20:12];
if (tags_q[i].is_g_1G) g_content.ppn[17:0] = lu_gpaddr_o[29:12];
g_content = content_q[i].gpte;
if (tags_q[i].is_page[HYP_EXT][HYP_EXT]) g_content.ppn[8:0] = lu_gpaddr_o[20:12];
if (tags_q[i].is_page[0][HYP_EXT]) g_content.ppn[17:0] = lu_gpaddr_o[29:12];
// Output G-stage and S-stage content
lu_g_content_o = g_content;
lu_content_o = content_q[i].pte;
lu_hit_o = 1'b1;
lu_hit[i] = 1'b1;
lu_g_content_o = level_match[i][CVA6Cfg.PtLevels-1] ? content_q[i].gpte : g_content;
end
end
end
end
end
logic asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
logic vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
logic [HYP_EXT:0]asid_to_be_flushed_is0; // indicates that the ASID provided by SFENCE.VMA (rs2)is 0, active high
logic [HYP_EXT:0] vaddr_to_be_flushed_is0; // indicates that the VADDR provided by SFENCE.VMA (rs1)is 0, active high
logic vmid_to_be_flushed_is0; // indicates that the VMID provided is 0, active high
logic gpaddr_to_be_flushed_is0; // indicates that the GPADDR provided is 0, active high
logic [TLB_ENTRIES-1:0] vaddr_vpn0_match;
logic [TLB_ENTRIES-1:0] vaddr_vpn1_match;
logic [TLB_ENTRIES-1:0] vaddr_vpn2_match;
logic [TLB_ENTRIES-1:0] gpaddr_gppn0_match;
logic [TLB_ENTRIES-1:0] gpaddr_gppn1_match;
logic [TLB_ENTRIES-1:0] gpaddr_gppn2_match;
logic [TLB_ENTRIES-1:0][(CVA6Cfg.GPPNW-1):0] gppn;
assign asid_to_be_flushed_is0 = ~(|asid_to_be_flushed_i);
assign vaddr_to_be_flushed_is0 = ~(|vaddr_to_be_flushed_i);
@ -188,64 +221,64 @@ module cva6_tlb_sv39x4
for (int unsigned i = 0; i < TLB_ENTRIES; i++) begin
vaddr_vpn0_match[i] = (vaddr_to_be_flushed_i[20:12] == tags_q[i].vpn0);
vaddr_vpn1_match[i] = (vaddr_to_be_flushed_i[29:21] == tags_q[i].vpn1);
vaddr_vpn2_match[i] = (vaddr_to_be_flushed_i[30+VPN2:30] == tags_q[i].vpn2[VPN2:0]);
gppn[i] = make_gppn(
tags_q[i].s_st_enbl,
tags_q[i].is_s_1G,
tags_q[i].is_s_2M,
{
tags_q[i].vpn2, tags_q[i].vpn1, tags_q[i].vpn0
},
content_q[i].pte
);
gpaddr_gppn0_match[i] = (gpaddr_to_be_flushed_i[20:12] == gppn[i][8:0]);
gpaddr_gppn1_match[i] = (gpaddr_to_be_flushed_i[29:21] == gppn[i][17:9]);
gpaddr_gppn2_match[i] = (gpaddr_to_be_flushed_i[30+GPPN2:30] == gppn[i][18+GPPN2:18]);
if (CVA6Cfg.RVH) begin
if (tags_q[i].v_st_enbl[0]) begin
gppn[i] = content_q[i].pte.ppn[(CVA6Cfg.GPPNW-1):0];
if (tags_q[i].is_page[HYP_EXT][0])
gppn[i][CVA6Cfg.VpnLen/CVA6Cfg.PtLevels-1:0] = tags_q[i].vpn[0];
if (tags_q[i].is_page[0][0])
gppn[i][2*(CVA6Cfg.VpnLen/CVA6Cfg.PtLevels)-1:0] = {
tags_q[i].vpn[HYP_EXT], tags_q[i].vpn[0]
};
end else begin
gppn[i][CVA6Cfg.VpnLen-1:0] = CVA6Cfg.VpnLen'(tags_q[i].vpn);
end
end
if (flush_i) begin
if (!tags_q[i].v) begin
if (!tags_q[i].v_st_enbl[HYP_EXT*2] || HYP_EXT == 0) begin
// invalidate logic
// flush everything if ASID is 0 and vaddr is 0 ("SFENCE.VMA x0 x0" case)
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0) tags_n[i].valid = 1'b0;
// flush vaddr in all addressing space ("SFENCE.VMA vaddr x0" case), it should happen only for leaf pages
else if (asid_to_be_flushed_is0 && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M) ) && (~vaddr_to_be_flushed_is0))
else if (asid_to_be_flushed_is0 && (|vaddr_level_match[i][0] ) && (~vaddr_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
// the entry is flushed if it's not global and asid and vaddr both matches with the entry to be flushed ("SFENCE.VMA vaddr asid" case)
else if ((!content_q[i].pte.g) && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M)) && (asid_to_be_flushed_i == tags_q[i].asid) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
else if ((!content_q[i].pte.g) && (|vaddr_level_match[i][0]) && (asid_to_be_flushed_i == tags_q[i].asid ) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
// the entry is flushed if it's not global, and the asid matches and vaddr is 0. ("SFENCE.VMA 0 asid" case)
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid) && (!asid_to_be_flushed_is0))
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid ) && (!asid_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
end
end else if (flush_vvma_i) begin
if (tags_q[i].v && tags_q[i].s_st_enbl) begin
end else if (flush_vvma_i && CVA6Cfg.RVH) begin
if (tags_q[i].v_st_enbl[HYP_EXT*2] && tags_q[i].v_st_enbl[0]) begin
// invalidate logic
// flush everything if current VMID matches and ASID is 0 and vaddr is 0 ("SFENCE.VMA/HFENCE.VVMA x0 x0" case)
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0 && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl))
if (asid_to_be_flushed_is0 && vaddr_to_be_flushed_is0 && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT]))
tags_n[i].valid = 1'b0;
// flush vaddr in all addressing space if current VMID matches ("SFENCE.VMA/HFENCE.VVMA vaddr x0" case), it should happen only for leaf pages
else if (asid_to_be_flushed_is0 && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M) ) && (~vaddr_to_be_flushed_is0) && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl))
else if (asid_to_be_flushed_is0 && (|vaddr_level_match[i][0]) && (~vaddr_to_be_flushed_is0) && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT]))
tags_n[i].valid = 1'b0;
// the entry is flushed if it's not global and asid and vaddr and current VMID matches with the entry to be flushed ("SFENCE.VMA/HFENCE.VVMA vaddr asid" case)
else if ((!content_q[i].pte.g) && ((vaddr_vpn0_match[i] && vaddr_vpn1_match[i] && vaddr_vpn2_match[i]) || (vaddr_vpn2_match[i] && tags_q[i].is_s_1G) || (vaddr_vpn1_match[i] && vaddr_vpn2_match[i] && tags_q[i].is_s_2M)) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl)) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
else if ((!content_q[i].pte.g) && (|vaddr_level_match[i][0]) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT])) && (!vaddr_to_be_flushed_is0) && (!asid_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
// the entry is flushed if it's not global, and the asid and the current VMID matches and vaddr is 0. ("SFENCE.VMA/HFENCE.VVMA 0 asid" case)
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].g_st_enbl && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].g_st_enbl)) && (!asid_to_be_flushed_is0))
else if ((!content_q[i].pte.g) && (vaddr_to_be_flushed_is0) && (asid_to_be_flushed_i == tags_q[i].asid && ((tags_q[i].v_st_enbl[HYP_EXT] && lu_vmid_i == tags_q[i].vmid) || !tags_q[i].v_st_enbl[HYP_EXT])) && (!asid_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
end
end else if (flush_gvma_i) begin
if (tags_q[i].g_st_enbl) begin
end else if (flush_gvma_i && CVA6Cfg.RVH) begin
if (tags_q[i].v_st_enbl[HYP_EXT]) begin
// invalidate logic
// flush everything if vmid is 0 and addr is 0 ("HFENCE.GVMA x0 x0" case)
if (vmid_to_be_flushed_is0 && gpaddr_to_be_flushed_is0) tags_n[i].valid = 1'b0;
// flush gpaddr in all addressing space ("HFENCE.GVMA gpaddr x0" case), it should happen only for leaf pages
else if (vmid_to_be_flushed_is0 && ((gpaddr_gppn0_match[i] && gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i]) || (gpaddr_gppn2_match[i] && tags_q[i].is_g_1G) || (gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i] && tags_q[i].is_g_2M) ) && (~gpaddr_to_be_flushed_is0))
else if (vmid_to_be_flushed_is0 && (|vaddr_level_match[i][HYP_EXT] ) && (~gpaddr_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
// the entry vmid and gpaddr both matches with the entry to be flushed ("HFENCE.GVMA gpaddr vmid" case)
else if (((gpaddr_gppn0_match[i] && gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i]) || (gpaddr_gppn2_match[i] && tags_q[i].is_g_1G) || (gpaddr_gppn1_match[i] && gpaddr_gppn2_match[i] && tags_q[i].is_g_2M)) && (vmid_to_be_flushed_i == tags_q[i].vmid) && (~gpaddr_to_be_flushed_is0) && (~vmid_to_be_flushed_is0))
else if ((|vaddr_level_match[i][HYP_EXT]) && (vmid_to_be_flushed_i == tags_q[i].vmid) && (~gpaddr_to_be_flushed_is0) && (~vmid_to_be_flushed_is0))
tags_n[i].valid = 1'b0;
// the entry is flushed if the vmid matches and gpaddr is 0. ("HFENCE.GVMA 0 vmid" case)
else if ((gpaddr_to_be_flushed_is0) && (vmid_to_be_flushed_i == tags_q[i].vmid) && (!vmid_to_be_flushed_is0))
@ -253,25 +286,19 @@ module cva6_tlb_sv39x4
end
// normal replacement
end else if (update_i.valid & replace_en[i]) begin
// update tag array
tags_n[i] = '{
asid: update_i.asid,
vmid: update_i.vmid,
vpn2: update_i.vpn[18+GPPN2:18],
vpn1: update_i.vpn[17:9],
vpn0: update_i.vpn[8:0],
s_st_enbl: s_st_enbl_i,
g_st_enbl: g_st_enbl_i,
v: v_i,
is_s_1G: update_i.is_s_1G,
is_s_2M: update_i.is_s_2M,
is_g_1G: update_i.is_g_1G,
is_g_2M: update_i.is_g_2M,
valid: 1'b1
// end else if (update_i.valid & replace_en[i] && !lu_hit_o) begin //to add this fix
//update tag
tags_n[i] = {
update_i.asid,
update_i.vmid,
((CVA6Cfg.PtLevels + HYP_EXT) * (CVA6Cfg.VpnLen / CVA6Cfg.PtLevels))'(update_i.vpn),
update_i.is_page,
update_i.v_st_enbl,
1'b1
};
// and content as well
// update content as well
content_n[i].pte = update_i.content;
content_n[i].gpte = update_i.g_content;
if (CVA6Cfg.RVH) content_n[i].gpte = update_i.g_content;
end
end
end
@ -285,27 +312,27 @@ module cva6_tlb_sv39x4
// The PLRU-tree indexing:
// lvl0 0
// / \
// / \
// lvl1 1 2
// / \ / \
// lvl2 3 4 5 6
// / \ /\/\ /\
// ... ... ... ...
// Just predefine which nodes will be set/cleared
// E.g. for a TLB with 8 entries, the for-loop is semantically
// equivalent to the following pseudo-code:
// unique case (1'b1)
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
// default: begin /* No hit */ end
// endcase
for (
// / \
// lvl1 1 2
// / \ / \
// lvl2 3 4 5 6
// / \ /\/\ /\
// ... ... ... ...
// Just predefine which nodes will be set/cleared
// E.g. for a TLB with 8 entries, the for-loop is semantically
// equivalent to the following pseudo-code:
// unique case (1'b1)
// lu_hit[7]: plru_tree_n[0, 2, 6] = {1, 1, 1};
// lu_hit[6]: plru_tree_n[0, 2, 6] = {1, 1, 0};
// lu_hit[5]: plru_tree_n[0, 2, 5] = {1, 0, 1};
// lu_hit[4]: plru_tree_n[0, 2, 5] = {1, 0, 0};
// lu_hit[3]: plru_tree_n[0, 1, 4] = {0, 1, 1};
// lu_hit[2]: plru_tree_n[0, 1, 4] = {0, 1, 0};
// lu_hit[1]: plru_tree_n[0, 1, 3] = {0, 0, 1};
// lu_hit[0]: plru_tree_n[0, 1, 3] = {0, 0, 0};
// default: begin /* No hit */ end
// endcase
for (
int unsigned i = 0; i < TLB_ENTRIES; i++
) begin
automatic int unsigned idx_base, shift, new_index;

View file

@ -571,12 +571,6 @@ package ariane_pkg;
endcase
endfunction
// ---------------
// MMU instanciation
// ---------------
localparam int unsigned INSTR_TLB_ENTRIES = cva6_config_pkg::CVA6ConfigInstrTlbEntries;
localparam int unsigned DATA_TLB_ENTRIES = cva6_config_pkg::CVA6ConfigDataTlbEntries;
// -------------------
// Performance counter
// -------------------

View file

@ -25,6 +25,10 @@ package build_config_pkg;
int unsigned DCACHE_INDEX_WIDTH = $clog2(CVA6Cfg.DcacheByteSize / CVA6Cfg.DcacheSetAssoc);
int unsigned DCACHE_OFFSET_WIDTH = $clog2(CVA6Cfg.DcacheLineWidth / 8);
// MMU
int unsigned VpnLen = (CVA6Cfg.XLEN == 64) ? (CVA6Cfg.RVH ? 29 : 27) : 20;
int unsigned PtLevels = (CVA6Cfg.XLEN == 64) ? 3 : 2;
config_pkg::cva6_cfg_t cfg;
cfg.XLEN = CVA6Cfg.XLEN;
@ -147,6 +151,10 @@ package build_config_pkg;
cfg.SVX = (cfg.MODE_SV == config_pkg::ModeSv32) ? 34 : 41;
cfg.InstrTlbEntries = CVA6Cfg.InstrTlbEntries;
cfg.DataTlbEntries = CVA6Cfg.DataTlbEntries;
cfg.UseSharedTlb = CVA6Cfg.UseSharedTlb;
cfg.SharedTlbDepth = CVA6Cfg.SharedTlbDepth;
cfg.VpnLen = VpnLen;
cfg.PtLevels = PtLevels;
return cfg;
endfunction

View file

@ -184,6 +184,10 @@ package config_pkg;
int unsigned InstrTlbEntries;
// MMU data TLB entries
int unsigned DataTlbEntries;
// MMU option to use shared TLB
bit unsigned UseSharedTlb;
// MMU depth of shared TLB
int unsigned SharedTlbDepth;
} cva6_user_cfg_t;
typedef struct packed {
@ -253,6 +257,10 @@ package config_pkg;
int unsigned BHTEntries;
int unsigned InstrTlbEntries;
int unsigned DataTlbEntries;
bit unsigned UseSharedTlb;
int unsigned SharedTlbDepth;
int unsigned VpnLen;
int unsigned PtLevels;
logic [63:0] DmBaseAddress;
bit TvalEn;

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 2;
localparam CVA6ConfigDataTlbEntries = 2;
localparam CVA6ConfigRASDepth = 0;
localparam CVA6ConfigBTBEntries = 0;
localparam CVA6ConfigBHTEntries = 0;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -26,9 +26,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrScoreboardEntries = 4; // cvxif_pkg.sv
localparam CVA6ConfigInstrTlbEntries = 2; // MMU
localparam CVA6ConfigDataTlbEntries = 2; // MMU
localparam config_pkg::cva6_user_cfg_t cva6_cfg = '{
XLEN: unsigned'(CVA6ConfigXlen),
FpgaEn: bit'(0),
@ -94,8 +91,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(2),
FetchUserWidth: unsigned'(32),
FetchUserEn: unsigned'(0),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(0),
NrStorePipeRegs: int'(0),
DcacheIdWidth: int'(1)

View file

@ -57,9 +57,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 1;
localparam CVA6ConfigInstrTlbEntries = 2;
localparam CVA6ConfigDataTlbEntries = 2;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 0;
localparam CVA6ConfigBHTEntries = 32;
@ -141,8 +138,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 2;
localparam CVA6ConfigDataTlbEntries = 2;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrLoadPipeRegs = 1;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigInstrTlbEntries = 2;
localparam CVA6ConfigDataTlbEntries = 2;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 2;
localparam CVA6ConfigDataTlbEntries = 2;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 2;
localparam CVA6ConfigDataTlbEntries = 2;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(2),
DataTlbEntries: int'(2),
UseSharedTlb: bit'(1),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -65,9 +65,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 8;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -149,8 +146,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
WtDcacheWbufDepth: int'(CVA6ConfigWtDcacheWbufDepth),
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -58,9 +58,6 @@ package cva6_config_pkg;
localparam CVA6ConfigNrStorePipeRegs = 0;
localparam CVA6ConfigNrLoadBufEntries = 2;
localparam CVA6ConfigInstrTlbEntries = 16;
localparam CVA6ConfigDataTlbEntries = 16;
localparam CVA6ConfigRASDepth = 2;
localparam CVA6ConfigBTBEntries = 32;
localparam CVA6ConfigBHTEntries = 128;
@ -142,8 +139,10 @@ package cva6_config_pkg;
FetchUserWidth: unsigned'(CVA6ConfigFetchUserWidth),
FetchUserEn: unsigned'(CVA6ConfigFetchUserEn),
DCacheType: CVA6ConfigDcacheType,
InstrTlbEntries: int'(CVA6ConfigInstrTlbEntries),
DataTlbEntries: int'(CVA6ConfigDataTlbEntries),
InstrTlbEntries: int'(16),
DataTlbEntries: int'(16),
UseSharedTlb: bit'(0),
SharedTlbDepth: int'(64),
NrLoadPipeRegs: int'(CVA6ConfigNrLoadPipeRegs),
NrStorePipeRegs: int'(CVA6ConfigNrStorePipeRegs),
DcacheIdWidth: int'(CVA6ConfigDcacheIdWidth)

View file

@ -228,120 +228,65 @@ module load_store_unit
logic hs_ld_st_inst;
logic hlvx_inst;
logic [2:0] enable_translation, en_ld_st_translation, flush_tlb;
logic [1:0] sum, mxr;
logic [CVA6Cfg.PPNW-1:0] satp_ppn[2:0];
logic [CVA6Cfg.ASID_WIDTH-1:0] asid[2:0], asid_to_be_flushed[1:0];
logic [CVA6Cfg.VLEN-1:0] vaddr_to_be_flushed[1:0];
// -------------------
// MMU e.g.: TLBs/PTW
// -------------------
if (CVA6Cfg.MmuPresent && CVA6Cfg.RVH && (CVA6Cfg.XLEN == 64)) begin : gen_mmu_sv39x4
cva6_mmu_sv39x4 #(
.CVA6Cfg (CVA6Cfg),
.exception_t (exception_t),
.icache_areq_t (icache_areq_t),
.icache_arsp_t (icache_arsp_t),
.icache_dreq_t (icache_dreq_t),
.icache_drsp_t (icache_drsp_t),
.dcache_req_i_t (dcache_req_i_t),
.dcache_req_o_t (dcache_req_o_t),
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
if (CVA6Cfg.MmuPresent) begin : gen_mmu
localparam HYP_EXT = CVA6Cfg.RVH ? 1 : 0;
cva6_mmu #(
.CVA6Cfg (CVA6Cfg),
.exception_t (exception_t),
.icache_areq_t (icache_areq_t),
.icache_arsp_t (icache_arsp_t),
.icache_dreq_t (icache_dreq_t),
.icache_drsp_t (icache_drsp_t),
.dcache_req_i_t(dcache_req_i_t),
.dcache_req_o_t(dcache_req_o_t),
.HYP_EXT (HYP_EXT)
) i_cva6_mmu (
.clk_i(clk_i),
.rst_ni(rst_ni),
.flush_i(flush_i),
.icache_areq_i(icache_areq_i),
.icache_areq_o(icache_areq_o),
// misaligned bypass
.misaligned_ex_i(misaligned_exception),
.lsu_is_store_i (st_translation_req),
.lsu_req_i (translation_req),
.lsu_vaddr_i (mmu_vaddr),
.lsu_tinst_i (mmu_tinst),
.lsu_req_i(translation_req),
.lsu_vaddr_i(mmu_vaddr),
.lsu_tinst_i(mmu_tinst),
.lsu_is_store_i(st_translation_req),
.csr_hs_ld_st_inst_o(csr_hs_ld_st_inst_o),
.lsu_dtlb_hit_o(dtlb_hit), // send in the same cycle as the request
.lsu_dtlb_ppn_o(dtlb_ppn), // send in the same cycle as the request
.lsu_valid_o (translation_valid),
.lsu_paddr_o (mmu_paddr),
.lsu_exception_o(mmu_exception),
.lsu_dtlb_hit_o (dtlb_hit), // send in the same cycle as the request
.lsu_dtlb_ppn_o (dtlb_ppn), // send in the same cycle as the request
// connecting PTW to D$ IF
.req_port_i (dcache_req_ports_i[0]),
.req_port_o (dcache_req_ports_o[0]),
// icache address translation requests
.icache_areq_i (icache_areq_i),
.asid_to_be_flushed_i,
.vmid_to_be_flushed_i,
.vaddr_to_be_flushed_i,
.gpaddr_to_be_flushed_i,
.icache_areq_o (icache_areq_o),
.pmpcfg_i,
.pmpaddr_i,
// Hypervisor load/store signals
.priv_lvl_i (priv_lvl_i),
.ld_st_priv_lvl_i(ld_st_priv_lvl_i),
.hlvx_inst_i (mmu_hlvx_inst),
.hs_ld_st_inst_i(mmu_hs_ld_st_inst),
.*
);
end else if (CVA6Cfg.MmuPresent && (CVA6Cfg.XLEN == 64)) begin : gen_mmu_sv39
mmu #(
.CVA6Cfg (CVA6Cfg),
.exception_t (exception_t),
.icache_areq_t (icache_areq_t),
.icache_arsp_t (icache_arsp_t),
.icache_dreq_t (icache_dreq_t),
.icache_drsp_t (icache_drsp_t),
.dcache_req_i_t (dcache_req_i_t),
.dcache_req_o_t (dcache_req_o_t),
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
) i_cva6_mmu (
// misaligned bypass
.misaligned_ex_i(misaligned_exception),
.lsu_is_store_i (st_translation_req),
.lsu_req_i (translation_req),
.lsu_vaddr_i (mmu_vaddr),
.lsu_valid_o (translation_valid),
.lsu_paddr_o (mmu_paddr),
.lsu_exception_o(mmu_exception),
.lsu_dtlb_hit_o (dtlb_hit), // send in the same cycle as the request
.lsu_dtlb_ppn_o (dtlb_ppn), // send in the same cycle as the request
// connecting PTW to D$ IF
.req_port_i (dcache_req_ports_i[0]),
.req_port_o (dcache_req_ports_o[0]),
// icache address translation requests
.icache_areq_i (icache_areq_i),
.asid_to_be_flushed_i,
.vaddr_to_be_flushed_i,
.icache_areq_o (icache_areq_o),
.pmpcfg_i,
.pmpaddr_i,
.*
);
end else if (CVA6Cfg.MmuPresent && (CVA6Cfg.XLEN == 32)) begin : gen_mmu_sv32
cva6_mmu_sv32 #(
.CVA6Cfg (CVA6Cfg),
.exception_t (exception_t),
.icache_areq_t (icache_areq_t),
.icache_arsp_t (icache_arsp_t),
.icache_dreq_t (icache_dreq_t),
.icache_drsp_t (icache_drsp_t),
.dcache_req_i_t (dcache_req_i_t),
.dcache_req_o_t (dcache_req_o_t),
.INSTR_TLB_ENTRIES(ariane_pkg::INSTR_TLB_ENTRIES),
.DATA_TLB_ENTRIES (ariane_pkg::DATA_TLB_ENTRIES)
) i_cva6_mmu (
// misaligned bypass
.misaligned_ex_i(misaligned_exception),
.lsu_is_store_i (st_translation_req),
.lsu_req_i (translation_req),
.lsu_vaddr_i (mmu_vaddr),
.lsu_valid_o (translation_valid),
.lsu_paddr_o (mmu_paddr),
.lsu_exception_o(mmu_exception),
.lsu_dtlb_hit_o (dtlb_hit), // send in the same cycle as the request
.lsu_dtlb_ppn_o (dtlb_ppn), // send in the same cycle as the request
// connecting PTW to D$ IF
.req_port_i (dcache_req_ports_i[0]),
.req_port_o (dcache_req_ports_o[0]),
// icache address translation requests
.icache_areq_i (icache_areq_i),
.asid_to_be_flushed_i,
.vaddr_to_be_flushed_i,
.icache_areq_o (icache_areq_o),
.itlb_miss_o(itlb_miss_o),
.dtlb_miss_o(dtlb_miss_o),
.req_port_i(dcache_req_ports_i[0]),
.req_port_o(dcache_req_ports_o[0]),
.pmpcfg_i,
.pmpaddr_i,
.*
);
end else begin : gen_no_mmu
if (CVA6Cfg.VLEN > CVA6Cfg.PLEN) begin

View file

@ -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

View file

@ -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 */

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -34,8 +34,7 @@ ariane:
src/load_unit.sv,
src/load_store_unit.sv,
src/miss_handler.sv,
src/mmu_sv39/mmu.sv,
src/mmu_sv32/cva6_mmu_sv32.sv,
src/cva6_mmu/cva6_mmu.sv,
src/mult.sv,
src/nbdcache.sv,
src/vdregs.sv,
@ -43,14 +42,12 @@ ariane:
src/sram_wrapper.sv,
src/pcgen_stage.sv,
src/perf_counters.sv,
src/mmu_sv39/ptw.sv,
src/mmu_sv32/cva6_ptw_sv32.sv,
src/cva6_mmu/cva6_ptw.sv,
src/re_name.sv,
src/scoreboard.sv,
src/store_buffer.sv,
src/store_unit.sv,
src/mmu_sv39/tlb.sv,
src/mmu_sv32/cva6_tlb_sv32.sv,
src/cva6_mmu/cva6_tlb.sv,
src/acc_dispatcher.sv,
src/debug/dm_csrs.sv,
src/debug/dm_mem.sv,