neorv32/rtl/core/neorv32_cpu_pmp.vhd

405 lines
19 KiB
VHDL

-- #################################################################################################
-- # << 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;