blob: 83bd8a423765167a028a1dc1659424e37015b167 [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 Phy Prog Module
//
// This module implements the flash phy program operation
//
// The flash phy prog module is mainly responsible for packing incoming data
// transactions into appropriate flash word sizes.
//
// This is done primarily for two reasons
// - Reduce program stress on the flash
// Flash modules usually have a limit to how many times adjacent words can be programmed.
// If a programming beat is longer than a flash word, it would be best to compact those
// beats into multiples of the flash word size to improve performance and reduce
// unecessary programmings
//
// - Observe minimum block cipher sizes for scrambling / descrambling and ECC.
// Scrambling algorithms and ECC work on a specific chunk of data. When these features
// are enabled, the phy controller needs to ensure there is enough data to satisfy that
// request.
module flash_phy_prog import flash_phy_pkg::*; (
input clk_i,
input rst_ni,
input prim_mubi_pkg::mubi4_t disable_i,
input req_i,
input scramble_i,
input ecc_i,
input [WordSelW-1:0] sel_i,
input [BusFullWidth-1:0] data_i,
input last_i,
input ack_i, // ack means request has been accepted by flash
input done_i, // done means requested transaction has completed
input calc_ack_i,
input scramble_ack_i,
input [DataWidth-1:0] mask_i,
input [DataWidth-1:0] scrambled_data_i,
output logic calc_req_o,
output logic scramble_req_o,
output logic req_o,
output logic last_o, // last beat of an incoming transaction
output logic ack_o,
// block data does not contain ecc / metadata portion
output logic [DataWidth-1:0] block_data_o,
output logic [FullDataWidth-1:0] data_o,
output logic fsm_err_o,
output logic intg_err_o
);
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 5 -m 11 -n 11 \
// -s 2968771430 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: --
// 4: --
// 5: |||||||||||||||||||| (40.00%)
// 6: ||||||||||||||||| (34.55%)
// 7: |||||| (12.73%)
// 8: ||||| (10.91%)
// 9: (1.82%)
// 10: --
// 11: --
//
// Minimum Hamming distance: 5
// Maximum Hamming distance: 9
// Minimum Hamming weight: 2
// Maximum Hamming weight: 8
//
localparam int StateWidth = 11;
typedef enum logic [StateWidth-1:0] {
StIdle = 11'b11111111110,
StPrePack = 11'b00001110111,
StPackData = 11'b10100100011,
StPostPack = 11'b11010000101,
StCalcPlainEcc = 11'b01101011011,
StReqFlash = 11'b01010110010,
StWaitFlash = 11'b00100111000,
StCalcMask = 11'b00000001110,
StScrambleData = 11'b00011101001,
StCalcEcc = 11'b00111010100,
StDisabled = 11'b10001000000
} state_e;
state_e state_d, state_q;
typedef enum logic [1:0] {
Filler,
Actual
} data_sel_e;
// The currently observed data beat
logic [WordSelW-1:0] idx;
logic [WordSelW-1:0] idx_sub_one;
logic pack_valid;
logic [BusWidth-1:0] pack_data;
logic align_next;
data_sel_e data_sel;
localparam int MaxIdx = WidthMultiple - 1;
logic [WidthMultiple-1:0][BusWidth-1:0] packed_data;
logic plain_ecc_en;
// selects empty data or real data
assign pack_data = (data_sel == Actual) ? data_i[BusWidth-1:0] : {BusWidth{1'b1}};
logic data_intg_ok;
logic data_err;
// use the tlul integrity module directly for bus integrity
// SEC_CM: MEM.BUS.INTEGRITY
tlul_data_integ_dec u_data_intg_chk (
.data_intg_i(data_i),
.data_err_o(data_err)
);
assign data_intg_ok = ~data_err;
logic data_invalid_q, data_invalid_d;
// hold on integrity failure indication until reset
assign data_invalid_d = data_invalid_q |
(pack_valid & ~data_intg_ok);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
data_invalid_q <= '0;
end else begin
data_invalid_q <= data_invalid_d;
end
end
// indication to upper layer prescence of error
assign intg_err_o = data_invalid_q;
// if integrity failure is seen, fake communication with flash
// and simply terminate
logic ack, done;
assign ack = ack_i | data_invalid_q;
assign done = done_i | data_invalid_q;
// next idx will be aligned
assign idx_sub_one = idx - 1'b1;
assign align_next = (idx > '0) ? (idx_sub_one == sel_i) : 1'b0;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
idx <= '0;
end else if (pack_valid && idx == MaxIdx) begin
// when a flash word is packed full, return index to 0
idx <= '0;
end else if (pack_valid) begin
// increment otherwise
idx <= idx + 1'b1;
end
end
// SEC_CM: PHY_PROG.FSM.SPARSE
`PRIM_FLOP_SPARSE_FSM(u_state_regs, state_d, state_q, state_e, StIdle)
// If the first beat of an incoming transaction is not aligned to word boundary (for example
// if each flash word is 4 bus words wide, and the first word to program starts at index 1),
// the fsm pre-packs the flash word with empty words until the supplied index.
// Once at the index, real data supplied from the flash controller is packed until the last
// beat of data. At the last beat of data, if it is not also aligned (index 3 in this example),
// more empty words are packed at the end to fill out the word.
//
always_comb begin
state_d = state_q;
pack_valid = 1'b0;
data_sel = Filler;
plain_ecc_en = 1'b0;
req_o = 1'b0;
ack_o = 1'b0;
last_o = 1'b0;
calc_req_o = 1'b0;
scramble_req_o = 1'b0;
fsm_err_o = 1'b0;
unique case (state_q)
StIdle: begin
// if first beat of a transaction is not aligned, prepack with empty bits
if (prim_mubi_pkg::mubi4_test_true_loose(disable_i)) begin
// only disable during idle state to ensure program is able to gracefully complete
// this is important as we do not want to accidentally disturb any electrical procedure
// internal to the flash macro
state_d = StDisabled;
end else if (req_i && |sel_i) begin
state_d = StPrePack;
end else if (req_i) begin
state_d = StPackData;
end
end
StPrePack: begin
// pack until currently supplied data
pack_valid = (idx < sel_i);
if (idx == align_next) begin
state_d = StPackData;
end
end
StPackData: begin
pack_valid = req_i;
data_sel = Actual;
if (req_i && idx == MaxIdx) begin
// last beat of a flash word
state_d = StCalcPlainEcc;
end else if (req_i && last_i) begin
// last beat is not aligned with the last entry of flash word
state_d = StPostPack;
end else if (req_i) begin
ack_o = 1'b1;
end
end
StPostPack: begin
// supply filler data
pack_valid = 1'b1;
data_sel = Filler;
// finish packing remaining entries
if (idx == MaxIdx) begin
state_d = StCalcPlainEcc;
end
end
StCalcPlainEcc: begin
plain_ecc_en = 1'b1;
state_d = scramble_i ? StCalcMask : StReqFlash;
end
StCalcMask: begin
calc_req_o = 1'b1;
if (calc_ack_i) begin
state_d = StScrambleData;
end
end
StScrambleData: begin
scramble_req_o = 1'b1;
if (scramble_ack_i) begin
state_d = StCalcEcc;
end
end
StCalcEcc: begin
state_d = StReqFlash;
end
StReqFlash: begin
// only request flash if data integrity was valid
req_o = ~data_invalid_q;
last_o = last_i;
// if this is the last beat of the program burst
// - wait for done
// if this is NOT the last beat
// - ack the upstream request and accept more beats
if (last_i) begin
state_d = ack ? StWaitFlash : StReqFlash;
end else begin
ack_o = ack;
state_d = ack ? StIdle : StReqFlash;
end
end
StWaitFlash: begin
if (done) begin
ack_o = 1'b1;
state_d = StIdle;
end
end
StDisabled: begin
state_d = StDisabled;
end
default: begin
fsm_err_o = 1'b1;
end
endcase // unique case (state_q)
end
logic [DataWidth-1:0] mask_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
packed_data <= '0;
mask_q <= '0;
end else if (req_o && ack) begin
packed_data <= '0;
end else if (calc_req_o && calc_ack_i) begin
packed_data <= packed_data ^ mask_i;
mask_q <= mask_i;
end else if (scramble_req_o && scramble_ack_i) begin
packed_data <= scrambled_data_i[DataWidth-1:0] ^ mask_q;
end else if (pack_valid) begin
packed_data[idx] <= pack_data;
end
end
assign block_data_o = packed_data;
// ECC handling
localparam int PlainDataEccWidth = DataWidth + 8;
logic [FullDataWidth-1:0] ecc_data;
logic [PlainDataEccWidth-1:0] plain_data_w_ecc;
logic [PlainIntgWidth-1:0] plain_data_ecc;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
plain_data_ecc <= '1;
end else if (plain_ecc_en) begin
plain_data_ecc <= plain_data_w_ecc[DataWidth +: PlainIntgWidth];
end
end
logic [PlainDataWidth-1:0] ecc_data_in;
assign ecc_data_in = {plain_data_ecc, packed_data};
// reliability ECC calculation
prim_secded_hamming_76_68_enc u_enc (
.data_i(ecc_data_in),
.data_o(ecc_data)
);
// integrity ECC calculation
// This instance can technically be merged with the instance above, but is
// kept separate for the sake of convenience
// The plain data ecc is calculated continously from packed data (which changes
// from packed data to masked/scrambled data based on software configuration).
// The actual plain data ECC is explicitly captured during this process when
// it is required.
prim_secded_hamming_72_64_enc u_plain_enc (
.data_i(packed_data),
.data_o(plain_data_w_ecc)
);
logic unused_data;
assign unused_data = |plain_data_w_ecc;
// pad the remaining bits with '1' if ecc is not used.
assign data_o = ecc_i ? ecc_data : {{EccWidth{1'b1}}, ecc_data_in};
/////////////////////////////////
// Assertions
/////////////////////////////////
`ifdef INC_ASSERT
logic txn_done;
logic [15:0] done_cnt_d, done_cnt_q;
assign txn_done = req_i && ack_o && last_i;
assign done_cnt_d = txn_done ? '0 : (done ? done_cnt_q + 16'h1 : done_cnt_q);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
done_cnt_q <= '0;
end else begin
done_cnt_q <= done_cnt_d;
end
end
// We can only observe one done per transaction.
`ASSERT(OneDonePerTxn_A, txn_done |-> done_cnt_d == '0)
`endif
// Prepack state can only pack up to WidthMultiple - 1
`ASSERT(PrePackRule_A, state_q == StPrePack && pack_valid |-> idx < MaxIdx)
// Postpack states should never pack the first index (as it would be aligned in that case)
`ASSERT(PostPackRule_A, state_q == StPostPack && pack_valid |-> idx != '0)
// The metadata width must always be greater than the ecc width
`ASSERT_INIT(WidthCheck_A, MetaDataWidth >= EccWidth)
endmodule // flash_phy_prog