blob: 31e7faeabf8017c6b8e360c469754b49d4110ee1 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Flash Memory Protection
//
`include "prim_assert.sv"
module flash_mp import flash_ctrl_pkg::*; import flash_ctrl_reg_pkg::*; #(
parameter int MpRegions = 8,
parameter int NumBanks = 2,
parameter int AllPagesW = 16,
localparam int TotalRegions = MpRegions+1,
localparam int BankW = $clog2(NumBanks)
) (
input clk_i,
input rst_ni,
// configuration from sw
input flash_ctrl_reg2hw_mp_region_cfg_mreg_t [TotalRegions-1:0] region_cfgs_i,
input flash_ctrl_reg2hw_mp_bank_cfg_mreg_t [NumBanks-1:0] bank_cfgs_i,
// interface signals to/from *_ctrl
input req_i,
input [AllPagesW-1:0] req_addr_i,
input req_part_i,
input addr_ovfl_i,
input [BankW-1:0] req_bk_i,
input rd_i,
input prog_i,
input pg_erase_i,
input bk_erase_i,
output logic rd_done_o,
output logic prog_done_o,
output logic erase_done_o,
output logic error_o,
output logic [AllPagesW-1:0] err_addr_o,
output logic [BankW-1:0] err_bank_o,
// interface signals to/from flash_phy
output logic req_o,
output logic rd_o,
output logic prog_o,
output logic pg_erase_o,
output logic bk_erase_o,
input rd_done_i,
input prog_done_i,
input erase_done_i
);
// Address range checks
localparam int LastValidInfoPage = InfosPerBank - 1;
// There could be multiple region matches due to region overlap
// region_end is +1 bit from however bits are needed to address regions
logic [AllPagesW:0] region_end[TotalRegions];
logic [TotalRegions-1:0] region_match;
logic [TotalRegions-1:0] region_sel;
logic [TotalRegions-1:0] rd_en;
logic [TotalRegions-1:0] prog_en;
logic [TotalRegions-1:0] pg_erase_en;
logic [NumBanks-1:0] bk_erase_en;
logic final_rd_en;
logic final_prog_en;
logic final_pg_erase_en;
logic final_bk_erase_en;
// Lower indices always have priority
assign region_sel[0] = region_match[0];
for (genvar i = 1; i < TotalRegions; i++) begin: gen_region_priority
assign region_sel[i] = region_match[i] & ~|region_match[i-1:0];
end
// check for region match
always_comb begin
for (int unsigned i = 0; i < TotalRegions; i++) begin: region_comps
region_end[i] = {1'b0, region_cfgs_i[i].base.q} + region_cfgs_i[i].size.q;
// region matches if address within range and if the partition matches
region_match[i] = req_addr_i >= region_cfgs_i[i].base.q &
{1'b0, req_addr_i} < region_end[i] &
req_part_i == region_cfgs_i[i].partition.q &
region_cfgs_i[i].en.q &
req_i;
rd_en[i] = region_cfgs_i[i].rd_en.q & region_sel[i];
prog_en[i] = region_cfgs_i[i].prog_en.q & region_sel[i];
pg_erase_en[i] = region_cfgs_i[i].erase_en.q & region_sel[i];
end
end
// check for bank erase
// bank erase allowed for only data partition
always_comb begin
for (int unsigned i = 0; i < NumBanks; i++) begin: bank_comps
bk_erase_en[i] = (req_bk_i == i) & bank_cfgs_i[i].q &
(req_part_i == DataPart);
end
end
logic invalid_info_access, invalid_info_erase, invalid_info_txn;
// invalid info page access
assign invalid_info_access = req_i &
(req_part_i == InfoPart) &
(rd_i | prog_i | pg_erase_i) &
(req_addr_i[0 +: PageW] > LastValidInfoPage);
// invalid info page erase
assign invalid_info_erase = req_i & bk_erase_i &
(req_part_i == InfoPart);
assign invalid_info_txn = invalid_info_access | invalid_info_erase;
assign final_rd_en = rd_i & |rd_en;
assign final_prog_en = prog_i & |prog_en;
assign final_pg_erase_en = pg_erase_i & |pg_erase_en;
assign final_bk_erase_en = bk_erase_i & |bk_erase_en;
assign rd_o = req_i & final_rd_en;
assign prog_o = req_i & final_prog_en;
assign pg_erase_o = req_i & final_pg_erase_en;
assign bk_erase_o = req_i & final_bk_erase_en;
assign req_o = rd_o | prog_o | pg_erase_o | bk_erase_o;
logic txn_err;
logic txn_ens;
logic no_allowed_txn;
assign txn_ens = final_rd_en | final_prog_en | final_pg_erase_en | final_bk_erase_en;
// if incoming address overflowed or is invalid or no transaction enables, error back
assign no_allowed_txn = req_i & (addr_ovfl_i | invalid_info_txn | ~txn_ens);
// return done and error the next cycle
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
txn_err <= 1'b0;
err_addr_o <= '0;
err_bank_o <= '0;
end else if (txn_err) begin
txn_err <= 1'b0;
end else if (no_allowed_txn) begin
txn_err <= 1'b1;
err_addr_o <= req_addr_i;
err_bank_o <= req_bk_i;
end
end
assign rd_done_o = rd_done_i | txn_err;
assign prog_done_o = prog_done_i | txn_err;
assign erase_done_o = erase_done_i | txn_err;
assign error_o = txn_err;
//////////////////////////////////////////////
// Assertions, Assumptions, and Coverpoints //
//////////////////////////////////////////////
// Bank erase enable should always be one-hot. We cannot erase multiple banks
// at the same time
`ASSERT(bkEraseEnOnehot_a, (req_o & bk_erase_o) |-> $onehot(bk_erase_en))
// Requests can only happen one at a time
`ASSERT(requestTypesOnehot_a, req_o |-> $onehot({rd_o, prog_o, pg_erase_o, bk_erase_o}))
endmodule // flash_erase_ctrl