// Copyright lowRISC contributors. // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 `include "dv_fcov_macros.svh" module ibex_pmp import ibex_pkg::*; #( parameter int unsigned DmBaseAddr = 32'h1A110000, parameter int unsigned DmAddrMask = 32'h00000FFF, // Granularity of NAPOT access, // 0 = No restriction, 1 = 8 byte, 2 = 16 byte, 3 = 32 byte, etc. parameter int unsigned PMPGranularity = 0, // Number of access channels (e.g. i-side + d-side) parameter int unsigned PMPNumChan = 2, // Number of implemented regions parameter int unsigned PMPNumRegions = 4 ) ( // Interface to CSRs input ibex_pkg::pmp_cfg_t csr_pmp_cfg_i [PMPNumRegions], input logic [PMP_ADDR_MSB:0] csr_pmp_addr_i [PMPNumRegions], input ibex_pkg::pmp_mseccfg_t csr_pmp_mseccfg_i, input logic debug_mode_i, input ibex_pkg::priv_lvl_e priv_mode_i [PMPNumChan], // Access checking channels input logic [PMP_ADDR_MSB:0] pmp_req_addr_i [PMPNumChan], input ibex_pkg::pmp_req_e pmp_req_type_i [PMPNumChan], output logic pmp_req_err_o [PMPNumChan] ); // Access Checking Signals logic [PMP_ADDR_MSB:0] region_start_addr [PMPNumRegions]; logic [PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB] region_addr_mask [PMPNumRegions]; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_gt; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_lt; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_eq; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_all; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_basic_perm_check; logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_perm_check; logic [PMPNumChan-1:0] access_fault_check_res; logic [PMPNumChan-1:0] debug_mode_allowed_access; /////////////////////// // Functions for PMP // /////////////////////// // Flow of the PMP checking operation follows as below // // basic_perm_check ---> perm_check_wrapper ---> mml_perm_check/orig_perm_check ---/ // | // region_match_all --------------------------------> access_fault_check <---------- // | // !debug_mode_allowed_access ------------------------------> & // \--> pmp_req_err_o // Compute permissions checks that apply when MSECCFG.MML is set. Added for Smepmp support. function automatic logic mml_perm_check(ibex_pkg::pmp_cfg_t region_csr_pmp_cfg, ibex_pkg::pmp_req_e pmp_req_type, ibex_pkg::priv_lvl_e priv_mode, logic permission_check); logic result = 1'b0; logic unused_cfg = |region_csr_pmp_cfg.mode; if (!region_csr_pmp_cfg.read && region_csr_pmp_cfg.write) begin // Special-case shared regions where R = 0, W = 1 unique case ({region_csr_pmp_cfg.lock, region_csr_pmp_cfg.exec}) // Read/write in M, read only in S/U 2'b00: result = (pmp_req_type == PMP_ACC_READ) | ((pmp_req_type == PMP_ACC_WRITE) & (priv_mode == PRIV_LVL_M)); // Read/write in M/S/U 2'b01: result = (pmp_req_type == PMP_ACC_READ) | (pmp_req_type == PMP_ACC_WRITE); // Execute only on M/S/U 2'b10: result = (pmp_req_type == PMP_ACC_EXEC); // Read/execute in M, execute only on S/U 2'b11: result = (pmp_req_type == PMP_ACC_EXEC) | ((pmp_req_type == PMP_ACC_READ) & (priv_mode == PRIV_LVL_M)); default: ; endcase end else begin if (region_csr_pmp_cfg.read & region_csr_pmp_cfg.write & region_csr_pmp_cfg.exec & region_csr_pmp_cfg.lock) begin // Special-case shared read only region when R = 1, W = 1, X = 1, L = 1 result = pmp_req_type == PMP_ACC_READ; end else begin // Otherwise use basic permission check. Permission is always denied if in S/U mode and // L is set or if in M mode and L is unset. result = permission_check & (priv_mode == PRIV_LVL_M ? region_csr_pmp_cfg.lock : ~region_csr_pmp_cfg.lock); end end return result; endfunction // Compute permissions checks that apply when MSECCFG.MML is unset. This is the original PMP // behaviour before Smepmp was added. function automatic logic orig_perm_check(logic pmp_cfg_lock, ibex_pkg::priv_lvl_e priv_mode, logic permission_check); return (priv_mode == PRIV_LVL_M) ? // For M-mode, any region which matches with the L-bit clear, or with sufficient // access permissions will be allowed (~pmp_cfg_lock | permission_check) : // For other modes, the lock bit doesn't matter permission_check; endfunction // A wrapper function in which it is decided which form of permission check function gets called function automatic logic perm_check_wrapper(logic csr_pmp_mseccfg_mml, ibex_pkg::pmp_cfg_t region_csr_pmp_cfg, ibex_pkg::pmp_req_e pmp_req_type, ibex_pkg::priv_lvl_e priv_mode, logic permission_check); return csr_pmp_mseccfg_mml ? mml_perm_check(region_csr_pmp_cfg, pmp_req_type, priv_mode, permission_check) : orig_perm_check(region_csr_pmp_cfg.lock, priv_mode, permission_check); endfunction // Access fault determination / prioritization function automatic logic access_fault_check (logic csr_pmp_mseccfg_mmwp, logic csr_pmp_mseccfg_mml, ibex_pkg::pmp_req_e pmp_req_type, logic [PMPNumRegions-1:0] match_all, ibex_pkg::priv_lvl_e priv_mode, logic [PMPNumRegions-1:0] final_perm_check); // When MSECCFG.MMWP is set default deny always, otherwise allow for M-mode, deny for other // modes. Also deny unmatched for M-mode whe MSECCFG.MML is set and request type is EXEC. logic access_fail = csr_pmp_mseccfg_mmwp | (priv_mode != PRIV_LVL_M) | (csr_pmp_mseccfg_mml && (pmp_req_type == PMP_ACC_EXEC)); logic matched = 1'b0; // PMP entries are statically prioritized, from 0 to N-1 // The lowest-numbered PMP entry which matches an address determines accessibility for (int r = 0; r < PMPNumRegions; r++) begin if (!matched && match_all[r]) begin access_fail = ~final_perm_check[r]; matched = 1'b1; end end return access_fail; endfunction // --------------- // Access checking // --------------- for (genvar r = 0; r < PMPNumRegions; r++) begin : g_addr_exp // Start address for TOR matching if (r == 0) begin : g_entry0 assign region_start_addr[r] = (csr_pmp_cfg_i[r].mode == PMP_MODE_TOR) ? 34'h000000000 : csr_pmp_addr_i[r]; end else begin : g_oth assign region_start_addr[r] = (csr_pmp_cfg_i[r].mode == PMP_MODE_TOR) ? csr_pmp_addr_i[r-1] : csr_pmp_addr_i[r]; end // Address mask for NA matching for (genvar b = PMPGranularity + PMP_ADDR_LSB; b < 34; b++) begin : g_bitmask if (b == PMP_ADDR_LSB) begin : g_bit0 // Always mask bit 2 (PMP_ADDR_LSB) for NAPOT assign region_addr_mask[r][b] = (csr_pmp_cfg_i[r].mode != PMP_MODE_NAPOT); end else begin : g_others // We will mask this bit if it is within the programmed granule // i.e. addr = yyyy 0111 // ^ // | This bit pos is the top of the mask, all lower bits set // thus mask = 1111 0000 if (PMPGranularity == 0) begin : g_region_addr_mask_zero_granularity assign region_addr_mask[r][b] = (csr_pmp_cfg_i[r].mode != PMP_MODE_NAPOT) | ~&csr_pmp_addr_i[r][b-1:PMP_ADDR_LSB]; end else begin : g_region_addr_mask_other_granularity assign region_addr_mask[r][b] = (csr_pmp_cfg_i[r].mode != PMP_MODE_NAPOT) | ~&csr_pmp_addr_i[r][b-1:PMPGranularity+1]; end end end end for (genvar c = 0; c < PMPNumChan; c++) begin : g_access_check for (genvar r = 0; r < PMPNumRegions; r++) begin : g_regions // Comparators are sized according to granularity assign region_match_eq[c][r] = (pmp_req_addr_i[c][PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB] & region_addr_mask[r]) == (region_start_addr[r][PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB] & region_addr_mask[r]); assign region_match_gt[c][r] = pmp_req_addr_i[c][PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB] > region_start_addr[r][PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB]; assign region_match_lt[c][r] = pmp_req_addr_i[c][PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB] < csr_pmp_addr_i[r][PMP_ADDR_MSB:PMPGranularity+PMP_ADDR_LSB]; always_comb begin region_match_all[c][r] = 1'b0; unique case (csr_pmp_cfg_i[r].mode) PMP_MODE_OFF: region_match_all[c][r] = 1'b0; PMP_MODE_NA4: region_match_all[c][r] = region_match_eq[c][r]; PMP_MODE_NAPOT: region_match_all[c][r] = region_match_eq[c][r]; PMP_MODE_TOR: begin region_match_all[c][r] = (region_match_eq[c][r] | region_match_gt[c][r]) & region_match_lt[c][r]; end default: region_match_all[c][r] = 1'b0; endcase end // Basic permission check compares cfg register only. assign region_basic_perm_check[c][r] = ((pmp_req_type_i[c] == PMP_ACC_EXEC) & csr_pmp_cfg_i[r].exec) | ((pmp_req_type_i[c] == PMP_ACC_WRITE) & csr_pmp_cfg_i[r].write) | ((pmp_req_type_i[c] == PMP_ACC_READ) & csr_pmp_cfg_i[r].read); // Check specific required permissions since the behaviour is different // between Smepmp implementation and original PMP. assign region_perm_check[c][r] = perm_check_wrapper(csr_pmp_mseccfg_i.mml, csr_pmp_cfg_i[r], pmp_req_type_i[c], priv_mode_i[c], region_basic_perm_check[c][r]); // Address bits below PMP granularity (which starts at 4 byte) are deliberately unused. logic unused_sigs; assign unused_sigs = ^{region_start_addr[r][PMPGranularity+PMP_ADDR_LSB-1:0], pmp_req_addr_i[c][PMPGranularity+PMP_ADDR_LSB-1:0]}; end // Determine whether the core is in debug mode and the access is to an address in the range of // the Debug Module. According to Section A.2 of the RISC-V Debug Specification, the PMP must // not disallow fetches, loads, or stores in the address range associated with the Debug Module // when the hart is in debug mode. assign debug_mode_allowed_access[c] = debug_mode_i & ((pmp_req_addr_i[c][31:0] & ~DmAddrMask) == DmBaseAddr); // Once the permission checks of the regions are done, decide if the access is // denied by figuring out the matching region and its permission check. assign access_fault_check_res[c] = access_fault_check(csr_pmp_mseccfg_i.mmwp, csr_pmp_mseccfg_i.mml, pmp_req_type_i[c], region_match_all[c], priv_mode_i[c], region_perm_check[c]); // Debug Module accesses in Debug Mode are always allowed. assign pmp_req_err_o[c] = ~debug_mode_allowed_access[c] & access_fault_check_res[c]; // Access fails check against one region but access allowed due to another higher-priority // region. `DV_FCOV_SIGNAL(logic, pmp_region_override, ~pmp_req_err_o[c] & |(region_match_all[c] & ~region_perm_check[c])) end // RLB, rule locking bypass, is only relevant to ibex_cs_registers which controls writes to the // PMP CSRs. Tie to unused signal here to prevent lint warnings. logic unused_csr_pmp_mseccfg_rlb; assign unused_csr_pmp_mseccfg_rlb = csr_pmp_mseccfg_i.rlb; endmodule