From b2f614bf228309be81f8a8c47d579985c65ce123 Mon Sep 17 00:00:00 2001 From: stnolting <22944758+stnolting@users.noreply.github.com> Date: Sat, 1 Apr 2023 08:32:53 +0200 Subject: [PATCH 1/6] [rtl] PMP: add support for NA4 and NAPOT modes --- rtl/core/neorv32_cpu_bus.vhd | 204 ++++++++++++++++--------------- rtl/core/neorv32_cpu_control.vhd | 76 ++++++------ rtl/core/neorv32_package.vhd | 4 +- 3 files changed, 148 insertions(+), 136 deletions(-) diff --git a/rtl/core/neorv32_cpu_bus.vhd b/rtl/core/neorv32_cpu_bus.vhd index 8afcf0c2b..178b49ec2 100644 --- a/rtl/core/neorv32_cpu_bus.vhd +++ b/rtl/core/neorv32_cpu_bus.vhd @@ -92,8 +92,9 @@ architecture neorv32_cpu_bus_rtl of neorv32_cpu_bus is constant pmp_cfg_ah_c : natural := 4; -- mode bit high constant pmp_cfg_l_c : natural := 7; -- locked entry - -- PMP minimal granularity -- - constant pmp_lsb_c : natural := index_size_f(PMP_MIN_GRANULARITY); -- min = 2 + -- PMP helpers -- + constant pmp_lsb_c : natural := index_size_f(PMP_MIN_GRANULARITY); -- min = 2 + constant pmp_zero_c : std_ulogic_vector(XLEN-1 downto pmp_lsb_c) := (others => '0'); -- misc -- signal data_sign : std_ulogic; -- signed load @@ -110,9 +111,12 @@ architecture neorv32_cpu_bus_rtl of neorv32_cpu_bus is signal arbiter : bus_arbiter_t; -- physical memory protection -- + type pmp_mask_t is array (0 to PMP_NUM_REGIONS-1) of std_ulogic_vector(XLEN-1 downto pmp_lsb_c); type pmp_t is record + i_cmp_mm : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); i_cmp_ge : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); i_cmp_lt : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); + d_cmp_mm : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); d_cmp_ge : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); d_cmp_lt : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); i_match : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); @@ -120,11 +124,15 @@ architecture neorv32_cpu_bus_rtl of neorv32_cpu_bus is perm_ex : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); perm_rd : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); perm_wr : std_ulogic_vector(PMP_NUM_REGIONS-1 downto 0); + fail_ex : std_ulogic_vector(PMP_NUM_REGIONS downto 0); + fail_rd : std_ulogic_vector(PMP_NUM_REGIONS downto 0); + fail_wr : std_ulogic_vector(PMP_NUM_REGIONS downto 0); if_fault : std_ulogic; ld_fault : std_ulogic; st_fault : std_ulogic; end record; - signal pmp : pmp_t; + signal pmp_mask : pmp_mask_t; + signal pmp : pmp_t; -- pmp faults -- signal if_pmp_fault : std_ulogic; -- pmp instruction access fault @@ -380,8 +388,7 @@ begin arbiter.pmp_r_err <= '0'; arbiter.pmp_w_err <= '0'; elsif rising_edge(clk_i) then - arbiter.pmp_r_err <= ld_pmp_fault; - arbiter.pmp_w_err <= st_pmp_fault; + -- arbiter -- if (arbiter.pend = '0') then -- idle if (ctrl_i.bus_req = '1') then -- start bus access arbiter.pend <= '1'; @@ -399,6 +406,11 @@ begin arbiter.pend <= '0'; end if; end if; + -- PMP error -- + if (ctrl_i.bus_mo_we = '1') then -- sample PMP errors only once + arbiter.pmp_r_err <= ld_pmp_fault; + arbiter.pmp_w_err <= st_pmp_fault; + end if; end if; end process data_access_arbiter; @@ -421,111 +433,103 @@ begin -- RISC-V Physical Memory Protection (PMP) ------------------------------------------------ -- ------------------------------------------------------------------------------------------- - -- check address -- - pmp_check_address: process(fetch_pc_i, addr_i, pmp_addr_i) - begin - for r in 0 to PMP_NUM_REGIONS-1 loop - if (r = 0) then -- first entry: use ZERO as base and current entry as bound - pmp.i_cmp_ge(r) <= '1'; -- address is always greater than or equal to zero - pmp.i_cmp_lt(r) <= '0'; -- unused - pmp.d_cmp_ge(r) <= '1'; -- address is always greater than or equal to zero - pmp.d_cmp_lt(r) <= '0'; -- unused - else -- use previous entry as base and current entry as bound - pmp.i_cmp_ge(r) <= bool_to_ulogic_f(unsigned(fetch_pc_i(XLEN-1 downto pmp_lsb_c)) >= unsigned(pmp_addr_i(r-1)(XLEN-1 downto pmp_lsb_c))); - pmp.i_cmp_lt(r) <= bool_to_ulogic_f(unsigned(fetch_pc_i(XLEN-1 downto pmp_lsb_c)) < unsigned(pmp_addr_i(r-0)(XLEN-1 downto pmp_lsb_c))); - pmp.d_cmp_ge(r) <= bool_to_ulogic_f(unsigned( addr_i(XLEN-1 downto pmp_lsb_c)) >= unsigned(pmp_addr_i(r-1)(XLEN-1 downto pmp_lsb_c))); - pmp.d_cmp_lt(r) <= bool_to_ulogic_f(unsigned( addr_i(XLEN-1 downto pmp_lsb_c)) < unsigned(pmp_addr_i(r-0)(XLEN-1 downto pmp_lsb_c))); - end if; - end loop; -- r - end process pmp_check_address; - - - -- check mode -- - pmp_check_mode: process(pmp_ctrl_i, pmp) - begin - for r in 0 to PMP_NUM_REGIONS-1 loop - if (pmp_ctrl_i(r)(pmp_cfg_ah_c downto pmp_cfg_al_c) = pmp_mode_tor_c) then -- TOR mode - if (r < (PMP_NUM_REGIONS-1)) then - -- this saves a LOT of comparators -- - pmp.i_match(r) <= pmp.i_cmp_ge(r) and (not pmp.i_cmp_ge(r+1)); - pmp.d_match(r) <= pmp.d_cmp_ge(r) and (not pmp.d_cmp_ge(r+1)); - else -- very last entry - pmp.i_match(r) <= pmp.i_cmp_ge(r) and pmp.i_cmp_lt(r); - pmp.d_match(r) <= pmp.d_cmp_ge(r) and pmp.d_cmp_lt(r); + -- compute address masks for NAPOT modes (iterative!) -- + pmp_masking_gen: + for r in 0 to PMP_NUM_REGIONS-1 generate + pmp_masking: process(rstn_i, clk_i) + begin + if (rstn_i = '0') then + pmp_mask(r) <= (others => '0'); + elsif rising_edge(clk_i) then -- address mask computation has a latency of max 32 cycles + if (pmp_ctrl_i(r)(pmp_cfg_al_c) = '1') then -- NAPOT (or TOR, but that's irrelevant here) + pmp_mask(r)(pmp_lsb_c) <= '0'; + for i in pmp_lsb_c+1 to XLEN-1 loop + pmp_mask(r)(i) <= pmp_mask(r)(i-1) or (not pmp_addr_i(r)(i-1)); -- skip address byte offset + end loop; -- i + else -- NA4 + pmp_mask(r) <= (others => '1'); end if; - else -- entry disabled - pmp.i_match(r) <= '0'; - pmp.d_match(r) <= '0'; end if; - end loop; -- r - end process pmp_check_mode; + end process pmp_masking; + end generate; - -- check permission -- - pmp_check_permission: process(ctrl_i, pmp_ctrl_i) - begin - for r in 0 to PMP_NUM_REGIONS-1 loop + -- check address -- + pmp_check_address: + for r in 0 to PMP_NUM_REGIONS-1 generate + -- NA4 and NAPOT -- + pmp.i_cmp_mm(r) <= '1' when ((fetch_pc_i(XLEN-1 downto pmp_lsb_c) and pmp_mask(r)) = (pmp_addr_i(r)(XLEN-1 downto pmp_lsb_c) and pmp_mask(r))) else '0'; + pmp.d_cmp_mm(r) <= '1' when (( addr_i(XLEN-1 downto pmp_lsb_c) and pmp_mask(r)) = (pmp_addr_i(r)(XLEN-1 downto pmp_lsb_c) and pmp_mask(r))) else '0'; + -- TOR region 0 -- + pmp_check_address_r0: + if (r = 0) generate -- first entry: use ZERO as base and current entry as bound + pmp.i_cmp_ge(r) <= '1'; -- address is always greater than or equal to zero + pmp.i_cmp_lt(r) <= '0'; -- unused + pmp.d_cmp_ge(r) <= '1'; -- address is always greater than or equal to zero + pmp.d_cmp_lt(r) <= '0'; -- unused + end generate; + -- TOR region any -- + pmp_check_address_rany: + if (r > 0) generate -- use previous entry as base and current entry as bound + pmp.i_cmp_ge(r) <= '1' when (unsigned(fetch_pc_i(XLEN-1 downto pmp_lsb_c)) >= unsigned(pmp_addr_i(r-1)(XLEN-1 downto pmp_lsb_c))) else '0'; + pmp.i_cmp_lt(r) <= '1' when (unsigned(fetch_pc_i(XLEN-1 downto pmp_lsb_c)) < unsigned(pmp_addr_i(r )(XLEN-1 downto pmp_lsb_c))) else '0'; + pmp.d_cmp_ge(r) <= '1' when (unsigned( addr_i(XLEN-1 downto pmp_lsb_c)) >= unsigned(pmp_addr_i(r-1)(XLEN-1 downto pmp_lsb_c))) else '0'; + pmp.d_cmp_lt(r) <= '1' when (unsigned( addr_i(XLEN-1 downto pmp_lsb_c)) < unsigned(pmp_addr_i(r )(XLEN-1 downto pmp_lsb_c))) else '0'; + end generate; + end generate; - -- instruction fetch access -- - if (ctrl_i.cpu_priv = priv_mode_m_c) then -- M mode: always allow if lock bit not set, otherwise check permission - pmp.perm_ex(r) <= (not pmp_ctrl_i(r)(pmp_cfg_l_c)) or pmp_ctrl_i(r)(pmp_cfg_x_c); - else -- U mode: always check permission - pmp.perm_ex(r) <= pmp_ctrl_i(r)(pmp_cfg_x_c); - end if; - -- load/store accesses from M mod (can also use U mode's permissions if MSTATUS.MPRV is set) -- - if (ctrl_i.bus_priv = priv_mode_m_c) then -- M mode: always allow if lock bit not set, otherwise check permission - pmp.perm_rd(r) <= (not pmp_ctrl_i(r)(pmp_cfg_l_c)) or pmp_ctrl_i(r)(pmp_cfg_r_c); - pmp.perm_wr(r) <= (not pmp_ctrl_i(r)(pmp_cfg_l_c)) or pmp_ctrl_i(r)(pmp_cfg_w_c); - else -- U mode: always check permission - pmp.perm_rd(r) <= pmp_ctrl_i(r)(pmp_cfg_r_c); - pmp.perm_wr(r) <= pmp_ctrl_i(r)(pmp_cfg_w_c); - end if; + -- check mode -- + pmp_check_mode_gen: + for r in 0 to PMP_NUM_REGIONS-1 generate + pmp_check_mode: process(pmp_ctrl_i, pmp) + begin + case pmp_ctrl_i(r)(pmp_cfg_ah_c downto pmp_cfg_al_c) is + when pmp_mode_off_c => -- entry disabled + pmp.i_match(r) <= '0'; + pmp.d_match(r) <= '0'; + when pmp_mode_tor_c => -- top of region + if (r = (PMP_NUM_REGIONS-1)) then -- very last entry + pmp.i_match(r) <= pmp.i_cmp_ge(r) and pmp.i_cmp_lt(r); + pmp.d_match(r) <= pmp.d_cmp_ge(r) and pmp.d_cmp_lt(r); + else -- this saves a LOT of comparators + pmp.i_match(r) <= pmp.i_cmp_ge(r) and (not pmp.i_cmp_ge(r+1)); + pmp.d_match(r) <= pmp.d_cmp_ge(r) and (not pmp.d_cmp_ge(r+1)); + end if; + when others => -- naturally-aligned region + pmp.i_match(r) <= pmp.i_cmp_mm(r); + pmp.d_match(r) <= pmp.d_cmp_mm(r); + end case; + end process pmp_check_mode; + end generate; - end loop; -- r - end process pmp_check_permission; + + -- check permission -- + -- M mode: always allow if lock bit not set, otherwise check permission + pmp_check_permission: + for r in 0 to PMP_NUM_REGIONS-1 generate + pmp.perm_ex(r) <= pmp_ctrl_i(r)(pmp_cfg_x_c) or (not pmp_ctrl_i(r)(pmp_cfg_l_c)) when (ctrl_i.cpu_priv = priv_mode_m_c) else pmp_ctrl_i(r)(pmp_cfg_x_c); + pmp.perm_rd(r) <= pmp_ctrl_i(r)(pmp_cfg_r_c) or (not pmp_ctrl_i(r)(pmp_cfg_l_c)) when (ctrl_i.bus_priv = priv_mode_m_c) else pmp_ctrl_i(r)(pmp_cfg_r_c); + pmp.perm_wr(r) <= pmp_ctrl_i(r)(pmp_cfg_w_c) or (not pmp_ctrl_i(r)(pmp_cfg_l_c)) when (ctrl_i.bus_priv = priv_mode_m_c) else pmp_ctrl_i(r)(pmp_cfg_w_c); + end generate; -- check for access fault (using static prioritization) -- - pmp_check_fault: process(ctrl_i, pmp) - variable tmp_if_v, tmp_ld_v, tmp_st_v : std_ulogic_vector(PMP_NUM_REGIONS downto 0); - begin - -- > This is a *structural* description of a prioritization logic (a multiplexer chain). - -- > I prefer this style as I do not like using a loop with 'exit' - and I also think this style might be smaller - -- > and faster (could use the carry chain?!) as the synthesizer has less freedom doing what *I* want. ;) - tmp_if_v(PMP_NUM_REGIONS) := bool_to_ulogic_f(ctrl_i.cpu_priv /= priv_mode_m_c); -- default: fault if U mode - tmp_ld_v(PMP_NUM_REGIONS) := bool_to_ulogic_f(ctrl_i.bus_priv /= priv_mode_m_c); -- default: fault if U mode - tmp_st_v(PMP_NUM_REGIONS) := bool_to_ulogic_f(ctrl_i.bus_priv /= priv_mode_m_c); -- default: fault if U mode - - for r in PMP_NUM_REGIONS-1 downto 0 loop -- start with lowest priority - -- instruction fetch access -- - if (pmp.i_match(r) = '1') then -- address matches region r - tmp_if_v(r) := not pmp.perm_ex(r); -- fault if no execute permission - else - tmp_if_v(r) := tmp_if_v(r+1); - end if; - -- data load/store access -- - if (pmp.d_match(r) = '1') then -- address matches region r - tmp_ld_v(r) := not pmp.perm_rd(r); -- fault if no read permission - tmp_st_v(r) := not pmp.perm_wr(r); -- fault if no write permission - else - tmp_ld_v(r) := tmp_ld_v(r+1); - tmp_st_v(r) := tmp_st_v(r+1); - end if; - end loop; -- r - pmp.if_fault <= tmp_if_v(0); - pmp.ld_fault <= tmp_ld_v(0); - pmp.st_fault <= tmp_st_v(0); - - -- > this is the behavioral version of the code above (instruction fetch access) --- pmp.if_fault <= bool_to_ulogic_f(ctrl_i.cpu_priv /= priv_mode_m_c); -- default: fault if U mode --- for r in 0 to PMP_NUM_REGIONS-1 loop --- if (pmp.i_match(r) = '1') then --- pmp.if_fault <= not pmp.perm_ex(r); -- fault if no execute permission --- exit; --- end if; --- end loop; -- r - end process pmp_check_fault; + -- default: fault if not M-mode -- + pmp.fail_ex(PMP_NUM_REGIONS) <= '1' when (ctrl_i.cpu_priv /= priv_mode_m_c) else '0'; + pmp.fail_rd(PMP_NUM_REGIONS) <= '1' when (ctrl_i.bus_priv /= priv_mode_m_c) else '0'; + pmp.fail_wr(PMP_NUM_REGIONS) <= '1' when (ctrl_i.bus_priv /= priv_mode_m_c) else '0'; + -- This is a *structural* description of a prioritization logic implemented as a multiplexer chain. -- + pmp_chech_fault: + for r in PMP_NUM_REGIONS-1 downto 0 generate -- start with lowest priority + pmp.fail_ex(r) <= not pmp.perm_ex(r) when (pmp.i_match(r) = '1') else pmp.fail_ex(r+1); + pmp.fail_rd(r) <= not pmp.perm_rd(r) when (pmp.d_match(r) = '1') else pmp.fail_rd(r+1); + pmp.fail_wr(r) <= not pmp.perm_wr(r) when (pmp.d_match(r) = '1') else pmp.fail_wr(r+1); + end generate; + pmp.if_fault <= pmp.fail_ex(0); + pmp.ld_fault <= pmp.fail_rd(0); + pmp.st_fault <= pmp.fail_wr(0); + -- final PMP access fault signals (ignored when in debug mode) -- if_pmp_fault <= '1' when (pmp.if_fault = '1') and (PMP_NUM_REGIONS > 0) and (ctrl_i.cpu_debug = '0') else '0'; diff --git a/rtl/core/neorv32_cpu_control.vhd b/rtl/core/neorv32_cpu_control.vhd index b61653495..6987389fa 100644 --- a/rtl/core/neorv32_cpu_control.vhd +++ b/rtl/core/neorv32_cpu_control.vhd @@ -313,7 +313,7 @@ architecture neorv32_cpu_control_rtl of neorv32_cpu_control is -- physical memory protection CSRs -- type pmp_cfg_t is array (0 to PMP_NUM_REGIONS-1) of std_ulogic_vector(7 downto 0); - type pmp_addr_t is array (0 to PMP_NUM_REGIONS-1) of std_ulogic_vector(XLEN-3 downto index_size_f(PMP_MIN_GRANULARITY)-2); + type pmp_addr_t is array (0 to PMP_NUM_REGIONS-1) of std_ulogic_vector(XLEN-1 downto 0); type pmp_cfg_rd_t is array (0 to 03) of std_ulogic_vector(XLEN-1 downto 0); type pmp_addr_rd_t is array (0 to 15) of std_ulogic_vector(XLEN-1 downto 0); type pmp_t is record @@ -2005,13 +2005,13 @@ begin -- write enable decoder -- pmp_we: process(csr) begin - pmp.we_cfg <= (others => '0'); - pmp.we_addr <= (others => '0'); -- Configuration registers -- + pmp.we_cfg <= (others => '0'); if (csr.addr(11 downto 2) = csr_class_pmpcfg_c) and (csr.we = '1') then pmp.we_cfg(to_integer(unsigned(csr.addr(1 downto 0)))) <= '1'; end if; -- Address registers -- + pmp.we_addr <= (others => '0'); if (csr.addr(11 downto 4) = csr_class_pmpaddr_c) and (csr.we = '1') then pmp.we_addr(to_integer(unsigned(csr.addr(3 downto 0)))) <= '1'; end if; @@ -2026,43 +2026,55 @@ begin pmp.cfg(i) <= (others => '0'); pmp.addr(i) <= (others => '0'); elsif rising_edge(clk_i) then - -- configuration register -- + -- configuration -- if (pmp.we_cfg(i/4) = '1') and (pmp.cfg(i)(7) = '0') then -- unlocked write access - pmp.cfg(i)(0) <= csr.wdata((i mod 4)*8+0); -- R - read - pmp.cfg(i)(1) <= csr.wdata((i mod 4)*8+1); -- W - write - pmp.cfg(i)(2) <= csr.wdata((i mod 4)*8+2); -- X - execute - pmp.cfg(i)(3) <= csr.wdata((i mod 4)*8+3); -- A_L - mode low [TOR-mode only!] - pmp.cfg(i)(4) <= '0'; -- A_H - mode high [TOR-mode only!] - pmp.cfg(i)(5) <= '0'; -- reserved - pmp.cfg(i)(6) <= '0'; -- reserved - pmp.cfg(i)(7) <= csr.wdata((i mod 4)*8+7); -- L (locked / also enforce in machine-mode) + pmp.cfg(i)(2 downto 0) <= csr.wdata((i mod 4)*8+2 downto (i mod 4)*8+0); -- X (execute), W (write), R (read) + if (PMP_MIN_GRANULARITY > 4) and (csr.wdata((i mod 4)*8+4 downto (i mod 4)*8+3) = pmp_mode_na4_c) then + pmp.cfg(i)(4 downto 3) <= pmp_mode_off_c; -- NA4 not available, fall back to OFF + else + pmp.cfg(i)(4 downto 3) <= csr.wdata((i mod 4)*8+4 downto (i mod 4)*8+3); -- A (mode) + end if; + pmp.cfg(i)(6 downto 5) <= "00"; -- reserved + pmp.cfg(i)(7) <= csr.wdata((i mod 4)*8+7); -- L (locked) end if; - -- address register -- + -- address -- if (pmp.we_addr(i) = '1') and (pmp.cfg(i)(7) = '0') then -- unlocked write access if (i < PMP_NUM_REGIONS-1) then - if (pmp.cfg(i+1)(7) = '0') or (pmp.cfg(i+1)(3) = '0') then -- cfg(i+1) not "LOCKED TOR" [TOR-mode only!] - pmp.addr(i) <= csr.wdata(XLEN-3 downto index_size_f(PMP_MIN_GRANULARITY)-2); + if (pmp.cfg(i+1)(7) = '0') or (pmp.cfg(i+1)(4 downto 3) /= pmp_mode_tor_c) then -- cfg(i+1) not "LOCKED TOR" + pmp.addr(i) <= "00" & csr.wdata(XLEN-3 downto 0); end if; else -- very last entry - pmp.addr(i) <= csr.wdata(XLEN-3 downto index_size_f(PMP_MIN_GRANULARITY)-2); + pmp.addr(i) <= "00" & csr.wdata(XLEN-3 downto 0); end if; end if; end if; end process pmp_reg; end generate; - -- PMP output to bus unit and read-back -- + -- PMP output to bus unit and CSR read-back -- pmp_connect: process(pmp) begin - pmp_addr_o <= (others => (others => '0')); pmp_ctrl_o <= (others => (others => '0')); - pmp_addr_rd <= (others => (others => '0')); + pmp_addr_o <= (others => (others => '0')); pmp_cfg_rd <= (others => (others => '0')); + pmp_addr_rd <= (others => (others => '0')); for i in 0 to PMP_NUM_REGIONS-1 loop - pmp_addr_o(i)(XLEN-1 downto index_size_f(PMP_MIN_GRANULARITY)) <= pmp.addr(i); pmp_ctrl_o(i) <= pmp.cfg(i); - pmp_addr_rd(i)(XLEN-3 downto index_size_f(PMP_MIN_GRANULARITY)-2) <= pmp.addr(i); + pmp_addr_o(i) <= pmp.addr(i) & "00"; -- word aligned address pmp_cfg_rd(i/4)(8*(i mod 4)+7 downto 8*(i mod 4)+0) <= pmp.cfg(i); + pmp_addr_rd(i)(XLEN-1 downto index_size_f(PMP_MIN_GRANULARITY)-2) <= pmp.addr(i)(XLEN-1 downto index_size_f(PMP_MIN_GRANULARITY)-2); + if (PMP_MIN_GRANULARITY = 8) then -- bit [G-1] reads as zero in TOR or OFF mode + if (pmp.cfg(i)(4) = '0') then -- TOR/OFF + pmp_addr_rd(i)(index_size_f(PMP_MIN_GRANULARITY)-1) <= '0'; + end if; + elsif (PMP_MIN_GRANULARITY > 8) then + -- in NAPOT mode, bits [G-2:0] must read as one + pmp_addr_rd(i)(index_size_f(PMP_MIN_GRANULARITY)-2 downto 0) <= (others => '1'); + -- in TOR or OFF mode, bits [G-1:0] must read as zero + if (pmp.cfg(i)(4) = '0') then -- TOR/OFF + pmp_addr_rd(i)(index_size_f(PMP_MIN_GRANULARITY)-1 downto 0) <= (others => '0'); + end if; + end if; end loop; end process pmp_connect; @@ -2093,7 +2105,7 @@ begin end process hpmevent_reg; end generate; - -- HPM event read-back -- + -- HPM event CSR read-back -- hpm_event_connect: process(hpmevent) begin hpmevent_rd <= (others => (others => '0')); @@ -2398,17 +2410,13 @@ begin begin cnt.we_lo <= (others => '0'); cnt.we_hi <= (others => '0'); - -- NOTE: no need to check bits 6:5 of the CSR address here as they're always zero (checked by illegal CSR logic) + -- no need to check bits 6:5 of the address as they're always zero (checked by illegal CSR logic) if (csr.we = '1') and (csr.addr(11 downto 8) = csr_class_mcnt_c) then - for i in 0 to 31 loop - if (csr.addr(4 downto 0) = std_ulogic_vector(to_unsigned(i, 5))) then - if (csr.addr(7) = '0') then -- low word - cnt.we_lo(i) <= '1'; - else -- high word - cnt.we_hi(i) <= '1'; - end if; - end if; - end loop; + if (csr.addr(7) = '0') then -- low word + cnt.we_lo(to_integer(unsigned(csr.addr(4 downto 0)))) <= '1'; + else -- high word + cnt.we_hi(to_integer(unsigned(csr.addr(4 downto 0)))) <= '1'; + end if; end if; end process cnt_we; @@ -2439,12 +2447,12 @@ begin end if; end process cnt_regs; - -- increment -- + -- low-word increment -- cnt.nxt(i) <= std_ulogic_vector(unsigned('0' & cnt.lo(i)) + 1) when (cnt.inc(i) = '1') else std_ulogic_vector(unsigned('0' & cnt.lo(i)) + 0); end generate; - -- counter read-back -- + -- counter CSR read-back -- cnt_connect: process(cnt) begin cnt_lo_rd <= (others => (others => '0')); diff --git a/rtl/core/neorv32_package.vhd b/rtl/core/neorv32_package.vhd index e24139041..d7f8940ce 100644 --- a/rtl/core/neorv32_package.vhd +++ b/rtl/core/neorv32_package.vhd @@ -60,7 +60,7 @@ package neorv32_package is -- Architecture Constants (do not modify!) ------------------------------------------------ -- ------------------------------------------------------------------------------------------- - constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01080300"; -- hardware version + constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01080301"; -- hardware version constant archid_c : natural := 19; -- official RISC-V architecture ID -- Check if we're inside the Matrix ------------------------------------------------------- @@ -2615,7 +2615,7 @@ package body neorv32_package is impure function mem32_init_f(init : mem32_t; depth : natural) return mem32_t is variable mem_v : mem32_t(0 to depth-1); begin - mem_v := (others => (others => '0')); -- make sure remaining memory entries are set to zero + mem_v := (others => (others => '0')); -- [IMPORTANT] make sure remaining memory entries are set to zero if (init'length > depth) then return mem_v; end if; From fec759647a80fbc82111a97d7037f530bf918b1d Mon Sep 17 00:00:00 2001 From: stnolting <22944758+stnolting@users.noreply.github.com> Date: Sat, 1 Apr 2023 08:36:54 +0200 Subject: [PATCH 2/6] [CHANGELOG] add v1.8.3.1 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78dc7a8fb..a396ab978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ mimpid = 0x01040312 => Version 01.04.03.12 => v1.4.3.12 | Date (*dd.mm.yyyy*) | Version | Comment | |:-------------------:|:-------:|:--------| +| 01.04.2023 | 1.8.3.1 | :sparkles: add full `NA4` and `NAPOT` support to the (now) RISC-V-compatible **physical memory protection (PMP)**; [#566](https://github.com/stnolting/neorv32/pull/566) | | 31.03.2023 | [**:rocket:1.8.3**](https://github.com/stnolting/neorv32/releases/tag/v1.8.3) | **New release** | | 29.03.2023 | 1.8.2.9 | :warning: remove `CPU_EXTENSION_RISCV_Zicsr` generic - `Zicsr` ISA extension is always enabled; optimize bus switch; VHDL code cleanups; [#562](https://github.com/stnolting/neorv32/pull/562) | | 25.03.2023 | 1.8.2.8 | :test_tube: add configurable data cache (**dCACHE**); [#560](https://github.com/stnolting/neorv32/pull/560) | From 4dbeb717e80ff42d6eb5921a225b9071603be0dc Mon Sep 17 00:00:00 2001 From: stnolting <22944758+stnolting@users.noreply.github.com> Date: Sat, 1 Apr 2023 08:37:20 +0200 Subject: [PATCH 3/6] [docs] update PMP sections, minor cleanups --- docs/datasheet/cpu.adoc | 13 +-- docs/datasheet/cpu_csr.adoc | 169 ++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 95 deletions(-) diff --git a/docs/datasheet/cpu.adoc b/docs/datasheet/cpu.adoc index c544510aa..8f0ccead6 100644 --- a/docs/datasheet/cpu.adoc +++ b/docs/datasheet/cpu.adoc @@ -35,11 +35,6 @@ instruction exception (-> <<_full_virtualization>>). **Incompatibility Issues and Limitations** -.Physical Memory Protection (PMP) -[WARNING] -The RISC-V-compatible NEORV32 <<_machine_physical_memory_protection_csrs>> only implements the **TOR** -(top of region) mode and only up to 16 PMP regions. - .No Hardware Support of Misaligned Memory Accesses [IMPORTANT] The CPU does not support resolving unaligned memory access by the hardware (this is not a @@ -561,14 +556,14 @@ to the RISC-V Privileged Architecture Specifications. In general, the PMP can ** which by default has none, and can **revoke permissions from M-mode**, which by default has full permissions. The PMP is configured via the <<_machine_physical_memory_protection_csrs>>. -[IMPORTANT] -The NEORV32 PMP only supports **TOR** (top of region) mode, which basically is a "base-and-bound" concept, and only -up to 16 PMP regions. - .PMP Rules when in Debug Mode [NOTE] When in debug-mode all PMP rules are ignored making the debugger have maximum access rights. +[IMPORTANT] +Instruction fetches are also triggered when denied by a certain PMP rule. However, the fetched instruction(s) +will not be executed and will not change CPU core state to preserve memory access protection. + ==== `Sdext` ISA Extension diff --git a/docs/datasheet/cpu_csr.adoc b/docs/datasheet/cpu_csr.adoc index 30e5874fa..0476e1b27 100644 --- a/docs/datasheet/cpu_csr.adoc +++ b/docs/datasheet/cpu_csr.adoc @@ -116,12 +116,12 @@ Any illegal read access to a CSR will return zero in the operation's destination [options="header",grid="rows"] |======================= | Bit | R/W | Function -| 31:5 | r/- | _reserved_, writes are ignored; reads always return 0 -| 4 | r/w | **NV**: invalid operation -| 3 | r/w | **DZ**: division by zero -| 2 | r/w | **OF**: overflow -| 1 | r/w | **UF**: underflow | 0 | r/w | **NX**: inexact +| 1 | r/w | **UF**: underflow +| 2 | r/w | **OF**: overflow +| 3 | r/w | **DZ**: division by zero +| 4 | r/w | **NV**: invalid operation +| 31:5 | r/- | _reserved_, writes are ignored; reads always return 0 |======================= {empty} + @@ -143,8 +143,8 @@ Any illegal read access to a CSR will return zero in the operation's destination [options="header",grid="rows"] |======================= | Bit | R/W | Function -| 31:3 | r/- | _reserved_, writes are ignored; reads always return 0 | 2:0 | r/w | Rounding mode +| 31:3 | r/- | _reserved_, writes are ignored; reads always return 0 |======================= @@ -167,9 +167,9 @@ Any illegal read access to a CSR will return zero in the operation's destination [options="header",grid="rows"] |======================= | Bit | R/W | Function -| 31:6 | r/- | _reserved_, writes are ignored; reads always return 0 -| 7:5 | r/w | Rounding mode (<<_frm>>) | 4:0 | r/w | Accrued exception flags (<<_fflags>>) +| 7:5 | r/w | Rounding mode (<<_frm>>) +| 31:6 | r/- | _reserved_, writes are ignored; reads always return 0 |======================= @@ -230,11 +230,11 @@ Any illegal read access to a CSR will return zero in the operation's destination [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Function -| 21 | _CSR_MSTATUS_TW_ | r/w | **TW**: Trap on execution of `wfi` instruction in user mode when set; hardwired to zero if user-mode not implemented -| 17 | _CSR_MSTATUS_MPRV_ | r/w | **MPRV**: Effective privilege level for load/stores in machine mode; use `MPP`'s as effective privilege level when set; hardwired to zero if user-mode not implemented -| 12:11 | _CSR_MSTATUS_MPP_H_ : _CSR_MSTATUS_MPP_L_ | r/w | **MPP**: Previous machine privilege level, 11 = machine (M) level, 00 = user (U) level -| 7 | _CSR_MSTATUS_MPIE_ | r/w | **MPIE**: Previous machine global interrupt enable flag state -| 3 | _CSR_MSTATUS_MIE_ | r/w | **MIE**: Machine global interrupt enable flag +| 3 | `CSR_MSTATUS_MIE` | r/w | **MIE**: Machine global interrupt enable flag +| 7 | `CSR_MSTATUS_MPIE` | r/w | **MPIE**: Previous machine global interrupt enable flag state +| 12:11 | `CSR_MSTATUS_MPP_H` : `CSR_MSTATUS_MPP_L` | r/w | **MPP**: Previous machine privilege level, 11 = machine (M) level, 00 = user (U) level +| 17 | `CSR_MSTATUS_MPRV` | r/w | **MPRV**: Effective privilege level for load/stores in machine mode; use `MPP`'s as effective privilege level when set; hardwired to zero if user-mode not implemented +| 21 | `CSR_MSTATUS_TW` | r/w | **TW**: Trap on execution of `wfi` instruction in user mode when set; hardwired to zero if user-mode not implemented |======================= [NOTE] @@ -267,13 +267,13 @@ will _not_ cause an illegal instruction exception. [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Function -| 31:30 | _CSR_MISA_MXL_HI_EXT_ : _CSR_MISA_MXL_LO_EXT_ | r/- | **MXL**: 32-bit architecture indicator (always _01_) -| 23 | _CSR_MISA_X_EXT_ | r/- | **X**: extension bit is always set to indicate custom non-standard extensions -| 20 | _CSR_MISA_U_EXT_ | r/- | **U**: CPU extension (user mode) available, set when <<_u_isa_extension>> enabled -| 12 | _CSR_MISA_M_EXT_ | r/- | **M**: CPU extension (mul/div) available, set when <<_m_isa_extension>> enabled -| 8 | _CSR_MISA_I_EXT_ | r/- | **I**: CPU base ISA, cleared when <<_e_isa_extension>> enabled -| 4 | _CSR_MISA_E_EXT_ | r/- | **E**: CPU extension (embedded) available, set when <<_e_isa_extension>> enabled -| 2 | _CSR_MISA_C_EXT_ | r/- | **C**: CPU extension (compressed instruction) available, set when <<_c_isa_extension>> enabled +| 2 | `CSR_MISA_C_EXT` | r/- | **C**: CPU extension (compressed instruction) available, set when <<_c_isa_extension>> enabled +| 4 | `CSR_MISA_E_EXT` | r/- | **E**: CPU extension (embedded) available, set when <<_e_isa_extension>> enabled +| 8 | `CSR_MISA_I_EXT` | r/- | **I**: CPU base ISA, cleared when <<_e_isa_extension>> enabled +| 12 | `CSR_MISA_M_EXT` | r/- | **M**: CPU extension (mul/div) available, set when <<_m_isa_extension>> enabled +| 20 | `CSR_MISA_U_EXT` | r/- | **U**: CPU extension (user mode) available, set when <<_u_isa_extension>> enabled +| 23 | `CSR_MISA_X_EXT` | r/- | **X**: extension bit is always set to indicate custom non-standard extensions +| 31:30 | `CSR_MISA_MXL_HI_EXT` : `CSR_MISA_MXL_LO_EXT` | r/- | **MXL**: 32-bit architecture indicator (always `01`) |======================= [TIP] @@ -300,10 +300,10 @@ Machine-mode software can discover available `Z*` _sub-extensions_ (like `Zicsr` [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Function -| 31:16 | _CSR_MIE_FIRQ15E_ : _CSR_MIE_FIRQ0E_ | r/w | Fast interrupt channel 15..0 enable -| 11 | _CSR_MIE_MEIE_ | r/w | **MEIE**: Machine _external_ interrupt enable -| 7 | _CSR_MIE_MTIE_ | r/w | **MTIE**: Machine _timer_ interrupt enable (from <<_machine_system_timer_mtime>>) -| 3 | _CSR_MIE_MSIE_ | r/w | **MSIE**: Machine _software_ interrupt enable +| 3 | `CSR_MIE_MSIE` | r/w | **MSIE**: Machine _software_ interrupt enable +| 7 | `CSR_MIE_MTIE` | r/w | **MTIE**: Machine _timer_ interrupt enable (from <<_machine_system_timer_mtime>>) +| 11 | `CSR_MIE_MEIE` | r/w | **MEIE**: Machine _external_ interrupt enable +| 31:16 | `CSR_MIE_FIRQ15E` : `CSR_MIE_FIRQ0E` | r/w | Fast interrupt channel 15..0 enable |======================= @@ -352,10 +352,10 @@ This CSR is also available if U mode is disabled, but the register is hardwired [options="header",grid="rows"] |======================= | Bit | R/W | Function -| 31:3 | r/w | **HPM** user-level code is allowed to read <<_hpmcounterh>> CSRs when set -| 2 | r/w | **IR** User-level code is allowed to read <<_instreth>> CSRs when set -| 1 | r/w | **TM** User-level code is allowed to read <<_timeh>> CSRs when set | 0 | r/w | **CY** User-level code is allowed to read <<_cycleh>> CSRs when set +| 1 | r/w | **TM** User-level code is allowed to read <<_timeh>> CSRs when set +| 2 | r/w | **IR** User-level code is allowed to read <<_instreth>> CSRs when set +| 31:3 | r/w | **HPM** user-level code is allowed to read <<_hpmcounterh>> CSRs when set |======================= @@ -429,9 +429,9 @@ an instruction is triggered / an exception is raised. See section <<_traps_excep [options="header",grid="rows"] |======================= | Bit | R/W | Function -| 31 | r/w | **Interrupt**: `1` if the trap is caused by an interrupt (`0` if the trap is caused by an exception) -| 30:5 | r/- | _Reserved_, read as zero | 4:0 | r/w | **Exception code**: see <<_neorv32_trap_listing>> +| 30:5 | r/- | _Reserved_, read as zero +| 31 | r/w | **Interrupt**: `1` if the trap is caused by an interrupt (`0` if the trap is caused by an exception) |======================= @@ -482,10 +482,10 @@ specific for the interrupt-causing modules. the according interrupt-generating d [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Function -| 31:16 | _CSR_MIP_FIRQ15P_ : _CSR_MIP_FIRQ0P_ | r/c | **FIRQxP**: Fast interrupt channel 15..0 pending; has to be cleared manually by writing zero -| 11 | _CSR_MIP_MEIP_ | r/- | **MEIP**: Machine _external_ interrupt pending; _cleared by platform-defined mechanism_ -| 7 | _CSR_MIP_MTIP_ | r/- | **MTIP**: Machine _timer_ interrupt pending; _cleared by platform-defined mechanism_ -| 3 | _CSR_MIP_MSIP_ | r/- | **MSIP**: Machine _software_ interrupt pending; _cleared by platform-defined mechanism_ +| 3 | `CSR_MIP_MSIP` | r/- | **MSIP**: Machine _software_ interrupt pending; _cleared by platform-defined mechanism_ +| 7 | `CSR_MIP_MTIP` | r/- | **MTIP**: Machine _timer_ interrupt pending; _cleared by platform-defined mechanism_ +| 11 | `CSR_MIP_MEIP` | r/- | **MEIP**: Machine _external_ interrupt pending; _cleared by platform-defined mechanism_ +| 31:16 | `CSR_MIP_FIRQ15P` : `CSR_MIP_FIRQ0P` | r/c | **FIRQxP**: Fast interrupt channel 15..0 pending; has to be cleared manually by writing zero |======================= .FIRQ Channel Mapping @@ -499,16 +499,12 @@ interrupt-triggering processor module. :sectnums: ==== Machine Physical Memory Protection CSRs -The available physical memory protection logic is configured via the `PMP_NUM_REGIONS` and -`PMP_MIN_GRANULARITY` top entity generics. `PMP_NUM_REGIONS` defines the number of implemented -protection regions and thus, the implementation of the available _PMP entries_. -See section <<_pmp_isa_extension>> for more information. - -If trying to access an PMP-related CSR beyond `PMP_NUM_REGIONS` **no illegal instruction -exception** is triggered. The according CSRs are read-only (writes are ignored) and always return zero. -However, any access beyond `pmpcfg3` or `pmpaddr15`, which are the last physically implemented registers if -`PMP_NUM_REGIONS` == 16, will raise an illegal instruction exception as these CSRs are not implemented at all. - +The physical memory protection system is configured via the `PMP_NUM_REGIONS` and `PMP_MIN_GRANULARITY` top entity +generics. `PMP_NUM_REGIONS` defines the total number of implemented regions. If trying to access a PMP-related CSR +beyond `PMP_NUM_REGIONS` **no illegal instruction exception** is triggered. The according CSRs are read-only (writes +are ignored) and always return zero. However, any access beyond `pmpcfg3` or `pmpaddr15`, which are the last physically +implemented registers if `PMP_NUM_REGIONS` == 16, will raise an illegal instruction exception as these CSRs are not +implemented at all. See section <<_pmp_isa_extension>> for more information. [discrete] ===== **`pmpcfg`** @@ -528,20 +524,14 @@ However, any access beyond `pmpcfg3` or `pmpaddr15`, which are the last physical [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Function -| 7 | _PMPCFG_L_ | r/w | **L**: Lock bit, prevents further write accesses, also enforces access rights in machine-mode, can only be cleared by CPU reset +| 7 | `PMPCFG_L` | r/w | **L**: Lock bit, prevents further write accesses, also enforces access rights in machine-mode, can only be cleared by CPU reset | 6:5 | - | r/- | _reserved_, read as zero -| 4 | _PMPCFG_A_MSB_ | r/- .2+<| **A**: Mode configuration; only **OFF** (`00`) and **TOR** (`01`) modes are supported, any other value will map back to OFF/TOR -as the MSB is hardwired to zero -| 3 | _PMPCFG_A_LSB_ | r/w -| 2 | _PMPCFG_X_ | r/w | **X**: Execute permission -| 1 | _PMPCFG_W_ | r/w | **W**: Write permission -| 0 | _PMPCFG_R_ | r/w | **R**: Read permission +| 4:3 | `PMPCFG_A_MSB` : `PMPCFG_A_LSB` | r/w | **A**: Mode configuration (`00` = OFF, `01` = TOR, `10` = NA4, `11` = NAPOT) +| 2 | `PMPCFG_X` | r/w | **X**: Execute permission +| 1 | `PMPCFG_W` | r/w | **W**: Write permission +| 0 | `PMPCFG_R` | r/w | **R**: Read permission |======================= -[WARNING] -Setting the lock bit `L` and setting TOR mode in `pmpcfg(i)` will also lock write access to `pmpaddr(i-1)`. -See the RISC-V specs. for more information. - {empty} + [discrete] @@ -559,6 +549,12 @@ The `pmpaddr*` CSRs are used to configure the region's address boundaries. | Description | Region address configuration. The two MSBs of each CSR are hardwired to zero (= bits 33:32 of the physical address). |======================= +.Address Register Update Latency +[IMPORTANT] +After writing a `pmpaddr` CSR the hardware requires up to 32 clock cycles to compute the according +address masks. Make sure to wait for this time before completing the PMP region configuration +(only relevant for `NA4` and `NAPOT` modes). + <<< // #################################################################################################################### @@ -691,22 +687,22 @@ cycle even if more than one trigger event is observed. [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Event -| 31:15 | - | r/- | _reserved_, writes are ignored, read always return zero -| 14 | _HPMCNT_EVENT_ILLEGAL_ | r/w | illegal instruction exception -| 13 | _HPMCNT_EVENT_TRAP_ | r/w | entered trap (synchronous exception or interrupt) -| 12 | _HPMCNT_EVENT_TBRANCH_ | r/w | _taken_ conditional branch -| 11 | _HPMCNT_EVENT_BRANCH_ | r/w | conditional branch (_taken_ or _not taken_) -| 10 | _HPMCNT_EVENT_JUMP_ | r/w | unconditional jump -| 9 | _HPMCNT_EVENT_WAIT_LS_ | r/w | load/store memory wait cycle: if more than 1 cycle memory latency or high bus traffic -| 8 | _HPMCNT_EVENT_STORE_ | r/w | memory data store operation -| 7 | _HPMCNT_EVENT_LOAD_ | r/w | memory data load operation -| 6 | _HPMCNT_EVENT_WAIT_MC_ | r/w | multi-cycle ALU operation wait cycle (like iterative shift operation) -| 5 | _HPMCNT_EVENT_WAIT_II_ | r/w | instruction issue pipeline wait cycle: if more than 1 cycle latency, pipelines flush (like taken branches) / cache miss or high bus traffic -| 4 | _HPMCNT_EVENT_WAIT_IF_ | r/w | instruction fetch memory wait cycle: if more than 1 cycle memory latency, cache miss or high bus traffic -| 3 | _HPMCNT_EVENT_CIR_ | r/w | retired compressed instruction -| 2 | _HPMCNT_EVENT_IR_ | r/w | retired instruction (compressed or uncompressed) +| 0 | `HPMCNT_EVENT_CY` | r/w | active clock cycle (CPU not in sleep mode) | 1 | - | r/- | _not implemented, always read as zero_ -| 0 | _HPMCNT_EVENT_CY_ | r/w | active clock cycle (CPU not in sleep mode) +| 2 | `HPMCNT_EVENT_IR` | r/w | retired instruction (compressed or uncompressed) +| 3 | `HPMCNT_EVENT_CIR` | r/w | retired compressed instruction +| 4 | `HPMCNT_EVENT_WAIT_IF` | r/w | instruction fetch memory wait cycle: if more than 1 cycle memory latency, cache miss or high bus traffic +| 5 | `HPMCNT_EVENT_WAIT_II` | r/w | instruction issue pipeline wait cycle: if more than 1 cycle latency, pipelines flush (like taken branches) / cache miss or high bus traffic +| 6 | `HPMCNT_EVENT_WAIT_MC` | r/w | multi-cycle ALU operation wait cycle (like iterative shift operation) +| 7 | `HPMCNT_EVENT_LOAD` | r/w | memory data load operation +| 8 | `HPMCNT_EVENT_STORE` | r/w | memory data store operation +| 9 | `HPMCNT_EVENT_WAIT_LS` | r/w | load/store memory wait cycle: if more than 1 cycle memory latency or high bus traffic +| 10 | `HPMCNT_EVENT_JUMP` | r/w | unconditional jump +| 11 | `HPMCNT_EVENT_BRANCH` | r/w | conditional branch (_taken_ or _not taken_) +| 12 | `HPMCNT_EVENT_TBRANCH` | r/w | _taken_ conditional branch +| 13 | `HPMCNT_EVENT_TRAP` | r/w | entered trap (synchronous exception or interrupt) +| 14 | `HPMCNT_EVENT_ILLEGAL` | r/w | illegal instruction exception +| 31:15 | - | r/- | _reserved_, writes are ignored, read as zero |======================= @@ -769,9 +765,10 @@ counter CSRs are read-only. Any write access will raise an illegal instruction e [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Event -| 3:31 | _CSR_MCOUNTINHIBIT_HPM3_ : _CSR_MCOUNTINHIBIT_HPM31_ | r/w | **HPMx**: Set to `1` to halt `[m]hpmcount*[h]`; hardwired to zero if `Zihpm` ISA extension is disabled -| 2 | _CSR_MCOUNTINHIBIT_CY_ | r/w | **CY**: Set to `1` to halt `[m]cycle[h]`; hardwired to zero if `Zicntr` ISA extension is disabled -| 0 | _CSR_MCOUNTINHIBIT_IR_ | r/w | **IR**: Set to `1` to halt `[m]instret[h]`; hardwired to zero if `Zicntr` ISA extension is disabled +| 0 | `CSR_MCOUNTINHIBIT_IR` | r/w | **IR**: Set to `1` to halt `[m]instret[h]`; hardwired to zero if `Zicntr` ISA extension is disabled +| 1 | - | r/- | _reserved_, read as zero +| 2 | `CSR_MCOUNTINHIBIT_CY` | r/w | **CY**: Set to `1` to halt `[m]cycle[h]`; hardwired to zero if `Zicntr` ISA extension is disabled +| 3:31 | `CSR_MCOUNTINHIBIT_HPM3` : `CSR_MCOUNTINHIBIT_HPM31` | r/w | **HPMx**: Set to `1` to halt `[m]hpmcount*[h]`; hardwired to zero if `Zihpm` ISA extension is disabled |======================= @@ -887,21 +884,21 @@ discover ISA sub-extensions and CPU configuration options [options="header",grid="rows"] |======================= | Bit | Name [C] | R/W | Function -| 0 | _CSR_MXISA_ZICSR_ | r/- | <<_zicsr_isa_extension>> available -| 1 | _CSR_MXISA_ZIFENCEI_ | r/- | <<_zifencei_isa_extension>> available -| 2 | _CSR_MXISA_ZMMUL_ | r/- | <<_zmmul_isa_extension>> available -| 3 | _CSR_MXISA_ZXCFU_ | r/- | <<_zxcfu_isa_extension>> available -| 4 | _CSR_MXISA_ZICOND_ | r/- | <<_zicond_isa_extension>> available -| 5 | _CSR_MXISA_ZFINX_ | r/- | <<_zfinx_isa_extension>> available +| 0 | `CSR_MXISA_ZICSR` | r/- | <<_zicsr_isa_extension>> available +| 1 | `CSR_MXISA_ZIFENCEI` | r/- | <<_zifencei_isa_extension>> available +| 2 | `CSR_MXISA_ZMMUL` | r/- | <<_zmmul_isa_extension>> available +| 3 | `CSR_MXISA_ZXCFU` | r/- | <<_zxcfu_isa_extension>> available +| 4 | `CSR_MXISA_ZICOND` | r/- | <<_zicond_isa_extension>> available +| 5 | `CSR_MXISA_ZFINX` | r/- | <<_zfinx_isa_extension>> available | 6 | - | r/- | _reserved_, read as zero -| 7 | _CSR_MXISA_ZICNTR_ | r/- | <<_zicntr_isa_extension>> available -| 8 | _CSR_MXISA_PMP_ | r/- | <<_pmp_isa_extension>> available -| 9 | _CSR_MXISA_ZIHPM_ | r/- | <<_zihpm_isa_extension>> available -| 10 | _CSR_MXISA_SDEXT_ | r/- | <<_sdext_isa_extension>> available -| 11 | _CSR_MXISA_SDTRIG_ | r/- | <<_sdtrig_isa_extension>> available +| 7 | `CSR_MXISA_ZICNTR` | r/- | <<_zicntr_isa_extension>> available +| 8 | `CSR_MXISA_PMP` | r/- | <<_pmp_isa_extension>> available +| 9 | `CSR_MXISA_ZIHPM` | r/- | <<_zihpm_isa_extension>> available +| 10 | `CSR_MXISA_SDEXT` | r/- | <<_sdext_isa_extension>> available +| 11 | `CSR_MXISA_SDTRIG` | r/- | <<_sdtrig_isa_extension>> available | 19:12 | - | r/- | _reserved_, read as zero -| 20 | _CSR_MXISA_IS_SIM_ | r/- | set if CPU is being **simulated** (⚠️ not guaranteed) +| 20 | `CSR_MXISA_IS_SIM` | r/- | set if CPU is being **simulated** (⚠️ not guaranteed) | 31:21 | - | r/- | _reserved_, read as zero -| 30 | _CSR_MXISA_FASTMUL_ | r/- | fast multiplication available when set (`FAST_MUL_EN`) -| 31 | _CSR_MXISA_FASTSHIFT_ | r/- | fast shifts available when set (`FAST_SHIFT_EN`) +| 30 | `CSR_MXISA_FASTMUL` | r/- | fast multiplication available when set (`FAST_MUL_EN`) +| 31 | `CSR_MXISA_FASTSHIFT` | r/- | fast shifts available when set (`FAST_SHIFT_EN`) |======================= From 59cdcce324c6d5e6aeaaeeab9656551fe2f554aa Mon Sep 17 00:00:00 2001 From: stnolting <22944758+stnolting@users.noreply.github.com> Date: Sat, 1 Apr 2023 13:21:01 +0200 Subject: [PATCH 4/6] :warning: [sw/lib] update PMP helper functions --- sw/lib/include/neorv32_cpu.h | 2 +- sw/lib/source/neorv32_cpu.c | 158 +++++++++++------------------------ 2 files changed, 52 insertions(+), 108 deletions(-) diff --git a/sw/lib/include/neorv32_cpu.h b/sw/lib/include/neorv32_cpu.h index 80f4976ef..bcd486c70 100644 --- a/sw/lib/include/neorv32_cpu.h +++ b/sw/lib/include/neorv32_cpu.h @@ -56,7 +56,7 @@ void neorv32_cpu_delay_ms(uint32_t time_ms); uint32_t neorv32_cpu_get_clk_from_prsc(int prsc); uint32_t neorv32_cpu_pmp_get_num_regions(void); uint32_t neorv32_cpu_pmp_get_granularity(void); -int neorv32_cpu_pmp_configure_region(uint32_t index, uint32_t base, uint8_t config); +int neorv32_cpu_pmp_configure_region(int index, uint32_t addr, uint8_t config); uint32_t neorv32_cpu_hpm_get_num_counters(void); uint32_t neorv32_cpu_hpm_get_size(void); void neorv32_cpu_goto_user_mode(void); diff --git a/sw/lib/source/neorv32_cpu.c b/sw/lib/source/neorv32_cpu.c index 83c2bc280..2cd03bf47 100644 --- a/sw/lib/source/neorv32_cpu.c +++ b/sw/lib/source/neorv32_cpu.c @@ -62,13 +62,6 @@ #endif -/**********************************************************************//** - * >Private< helper functions. - **************************************************************************/ -static uint32_t __neorv32_cpu_pmp_cfg_read(uint32_t index); -static void __neorv32_cpu_pmp_cfg_write(uint32_t index, uint32_t data); - - /**********************************************************************//** * Enable specific interrupt channel. * @note This functions also tries to clear the pending flag of the interrupt. @@ -296,10 +289,10 @@ uint32_t neorv32_cpu_pmp_get_num_regions(void) { // try setting R bit in all PMPCFG CSRs const uint32_t mask = 0x01010101; - __neorv32_cpu_pmp_cfg_write(0, mask); - __neorv32_cpu_pmp_cfg_write(1, mask); - __neorv32_cpu_pmp_cfg_write(2, mask); - __neorv32_cpu_pmp_cfg_write(3, mask); + neorv32_cpu_csr_write(CSR_PMPCFG0, mask); + neorv32_cpu_csr_write(CSR_PMPCFG1, mask); + neorv32_cpu_csr_write(CSR_PMPCFG2, mask); + neorv32_cpu_csr_write(CSR_PMPCFG3, mask); // sum up all written ones (only available PMPCFG* CSRs/entries will return =! 0) union { @@ -308,10 +301,10 @@ uint32_t neorv32_cpu_pmp_get_num_regions(void) { } cnt; cnt.uint32 = 0; - cnt.uint32 += __neorv32_cpu_pmp_cfg_read(0) & mask; - cnt.uint32 += __neorv32_cpu_pmp_cfg_read(1) & mask; - cnt.uint32 += __neorv32_cpu_pmp_cfg_read(2) & mask; - cnt.uint32 += __neorv32_cpu_pmp_cfg_read(3) & mask; + cnt.uint32 += neorv32_cpu_csr_read(CSR_PMPCFG0) & mask; + cnt.uint32 += neorv32_cpu_csr_read(CSR_PMPCFG1) & mask; + cnt.uint32 += neorv32_cpu_csr_read(CSR_PMPCFG2) & mask; + cnt.uint32 += neorv32_cpu_csr_read(CSR_PMPCFG3) & mask; // sum up bytes uint32_t num_regions = 0; @@ -339,7 +332,7 @@ uint32_t neorv32_cpu_pmp_get_granularity(void) { return 0; } - neorv32_cpu_csr_write(CSR_PMPCFG0, neorv32_cpu_csr_read(CSR_PMPCFG0) & 0xffffff00); // disable entry 0 + neorv32_cpu_csr_write(CSR_PMPCFG0, 0); neorv32_cpu_csr_write(CSR_PMPADDR0, -1); // try to set all bits uint32_t tmp = neorv32_cpu_csr_read(CSR_PMPADDR0); @@ -348,7 +341,7 @@ uint32_t neorv32_cpu_pmp_get_granularity(void) { return 0; } - // count trailing zeros + // find first trailing 1 uint32_t i = 2; while(1) { if (tmp & 1) { @@ -365,110 +358,64 @@ uint32_t neorv32_cpu_pmp_get_granularity(void) { /**********************************************************************//** * Physical memory protection (PMP): Configure region. * - * @warning Only TOR mode is supported. - * * @note This function requires the PMP CPU extension. - * @note Only use available PMP regions. Check before via neorv32_cpu_pmp_get_regions(void). + * + * @warning This function expects a WORD address! * * @param[in] index Region number (index, 0..PMP_NUM_REGIONS-1). - * @param[in] base Region base address. + * @param[in] addr Region address (word address!). * @param[in] config Region configuration byte (see #NEORV32_PMPCFG_ATTRIBUTES_enum). * @return Returns 0 on success, !=0 on failure. **************************************************************************/ -int neorv32_cpu_pmp_configure_region(uint32_t index, uint32_t base, uint8_t config) { +int neorv32_cpu_pmp_configure_region(int index, uint32_t addr, uint8_t config) { if ((index > 15) || ((neorv32_cpu_csr_read(CSR_MXISA) & (1<> 2; + // set address switch(index & 0xf) { - case 0: neorv32_cpu_csr_write(CSR_PMPADDR0, base); break; - case 1: neorv32_cpu_csr_write(CSR_PMPADDR1, base); break; - case 2: neorv32_cpu_csr_write(CSR_PMPADDR2, base); break; - case 3: neorv32_cpu_csr_write(CSR_PMPADDR3, base); break; - case 4: neorv32_cpu_csr_write(CSR_PMPADDR4, base); break; - case 5: neorv32_cpu_csr_write(CSR_PMPADDR5, base); break; - case 6: neorv32_cpu_csr_write(CSR_PMPADDR6, base); break; - case 7: neorv32_cpu_csr_write(CSR_PMPADDR7, base); break; - case 8: neorv32_cpu_csr_write(CSR_PMPADDR8, base); break; - case 9: neorv32_cpu_csr_write(CSR_PMPADDR9, base); break; - case 10: neorv32_cpu_csr_write(CSR_PMPADDR10, base); break; - case 11: neorv32_cpu_csr_write(CSR_PMPADDR11, base); break; - case 12: neorv32_cpu_csr_write(CSR_PMPADDR12, base); break; - case 13: neorv32_cpu_csr_write(CSR_PMPADDR13, base); break; - case 14: neorv32_cpu_csr_write(CSR_PMPADDR14, base); break; - case 15: neorv32_cpu_csr_write(CSR_PMPADDR15, base); break; + case 0: neorv32_cpu_csr_write(CSR_PMPADDR0, addr); break; + case 1: neorv32_cpu_csr_write(CSR_PMPADDR1, addr); break; + case 2: neorv32_cpu_csr_write(CSR_PMPADDR2, addr); break; + case 3: neorv32_cpu_csr_write(CSR_PMPADDR3, addr); break; + case 4: neorv32_cpu_csr_write(CSR_PMPADDR4, addr); break; + case 5: neorv32_cpu_csr_write(CSR_PMPADDR5, addr); break; + case 6: neorv32_cpu_csr_write(CSR_PMPADDR6, addr); break; + case 7: neorv32_cpu_csr_write(CSR_PMPADDR7, addr); break; + case 8: neorv32_cpu_csr_write(CSR_PMPADDR8, addr); break; + case 9: neorv32_cpu_csr_write(CSR_PMPADDR9, addr); break; + case 10: neorv32_cpu_csr_write(CSR_PMPADDR10, addr); break; + case 11: neorv32_cpu_csr_write(CSR_PMPADDR11, addr); break; + case 12: neorv32_cpu_csr_write(CSR_PMPADDR12, addr); break; + case 13: neorv32_cpu_csr_write(CSR_PMPADDR13, addr); break; + case 14: neorv32_cpu_csr_write(CSR_PMPADDR14, addr); break; + case 15: neorv32_cpu_csr_write(CSR_PMPADDR15, addr); break; default: break; } - // pmpcfg register index - uint32_t pmpcfg_index = index >> 4; // 4 entries per pmpcfg csr - - // get current configuration - uint32_t tmp = __neorv32_cpu_pmp_cfg_read(pmpcfg_index); - - // clear old configuration - uint32_t config_mask = (((uint32_t)0xFF) << ((index%4)*8)); - tmp = tmp & (~config_mask); - - // set configuration - uint32_t config_new = ((uint32_t)config) << ((index%4)*8); - tmp = tmp | config_new; - __neorv32_cpu_pmp_cfg_write(pmpcfg_index, tmp); - - // flush instruction and data queues and caches - if (neorv32_cpu_csr_read(CSR_MXISA) & (1<> 2) & 3) { + case 0: neorv32_cpu_csr_clr(CSR_PMPCFG0, clr_mask); neorv32_cpu_csr_set(CSR_PMPCFG0, set_mask); break; + case 1: neorv32_cpu_csr_clr(CSR_PMPCFG1, clr_mask); neorv32_cpu_csr_set(CSR_PMPCFG1, set_mask); break; + case 2: neorv32_cpu_csr_clr(CSR_PMPCFG2, clr_mask); neorv32_cpu_csr_set(CSR_PMPCFG2, set_mask); break; + case 3: neorv32_cpu_csr_clr(CSR_PMPCFG3, clr_mask); neorv32_cpu_csr_set(CSR_PMPCFG3, set_mask); break; default: break; } - return tmp; -} - - -/**********************************************************************//** - * Internal helper function: Write PMP configuration register 0..3. - * - * @param[in] index PMP CFG configuration register ID (0..4). - * @param[in] data PMP CFG write data. - **************************************************************************/ -static void __neorv32_cpu_pmp_cfg_write(uint32_t index, uint32_t data) { - - switch (index & 3) { - case 0: neorv32_cpu_csr_write(CSR_PMPCFG0, data); break; - case 1: neorv32_cpu_csr_write(CSR_PMPCFG1, data); break; - case 2: neorv32_cpu_csr_write(CSR_PMPCFG2, data); break; - case 3: neorv32_cpu_csr_write(CSR_PMPCFG3, data); break; - default: break; - } + return 0; } @@ -484,16 +431,15 @@ uint32_t neorv32_cpu_hpm_get_num_counters(void) { return 0; } + // backup uint32_t mcountinhibit_tmp = neorv32_cpu_csr_read(CSR_MCOUNTINHIBIT); // try to set all HPM bits - uint32_t tmp = mcountinhibit_tmp; - tmp |= 0xfffffff8U; - neorv32_cpu_csr_write(CSR_MCOUNTINHIBIT, tmp); + neorv32_cpu_csr_set(CSR_MCOUNTINHIBIT, 0xfffffff8U); // count actually set bits uint32_t cnt = 0; - tmp = neorv32_cpu_csr_read(CSR_MCOUNTINHIBIT) >> 3; // remove IR, TM and CY + uint32_t tmp = neorv32_cpu_csr_read(CSR_MCOUNTINHIBIT) >> 3; // remove IR, TM and CY while (tmp) { cnt++; tmp >>= 1; @@ -523,9 +469,7 @@ uint32_t neorv32_cpu_hpm_get_size(void) { } // inhibit auto-update of HPM counter3 - tmp = neorv32_cpu_csr_read(CSR_MCOUNTINHIBIT); - tmp |= 1 << CSR_MCOUNTINHIBIT_HPM3; - neorv32_cpu_csr_write(CSR_MCOUNTINHIBIT, tmp); + neorv32_cpu_csr_set(CSR_MCOUNTINHIBIT, 1 << CSR_MCOUNTINHIBIT_HPM3); // try to set all 64 counter bits neorv32_cpu_csr_write(CSR_MHPMCOUNTER3, -1); From a8695a78129fc39ac475dc0637e047bdf1788afe Mon Sep 17 00:00:00 2001 From: stnolting <22944758+stnolting@users.noreply.github.com> Date: Sat, 1 Apr 2023 13:21:26 +0200 Subject: [PATCH 5/6] [RTE] minor cleanup --- sw/lib/source/neorv32_rte.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sw/lib/source/neorv32_rte.c b/sw/lib/source/neorv32_rte.c index fdf1b12ed..7e54fd142 100644 --- a/sw/lib/source/neorv32_rte.c +++ b/sw/lib/source/neorv32_rte.c @@ -393,7 +393,7 @@ void neorv32_rte_print_hw_config(void) { neorv32_uart0_printf("\nPhys. Mem. Prot.: "); uint32_t pmp_num_regions = neorv32_cpu_pmp_get_num_regions(); if (pmp_num_regions != 0) { - neorv32_uart0_printf("%u region(s), %u bytes min. granularity", pmp_num_regions, neorv32_cpu_pmp_get_granularity()); + neorv32_uart0_printf("%u region(s), %u bytes granularity", pmp_num_regions, neorv32_cpu_pmp_get_granularity()); } else { neorv32_uart0_printf("none"); From a8e54c84899e00ef994b47b531597c4d5bc7cd65 Mon Sep 17 00:00:00 2001 From: stnolting <22944758+stnolting@users.noreply.github.com> Date: Sat, 1 Apr 2023 13:22:16 +0200 Subject: [PATCH 6/6] update processor check program --- sim/neorv32_tb.vhd | 2 +- sw/example/processor_check/main.c | 114 +++++++++++++++++------------- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/sim/neorv32_tb.vhd b/sim/neorv32_tb.vhd index e99753e26..817cef218 100644 --- a/sim/neorv32_tb.vhd +++ b/sim/neorv32_tb.vhd @@ -180,7 +180,7 @@ begin if ci_mode then -- No need to send the full expectation in one big chunk check_uart(net, uart1_rx_handle, nul & nul); - check_uart(net, uart1_rx_handle, "0/46" & cr & lf); + check_uart(net, uart1_rx_handle, "0/47" & cr & lf); end if; -- Wait until all expected data has been received diff --git a/sw/example/processor_check/main.c b/sw/example/processor_check/main.c index 06d4436ae..5feecb83b 100644 --- a/sw/example/processor_check/main.c +++ b/sw/example/processor_check/main.c @@ -108,7 +108,7 @@ uint32_t xirq_trap_handler_ack = 0; volatile uint32_t store_access_addr[2]; /// Variable to test PMP -volatile uint32_t pmp_access_addr; +volatile uint32_t __attribute__((aligned(4))) pmp_access; /// Number of implemented PMP regions uint32_t pmp_num_regions; @@ -260,23 +260,19 @@ int main() { // check if PMP is already locked tmp_a = neorv32_cpu_csr_read(CSR_PMPCFG0); - if ((tmp_a & ((1 << PMPCFG_L) << 0*8)) || - (tmp_a & ((1 << PMPCFG_L) << 1*8)) || - (tmp_a & ((1 << PMPCFG_L) << 2*8)) || - (tmp_a & ((1 << PMPCFG_L) << 3*8))) { + tmp_b = ((1 << PMPCFG_L) << 0) | ((1 << PMPCFG_L) << 8) | ((1 << PMPCFG_L) << 16); + + if ((tmp_a & tmp_b)) { PRINT_CRITICAL("\nERROR! PMP locked!\n"); return 1; } - if (pmp_num_regions >= 4) { // sufficient regions for tests + if (pmp_num_regions >= 3) { // sufficient regions for tests cnt_test++; - // full access for M & U mode - // use entries 2 & 3 so we can use entries 0 & 1 later on for higher-prioritized configurations - tmp_a = neorv32_cpu_pmp_configure_region(0, 0x00000000, (PMP_OFF << PMPCFG_A_LSB)); - tmp_a = neorv32_cpu_pmp_configure_region(1, 0x00000000, (PMP_OFF << PMPCFG_A_LSB)); - tmp_a = neorv32_cpu_pmp_configure_region(2, 0x00000000, (PMP_OFF << PMPCFG_A_LSB)); - tmp_a += neorv32_cpu_pmp_configure_region(3, 0xFFFFFFFF, (PMP_TOR << PMPCFG_A_LSB) | (1 << PMPCFG_L) | (1 << PMPCFG_R) | (1 << PMPCFG_W) | (1 << PMPCFG_X)); + // execute permission for u-mode + // use entry 2 so we can use entries 0 & 1 later on for higher-prioritized configurations + tmp_a = neorv32_cpu_pmp_configure_region(2, -1, (PMP_NAPOT << PMPCFG_A_LSB) | (1 << PMPCFG_X)); if ((neorv32_cpu_csr_read(CSR_MCAUSE) == mcause_never_c) && (tmp_a == 0)) { test_ok(); @@ -285,10 +281,6 @@ int main() { test_fail(); } } - else if ((pmp_num_regions > 0) && (pmp_num_regions < 4)) { - PRINT_CRITICAL("\nERROR! Insufficient PMP regions!\n"); - return 1; - } // ---------------------------------------------------------- @@ -1619,8 +1611,6 @@ int main() { neorv32_cpu_goto_user_mode(); { // access to misa not allowed for user-level programs - tmp_a = neorv32_cpu_csr_read(CSR_MISA); - asm volatile ("addi %[rd], zero, 234 \n" // this value must not change "csrr %[rd], misa " : [rd] "=r" (tmp_a) : ); // has to fail } @@ -1641,29 +1631,50 @@ int main() { // Test physical memory protection // ---------------------------------------------------------- neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); - PRINT_STANDARD("[%i] PMP:\n", cnt_test); // check if PMP is implemented - if (pmp_num_regions >= 4) { + if (pmp_num_regions >= 3) { + + pmp_access = 0xcafe1234; // initialize + + + // General memory access from user mode - has to + // fail as u-mode has no permissions by default + // --------------------------------------------- + neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); + PRINT_STANDARD("[%i] PMP: U-mode read (denied) ", cnt_test); + cnt_test++; + + // switch to user mode (hart will be back in MACHINE mode when trap handler returns) + neorv32_cpu_goto_user_mode(); + { + asm volatile ("addi %[rd], zero, 0 \n" + "lw %[rd], 0(%[rs])" : [rd] "=r" (tmp_a) : [rs] "r" ((uint32_t)(&pmp_access)) ); + } + + if ((tmp_a == 0) && (neorv32_cpu_csr_read(CSR_MCAUSE) == TRAP_CODE_L_ACCESS)) { + test_ok(); + } + else { + test_fail(); + } + // Create PMP protected region // --------------------------------------------- neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); + PRINT_STANDARD("[%i] PMP: setup ", cnt_test); cnt_test++; - pmp_access_addr = 0xcafe1234; // initialize - tmp_a = (uint32_t)(&pmp_access_addr); // base address of protected region + tmp_a = (uint32_t)(&pmp_access); // base address of protected region // configure new region (with highest priority) - int pmp_res = 0; - // base - PRINT_STANDARD(" Setup PMP(0) OFF [-,-,-,-] @ 0x%x\n", tmp_a); - pmp_res += neorv32_cpu_pmp_configure_region(0, tmp_a, 0); - // bound - PRINT_STANDARD(" Setup PMP(1) TOR [!L,!X,!W,R] @ 0x%x ", tmp_a+4); - pmp_res += neorv32_cpu_pmp_configure_region(1, tmp_a+4, (PMP_TOR << PMPCFG_A_LSB) | (1 << PMPCFG_R)); // read-only - - if ((pmp_res == 0) && (neorv32_cpu_csr_read(CSR_MCAUSE) == mcause_never_c)) { + PRINT_STANDARD("[0] OFF @ 0x%x, ", tmp_a); // base + tmp_b = neorv32_cpu_pmp_configure_region(0, tmp_a >> 2, 0); + PRINT_STANDARD("[1] TOR (!L,!X,!W,R) @ 0x%x ", tmp_a+4); // bound + tmp_b += neorv32_cpu_pmp_configure_region(1, (tmp_a+4) >> 2, (PMP_TOR << PMPCFG_A_LSB) | (1 << PMPCFG_R)); // read-only + + if ((tmp_b == 0) && (neorv32_cpu_csr_read(CSR_MCAUSE) == mcause_never_c)) { test_ok(); } else { @@ -1671,16 +1682,17 @@ int main() { } - // ------ LOAD from U-mode: should succeed ------ + // LOAD from U-mode: should succeed + // --------------------------------------------- neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); - PRINT_STANDARD("[%i] PMP: U-mode read (SUCCEED) ", cnt_test); + PRINT_STANDARD("[%i] PMP: U-mode read (granted) ", cnt_test); cnt_test++; // switch to user mode (hart will be back in MACHINE mode when trap handler returns) neorv32_cpu_goto_user_mode(); { - tmp_b = 0; - tmp_b = neorv32_cpu_load_unsigned_word((uint32_t)(&pmp_access_addr)); + asm volatile ("addi %[rd], zero, 0 \n" + "lw %[rd], 0(%[rs])" : [rd] "=r" (tmp_b) : [rs] "r" ((uint32_t)(&pmp_access)) ); } asm volatile ("ecall"); // switch back to machine mode @@ -1692,19 +1704,20 @@ int main() { } - // ------ STORE from U-mode: should fail ------ + // STORE from U-mode: should fail + // --------------------------------------------- neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); - PRINT_STANDARD("[%i] PMP: U-mode write (FAIL) ", cnt_test); + PRINT_STANDARD("[%i] PMP: U-mode write (denied) ", cnt_test); cnt_test++; // switch to user mode (hart will be back in MACHINE mode when trap handler returns) neorv32_cpu_goto_user_mode(); { - neorv32_cpu_store_unsigned_word((uint32_t)(&pmp_access_addr), 0); // store access -> should fail + neorv32_cpu_store_unsigned_word((uint32_t)(&pmp_access), 0); // store access -> should fail } asm volatile ("ecall"); // switch back to machine mode - if (pmp_access_addr == 0xcafe1234) { + if (pmp_access == 0xcafe1234) { test_ok(); } else { @@ -1712,20 +1725,21 @@ int main() { } - // ------ STORE from M mode using U mode permissions: should fail ------ + // STORE from M mode using U mode permissions: should fail + // --------------------------------------------- neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); - PRINT_STANDARD("[%i] PMP: M-mode (U-mode permissions) write (FAIL) ", cnt_test); + PRINT_STANDARD("[%i] PMP: M-mode (U-mode permissions) write (denied) ", cnt_test); cnt_test++; // make M-mode load/store accesses use U-mode rights neorv32_cpu_csr_set(CSR_MSTATUS, 1 << CSR_MSTATUS_MPRV); // set MPRV: M uses U permissions for load/stores neorv32_cpu_csr_clr(CSR_MSTATUS, 3 << CSR_MSTATUS_MPP_L); // clear MPP: use U as effective privilege mode - neorv32_cpu_store_unsigned_word((uint32_t)(&pmp_access_addr), 0); // store access -> should fail + neorv32_cpu_store_unsigned_word((uint32_t)(&pmp_access), 0); // store access -> should fail neorv32_cpu_csr_clr(CSR_MSTATUS, 1 << CSR_MSTATUS_MPRV); - if ((neorv32_cpu_csr_read(CSR_MCAUSE) == TRAP_CODE_S_ACCESS) && (pmp_access_addr == 0xcafe1234)) { + if ((neorv32_cpu_csr_read(CSR_MCAUSE) == TRAP_CODE_S_ACCESS) && (pmp_access == 0xcafe1234)) { test_ok(); } else { @@ -1733,19 +1747,18 @@ int main() { } - // ------ STORE from M mode with LOCKED: should fail ------ + // STORE from M mode with LOCKED: should fail + // --------------------------------------------- neorv32_cpu_csr_write(CSR_MCAUSE, mcause_never_c); - PRINT_STANDARD("[%i] PMP: M-mode (LOCKED) write (FAIL) ", cnt_test); + PRINT_STANDARD("[%i] PMP: M-mode LOCKED write (denied) ", cnt_test); cnt_test++; // set lock bit - tmp_a = neorv32_cpu_csr_read(CSR_PMPCFG0); - tmp_a |= (1 << PMPCFG_L) << 8; // set lock bit in entry 1 - neorv32_cpu_csr_write(CSR_PMPCFG0, tmp_a); + neorv32_cpu_csr_set(CSR_PMPCFG0, (1 << PMPCFG_L) << 8); // set lock bit in entry 1 - neorv32_cpu_store_unsigned_word((uint32_t)(&pmp_access_addr), 0); // store access -> should fail + neorv32_cpu_store_unsigned_word((uint32_t)(&pmp_access), 0); // store access -> should fail - if ((neorv32_cpu_csr_read(CSR_MCAUSE) == TRAP_CODE_S_ACCESS) && (pmp_access_addr == 0xcafe1234)) { + if ((neorv32_cpu_csr_read(CSR_MCAUSE) == TRAP_CODE_S_ACCESS) && (pmp_access == 0xcafe1234)) { test_ok(); } else { @@ -1754,6 +1767,7 @@ int main() { } else { + PRINT_STANDARD("[%i] PMP tests: ", cnt_test); PRINT_STANDARD("[skipped, n.a.]\n"); }