-- ################################################################################################# -- # << NEORV32 CPU - Physical Memory Protection Unit >> # -- # ********************************************************************************************* # -- # Compatible to the RISC-V PMP privilege architecture specifications. # -- # ********************************************************************************************* # -- # BSD 3-Clause License # -- # # -- # The NEORV32 RISC-V Processor, https://github.com/stnolting/neorv32 # -- # Copyright (c) 2024, Stephan Nolting. All rights reserved. # -- # # -- # Redistribution and use in source and binary forms, with or without modification, are # -- # permitted provided that the following conditions are met: # -- # # -- # 1. Redistributions of source code must retain the above copyright notice, this list of # -- # conditions and the following disclaimer. # -- # # -- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # -- # conditions and the following disclaimer in the documentation and/or other materials # -- # provided with the distribution. # -- # # -- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # -- # endorse or promote products derived from this software without specific prior written # -- # permission. # -- # # -- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # -- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # -- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # -- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # -- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # -- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # -- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # -- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # -- # OF THE POSSIBILITY OF SUCH DAMAGE. # -- ################################################################################################# library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library neorv32; use neorv32.neorv32_package.all; entity neorv32_cpu_pmp is generic ( NUM_REGIONS : natural range 0 to 16; -- number of regions (0..16) GRANULARITY : natural range 4 to natural'high; -- minimal region granularity in bytes, has to be a power of 2, min 4 bytes TOR_EN : boolean; -- implement TOR mode NAP_EN : boolean -- implement NAPOT/NA4 modes ); port ( -- global control -- clk_i : in std_ulogic; -- global clock, rising edge rstn_i : in std_ulogic; -- global reset, low-active, async ctrl_i : in ctrl_bus_t; -- main control bus -- CSR interface -- csr_we_i : in std_ulogic; -- global write enable csr_addr_i : in std_ulogic_vector(11 downto 0); -- address csr_wdata_i : in std_ulogic_vector(XLEN-1 downto 0); -- write data csr_rdata_o : out std_ulogic_vector(XLEN-1 downto 0); -- read data -- address input -- addr_if_i : in std_ulogic_vector(XLEN-1 downto 0); -- instruction fetch address addr_ls_i : in std_ulogic_vector(XLEN-1 downto 0); -- load/store address -- faults -- fault_ex_o : out std_ulogic; -- instruction fetch fault fault_rw_o : out std_ulogic -- read/write access fault ); end neorv32_cpu_pmp; architecture neorv32_cpu_pmp_rtl of neorv32_cpu_pmp is -- auto-configuration -- constant granularity_valid_c : boolean := is_power_of_two_f(GRANULARITY); constant granularity_c : natural := cond_sel_natural_f(granularity_valid_c, GRANULARITY, 2**index_size_f(GRANULARITY)); -- PMP configuration register bits -- constant cfg_r_c : natural := 0; -- read permit constant cfg_w_c : natural := 1; -- write permit constant cfg_x_c : natural := 2; -- execute permit constant cfg_al_c : natural := 3; -- mode bit low constant cfg_ah_c : natural := 4; -- mode bit high constant cfg_rl_c : natural := 5; -- reserved constant cfg_rh_c : natural := 6; -- reserved constant cfg_l_c : natural := 7; -- locked entry -- PMP modes -- constant mode_off_c : std_ulogic_vector(1 downto 0) := "00"; -- null region (disabled) constant mode_tor_c : std_ulogic_vector(1 downto 0) := "01"; -- top of range constant mode_na4_c : std_ulogic_vector(1 downto 0) := "10"; -- naturally aligned four-byte region constant mode_napot_c : std_ulogic_vector(1 downto 0) := "11"; -- naturally aligned power-of-two region (>= 8 bytes) -- PMP helpers -- constant pmp_lsb_c : natural := index_size_f(granularity_c); -- min = 2 -- PMP CSRs -- type csr_cfg_t is array (0 to NUM_REGIONS-1) of std_ulogic_vector(7 downto 0); type csr_addr_t is array (0 to NUM_REGIONS-1) of std_ulogic_vector(XLEN-1 downto 0); type csr_cfg_rd_t is array (0 to 15) of std_ulogic_vector(7 downto 0); type csr_cfg_rd32_t is array (0 to 03) of std_ulogic_vector(XLEN-1 downto 0); type csr_addr_rd_t is array (0 to 15) of std_ulogic_vector(XLEN-1 downto 0); type csr_t is record we_cfg : std_ulogic_vector(03 downto 0); we_addr : std_ulogic_vector(15 downto 0); cfg : csr_cfg_t; addr : csr_addr_t; end record; signal csr : csr_t; signal cfg_rd : csr_cfg_rd_t; signal cfg_rd32 : csr_cfg_rd32_t; signal addr_rd : csr_addr_rd_t; -- PMP address extension to 34 bit -- type xaddr_t is array (0 to NUM_REGIONS-1) of std_ulogic_vector(XLEN+1 downto 0); signal xaddr : xaddr_t; -- region access logic -- type addr_mask_t is array (0 to NUM_REGIONS-1) of std_ulogic_vector(XLEN-1 downto pmp_lsb_c); signal addr_mask_napot, addr_mask : addr_mask_t; type region_t is record i_cmp_mm, d_cmp_mm : std_ulogic_vector(NUM_REGIONS-1 downto 0); -- masked match i_cmp_ge, d_cmp_ge : std_ulogic_vector(NUM_REGIONS-1 downto 0); -- greater or equal i_cmp_lt, d_cmp_lt : std_ulogic_vector(NUM_REGIONS-1 downto 0); -- less than i_match, d_match : std_ulogic_vector(NUM_REGIONS-1 downto 0); -- region address match perm_ex, perm_rw : std_ulogic_vector(NUM_REGIONS-1 downto 0); -- region's permission end record; signal region : region_t; -- permission check violation -- signal fail_ex, fail_rw : std_ulogic_vector(NUM_REGIONS downto 0); begin -- Sanity Checks -------------------------------------------------------------------------- -- ------------------------------------------------------------------------------------------- assert not (granularity_valid_c = false) report "[NEORV32] Auto-adjusting invalid PMP granularity configuration." severity warning; -- CSR Write Access ----------------------------------------------------------------------- -- ------------------------------------------------------------------------------------------- csr_we: process(csr_we_i, csr_addr_i) -- write enable decoder begin -- Configuration registers -- csr.we_cfg <= (others => '0'); if (csr_addr_i(11 downto 2) = csr_pmpcfg0_c(11 downto 2)) and (csr_we_i = '1') then csr.we_cfg(to_integer(unsigned(csr_addr_i(1 downto 0)))) <= '1'; end if; -- Address registers -- csr.we_addr <= (others => '0'); if (csr_addr_i(11 downto 4) = csr_pmpaddr0_c(11 downto 4)) and (csr_we_i = '1') then csr.we_addr(to_integer(unsigned(csr_addr_i(3 downto 0)))) <= '1'; end if; end process csr_we; -- PMP CSR registers -- csr_reg_gen: for i in 0 to NUM_REGIONS-1 generate csr_reg: process(rstn_i, clk_i) variable mode_v : std_ulogic_vector(1 downto 0); begin if (rstn_i = '0') then csr.cfg(i) <= (others => '0'); csr.addr(i) <= (others => '0'); elsif rising_edge(clk_i) then -- configuration -- if (csr.we_cfg(i/4) = '1') and (csr.cfg(i)(7) = '0') then -- unlocked write access csr.cfg(i)(cfg_r_c) <= csr_wdata_i((i mod 4)*8+0); -- R (read) csr.cfg(i)(cfg_w_c) <= csr_wdata_i((i mod 4)*8+1); -- W (write) csr.cfg(i)(cfg_x_c) <= csr_wdata_i((i mod 4)*8+2); -- X (execute) -- A (mode) -- mode_v := csr_wdata_i((i mod 4)*8+4 downto (i mod 4)*8+3); if ((mode_v = mode_tor_c) and (TOR_EN = false)) or -- TOR mode not implemented ((mode_v = mode_na4_c) and (NAP_EN = false)) or -- NA4 mode not implemented ((mode_v = mode_napot_c) and (NAP_EN = false)) or -- NAPOT mode not implemented ((mode_v = mode_na4_c) and (granularity_c > 4)) then -- NA4 not available csr.cfg(i)(cfg_ah_c downto cfg_al_c) <= mode_off_c; else -- valid configuration csr.cfg(i)(cfg_ah_c downto cfg_al_c) <= mode_v; end if; -- csr.cfg(i)(cfg_rl_c) <= '0'; -- reserved csr.cfg(i)(cfg_rh_c) <= '0'; -- reserved csr.cfg(i)(cfg_l_c) <= csr_wdata_i((i mod 4)*8+7); -- L (locked) end if; -- address -- if (csr.we_addr(i) = '1') and (csr.cfg(i)(cfg_l_c) = '0') then -- unlocked write access if (i < NUM_REGIONS-1) then if (csr.cfg(i+1)(cfg_l_c) = '0') or (csr.cfg(i+1)(cfg_ah_c downto cfg_al_c) /= mode_tor_c) then -- cfg(i+1) not "LOCKED TOR" csr.addr(i) <= "00" & csr_wdata_i(XLEN-3 downto 0); end if; else -- very last entry csr.addr(i) <= "00" & csr_wdata_i(XLEN-3 downto 0); end if; end if; end if; end process csr_reg; end generate; -- CSR Read Access ------------------------------------------------------------------------ -- ------------------------------------------------------------------------------------------- csr_read_access: process(csr_addr_i, cfg_rd32, addr_rd) begin if (csr_addr_i(11 downto 5) = csr_pmpcfg0_c(11 downto 5)) then -- PMP CSR if (csr_addr_i(4) = '0') then -- PMP configuration CSR csr_rdata_o <= cfg_rd32(to_integer(unsigned(csr_addr_i(1 downto 0)))); else -- PMP address CSR csr_rdata_o <= addr_rd(to_integer(unsigned(csr_addr_i(3 downto 0)))); end if; else csr_rdata_o <= (others => '0'); end if; end process csr_read_access; -- CSR read-back -- csr_read_back_gen: for i in 0 to NUM_REGIONS-1 generate -- configuration -- cfg_rd(i) <= csr.cfg(i); -- address -- address_read_back: process(csr) begin addr_rd(i) <= (others => '0'); addr_rd(i)(XLEN-1 downto pmp_lsb_c-2) <= csr.addr(i)(XLEN-1 downto pmp_lsb_c-2); if (granularity_c = 8) and TOR_EN then -- bit G-1 reads as zero in TOR or OFF mode if (csr.cfg(i)(cfg_ah_c) = '0') then -- TOR/OFF mode addr_rd(i)(pmp_lsb_c) <= '0'; end if; elsif (granularity_c > 8) then if NAP_EN then addr_rd(i)(pmp_lsb_c-2 downto 0) <= (others => '1'); -- in NAPOT mode bits G-2:0 must read as one end if; if TOR_EN then if (csr.cfg(i)(cfg_ah_c) = '0') then -- TOR/OFF mode addr_rd(i)(pmp_lsb_c-1 downto 0) <= (others => '0'); -- in TOR or OFF mode bits G-1:0 must read as zero end if; end if; end if; end process address_read_back; end generate; -- terminate unused CSR read-backs -- csr_read_back_terminate: for i in NUM_REGIONS to 15 generate cfg_rd(i) <= (others => '0'); addr_rd(i) <= (others => '0'); end generate; -- pack configuration read-back -- csr_read_back_pack: for i in 0 to 3 generate cfg_rd32(i) <= cfg_rd(i*4+3) & cfg_rd(i*4+2) & cfg_rd(i*4+1) & cfg_rd(i*4+0); end generate; -- Region Access Logic -------------------------------------------------------------------- -- ------------------------------------------------------------------------------------------- region_gen: for r in 0 to NUM_REGIONS-1 generate -- extend region addresses to 34-bit -- xaddr(r) <= csr.addr(r) & "00"; -- mask byte offset nap_mode_enable: if NAP_EN generate -- compute address masks for NAPOT mode -- addr_mask_napot(r)(pmp_lsb_c) <= '0'; addr_mask_napot_gen: for i in pmp_lsb_c+1 to XLEN-1 generate addr_mask_napot(r)(i) <= addr_mask_napot(r)(i-1) or (not xaddr(r)(i-1)); end generate; -- address mask select -- addr_masking: process(rstn_i, clk_i) begin if (rstn_i = '0') then addr_mask(r) <= (others => '0'); elsif rising_edge(clk_i) then if (csr.cfg(r)(cfg_al_c) = '1') then -- NAPOT addr_mask(r) <= addr_mask_napot(r); else -- NA4 addr_mask(r) <= (others => '1'); end if; end if; end process addr_masking; end generate; -- check region address match -- -- NA4 and NAPOT -- region.i_cmp_mm(r) <= '1' when ((addr_if_i(XLEN-1 downto pmp_lsb_c) and addr_mask(r)) = (xaddr(r)(XLEN-1 downto pmp_lsb_c) and addr_mask(r))) and NAP_EN else '0'; region.d_cmp_mm(r) <= '1' when ((addr_ls_i(XLEN-1 downto pmp_lsb_c) and addr_mask(r)) = (xaddr(r)(XLEN-1 downto pmp_lsb_c) and addr_mask(r))) and NAP_EN else '0'; -- TOR region 0 -- addr_match_r0_gen: if (r = 0) generate -- first entry: use ZERO as base and current entry as bound region.i_cmp_ge(r) <= '1' when TOR_EN else '0'; -- address is always greater than or equal to zero (and TOR mode enabled) region.i_cmp_lt(r) <= '0'; -- unused region.d_cmp_ge(r) <= '1' when TOR_EN else '0'; -- address is always greater than or equal to zero (and TOR mode enabled) region.d_cmp_lt(r) <= '0'; -- unused end generate; -- TOR region any -- addr_match_rx_gen: if (r > 0) generate -- use previous entry as base and current entry as bound region.i_cmp_ge(r) <= '1' when (unsigned(addr_if_i(XLEN-1 downto pmp_lsb_c)) >= unsigned(xaddr(r-1)(XLEN-1 downto pmp_lsb_c))) and TOR_EN else '0'; region.i_cmp_lt(r) <= '1' when (unsigned(addr_if_i(XLEN-1 downto pmp_lsb_c)) < unsigned(xaddr(r )(XLEN-1 downto pmp_lsb_c))) and TOR_EN else '0'; region.d_cmp_ge(r) <= '1' when (unsigned(addr_ls_i(XLEN-1 downto pmp_lsb_c)) >= unsigned(xaddr(r-1)(XLEN-1 downto pmp_lsb_c))) and TOR_EN else '0'; region.d_cmp_lt(r) <= '1' when (unsigned(addr_ls_i(XLEN-1 downto pmp_lsb_c)) < unsigned(xaddr(r )(XLEN-1 downto pmp_lsb_c))) and TOR_EN else '0'; end generate; -- check region match according to configured mode -- match_gen: process(csr, region) begin case csr.cfg(r)(cfg_ah_c downto cfg_al_c) is when mode_off_c => -- entry disabled region.i_match(r) <= '0'; region.d_match(r) <= '0'; when mode_tor_c => -- top of region if TOR_EN then -- TOR mode implemented? if (r = (NUM_REGIONS-1)) then -- very last entry region.i_match(r) <= region.i_cmp_ge(r) and region.i_cmp_lt(r); region.d_match(r) <= region.d_cmp_ge(r) and region.d_cmp_lt(r); else -- this saves a LOT of comparators region.i_match(r) <= region.i_cmp_ge(r) and (not region.i_cmp_ge(r+1)); region.d_match(r) <= region.d_cmp_ge(r) and (not region.d_cmp_ge(r+1)); end if; else region.i_match(r) <= '0'; region.d_match(r) <= '0'; end if; when others => -- naturally-aligned region if NAP_EN then -- NAPOT/NA4 modes implemented? region.i_match(r) <= region.i_cmp_mm(r); region.d_match(r) <= region.d_cmp_mm(r); else region.i_match(r) <= '0'; region.d_match(r) <= '0'; end if; end case; end process match_gen; -- compute region permissions -- perm_gen: process(csr.cfg, ctrl_i) begin -- execute (X) -- if (ctrl_i.cpu_priv = priv_mode_m_c) then -- M mode: always allow if lock bit region.perm_ex(r) <= csr.cfg(r)(cfg_x_c) or (not csr.cfg(r)(cfg_l_c)); else -- U mode: check actual permission region.perm_ex(r) <= csr.cfg(r)(cfg_x_c); end if; -- read (R) -- if (ctrl_i.lsu_rw = '0') then if (ctrl_i.lsu_priv = priv_mode_m_c) then -- M mode: always allow if lock bit region.perm_rw(r) <= csr.cfg(r)(cfg_r_c) or (not csr.cfg(r)(cfg_l_c)); else -- U mode: check actual permission region.perm_rw(r) <= csr.cfg(r)(cfg_r_c); end if; -- write (W) -- else if (ctrl_i.lsu_priv = priv_mode_m_c) then -- M mode: always allow if lock bit region.perm_rw(r) <= csr.cfg(r)(cfg_w_c) or (not csr.cfg(r)(cfg_l_c)); else -- U mode: check actual permission region.perm_rw(r) <= csr.cfg(r)(cfg_w_c); end if; end if; end process perm_gen; end generate; -- Access Permission Check ---------------------------------------------------------------- -- ------------------------------------------------------------------------------------------- -- check for access fault (using static prioritization) -- fail_ex(NUM_REGIONS) <= '1' when (ctrl_i.cpu_priv /= priv_mode_m_c) else '0'; -- default (if not match): fault if not M-mode fail_rw(NUM_REGIONS) <= '1' when (ctrl_i.lsu_priv /= priv_mode_m_c) else '0'; -- default (if not match): fault if not M-mode -- this is a *structural* description of a prioritization logic implemented as a multiplexer chain -- fault_check_gen: for r in NUM_REGIONS-1 downto 0 generate -- start with lowest priority fail_ex(r) <= not region.perm_ex(r) when (region.i_match(r) = '1') else fail_ex(r+1); fail_rw(r) <= not region.perm_rw(r) when (region.d_match(r) = '1') else fail_rw(r+1); end generate; -- final access check -- access_check: process(rstn_i, clk_i) begin if (rstn_i = '0') then fault_ex_o <= '0'; fault_rw_o <= '0'; elsif rising_edge(clk_i) then fault_ex_o <= (not ctrl_i.cpu_debug) and fail_ex(0); -- ignore PMP rules when in debug mode fault_rw_o <= (not ctrl_i.cpu_debug) and fail_rw(0); end if; end process access_check; end neorv32_cpu_pmp_rtl;