| // 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 - TBD: This should be checked with software. Hardware could also always behave |
| // as it does in the point above and rely on software to correctly compact the data. |
| |
| module flash_phy_prog import flash_phy_pkg::*; ( |
| input clk_i, |
| input rst_ni, |
| input req_i, |
| input scramble_i, |
| input ecc_i, |
| input [WordSelW-1:0] sel_i, |
| input [BusWidth-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 |
| ); |
| |
| typedef enum logic [3:0] { |
| StIdle, |
| StPrePack, |
| StPackData, |
| StPostPack, |
| StReqFlash, |
| StWaitFlash, |
| StCalcMask, |
| StScrambleData, |
| StCalcEcc |
| } prog_state_e; |
| |
| typedef enum logic [1:0] { |
| Filler, |
| Actual |
| } data_sel_e; |
| |
| prog_state_e state_d, state_q; |
| |
| // 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 bit [WordSelW-1:0] MaxIdx = WordSelW'(WidthMultiple - 1); |
| |
| logic [WidthMultiple-1:0][BusWidth-1:0] packed_data; |
| |
| // selects empty data or real data |
| assign pack_data = (data_sel == Actual) ? data_i : {BusWidth{1'b1}}; |
| |
| // 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 |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| state_q <= StIdle; |
| end else begin |
| state_q <= state_d; |
| end |
| end |
| |
| // 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; |
| req_o = 1'b0; |
| ack_o = 1'b0; |
| last_o = 1'b0; |
| calc_req_o = 1'b0; |
| scramble_req_o = 1'b0; |
| |
| unique case (state_q) |
| StIdle: begin |
| // if first beat of a transaction is not aligned, prepack with empty bits |
| 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 = scramble_i ? StCalcMask : StReqFlash; |
| 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 = scramble_i ? StCalcMask : StReqFlash; |
| end |
| 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 |
| req_o = 1'b1; |
| 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_i ? StWaitFlash : StReqFlash; |
| end else begin |
| ack_o = ack_i; |
| state_d = ack_i ? StIdle : StReqFlash; |
| end |
| end |
| |
| StWaitFlash: begin |
| |
| if (done_i) begin |
| ack_o = 1'b1; |
| state_d = StIdle; |
| end |
| end |
| |
| default:; |
| 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_i) 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 |
| logic [ScrDataWidth-1:0] ecc_data; |
| |
| prim_secded_hamming_72_64_enc u_enc ( |
| .in(packed_data), |
| .out(ecc_data) |
| ); |
| |
| // pad the remaining bits to '0', this effectively "programs" them. |
| assign data_o = ecc_i ? FullDataWidth'(ecc_data) : FullDataWidth'(packed_data); |
| |
| ///////////////////////////////// |
| // Assertions |
| ///////////////////////////////// |
| |
| `ifndef SYNTHESIS |
| logic txn_done; |
| logic [15:0] done_cnt; |
| |
| assign txn_done = req_i && ack_o && last_i; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (rst_ni) begin |
| done_cnt <= '0; |
| end else if (txn_done) begin |
| done_cnt <= '0; |
| end else if (done_i) begin |
| done_cnt <= done_cnt + 1'b1; |
| end |
| end |
| |
| // We can only observe one done per transaction. |
| `ASSERT(OneDonePerTxn_A, txn_done |-> done_cnt == '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) |
| |
| |
| |
| endmodule // flash_phy_prog |