Sketch in PMP implementation
This commit is contained in:
parent
06647b78c6
commit
5466c8131e
|
@ -77,6 +77,14 @@ parameter CSR_M_TRAP = 1,
|
|||
// CSR_COUNTER: Include performance counters and relevant M-mode CSRs
|
||||
parameter CSR_COUNTER = 1,
|
||||
|
||||
// U_MODE: support for the U (user, unprivileged) execution mode. If U_MODE is
|
||||
// 1, PMP support is recommended.
|
||||
parameter U_MODE = 0,
|
||||
|
||||
// PMP_REGIONS: Number of physical memory protection regions, or 0 for no PMP.
|
||||
// PMP is more useful if U mode is supported, but has use on M-only systems.
|
||||
parameter PMP_REGIONS = 0,
|
||||
|
||||
// DEBUG_SUPPORT: Support for run/halt and instruction injection from an
|
||||
// external Debug Module, support for Debug Mode, and Debug Mode CSRs.
|
||||
// Requires: CSR_M_MANDATORY, CSR_M_TRAP.
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
.CSR_M_MANDATORY (CSR_M_MANDATORY),
|
||||
.CSR_M_TRAP (CSR_M_TRAP),
|
||||
.CSR_COUNTER (CSR_COUNTER),
|
||||
.U_MODE (U_MODE),
|
||||
.PMP_REGIONS (PMP_REGIONS),
|
||||
.DEBUG_SUPPORT (DEBUG_SUPPORT),
|
||||
.NUM_IRQ (NUM_IRQ),
|
||||
.MVENDORID_VAL (MVENDORID_VAL),
|
||||
|
|
|
@ -38,7 +38,21 @@ localparam PMPCFG1 = 12'h3a1; // Physical memory protection configuration
|
|||
localparam PMPCFG2 = 12'h3a2; // Physical memory protection configuration.
|
||||
localparam PMPCFG3 = 12'h3a3; // Physical memory protection configuration, RV32 only.
|
||||
localparam PMPADDR0 = 12'h3b0; // Physical memory protection address register.
|
||||
localparam PMPADDR1 = 12'h3b1; // Physical memory protection address register.
|
||||
localparam PMPADDR1 = 12'h3b1; // ...
|
||||
localparam PMPADDR2 = 12'h3b2;
|
||||
localparam PMPADDR3 = 12'h3b3;
|
||||
localparam PMPADDR4 = 12'h3b4;
|
||||
localparam PMPADDR5 = 12'h3b5;
|
||||
localparam PMPADDR6 = 12'h3b6;
|
||||
localparam PMPADDR7 = 12'h3b7;
|
||||
localparam PMPADDR8 = 12'h3b8;
|
||||
localparam PMPADDR9 = 12'h3b9;
|
||||
localparam PMPADDR10 = 12'h3ba;
|
||||
localparam PMPADDR11 = 12'h3bb;
|
||||
localparam PMPADDR12 = 12'h3bc;
|
||||
localparam PMPADDR13 = 12'h3bd;
|
||||
localparam PMPADDR14 = 12'h3be;
|
||||
localparam PMPADDR15 = 12'h3bf;
|
||||
|
||||
// Performance counters (RW)
|
||||
localparam MCYCLE = 12'hb00; // Raw cycles since start of day
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
/*****************************************************************************\
|
||||
| Copyright (C) 2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
`default_nettype none
|
||||
|
||||
// Physical memory protection unit
|
||||
|
||||
module hazard3_pmp #(
|
||||
`include "hazard3_config.vh"
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst_n,
|
||||
input wire m_mode,
|
||||
input wire mstatus_mxr, // make executable readable
|
||||
|
||||
// Config interface passed through CSR block
|
||||
input wire [11:0] cfg_addr,
|
||||
input wire cfg_wen,
|
||||
input wire [W_DATA-1:0] cfg_wdata,
|
||||
output reg [W_DATA-1:0] cfg_rdata,
|
||||
|
||||
// Fetch address query
|
||||
input wire [W_ADDR-1:0] i_addr,
|
||||
input wire i_instr_is_32bit,
|
||||
input wire i_m_mode,
|
||||
output wire i_kill,
|
||||
|
||||
// Load/store address query
|
||||
input wire [W_ADDR-1:0] d_addr,
|
||||
input wire d_m_mode,
|
||||
input wire d_write,
|
||||
output wire d_kill
|
||||
);
|
||||
|
||||
localparam PMP_A_OFF = 2'b00;
|
||||
localparam PMP_A_TOR = 2'b01; // we don't implement
|
||||
localparam PMP_A_NA4 = 2'b10;
|
||||
localparam PMP_A_NAPOT = 2'b11;
|
||||
|
||||
`include "hazard3_csr_addr.vh"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Config registers and read/write interface
|
||||
|
||||
reg pmpcfg_l [0:PMP_REGIONS-1];
|
||||
reg [1:0] pmpcfg_a [0:PMP_REGIONS-1];
|
||||
reg pmpcfg_r [0:PMP_REGIONS-1];
|
||||
reg pmpcfg_w [0:PMP_REGIONS-1];
|
||||
reg pmpcfg_x [0:PMP_REGIONS-1];
|
||||
|
||||
// Address register contains bits 33:2 of the address (to support 16 GiB
|
||||
// physical address space). We don't implement bits 33 or 32.
|
||||
reg [W_ADDR-3:0] pmpaddr [0:PMP_REGIONS-1];
|
||||
|
||||
always @ (posedge clk or negedge rst_n) begin: cfg_update
|
||||
integer i;
|
||||
if (!rst_n) begin
|
||||
for (i = 0; i < PMP_REGIONS; i = i + 1) begin
|
||||
pmpcfg_l[i] <= 1'b0;
|
||||
pmpcfg_a[i] <= 2'd0;
|
||||
pmpcfg_r[i] <= 1'b0;
|
||||
pmpcfg_w[i] <= 1'b0;
|
||||
pmpcfg_x[i] <= 1'b0;
|
||||
pmpaddr[i] <= {W_ADDR-2{1'b0}};
|
||||
end
|
||||
end else if (cfg_wen) begin
|
||||
for (i = 0; i < PMP_REGIONS; i = i + 1) begin
|
||||
if (cfg_addr == PMPCFG0 + i / 4) begin
|
||||
pmpcfg_l[i] <= cfg_wdata[i % 4 * 8 + 7];
|
||||
// TOR is not supported, gets mapped to OFF:
|
||||
pmpcfg_a[i] <= {
|
||||
cfg_wdata[i % 4 * 8 + 4],
|
||||
cfg_wdata[i % 4 * 8 + 3] && csr[i % 4 * 8 + 4]
|
||||
};
|
||||
pmpcfg_r[i] <= cfg_wdata[i % 4 * 8 + 2];
|
||||
pmpcfg_w[i] <= cfg_wdata[i % 4 * 8 + 1];
|
||||
pmpcfg_x[i] <= cfg_wdata[i % 4 * 8 + 0];
|
||||
end
|
||||
if (cfg_addr == PMPADDR0 + i) begin
|
||||
pmpaddr[i] <= cfg_wdata[W_ADDR-3:0];
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always @ (*) begin: cfg_read
|
||||
integer i;
|
||||
cfg_rdata = {W_DATA{1'b0}};
|
||||
for (i = 0; i < PMP_REGIONS; i = i + 1) begin
|
||||
if (cfg_addr == PMPCFG0 + i / 4) begin
|
||||
cfg_rdata[i % 4 * 8 +: 8] = {
|
||||
pmpcfg_l[i],
|
||||
2'b00,
|
||||
pmpcfg_a[i],
|
||||
pmpcfg_r[i],
|
||||
pmpcfg_w[i],
|
||||
pmpcfg_x[i]
|
||||
};
|
||||
end else if (cfg_addr == PMPADDR0 + i) begin
|
||||
cfg_rdata[W_ADDR-3:0] = pmpaddr[i];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Address query lookup
|
||||
|
||||
// Decode PMPCFGx.A and PMPADDRx into a 32-bit address mask and address value
|
||||
reg [W_ADDR-1:0] match_mask [0:PMP_REGIONS-1];
|
||||
reg [W_ADDR-1:0] match_addr [0:PMP_REGIONS-1];
|
||||
|
||||
// Encoding: (noting ADDR is a 4-byte address, not a word address):
|
||||
// CFG.A | ADDR | Region size
|
||||
// ------+----------+------------
|
||||
// NA4 | y..yyyyy | 4 bytes
|
||||
// NAPOT | y..yyyy0 | 8 bytes
|
||||
// NAPOT | y..yyy01 | 16 bytes
|
||||
// NAPOT | y..yy011 | 32 bytes
|
||||
// NAPOT | y..y0111 | 64 bytes
|
||||
// etc.
|
||||
//
|
||||
// So, with the exception of NA4, the rule is to check all bits more
|
||||
// significant than the least-significant 0 bit.
|
||||
|
||||
always @ (*) begin: decode_match_mask_addr
|
||||
integer i, j;
|
||||
for (i = 0; i < PMP_REGIONS; i = i + 1) begin
|
||||
if (pmpcfg_a[i] == PMP_A_NA4) begin
|
||||
match_mask[i] = {{W_ADDR-2{1'b1}}, 2'b00};
|
||||
end else begin
|
||||
// Bits 1:0 are always 0. Bit 2 is 0 because NAPOT is at least 8 bytes.
|
||||
match_mask[i] = {W_ADDR{1'b0}};
|
||||
for (j = 3; j < W_ADDR; j = j + 1) begin
|
||||
match_mask[i][j] = match_mask[i][j - 1] || !pmpaddr[i][j - 3];
|
||||
end
|
||||
end
|
||||
match_addr[i] = {pmpaddr[i], 2'b00} & match_mask[i];
|
||||
end
|
||||
end
|
||||
|
||||
// For load/stores we assume any non-naturally-aligned transfers trigger a
|
||||
// misaligned load/store/AMO exception, so we only need to decode the PMP
|
||||
// attribute for the first byte of the access. Note the spec gives us freedom
|
||||
// to report *either* a load/store/AMO access fault (mcause = 5, 7) or a
|
||||
// load/store/AMO alignment fault (mcause = 4, 6), in the case that both
|
||||
// happen, and we choose alignment fault in this case.
|
||||
|
||||
reg d_match;
|
||||
reg d_l;
|
||||
reg d_r;
|
||||
reg d_w;
|
||||
reg d_x; // needed because of MXR
|
||||
|
||||
always @ (*) begin: check_d_match
|
||||
integer i;
|
||||
d_match = 1'b0;
|
||||
d_l = 1'b0;
|
||||
d_r = 1'b0;
|
||||
d_w = 1'b0;
|
||||
d_x = 1'b0;
|
||||
// Lowest-numbered match wins, so work down from the top. This should be
|
||||
// inferred as a priority mux structure (cascade mux).
|
||||
for (i = PMP_REGIONS - 1; i >= 0; i = i - 1) begin
|
||||
if (|pmpcfg_a[i] && (d_addr & match_mask[i]) == match_addr[i]) begin
|
||||
d_match = 1'b1;
|
||||
d_l = pmpcfg_l[i];
|
||||
d_r = pmpcfg_r[i];
|
||||
d_w = pmpcfg_w[i];
|
||||
d_x = pmpcfg_x[i];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Instruction fetches are more complex. For IALIGN=4 (i.e. non-RVC)
|
||||
// implementations, we can assume that instruction fetches are naturally
|
||||
// aligned, because any control flow transfer which would cause a
|
||||
// non-naturally-aligned fetch will have trapped. Therefore we never have to
|
||||
// worry about priority of instruction alignment fault vs instruction access
|
||||
// fault exceptions. However, when IALIGN=2, there are some cases to
|
||||
// consider:
|
||||
//
|
||||
// - An instruction which straddles a protection boundary must fail the PMP
|
||||
// check as it has a partial match
|
||||
//
|
||||
// - A jump to an illegal instruction starting at the last halfword of a PMP
|
||||
// region: in this case the size of the instruction is unknown, so it is
|
||||
// ambiguous whether this should be an access fault (if we treat the
|
||||
// illegal instruction as 32-bit) or an illegal instruction fault (if we
|
||||
// treat the instruction as 16-bit). To disambiguate, we decode the two
|
||||
// LSBs of the instruction to determine its size, as though it were valid.
|
||||
//
|
||||
// To detect partial matches of instruction fetches, we take the simple but
|
||||
// possibly suboptimal choice of querying both PC and PC + 2. On the topic of
|
||||
// partial matches, note the spec wording, as it's tricky:
|
||||
//
|
||||
// "The lowest-numbered PMP entry that matches any byte of an access
|
||||
// determines whether that access succeeds or fails. The matching PMP entry
|
||||
// must match all bytes of an access, or the access fails, irrespective of
|
||||
// the L, R, W, and X bits."
|
||||
//
|
||||
// This means that a partial match *is* permitted, if and only if you also
|
||||
// completely match a lower-numbered region. We don't accumulate the partial
|
||||
// match across all regions.
|
||||
|
||||
reg i_match;
|
||||
reg i_partial_match;
|
||||
reg i_l;
|
||||
reg i_x;
|
||||
|
||||
wire [W_ADDR-1:0] i_addr_hw1 = i_addr + 2'h2;
|
||||
|
||||
always @ (*) begin: check_i_match
|
||||
integer i;
|
||||
reg match_hw0, match_hw1;
|
||||
i_match = 1'b0;
|
||||
i_partial_match = 1'b0;
|
||||
i_l = 1'b0;
|
||||
i_x = 1'b0;
|
||||
for (i = PMP_REGIONS - 1; i >= 0; i = i + 1) begin
|
||||
match_hw0 = |pmpcfg_a[i] && (i_addr & match_mask[i]) == match_addr[i];
|
||||
match_hw1 = |pmpcfg_a[i] && (i_addr_hw1 & match_mask[i]) == match_addr[i];
|
||||
if (match_hw0 || match_hw1) begin
|
||||
i_match = 1'b1;
|
||||
i_partial_match = (match_hw0 ^ match_hw1) && i_instr_is_32bit;
|
||||
i_l = pmpcfg_l[i];
|
||||
i_x = pmpcfg_x[i];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Access rules
|
||||
|
||||
// M-mode gets to ignore protections, unless the lock bit is set.
|
||||
|
||||
assign d_kill = !(d_m_mode && !d_l) && (
|
||||
(!d_write && !(d_r || (d_x && mstatus_mxr))) ||
|
||||
( d_write && !d_w)
|
||||
);
|
||||
|
||||
// Straddling a protection boundary is always an error.
|
||||
|
||||
assign i_kill = i_partial_match || (
|
||||
!(i_m_mode && !i_l) && !i_x
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
Loading…
Reference in New Issue