| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Emulate a single generic flash bank |
| // |
| |
| module prim_generic_flash_bank #( |
| parameter int InfosPerBank = 1, // info pages per bank |
| parameter int InfoTypes = 1, // different info types |
| parameter int InfoTypesWidth = 1, // different info types |
| parameter int PagesPerBank = 256, // data pages per bank |
| parameter int WordsPerPage = 256, // words per page |
| parameter int DataWidth = 32, // bits per word |
| |
| // Derived parameters |
| localparam int PageW = $clog2(PagesPerBank), |
| localparam int WordW = $clog2(WordsPerPage), |
| localparam int AddrW = PageW + WordW |
| ) ( |
| input clk_i, |
| input rst_ni, |
| input rd_i, |
| input prog_i, |
| input prog_last_i, |
| // the generic model does not make use of program types |
| input flash_ctrl_pkg::flash_prog_e prog_type_i, |
| input pg_erase_i, |
| input bk_erase_i, |
| input erase_suspend_req_i, |
| input he_i, |
| input [AddrW-1:0] addr_i, |
| input flash_ctrl_pkg::flash_part_e part_i, |
| input [InfoTypesWidth-1:0] info_sel_i, |
| input [DataWidth-1:0] prog_data_i, |
| output logic ack_o, |
| output logic done_o, |
| output logic [DataWidth-1:0] rd_data_o, |
| input init_i, |
| output logic init_busy_o, |
| input flash_power_ready_h_i, |
| input flash_power_down_h_i |
| ); |
| |
| `ifdef SYNTHESIS |
| localparam int ReadLatency = 1; |
| localparam int ProgLatency = 50; |
| localparam int EraseLatency = 200; |
| |
| `else |
| int ReadLatency = 1; |
| int ProgLatency = 50; |
| int EraseLatency = 200; |
| |
| initial begin |
| bit flash_rand_delay_en; |
| void'($value$plusargs("flash_rand_delay_en=%0b", flash_rand_delay_en)); |
| |
| if (flash_rand_delay_en) begin |
| ReadLatency = $urandom_range(1, 5); |
| ProgLatency = $urandom_range(25, 50); |
| EraseLatency = $urandom_range(125, 200); |
| end |
| void'($value$plusargs("flash_read_latency=%0d", ReadLatency)); |
| void'($value$plusargs("flash_program_latency=%0d", ProgLatency)); |
| void'($value$plusargs("flash_erase_latency=%0d", EraseLatency)); |
| $display("%m: ReadLatency:%0d ProgLatency:%0d EraseLatency:%0d", |
| ReadLatency, ProgLatency, EraseLatency); |
| end |
| `endif |
| |
| // Emulated flash macro values |
| localparam int BkEraseCycles = 2000; |
| localparam int InitCycles = 100; |
| |
| // Locally derived values |
| localparam int WordsPerBank = PagesPerBank * WordsPerPage; |
| localparam int WordsPerInfoBank = InfosPerBank * WordsPerPage; |
| localparam int InfoAddrW = $clog2(WordsPerInfoBank); |
| |
| typedef enum logic [2:0] { |
| StReset = 'h0, |
| StInit = 'h1, |
| StIdle = 'h2, |
| StRead = 'h3, |
| StProg = 'h4, |
| StErase = 'h5, |
| StErSuspend = 'h6 |
| } state_e; |
| |
| state_e st_q, st_d; |
| |
| logic [31:0] time_cnt; |
| logic [31:0] index_cnt; |
| logic time_cnt_inc ,time_cnt_clr, time_cnt_set1; |
| logic index_cnt_inc, index_cnt_clr; |
| logic [31:0] index_limit_q, index_limit_d; |
| logic [31:0] time_limit_q, time_limit_d; |
| logic prog_pend_q, prog_pend_d; |
| logic mem_req; |
| logic mem_wr; |
| logic [DataWidth-1:0] mem_wdata; |
| logic [AddrW-1:0] mem_addr; |
| flash_ctrl_pkg::flash_part_e mem_part; |
| logic mem_bk_erase; |
| logic [InfoTypesWidth-1:0] mem_info_sel; |
| |
| // insert a fifo here to break the large fanout from inputs to memories on reads |
| typedef struct packed { |
| logic rd; |
| logic prog; |
| logic prog_last; |
| flash_ctrl_pkg::flash_prog_e prog_type; |
| logic pg_erase; |
| logic bk_erase; |
| logic [AddrW-1:0] addr; |
| flash_ctrl_pkg::flash_part_e part; |
| logic [InfoTypesWidth-1:0] info_sel; |
| logic [DataWidth-1:0] prog_data; |
| } cmd_payload_t; |
| |
| cmd_payload_t cmd_d, cmd_q; |
| logic cmd_valid; |
| logic pop_cmd; |
| logic mem_rd_q, mem_rd_d; |
| |
| assign cmd_d = '{ |
| rd : rd_i, |
| prog: prog_i, |
| prog_last: prog_last_i, |
| prog_type: prog_type_i, |
| pg_erase: pg_erase_i, |
| bk_erase: bk_erase_i, |
| addr: addr_i, |
| part: part_i, |
| info_sel: info_sel_i, |
| prog_data: prog_data_i |
| }; |
| |
| // for read transactions, in order to reduce latency, the |
| // command fifo is popped early (before done_o). This is to ensure that when |
| // the current transaction is complete, during the same cycle |
| // a new read can be issued. As a result, the command is popped |
| // immediately after the read is issued, rather than waiting for |
| // the read to be completed. The same restrictions are not necessary |
| // for program / erase, which do not have the same performance |
| // requirements. |
| |
| // when the flash is going through init, do not accept any transactions |
| logic wvalid; |
| logic ack; |
| assign wvalid = (rd_i | prog_i | pg_erase_i | bk_erase_i) & !init_busy_o; |
| assign ack_o = ack & !init_busy_o; |
| |
| prim_fifo_sync #( |
| .Width ($bits(cmd_payload_t)), |
| .Pass (0), |
| .Depth (2) |
| ) u_cmd_fifo ( |
| .clk_i, |
| .rst_ni, |
| .clr_i (1'b0), |
| .wvalid_i(wvalid), |
| .wready_o(ack), |
| .wdata_i (cmd_d), |
| .depth_o (), |
| .full_o (), |
| .rvalid_o(cmd_valid), |
| .rready_i(pop_cmd), |
| .rdata_o (cmd_q), |
| .err_o () |
| ); |
| |
| logic rd_req, prog_req, pg_erase_req, bk_erase_req; |
| assign rd_req = cmd_valid & cmd_q.rd; |
| assign prog_req = cmd_valid & cmd_q.prog; |
| assign pg_erase_req = cmd_valid & cmd_q.pg_erase; |
| assign bk_erase_req = cmd_valid & cmd_q.bk_erase; |
| |
| // for read / program operations, the index cnt should be 0 |
| assign mem_rd_d = mem_req & ~mem_wr; |
| assign mem_addr = cmd_q.addr + index_cnt[AddrW-1:0]; |
| assign mem_part = cmd_q.part; |
| assign mem_bk_erase = cmd_q.bk_erase; |
| |
| assign mem_info_sel = cmd_q.info_sel; |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) st_q <= StReset; |
| else st_q <= st_d; |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| time_limit_q <= 'h0; |
| index_limit_q <= 'h0; |
| prog_pend_q <= 'h0; |
| mem_rd_q <= 'h0; |
| end else begin |
| time_limit_q <= time_limit_d; |
| index_limit_q <= index_limit_d; |
| prog_pend_q <= prog_pend_d; |
| mem_rd_q <= mem_rd_d; |
| end |
| end |
| |
| // latch read data from emulated memories the cycle after a read |
| logic [DataWidth-1:0] rd_data_q, rd_data_d; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| rd_data_q <= '0; |
| end else if (mem_rd_q) begin |
| rd_data_q <= rd_data_d; |
| end |
| end |
| |
| // latch partiton being read since the command fifo is popped early |
| flash_ctrl_pkg::flash_part_e rd_part_q; |
| logic [InfoTypesWidth-1:0] info_sel_q; |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| rd_part_q <= flash_ctrl_pkg::FlashPartData; |
| info_sel_q <= '0; |
| end else if (mem_rd_d) begin |
| rd_part_q <= cmd_q.part; |
| info_sel_q <= cmd_q.info_sel; |
| end |
| end |
| |
| // if read cycle is only 1, we can expose the unlatched data directly |
| assign rd_data_o = ReadLatency == 1 ? rd_data_d : rd_data_q; |
| |
| // prog_pend_q is necessary to emulate flash behavior that a bit written to 0 cannot be written |
| // back to 1 without an erase |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| time_cnt <= 'h0; |
| index_cnt <= 'h0; |
| end else begin |
| if (time_cnt_inc) time_cnt <= time_cnt + 1'b1; |
| else if (time_cnt_set1) time_cnt <= 32'h1; |
| else if (time_cnt_clr) time_cnt <= 32'h0; |
| |
| if (index_cnt_inc) index_cnt <= index_cnt + 1'b1; |
| else if (index_cnt_clr) index_cnt <= 32'h0; |
| end |
| end |
| |
| always_comb begin |
| // state |
| st_d = st_q; |
| |
| // internally consumed signals |
| index_limit_d = index_limit_q; |
| time_limit_d = time_limit_q; |
| prog_pend_d = prog_pend_q; |
| mem_req = '0; |
| mem_wr = '0; |
| mem_wdata = '0; |
| time_cnt_inc = '0; |
| time_cnt_clr = '0; |
| time_cnt_set1 = '0; |
| index_cnt_inc = '0; |
| index_cnt_clr = '0; |
| |
| // i/o |
| init_busy_o = '0; |
| pop_cmd = '0; |
| done_o = '0; |
| |
| unique case (st_q) |
| StReset: begin |
| init_busy_o = 1'b1; |
| if (init_i && flash_power_ready_h_i && !flash_power_down_h_i) begin |
| st_d = StInit; |
| end |
| end |
| |
| // Emulate flash initilaization with a wait timer |
| StInit: begin |
| init_busy_o = 1'h1; |
| if (index_cnt < InitCycles) begin |
| st_d = StInit; |
| index_cnt_inc = 1'b1; |
| end else begin |
| st_d = StIdle; |
| index_cnt_clr = 1'b1; |
| end |
| end |
| |
| StIdle: begin |
| if (rd_req) begin |
| pop_cmd = 1'b1; |
| mem_req = 1'b1; |
| time_cnt_inc = 1'b1; |
| st_d = StRead; |
| end else if (prog_req) begin |
| mem_req = 1'b1; |
| prog_pend_d = 1'b1; |
| st_d = StRead; |
| end else if (pg_erase_req) begin |
| st_d = StErase; |
| index_limit_d = WordsPerPage; |
| time_limit_d = EraseLatency; |
| end else if (bk_erase_req) begin |
| st_d = StErase; |
| index_limit_d = WordsPerBank; |
| time_limit_d = BkEraseCycles; |
| end |
| end |
| |
| StRead: begin |
| if (time_cnt < ReadLatency) begin |
| time_cnt_inc = 1'b1; |
| |
| end else if (!prog_pend_q) begin |
| done_o = 1'b1; |
| |
| // if another request already pending |
| if (rd_req) begin |
| pop_cmd = 1'b1; |
| mem_req = 1'b1; |
| time_cnt_set1 = 1'b1; |
| st_d = StRead; |
| end else begin |
| time_cnt_clr = 1'b1; |
| st_d = StIdle; |
| end |
| |
| end else if (prog_pend_q) begin |
| // this is the read performed before a program operation |
| prog_pend_d = 1'b0; |
| time_cnt_clr = 1'b1; |
| st_d = StProg; |
| end |
| end |
| |
| StProg: begin |
| // if data is already 0, cannot program to 1 without erase |
| mem_wdata = cmd_q.prog_data & rd_data_q; |
| if (time_cnt < ProgLatency) begin |
| mem_req = 1'b1; |
| mem_wr = 1'b1; |
| time_cnt_inc = 1'b1; |
| end else begin |
| st_d = StIdle; |
| pop_cmd = 1'b1; |
| done_o = cmd_q.prog_last; |
| time_cnt_clr = 1'b1; |
| end |
| end |
| |
| StErase: begin |
| // Actual erasing of the page |
| if (erase_suspend_req_i) begin |
| st_d = StErSuspend; |
| end else if (index_cnt < index_limit_q || time_cnt < time_limit_q) begin |
| mem_req = 1'b1; |
| mem_wr = 1'b1; |
| mem_wdata = {DataWidth{1'b1}}; |
| time_cnt_inc = (time_cnt < time_limit_q); |
| index_cnt_inc = (index_cnt < index_limit_q); |
| end else begin |
| st_d = StIdle; |
| pop_cmd = 1'b1; |
| done_o = 1'b1; |
| time_cnt_clr = 1'b1; |
| index_cnt_clr = 1'b1; |
| end |
| end // case: StErase |
| |
| // The done can actually be signaled back in `StErase`, but move it |
| // to a different state to better model the ack_o/done_o timing separation |
| StErSuspend: begin |
| done_o = 1'b1; |
| pop_cmd = 1'b1; |
| time_cnt_clr = 1'b1; |
| index_cnt_clr = 1'b1; |
| st_d = StIdle; |
| end |
| |
| |
| default: begin |
| st_d = StIdle; |
| end |
| |
| endcase // unique case (st_q) |
| |
| // Emulate power down and power loss behavior |
| if (!flash_power_ready_h_i || flash_power_down_h_i) begin |
| st_d = StReset; |
| end |
| |
| end // always_comb |
| |
| logic [DataWidth-1:0] rd_data_main, rd_data_info; |
| logic [InfoTypes-1:0][DataWidth-1:0] rd_nom_data_info; |
| |
| // data memory is requested whenver it's a transaction targetted at the data partition |
| // OR if it's a bank erase |
| logic data_mem_req; |
| assign data_mem_req = mem_req & |
| (mem_part == flash_ctrl_pkg::FlashPartData | |
| mem_bk_erase); |
| |
| prim_ram_1p #( |
| .Width(DataWidth), |
| .Depth(WordsPerBank), |
| .DataBitsPerMask(DataWidth) |
| ) u_mem ( |
| .clk_i, |
| .req_i (data_mem_req), |
| .write_i (mem_wr), |
| .addr_i (mem_addr), |
| .wdata_i (mem_wdata), |
| .wmask_i ({DataWidth{1'b1}}), |
| .rdata_o (rd_data_main), |
| .cfg_i ('0) |
| ); |
| |
| for (genvar info_type = 0; info_type < InfoTypes; info_type++) begin : gen_info_types |
| |
| // when info partitions are selected for bank erase, all info types are erased. |
| // if NOT bank erase, then only the selected info partition is erased |
| logic info_mem_req; |
| assign info_mem_req = mem_req & |
| (mem_part == flash_ctrl_pkg::FlashPartInfo) & |
| ((mem_info_sel == info_type) | mem_bk_erase); |
| |
| prim_ram_1p #( |
| .Width(DataWidth), |
| .Depth(WordsPerInfoBank), |
| .DataBitsPerMask(DataWidth) |
| ) u_info_mem ( |
| .clk_i, |
| .req_i (info_mem_req), |
| .write_i (mem_wr), |
| .addr_i (mem_addr[0 +: InfoAddrW]), |
| .wdata_i (mem_wdata), |
| .wmask_i ({DataWidth{1'b1}}), |
| .rdata_o (rd_nom_data_info[info_type]), |
| .cfg_i ('0) |
| ); |
| end |
| |
| assign rd_data_info = rd_nom_data_info[info_sel_q]; |
| assign rd_data_d = rd_part_q == flash_ctrl_pkg::FlashPartData ? rd_data_main : rd_data_info; |
| |
| flash_ctrl_pkg::flash_prog_e unused_prog_type; |
| assign unused_prog_type = cmd_q.prog_type; |
| |
| logic unused_he; |
| assign unused_he = he_i; |
| |
| endmodule // prim_generic_flash |