From 5466c8131ea4b096309e4a8a0b9e8e5cf7158540 Mon Sep 17 00:00:00 2001 From: Luke Wren Date: Mon, 23 May 2022 17:52:08 +0100 Subject: [PATCH] Sketch in PMP implementation --- hdl/hazard3_config.vh | 8 ++ hdl/hazard3_config_inst.vh | 2 + hdl/hazard3_csr_addr.vh | 16 ++- hdl/hazard3_pmp.v | 251 +++++++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 hdl/hazard3_pmp.v diff --git a/hdl/hazard3_config.vh b/hdl/hazard3_config.vh index 8620cf0..da973b0 100644 --- a/hdl/hazard3_config.vh +++ b/hdl/hazard3_config.vh @@ -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. diff --git a/hdl/hazard3_config_inst.vh b/hdl/hazard3_config_inst.vh index 0c4bdaf..c1b2297 100644 --- a/hdl/hazard3_config_inst.vh +++ b/hdl/hazard3_config_inst.vh @@ -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), diff --git a/hdl/hazard3_csr_addr.vh b/hdl/hazard3_csr_addr.vh index cfa0769..399e210 100644 --- a/hdl/hazard3_csr_addr.vh +++ b/hdl/hazard3_csr_addr.vh @@ -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 diff --git a/hdl/hazard3_pmp.v b/hdl/hazard3_pmp.v new file mode 100644 index 0000000..73e5e04 --- /dev/null +++ b/hdl/hazard3_pmp.v @@ -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 \ No newline at end of file