[RTL] - Add PMP module

- Instantiate generic PMP module
- Wire up I-side and D-side PMP faults
- The output of the PMP check is used to gate external bus requests from the
  I-side and LSU
- Each of those units progresses with their request as-if it was granted
  externally and registers the PMP error
- The error is then sent to the controller at the appropriate time to trigger
  an exception
This commit is contained in:
Tom Roberts 2019-08-21 09:30:23 +01:00 committed by Tom Roberts
parent 6ecf83124a
commit 892ad8a621
15 changed files with 574 additions and 23 deletions

View file

@ -34,6 +34,18 @@ Ibex implements all the Control and Status Registers (CSRs) listed in the follow
+---------+--------------------+--------+-----------------------------------------------+
| 0x344 | ``mip`` | R | Machine Interrupt Pending Register |
+---------+--------------------+--------+-----------------------------------------------+
| 0x3A0 | ``pmpcfg0`` | WARL | PMP Configuration Register |
+---------+--------------------+--------+-----------------------------------------------+
| . . . . |
+---------+--------------------+--------+-----------------------------------------------+
| 0x3A3 | ``pmpcfg3`` | WARL | PMP Configuration Register |
+---------+--------------------+--------+-----------------------------------------------+
| 0x3B0 | ``pmpaddr0`` | WARL | PMP Address Register |
+---------+--------------------+--------+-----------------------------------------------+
| . . . . |
+---------+--------------------+--------+-----------------------------------------------+
| 0x3BF | ``pmpaddr15`` | WARL | PMP Address Register |
+---------+--------------------+--------+-----------------------------------------------+
| 0x7B0 | ``dcsr`` | RW | Debug Control and Status Register |
+---------+--------------------+--------+-----------------------------------------------+
| 0x7B1 | ``dpc`` | RW | Debug PC |
@ -212,6 +224,57 @@ A particular bit in the register reads as one if the corresponding interrupt inp
| 3 | **Machine Software Interrupt Pending (MSIP):** if set, ``irq_software_i`` is pending. |
+-------+---------------------------------------------------------------------------------------+
PMP Configuration Register (pmpcfgx)
----------------------------------------
CSR Address: ``0x3A0 - 0x3A3``
Reset Value: ``0x0000_0000``
``pmpcfgx`` are registers to configure PMP regions. Each register configures 4 PMP regions.
+---------------------------------------+
| 31:24 | 23:16 | 15:8 | 7:0 |
+---------------------------------------+
| pmp3cfg | pmp2cfg | pmp1cfg | pmp0cfg |
+---------------------------------------+
The configuration fields for each region are as follows:
+-------+--------------------------+
| Bit# | Definition |
+-------+--------------------------+
| 7 | Lock |
+-------+--------------------------+
| 6:5 | Reserved (Read as zero) |
+-------+--------------------------+
| 4:3 | Mode |
+-------+--------------------------+
| 2 | Execute permission |
+-------+--------------------------+
| 1 | Write permission |
+-------+--------------------------+
| 0 | Read permission |
+-------+--------------------------+
Details of these configuration bits can be found in the RISC-V Privileged Specification, version 1.11 (see Physical Memory Protection CSRs, Section 3.6.1).
Note that the combination of Write permission = 1, Read permission = 0 is reserved, and will be treated by the core as Read/Write permission = 0.
PMP Address Register (pmpaddrx)
----------------------------------------
CSR Address: ``0x3B0 - 0x3BF``
Reset Value: ``0x0000_0000``
``pmpaddrx`` are registers to set address matching for PMP regions.
+----------------+
| 31:0 |
+----------------+
| address[33:2] |
+----------------+
.. _csr-mhartid:

View file

@ -14,6 +14,7 @@ Ibex User Manual
cs_registers
performance_counters
exception_interrupts
pmp
debug
tracer
rvfi

View file

@ -12,6 +12,9 @@ Instantiation Template
.. code-block:: verilog
ibex_core #(
.PMPEnable (0),
.PMPGranularity (0),
.PMPNumRegions (4),
.MHPMCounterNum (0),
.MHPMCounterWidth (40),
.RV32E (0),
@ -68,6 +71,12 @@ Parameters
+-----------------------+-------------+------------+-----------------------------------------------------------------+
| Name | Type/Range | Default | Description |
+=======================+=============+============+=================================================================+
| ``PMPEnable`` | bit | 0 | Enable PMP support |
+-----------------------+-------------+------------+-----------------------------------------------------------------+
| ``PMPGranularity`` | int (0..31) | 0 | Minimum granularity of PMP address matching |
+-----------------------+-------------+------------+-----------------------------------------------------------------+
| ``PMPNumRegions`` | int (1..16) | 4 | Number implemented PMP regions (ignored if PMPEnable == 0) |
+-----------------------+-------------+------------+-----------------------------------------------------------------+
| ``MHPMCounterNum`` | int (0..8) | 0 | Number of performance monitor event counters |
+-----------------------+-------------+------------+-----------------------------------------------------------------+
| ``MHPMCounterWidth`` | int (64..1) | 40 | Bit width of performance monitor event counters |

View file

@ -82,6 +82,7 @@ The two register-file flavors are described in :ref:`register-file`.
The control and status registers are explained in :ref:`cs-registers`.
:ref:`performance-counters` gives an overview of the performance monitors and event counters available in Ibex.
:ref:`exceptions-interrupts` deals with the infrastructure for handling exceptions and interrupts,
:ref:`pmp` gives a brief overview of PMP support.
:ref:`debug-support` gives a brief overview on the debug infrastructure.
:ref:`tracer` gives a brief overview of the tracer module.
For information regarding formal verification support, check out :ref:`rvfi`.

