| // 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 |