// Copyright (c) 2025 Eclipse Foundation
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// The CVE2 does not officially support Physical Memory Protection (PMP), as 
// defined by rule PVL-40 of the CV32E20 core functional requirements.

module cve2_pmp #(
  // 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
) (
  // Clock and Reset
  input  logic                    clk_i,
  input  logic                    rst_ni,

  // Interface to CSRs
  input  cve2_pkg::pmp_cfg_t      csr_pmp_cfg_i     [PMPNumRegions],
  input  logic [33:0]             csr_pmp_addr_i    [PMPNumRegions],
  input  cve2_pkg::pmp_mseccfg_t  csr_pmp_mseccfg_i,

  input  cve2_pkg::priv_lvl_e     priv_mode_i    [PMPNumChan],
  // Access checking channels
  input  logic [33:0]             pmp_req_addr_i [PMPNumChan],
  input  cve2_pkg::pmp_req_e      pmp_req_type_i [PMPNumChan],
  output logic                    pmp_req_err_o  [PMPNumChan]

);

  import cve2_pkg::*;

  // Access Checking Signals
  logic [33:0]                                region_start_addr [PMPNumRegions];
  logic [33:PMPGranularity+2]                 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_mml_perm_check;
  logic [PMPNumChan-1:0]                      access_fault;

  $warning("CVE2 does not officially support PMP, see rule PVL-40.");

  // ---------------
  // 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 + 2; b < 34; b++) begin : g_bitmask
      if (b == 2) begin : g_bit0
        // Always mask bit 2 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:2];
        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][33:PMPGranularity+2] &
                                      region_addr_mask[r]) ==
                                     (region_start_addr[r][33:PMPGranularity+2] &
                                      region_addr_mask[r]);
      assign region_match_gt[c][r] = pmp_req_addr_i[c][33:PMPGranularity+2] >
                                     region_start_addr[r][33:PMPGranularity+2];
      assign region_match_lt[c][r] = pmp_req_addr_i[c][33:PMPGranularity+2] <
                                     csr_pmp_addr_i[r][33:PMPGranularity+2];

      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

      // Check specific required permissions
      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);


      // Compute permission checks that apply when MSECCFG.MML is set.
      always_comb begin
        region_mml_perm_check[c][r] = 1'b0;

        if (!csr_pmp_cfg_i[r].read && csr_pmp_cfg_i[r].write) begin
          // Special-case shared regions where R = 0, W = 1
          unique case ({csr_pmp_cfg_i[r].lock, csr_pmp_cfg_i[r].exec})
            // Read/write in M, read only in S/U
            2'b00: region_mml_perm_check[c][r] =
                (pmp_req_type_i[c] == PMP_ACC_READ) |
                ((pmp_req_type_i[c] == PMP_ACC_WRITE) & (priv_mode_i[c] == PRIV_LVL_M));
            // Read/write in M/S/U
            2'b01: region_mml_perm_check[c][r] =
                (pmp_req_type_i[c] == PMP_ACC_READ) | (pmp_req_type_i[c] == PMP_ACC_WRITE);
            // Execute only on M/S/U
            2'b10: region_mml_perm_check[c][r] = (pmp_req_type_i[c] == PMP_ACC_EXEC);
            // Read/execute in M, execute only on S/U
            2'b11: region_mml_perm_check[c][r] =
                (pmp_req_type_i[c] == PMP_ACC_EXEC) |
                ((pmp_req_type_i[c] == PMP_ACC_READ) & (priv_mode_i[c] == PRIV_LVL_M));
            default: ;
          endcase
        end else begin
          if (csr_pmp_cfg_i[r].read & csr_pmp_cfg_i[r].write & csr_pmp_cfg_i[r].exec
              & csr_pmp_cfg_i[r].lock) begin
            // Special-case shared read only region when R = 1, W = 1, X = 1, L = 1
            region_mml_perm_check[c][r] = pmp_req_type_i[c] == 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.
            region_mml_perm_check[c][r] =
              priv_mode_i[c] == PRIV_LVL_M ? csr_pmp_cfg_i[r].lock & region_basic_perm_check[c][r] :
                                            ~csr_pmp_cfg_i[r].lock & region_basic_perm_check[c][r];
          end
        end
      end
    end

    // Access fault determination / prioritization
    always_comb begin
      // When MSECCFG.MMWP is set default deny always, otherwise allow for M-mode, deny for other
      // modes
      access_fault[c] = csr_pmp_mseccfg_i.mmwp | (priv_mode_i[c] != PRIV_LVL_M);

      // PMP entries are statically prioritized, from 0 to N-1
      // The lowest-numbered PMP entry which matches an address determines accessability
      for (int r = PMPNumRegions - 1; r >= 0; r--) begin
        if (region_match_all[c][r]) begin
          if (csr_pmp_mseccfg_i.mml) begin
            // When MSECCFG.MML is set use MML specific permission check
            access_fault[c] = ~region_mml_perm_check[c][r];
          end else begin
            // Otherwise use original PMP behaviour
            access_fault[c] = (priv_mode_i[c] == PRIV_LVL_M) ?
                // For M-mode, any region which matches with the L-bit clear, or with sufficient
                // access permissions will be allowed
                (csr_pmp_cfg_i[r].lock & ~region_basic_perm_check[c][r]) :
                // For other modes, the lock bit doesn't matter
                ~region_basic_perm_check[c][r];
          end
        end
      end
    end

    assign pmp_req_err_o[c] = access_fault[c];
  end

  // RLB, rule locking bypass, is only relevant to cve2_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