33
doc/pmp.rst Normal file
View file

@ -0,0 +1,33 @@
.. _pmp:
Physical Memory Protection (PMP)
================================
The Physical Memory Protection (PMP) unit implements region-based memory access checking in-accordance with the RISC-V Privileged Specification, version 1.11.
The following configuration parameters are available to control PMP checking:
+----------------+---------------+-------------------------------------------------+
| Parameter | Default value | Description |
+================+===============+=================================================+
| PMPEnable | 0 | PMP support enabled |
+----------------+---------------+-------------------------------------------------+
| PMPNumRegions | 4 | Number of implemented regions (1 - 16) |
+----------------+---------------+-------------------------------------------------+
| PMPGranularity | 0 | Minimum match granularity 2^G\+2 bytes (0 - 31) |
+----------------+---------------+-------------------------------------------------+
When PMPEnable is zero, the PMP module is not instantiated and all PMP registers read as zero (regardless of the value of PMPNumRegions)
PMP Integration
---------------
Addresses from the instruction fetch unit and load-store unit are passed to the PMP module for checking, and the output of the PMP check is used to gate the external request.
To maintain consistency with external errors, the instruction fetch unit and load-store unit progress with their request as if it was granted externally.
The PMP error is registered and consumed by the core when the data would have been consumed.
PMP Granularity
---------------
The PMP granularity parameter is used to reduce the size of the address matching comparators by increasing the minimum region size.
When the granularity is greater than zero, NA4 mode is not available and will be treated as OFF mode.

View file

@ -21,6 +21,7 @@ filesets:
- rtl/ibex_multdiv_fast.sv
- rtl/ibex_multdiv_slow.sv
- rtl/ibex_prefetch_buffer.sv
- rtl/ibex_pmp.sv
# XXX: Figure out the best way to switch these two implementations
# dynamically on the target.
# - rtl/ibex_register_file_latch.sv # ASIC

View file

@ -24,7 +24,7 @@ lint_off -msg UNUSED -file "*/rtl/ibex_if_stage.sv" -lines 40
// Bits of signal are not used: fetch_addr_n[0]
// cleaner to write all bits even if not all are used
lint_off -msg UNUSED -file "*/rtl/ibex_if_stage.sv" -lines 79
lint_off -msg UNUSED -file "*/rtl/ibex_if_stage.sv" -lines 80
// Bits of signal are not used: shift_right_result_ext[32]
// cleaner to write all bits even if not all are used
@ -46,6 +46,26 @@ lint_off -msg UNUSED -file "*/rtl/ibex_multdiv_fast.sv" -lines 68
// testability signal
lint_off -msg UNUSED -file "*/rtl/ibex_register_file_ff.sv" -lines 21
// Signal is not used: clk_i
// leaving clk and reset connected in-case we want to add assertions
lint_off -msg UNUSED -file "*/rtl/ibex_pmp.sv" -lines 15
// Signal is not used: rst_ni
// leaving clk and reset connected in-case we want to add assertions
lint_off -msg UNUSED -file "*/rtl/ibex_pmp.sv" -lines 16
// Signal is not used: csr_pmp_addr
// Signal not connected when PMP is not configured
lint_off -msg UNUSED -file "*/rtl/ibex_core.sv" -lines 188
// Signal is not used: csr_pmp_cfg
// Signal not connected when PMP is not configured
lint_off -msg UNUSED -file "*/rtl/ibex_core.sv" -lines 189
// Signal is not used: priv_mode
// Signal not connected when PMP is not configured
lint_off -msg UNUSED -file "*/rtl/ibex_core.sv" -lines 201
// Signal unoptimizable: Feedback to clock or circular logic:
// ibex_core.id_stage_i.controller_i.ctrl_fsm_cs
// Issue lowrisc/ibex#211
@ -54,4 +74,4 @@ lint_off -msg UNOPTFLAT -file "*/rtl/ibex_controller.sv" -lines 97
// Signal unoptimizable: Feedback to clock or circular logic:
// ibex_core.cs_registers_i.mie_q
// Issue lowrisc/ibex#212
lint_off -msg UNOPTFLAT -file "*/rtl/ibex_cs_registers.sv" -lines 141
lint_off -msg UNOPTFLAT -file "*/rtl/ibex_cs_registers.sv" -lines 149

View file

@ -11,6 +11,9 @@
* Top level module of the ibex RISC-V core
*/
module ibex_core #(
parameter bit PMPEnable = 0,
parameter int unsigned PMPGranularity = 0,
parameter int unsigned PMPNumRegions = 4,
parameter int unsigned MHPMCounterNum = 0,
parameter int unsigned MHPMCounterWidth = 40,
parameter bit RV32E = 0,
@ -92,6 +95,8 @@ module ibex_core #(
import ibex_pkg::*;
localparam int unsigned PMP_NUM_CHAN = 2;
// IF/ID signals
logic instr_valid_id;
logic instr_new_id;
@ -179,6 +184,13 @@ module ibex_core #(
logic csr_mstatus_mie;
logic [31:0] csr_mepc, csr_depc;
// PMP signals
logic [33:0] csr_pmp_addr [PMPNumRegions];
pmp_cfg_t csr_pmp_cfg [PMPNumRegions];
logic pmp_req_err [PMP_NUM_CHAN];
logic instr_req_out;
logic data_req_out;
logic csr_save_if;
logic csr_save_id;
logic csr_restore_mret_id;
@ -186,6 +198,7 @@ module ibex_core #(
logic csr_mtvec_init;
logic [31:0] csr_mtvec;
logic [31:0] csr_mtval;
priv_lvl_e priv_mode;
// debug mode and dcsr configuration
dbg_cause_e debug_cause;
@ -287,12 +300,13 @@ module ibex_core #(
.req_i ( instr_req_int ), // instruction request control
// instruction cache interface
.instr_req_o ( instr_req_o ),
.instr_req_o ( instr_req_out ),
.instr_addr_o ( instr_addr_o ),
.instr_gnt_i ( instr_gnt_i ),
.instr_rvalid_i ( instr_rvalid_i ),
.instr_rdata_i ( instr_rdata_i ),
.instr_err_i ( instr_err_i ),
.instr_pmp_err_i ( pmp_req_err[PMP_I] ),
// outputs to ID stage
.instr_valid_id_o ( instr_valid_id ),
@ -328,6 +342,8 @@ module ibex_core #(
.perf_imiss_o ( perf_imiss )
);
// Qualify the instruction request with PMP error
assign instr_req_o = instr_req_out & ~pmp_req_err[PMP_I];
//////////////
// ID stage //
@ -485,15 +501,18 @@ module ibex_core #(
// Load/store unit //
/////////////////////
assign data_req_o = data_req_out & ~pmp_req_err[PMP_D];
ibex_load_store_unit load_store_unit_i (
.clk_i ( clk ),
.rst_ni ( rst_ni ),
// data interface
.data_req_o ( data_req_o ),
.data_req_o ( data_req_out ),
.data_gnt_i ( data_gnt_i ),
.data_rvalid_i ( data_rvalid_i ),
.data_err_i ( data_err_i ),
.data_pmp_err_i ( pmp_req_err[PMP_D] ),
.data_addr_o ( data_addr_o ),
.data_we_o ( data_we_o ),
@ -537,6 +556,8 @@ module ibex_core #(
ibex_cs_registers #(
.MHPMCounterNum ( MHPMCounterNum ),
.MHPMCounterWidth ( MHPMCounterWidth ),
.PMPGranularity ( PMPGranularity ),
.PMPNumRegions ( PMPNumRegions ),
.RV32E ( RV32E ),
.RV32M ( RV32M )
) cs_registers_i (
@ -546,6 +567,7 @@ module ibex_core #(
// Core and Cluster ID from outside
.core_id_i ( core_id_i ),
.cluster_id_i ( cluster_id_i ),
.priv_mode_o ( priv_mode ),
// mtvec
.csr_mtvec_o ( csr_mtvec ),
@ -572,6 +594,10 @@ module ibex_core #(
.csr_mstatus_mie_o ( csr_mstatus_mie ),
.csr_mepc_o ( csr_mepc ),
// PMP
.csr_pmp_cfg_o ( csr_pmp_cfg ),
.csr_pmp_addr_o ( csr_pmp_addr ),
// debug
.csr_depc_o ( csr_depc ),
.debug_cause_i ( debug_cause ),
@ -605,6 +631,36 @@ module ibex_core #(
.lsu_busy_i ( lsu_busy )
);
if (PMPEnable) begin : g_pmp
logic [33:0] pmp_req_addr [PMP_NUM_CHAN];
pmp_req_e pmp_req_type [PMP_NUM_CHAN];
assign pmp_req_addr[PMP_I] = {2'b00,instr_addr_o[31:0]};
assign pmp_req_type[PMP_I] = PMP_ACC_EXEC;
assign pmp_req_addr[PMP_D] = {2'b00,data_addr_o[31:0]};
assign pmp_req_type[PMP_D] = data_we_o ? PMP_ACC_WRITE : PMP_ACC_READ;
ibex_pmp #(
.PMPGranularity ( PMPGranularity ),
.PMPNumChan ( PMP_NUM_CHAN ),
.PMPNumRegions ( PMPNumRegions )
) pmp_i (
.clk_i ( clk ),
.rst_ni ( rst_ni ),
// Interface to CSRs
.csr_pmp_cfg_i ( csr_pmp_cfg ),
.csr_pmp_addr_i ( csr_pmp_addr ),
.priv_mode_i ( priv_mode ),
// Access checking channels
.pmp_req_addr_i ( pmp_req_addr ),
.pmp_req_type_i ( pmp_req_type ),
.pmp_req_err_o ( pmp_req_err )
);
end else begin : g_no_pmp
assign pmp_req_err[PMP_I] = 1'b0;
assign pmp_req_err[PMP_D] = 1'b0;
end
`ifdef RVFI
always_ff @(posedge clk or negedge rst_ni) begin
if (!rst_ni) begin

View file

@ -12,6 +12,9 @@
module ibex_cs_registers #(
parameter int unsigned MHPMCounterNum = 8,
parameter int unsigned MHPMCounterWidth = 40,
parameter bit PMPEnable = 0,
parameter int unsigned PMPGranularity = 0,
parameter int unsigned PMPNumRegions = 4,
parameter bit RV32E = 0,
parameter bit RV32M = 0
) (
@ -22,6 +25,7 @@ module ibex_cs_registers #(
// Core and Cluster ID
input logic [3:0] core_id_i,
input logic [5:0] cluster_id_i,
output ibex_pkg::priv_lvl_e priv_mode_o,
// mtvec
output logic [31:0] csr_mtvec_o,
@ -48,6 +52,10 @@ module ibex_cs_registers #(
output logic csr_mstatus_mie_o,
output logic [31:0] csr_mepc_o,
// PMP
output ibex_pkg::pmp_cfg_t csr_pmp_cfg_o [PMPNumRegions],
output logic [33:0] csr_pmp_addr_o [PMPNumRegions],
// debug
input ibex_pkg::dbg_cause_e debug_cause_i,
input logic debug_csr_save_i,
@ -156,6 +164,10 @@ module ibex_cs_registers #(
logic [31:0] mstack_epc_q, mstack_epc_d;
logic [5:0] mstack_cause_q, mstack_cause_d;
// PMP Signals
logic [31:0] pmp_addr_rdata [PMP_MAX_REGIONS];
logic [PMP_CFG_W-1:0] pmp_cfg_rdata [PMP_MAX_REGIONS];
// Hardware performance monitor signals
logic [31:0] mcountinhibit_d, mcountinhibit_q, mcountinhibit;
logic [31:0] mcountinhibit_force;
@ -254,6 +266,32 @@ module ibex_cs_registers #(
csr_rdata_int[CSR_MFIX_BIT_HIGH:CSR_MFIX_BIT_LOW] = mip.irq_fast;
end
// PMP registers
CSR_PMPCFG0: csr_rdata_int = {pmp_cfg_rdata[3], pmp_cfg_rdata[2],
pmp_cfg_rdata[1], pmp_cfg_rdata[0]};
CSR_PMPCFG1: csr_rdata_int = {pmp_cfg_rdata[7], pmp_cfg_rdata[6],
pmp_cfg_rdata[5], pmp_cfg_rdata[4]};
CSR_PMPCFG2: csr_rdata_int = {pmp_cfg_rdata[11], pmp_cfg_rdata[10],
pmp_cfg_rdata[9], pmp_cfg_rdata[8]};
CSR_PMPCFG3: csr_rdata_int = {pmp_cfg_rdata[15], pmp_cfg_rdata[14],
pmp_cfg_rdata[13], pmp_cfg_rdata[12]};
CSR_PMPADDR0: csr_rdata_int = pmp_addr_rdata[0];
CSR_PMPADDR1: csr_rdata_int = pmp_addr_rdata[1];
CSR_PMPADDR2: csr_rdata_int = pmp_addr_rdata[2];
CSR_PMPADDR3: csr_rdata_int = pmp_addr_rdata[3];
CSR_PMPADDR4: csr_rdata_int = pmp_addr_rdata[4];
CSR_PMPADDR5: csr_rdata_int = pmp_addr_rdata[5];
CSR_PMPADDR6: csr_rdata_int = pmp_addr_rdata[6];
CSR_PMPADDR7: csr_rdata_int = pmp_addr_rdata[7];
CSR_PMPADDR8: csr_rdata_int = pmp_addr_rdata[8];
CSR_PMPADDR9: csr_rdata_int = pmp_addr_rdata[9];
CSR_PMPADDR10: csr_rdata_int = pmp_addr_rdata[10];
CSR_PMPADDR11: csr_rdata_int = pmp_addr_rdata[11];
CSR_PMPADDR12: csr_rdata_int = pmp_addr_rdata[12];
CSR_PMPADDR13: csr_rdata_int = pmp_addr_rdata[13];
CSR_PMPADDR14: csr_rdata_int = pmp_addr_rdata[14];
CSR_PMPADDR15: csr_rdata_int = pmp_addr_rdata[15];
CSR_DCSR: csr_rdata_int = dcsr_q;
CSR_DPC: csr_rdata_int = depc_q;
CSR_DSCRATCH0: csr_rdata_int = dscratch0_q;
@ -511,7 +549,7 @@ module ibex_cs_registers #(
end
// only write CSRs during one clock cycle
assign csr_we_int = csr_wreq & instr_new_id_i;
assign csr_we_int = csr_wreq & ~illegal_csr_priv & instr_new_id_i;
assign csr_rdata_o = csr_rdata_int;
@ -584,6 +622,131 @@ module ibex_cs_registers #(
end
end
assign priv_mode_o = mstatus_q.mpp;
// -----------------
// PMP registers
// -----------------
if (PMPEnable) begin : g_pmp_registers
pmp_cfg_t pmp_cfg [PMPNumRegions];
pmp_cfg_t pmp_cfg_wdata [PMPNumRegions];
logic [31:0] pmp_addr [PMPNumRegions];
logic [PMPNumRegions-1:0] pmp_cfg_we;
logic [PMPNumRegions-1:0] pmp_addr_we;
// Expanded / qualified register read data
for (genvar i = 0; i < PMP_MAX_REGIONS; i++) begin : g_exp_rd_data
if (i < PMPNumRegions) begin : g_implemented_regions
// Add in zero padding for reserved fields
assign pmp_cfg_rdata[i] = {pmp_cfg[i].lock, 2'b00, pmp_cfg[i].mode,
pmp_cfg[i].exec, pmp_cfg[i].write, pmp_cfg[i].read};
// Address field read data depends on the current programmed mode and the granularity
// See RISC-V Privileged Specification, version 1.11, Section 3.6.1
if (PMPGranularity == 0) begin : g_pmp_g0
// If G == 0, read data is unmodified
assign pmp_addr_rdata[i] = pmp_addr[i];
end else if (PMPGranularity == 1) begin : g_pmp_g1
// If G == 1, bit [G-1] reads as zero in TOR or OFF mode
always_comb begin
pmp_addr_rdata[i] = pmp_addr[i];
if ((pmp_cfg[i].mode == PMP_MODE_OFF) || (pmp_cfg[i].mode == PMP_MODE_TOR)) begin
pmp_addr_rdata[i][PMPGranularity-1:0] = '0;
end
end
end else begin
// For G >= 2, bits are masked to one or zero depending on the mode
always_comb begin
pmp_addr_rdata[i] = pmp_addr[i];
if ((pmp_cfg[i].mode == PMP_MODE_OFF) || (pmp_cfg[i].mode == PMP_MODE_TOR)) begin
// In TOR or OFF mode, bits [G-1:0] must read as zero
pmp_addr_rdata[i][PMPGranularity-1:0] = '0;
end else if (pmp_cfg[i].mode == PMP_MODE_NAPOT) begin
// In NAPOT mode, bits [G-2:0] must read as one
pmp_addr_rdata[i][PMPGranularity-2:0] = '1;
end
end
end
end else begin : g_other_regions
// Non-implemented regions read as zero
assign pmp_cfg_rdata[i] = '0;
assign pmp_addr_rdata[i] = '0;
end
end
// Write data calculation
for (genvar i = 0; i < PMPNumRegions; i++) begin : g_pmp_csrs
// -------------------------
// Instantiate cfg registers
// -------------------------
assign pmp_cfg_we[i] = csr_we_int & ~pmp_cfg[i].lock &
(csr_addr == (CSR_OFF_PMP_CFG + (i[11:0] >> 2)));
// Select the correct WDATA (each CSR contains 4 CFG fields, each with 2 RES bits)
assign pmp_cfg_wdata[i].lock = csr_wdata_int[(i%4)*PMP_CFG_W+7];
// NA4 mode is not selectable when G > 0, mode is treated as OFF
always_comb begin
unique case (csr_wdata_int[(i%4)*PMP_CFG_W+3+:2])
2'b00 : pmp_cfg_wdata[i].mode = PMP_MODE_OFF;
2'b01 : pmp_cfg_wdata[i].mode = PMP_MODE_TOR;
2'b10 : pmp_cfg_wdata[i].mode = (PMPGranularity == 0) ? PMP_MODE_NA4:
PMP_MODE_OFF;
2'b11 : pmp_cfg_wdata[i].mode = PMP_MODE_NAPOT;
default : pmp_cfg_wdata[i].mode = pmp_cfg_mode_e'('X);
endcase
end
assign pmp_cfg_wdata[i].exec = csr_wdata_int[(i%4)*PMP_CFG_W+2];
// W = 1, R = 0 is a reserved combination. For now, we force W to 0 if R == 0
assign pmp_cfg_wdata[i].write = &csr_wdata_int[(i%4)*PMP_CFG_W+:2];
assign pmp_cfg_wdata[i].read = csr_wdata_int[(i%4)*PMP_CFG_W];
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
pmp_cfg[i] <= pmp_cfg_t'('b0);
end else if (pmp_cfg_we[i]) begin
pmp_cfg[i] <= pmp_cfg_wdata[i];
end
end
// --------------------------
// Instantiate addr registers
// --------------------------
if (i < PMPNumRegions - 1) begin : g_lower
assign pmp_addr_we[i] = csr_we_int & ~pmp_cfg[i].lock &
(pmp_cfg[i+1].mode != PMP_MODE_TOR) &
(csr_addr == (CSR_OFF_PMP_ADDR + i[11:0]));
end else begin : g_upper
assign pmp_addr_we[i] = csr_we_int & ~pmp_cfg[i].lock &
(csr_addr == (CSR_OFF_PMP_ADDR + i[11:0]));
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
pmp_addr[i] <= 'b0;
end else if (pmp_addr_we[i]) begin
pmp_addr[i] <= csr_wdata_int;
end
end
assign csr_pmp_cfg_o[i] = pmp_cfg[i];
assign csr_pmp_addr_o[i] = {pmp_addr[i],2'b00};
end
end else begin : g_no_pmp_tieoffs
// Generate tieoffs when PMP is not configured
for (genvar i = 0; i < PMP_MAX_REGIONS; i++) begin : g_rdata
assign pmp_addr_rdata[i] = '0;
assign pmp_cfg_rdata[i] = '0;
end
for (genvar i = 0; i < PMPNumRegions; i++) begin : g_outputs
assign csr_pmp_cfg_o[i] = pmp_cfg_t'('0);
assign csr_pmp_addr_o[i] = '0;
end
end
//////////////////////////
// Performance monitor //
//////////////////////////

View file

@ -26,6 +26,7 @@ module ibex_if_stage #(
input logic instr_rvalid_i,
input logic [31:0] instr_rdata_i,
input logic instr_err_i,
input logic instr_pmp_err_i,
// output of ID stage
output logic instr_valid_id_o, // instr in IF-ID is valid
@ -150,6 +151,7 @@ module ibex_if_stage #(
.instr_rvalid_i ( instr_rvalid_i ),
.instr_rdata_i ( instr_rdata_i ),
.instr_err_i ( instr_err_i ),
.instr_pmp_err_i ( instr_pmp_err_i ),
// Prefetch Buffer Status
.busy_o ( prefetch_busy )

View file

@ -18,6 +18,7 @@ module ibex_load_store_unit (
input logic data_gnt_i,
input logic data_rvalid_i,
input logic data_err_i,
input logic data_pmp_err_i,
output logic [31:0] data_addr_o,
output logic data_we_o,
@ -54,6 +55,7 @@ module ibex_load_store_unit (
logic [31:0] data_addr_w_aligned;
logic [31:0] addr_last_q, addr_last_d;
logic data_update;
logic [31:0] rdata_q, rdata_d;
logic [1:0] rdata_offset_q, rdata_offset_d;
logic [1:0] data_type_q, data_type_d;
@ -74,6 +76,8 @@ module ibex_load_store_unit (
logic split_misaligned_access;
logic handle_misaligned_q, handle_misaligned_d; // high after receiving grant for first
// part of a misaligned access
logic pmp_err_q;
logic data_or_pmp_err;
typedef enum logic [2:0] {
IDLE, WAIT_GNT_MIS, WAIT_RVALID_MIS, WAIT_GNT, WAIT_RVALID
@ -168,10 +172,16 @@ module ibex_load_store_unit (
end
// update control signals for next read data upon receiving grant
assign rdata_offset_d = data_gnt_i ? data_addr[1:0] : rdata_offset_q;
assign data_type_d = data_gnt_i ? data_type_ex_i : data_type_q;
assign data_sign_ext_d = data_gnt_i ? data_sign_ext_ex_i : data_sign_ext_q;
assign data_we_d = data_gnt_i ? data_we_ex_i : data_we_q;
// This must also be set for a pmp error (which might not actually be granted) to force
// data_we_q to update in order to signal the correct exception type (load or store)
// Note that we can use the registered pmp_err_q here since we will always take an
// extra cycle to progress to the RVALID state
assign data_update = data_gnt_i | pmp_err_q;
assign rdata_offset_d = data_update ? data_addr[1:0] : rdata_offset_q;
assign data_type_d = data_update ? data_type_ex_i : data_type_q;
assign data_sign_ext_d = data_update ? data_sign_ext_ex_i : data_sign_ext_q;
assign data_we_d = data_update ? data_we_ex_i : data_we_q;
// registers for rdata alignment and sign-extension
always_ff @(posedge clk_i or negedge rst_ni) begin
@ -310,6 +320,7 @@ module ibex_load_store_unit (
data_valid_o = 1'b0;
addr_incr_req_o = 1'b0;
handle_misaligned_d = handle_misaligned_q;
data_or_pmp_err = 1'b0;
unique case (ls_fsm_cs)
@ -327,7 +338,7 @@ module ibex_load_store_unit (
WAIT_GNT_MIS: begin
data_req_o = 1'b1;
if (data_gnt_i) begin
if (data_gnt_i || pmp_err_q) begin
handle_misaligned_d = 1'b1;
ls_fsm_ns = WAIT_RVALID_MIS;
end
@ -336,11 +347,14 @@ module ibex_load_store_unit (
WAIT_RVALID_MIS: begin
// tell ID/EX stage to update the address
addr_incr_req_o = 1'b1;
if (data_rvalid_i) begin
// first part rvalid is received
if (data_err_i) begin
// first part rvalid is received, or gets a pmp error
// pmp_err_i will hold stable until the address is updated, and
// therefore pmp_err_q is valid in both WAIT_GNT_MIS and WAIT_RVALID_MIS states
if (data_rvalid_i || pmp_err_q) begin
if (pmp_err_q || data_err_i) begin
// first part created an error, abort transaction
data_valid_o = 1'b1;
data_or_pmp_err = 1'b1;
handle_misaligned_d = 1'b0;
ls_fsm_ns = IDLE;
end else begin
@ -364,15 +378,18 @@ module ibex_load_store_unit (
// tell ID/EX stage to update the address
addr_incr_req_o = handle_misaligned_q;
data_req_o = 1'b1;
if (data_gnt_i) begin
if (data_gnt_i || pmp_err_q) begin
ls_fsm_ns = WAIT_RVALID;
end
end
WAIT_RVALID: begin
data_req_o = 1'b0;
if (data_rvalid_i) begin
// pmp_err_i will hold stable until the address is updated, and
// therefore pmp_err_q is valid in both WAIT_GNT and WAIT_RVALID states
if (data_rvalid_i || pmp_err_q) begin
data_valid_o = 1'b1;
data_or_pmp_err = data_err_i | pmp_err_q;
handle_misaligned_d = 1'b0;
ls_fsm_ns = IDLE;
end else begin
@ -402,10 +419,12 @@ module ibex_load_store_unit (
ls_fsm_cs <= IDLE;
addr_last_q <= '0;
handle_misaligned_q <= '0;
pmp_err_q <= '0;
end else begin
ls_fsm_cs <= ls_fsm_ns;
addr_last_q <= addr_last_d;
handle_misaligned_q <= handle_misaligned_d;
pmp_err_q <= data_pmp_err_i;
end
end
@ -428,10 +447,9 @@ module ibex_load_store_unit (
// output to ID stage: mtval + AGU for misaligned transactions
assign addr_last_o = addr_last_q;
// to know what kind of error to signal, we need to know the type of the transaction to which
// the outsanding rvalid belongs.
assign load_err_o = data_err_i & data_rvalid_i & ~data_we_q;
assign store_err_o = data_err_i & data_rvalid_i & data_we_q;
// Signal a load or store error depending on the transaction type outstanding
assign load_err_o = data_or_pmp_err & ~data_we_q;
assign store_err_o = data_or_pmp_err & data_we_q;
assign busy_o = (ls_fsm_cs == WAIT_RVALID) | (data_req_o == 1'b1);

View file

@ -192,6 +192,36 @@ typedef enum logic [2:0] {
DBG_CAUSE_STEP = 3'h4
} dbg_cause_e;
// PMP constants
parameter int unsigned PMP_MAX_REGIONS = 16;
parameter int unsigned PMP_CFG_W = 8;
// PMP acces type
parameter int unsigned PMP_I = 0;
parameter int unsigned PMP_D = 1;
typedef enum logic [1:0] {
PMP_ACC_EXEC = 2'b00,
PMP_ACC_WRITE = 2'b01,
PMP_ACC_READ = 2'b10
} pmp_req_e;
// PMP cfg structures
typedef enum logic [1:0] {
PMP_MODE_OFF = 2'b00,
PMP_MODE_TOR = 2'b01,
PMP_MODE_NA4 = 2'b10,
PMP_MODE_NAPOT = 2'b11
} pmp_cfg_mode_e;
typedef struct packed {
logic lock;
pmp_cfg_mode_e mode;
logic exec;
logic write;
logic read;
} pmp_cfg_t;
// CSRs
typedef enum logic[11:0] {
// Machine information
@ -210,6 +240,28 @@ typedef enum logic[11:0] {
CSR_MTVAL = 12'h343,
CSR_MIP = 12'h344,
// Physical memory protection
CSR_PMPCFG0 = 12'h3A0,
CSR_PMPCFG1 = 12'h3A1,
CSR_PMPCFG2 = 12'h3A2,
CSR_PMPCFG3 = 12'h3A3,
CSR_PMPADDR0 = 12'h3B0,
CSR_PMPADDR1 = 12'h3B1,
CSR_PMPADDR2 = 12'h3B2,
CSR_PMPADDR3 = 12'h3B3,
CSR_PMPADDR4 = 12'h3B4,
CSR_PMPADDR5 = 12'h3B5,
CSR_PMPADDR6 = 12'h3B6,
CSR_PMPADDR7 = 12'h3B7,
CSR_PMPADDR8 = 12'h3B8,
CSR_PMPADDR9 = 12'h3B9,
CSR_PMPADDR10 = 12'h3BA,
CSR_PMPADDR11 = 12'h3BB,
CSR_PMPADDR12 = 12'h3BC,
CSR_PMPADDR13 = 12'h3BD,
CSR_PMPADDR14 = 12'h3BE,
CSR_PMPADDR15 = 12'h3BF,
// Debug/trace
CSR_DCSR = 12'h7b0,
CSR_DPC = 12'h7b1,
@ -226,6 +278,10 @@ typedef enum logic[11:0] {
CSR_MINSTRETH = 12'hB82
} csr_num_e;
// CSR pmp-related offsets
parameter logic [11:0] CSR_OFF_PMP_CFG = 12'h3A0; // pmp_cfg @ 12'h3a0 - 12'h3a3
parameter logic [11:0] CSR_OFF_PMP_ADDR = 12'h3B0; // pmp_addr @ 12'h3b0 - 12'h3bf
// CSR mhpmcounter-related offsets and mask
parameter logic [11:0] CSR_OFF_MCOUNTER_SETUP = 12'h320; // mcounter_setup @ 12'h323 - 12'h33F
parameter logic [11:0] CSR_OFF_MCOUNTER = 12'hB00; // mcounter @ 12'hB03 - 12'hB1F

113
rtl/ibex_pmp.sv Normal file
View file

@ -0,0 +1,113 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
module ibex_pmp #(
// Granularity of NAPOT access,
// 0 = No restriction, 1 = 8 byte, 2 = 16 byte, 3 = 32 byte, etc.
parameter int unsigned PMPGranularity = 0,
// Number of access channels (e.g. i-side + d-side)
parameter int unsigned PMPNumChan = 2,
// Number of implemented regions
parameter int unsigned PMPNumRegions = 4
) (
// Clock and Reset
input logic clk_i,
input logic rst_ni,
// Interface to CSRs
input ibex_pkg::pmp_cfg_t csr_pmp_cfg_i [PMPNumRegions],
input logic [33:0] csr_pmp_addr_i [PMPNumRegions],
input ibex_pkg::priv_lvl_e priv_mode_i, // Current priv mode, assumed to
// be the same for all channels
// Access checking channels
input logic [33:0] pmp_req_addr_i [PMPNumChan],
input ibex_pkg::pmp_req_e pmp_req_type_i [PMPNumChan],
output logic pmp_req_err_o [PMPNumChan]
);
import ibex_pkg::*;
// Access Checking Signals
logic [33:0] region_start_addr [PMPNumRegions];
logic [33:PMPGranularity+2] region_addr_mask [PMPNumRegions];
logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_high;
logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_low;
logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_both;
logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_match_partial;
logic [PMPNumChan-1:0][PMPNumRegions-1:0] region_perm_check;
logic [PMPNumChan-1:0][PMPNumRegions-1:0] machine_access_fault;
logic [PMPNumChan-1:0][PMPNumRegions-1:0] user_access_allowed;
logic [PMPNumChan-1:0] access_fault;
// ---------------
// Access checking
// ---------------
for (genvar r = 0; r < PMPNumRegions; r++) begin : g_addr_exp
// Start address for TOR matching
if (r == 0) begin : g_entry0
assign region_start_addr[r] = (csr_pmp_cfg_i[r].mode == PMP_MODE_TOR) ? 34'h000000000 :
csr_pmp_addr_i[r];
end else begin : g_oth
assign region_start_addr[r] = (csr_pmp_cfg_i[r].mode == PMP_MODE_TOR) ? csr_pmp_addr_i[r-1] :
csr_pmp_addr_i[r];
end
// Address mask for NA matching
for (genvar b = PMPGranularity+2; b < 34; b++) begin : g_bitmask
if (b == PMPGranularity+2) begin : g_bit0
// Always mask bit (G+2) for NAPOT
assign region_addr_mask[r][b] = (csr_pmp_cfg_i[r].mode != PMP_MODE_NAPOT);
end else begin : g_others
// We will mask this bit if it is within the programmed granule
// i.e. addr = yyyy 0111
// ^
// | This bit pos is the top of the mask, all lower bits set
// thus mask = 1111 0000
assign region_addr_mask[r][b] = (csr_pmp_cfg_i[r].mode != PMP_MODE_NAPOT) |
~&csr_pmp_addr_i[r][b-1:PMPGranularity+2];
end
end
end
for (genvar c = 0; c < PMPNumChan; c++) begin : g_access_check
for (genvar r = 0; r < PMPNumRegions; r++) begin : g_regions
// TOR Region high/low matching is reused for all match types
assign region_match_low[c][r] = (pmp_req_addr_i[c][33:PMPGranularity+2] >=
// Comparators are sized according to granularity
(region_start_addr[r][33:PMPGranularity+2] &
region_addr_mask[r]));
assign region_match_high[c][r] = (pmp_req_addr_i[c][33:PMPGranularity+2] <=
(csr_pmp_addr_i[r][33:PMPGranularity+2] &
region_addr_mask[r]));
assign region_match_both[c][r] = region_match_low[c][r] & region_match_high[c][r] &
(csr_pmp_cfg_i[r].mode != PMP_MODE_OFF);
assign region_match_partial[c][r] = (region_match_low[c][r] ^ region_match_high[c][r]) &
(csr_pmp_cfg_i[r].mode != PMP_MODE_OFF);
// Check specific required permissions
assign region_perm_check[c][r] =
((pmp_req_type_i[c] == PMP_ACC_EXEC) & csr_pmp_cfg_i[r].exec) |
((pmp_req_type_i[c] == PMP_ACC_WRITE) & csr_pmp_cfg_i[r].write) |
((pmp_req_type_i[c] == PMP_ACC_READ) & csr_pmp_cfg_i[r].read);
// In machine mode, any match to a locked region without sufficient permissions is a fault
assign machine_access_fault[c][r] = region_match_both[c][r] & csr_pmp_cfg_i[r].lock &
~region_perm_check[c][r];
if (r == 0) begin : g_region0
// In any other mode, any access should fault unless is matches a region
assign user_access_allowed[c][r] = region_match_both[c][r] & region_perm_check[c][r];
end else begin : g_oth_regions
assign user_access_allowed[c][r] = region_match_both[c][r] & region_perm_check[c][r] &
// any higher priority (lower region number) partial match should also cause a fault
~|region_match_partial[c][r-1:0];
end
end
assign access_fault[c] = (priv_mode_i == PRIV_LVL_M) ? |machine_access_fault[c] :
~|user_access_allowed[c];
assign pmp_req_err_o[c] = access_fault[c];
end
endmodule

View file

@ -32,6 +32,7 @@ module ibex_prefetch_buffer (
output logic [31:0] instr_addr_o,
input logic [31:0] instr_rdata_i,
input logic instr_err_i,
input logic instr_pmp_err_i,
input logic instr_rvalid_i,
// Prefetch Buffer Status
@ -47,6 +48,8 @@ module ibex_prefetch_buffer (
logic [31:0] instr_addr_q, fetch_addr;
logic [31:0] instr_addr, instr_addr_w_aligned;
logic addr_valid;
logic pmp_err_q;
logic instr_or_pmp_err;
logic fifo_valid;
logic fifo_ready;
@ -62,6 +65,10 @@ module ibex_prefetch_buffer (
// Fetch fifo - consumes addresses and data //
//////////////////////////////////////////////
// Instruction fetch errors are valid on the data phase of a request
// PMP errors are generated in the address phase, and registered into a fake data phase
assign instr_or_pmp_err = instr_err_i | pmp_err_q;
ibex_fetch_fifo fifo_i (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
@ -70,7 +77,7 @@ module ibex_prefetch_buffer (
.in_addr_i ( instr_addr_q ),
.in_rdata_i ( instr_rdata_i ),
.in_err_i ( instr_err_i ),
.in_err_i ( instr_or_pmp_err ),
.in_valid_i ( fifo_valid ),
.in_ready_o ( fifo_ready ),
@ -134,7 +141,10 @@ module ibex_prefetch_buffer (
end
//~> granted request or not
pf_fsm_ns = instr_gnt_i ? WAIT_RVALID : WAIT_GNT;
// If the instruction generated a PMP error, we may or may not
// get granted (the external valid is suppressed by the error)
// but we proceed to WAIT_RVALID to push the error to the fifo
pf_fsm_ns = (instr_gnt_i || pmp_err_q) ? WAIT_RVALID : WAIT_GNT;
end // case: WAIT_GNT
// we wait for rvalid, after that we are ready to serve a new request
@ -148,7 +158,8 @@ module ibex_prefetch_buffer (
if (req_i && (fifo_ready || branch_i)) begin
// prepare for next request
if (instr_rvalid_i) begin
// Fake the rvalid for PMP errors to push the error to the fifo
if (instr_rvalid_i || pmp_err_q) begin
instr_req_o = 1'b1;
fifo_valid = 1'b1;
addr_valid = 1'b1;
@ -166,7 +177,8 @@ module ibex_prefetch_buffer (
end else begin
// just wait for rvalid and go back to IDLE, no new request
if (instr_rvalid_i) begin
// Fake the rvalid for PMP errors to push the error to the fifo
if (instr_rvalid_i || pmp_err_q) begin
fifo_valid = 1'b1;
pf_fsm_ns = IDLE;
end
@ -207,11 +219,13 @@ module ibex_prefetch_buffer (
if (!rst_ni) begin
pf_fsm_cs <= IDLE;
instr_addr_q <= '0;
pmp_err_q <= '0;
end else begin
pf_fsm_cs <= pf_fsm_ns;
if (addr_valid) begin
instr_addr_q <= instr_addr;
pmp_err_q <= instr_pmp_err_i;
end
end
end

View file

@ -17,6 +17,7 @@ ibex:
rtl/ibex_multdiv_fast.sv,
rtl/ibex_prefetch_buffer.sv,
rtl/ibex_fetch_fifo.sv,
rtl/ibex_pmp.sv,
rtl/ibex_core.sv,
]
ibex_vip_rtl: