|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  | // | 
|  | // prim flash module - Emulated using memory | 
|  | // | 
|  |  | 
|  | module prim_generic_flash #( | 
|  | parameter int PagesPerBank = 256, // pages per bank | 
|  | parameter int WordsPerPage = 256, // words per page | 
|  | parameter int DataWidth   = 32,   // bits per word | 
|  | parameter bit SkipInit = 1,       // this is an option to reset flash to all F's at reset | 
|  |  | 
|  | //Do not touch - Derived parameters | 
|  | localparam int PageW = $clog2(PagesPerBank), | 
|  | localparam int WordW = $clog2(WordsPerPage), | 
|  | localparam int AddrW = PageW + WordW | 
|  | ) ( | 
|  | input                        clk_i, | 
|  | input                        rst_ni, | 
|  | input                        req_i, | 
|  | input                        host_req_i, | 
|  | input [AddrW-1:0]            host_addr_i, | 
|  | input                        rd_i, | 
|  | input                        prog_i, | 
|  | input                        pg_erase_i, | 
|  | input                        bk_erase_i, | 
|  | input [AddrW-1:0]            addr_i, | 
|  | input [DataWidth-1:0]        prog_data_i, | 
|  | output logic                 host_req_rdy_o, | 
|  | output logic                 host_req_done_o, | 
|  | output logic                 rd_done_o, | 
|  | output logic                 prog_done_o, | 
|  | output logic                 erase_done_o, | 
|  | output logic [DataWidth-1:0] rd_data_o, | 
|  | output logic                 init_busy_o | 
|  | ); | 
|  |  | 
|  | // Emulated flash macro values | 
|  | localparam int ReadCycles = 1; | 
|  | localparam int ProgCycles = 50; | 
|  | localparam int PgEraseCycles = 200; | 
|  | localparam int BkEraseCycles = 2000; | 
|  |  | 
|  | // Locally derived values | 
|  | localparam int WordsPerBank  = PagesPerBank * WordsPerPage; | 
|  |  | 
|  | typedef enum logic [2:0] { | 
|  | StReset    = 'h0, | 
|  | StInit     = 'h1, | 
|  | StIdle     = 'h2, | 
|  | StHostRead = 'h3, | 
|  | StRead     = 'h4, | 
|  | StProg     = 'h5, | 
|  | StErase    = 'h6 | 
|  | } state_e; | 
|  |  | 
|  | state_e st_next, st; | 
|  |  | 
|  | 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, index_limit_next; | 
|  | logic [31:0]              time_limit, time_limit_next; | 
|  | logic                     prog_pend, prog_pend_next; | 
|  | logic                     mem_req; | 
|  | logic                     mem_wr; | 
|  | logic [AddrW-1:0]         mem_addr; | 
|  | logic [DataWidth-1:0]     held_data; | 
|  | logic [DataWidth-1:0]     mem_wdata; | 
|  | logic                     hold_rd_cmd; | 
|  | logic [AddrW-1:0]         held_rd_addr; | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) st <= StReset; | 
|  | else st <= st_next; | 
|  | end | 
|  |  | 
|  | always_ff @(posedge clk_i or negedge rst_ni) begin | 
|  | if (!rst_ni) held_rd_addr <= '0; | 
|  | else if (hold_rd_cmd) held_rd_addr <= host_addr_i; | 
|  | end | 
|  |  | 
|  | // prog_pend 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 <= 32'h0; | 
|  | index_cnt <= 32'h0; | 
|  | time_limit <= 32'h0; | 
|  | index_limit <= 32'h0; | 
|  | held_data <= 'h0; | 
|  | prog_pend <= 1'h0; | 
|  | end else begin | 
|  |  | 
|  | time_limit <= time_limit_next; | 
|  | index_limit <= index_limit_next; | 
|  | prog_pend <= prog_pend_next; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | if (prog_pend) held_data <= rd_data_o; | 
|  |  | 
|  | end | 
|  | end | 
|  |  | 
|  |  | 
|  | always_comb begin | 
|  | st_next          = st; | 
|  | index_limit_next = index_limit; | 
|  | time_limit_next  = time_limit; | 
|  | prog_pend_next   = prog_pend; | 
|  | mem_req          = 'h0; | 
|  | mem_wr           = 'h0; | 
|  | mem_addr         = 'h0; | 
|  | mem_wdata        = 'h0; | 
|  | time_cnt_inc     = 1'h0; | 
|  | time_cnt_clr     = 1'h0; | 
|  | time_cnt_set1    = 1'h0; | 
|  | index_cnt_inc    = 1'h0; | 
|  | index_cnt_clr    = 1'h0; | 
|  | rd_done_o        = 1'h0; | 
|  | prog_done_o      = 1'h0; | 
|  | erase_done_o     = 1'h0; | 
|  | init_busy_o      = 1'h0; | 
|  | host_req_rdy_o   = 1'h1; | 
|  | host_req_done_o  = 1'h0; | 
|  | hold_rd_cmd      = 1'h0; | 
|  |  | 
|  | unique case (st) | 
|  | StReset: begin | 
|  | host_req_rdy_o = 1'b0; | 
|  | init_busy_o = 1'h1; | 
|  | st_next = StInit; | 
|  | end | 
|  | // Emulate flash power up to all 1's | 
|  | // This implies this flash will not survive a reset | 
|  | // Might need a different RESET for FPGA purposes | 
|  | StInit: begin | 
|  | host_req_rdy_o = 1'b0; | 
|  | init_busy_o = 1'h1; | 
|  | if (index_cnt < WordsPerBank && !SkipInit) begin | 
|  | st_next = StInit; | 
|  | index_cnt_inc = 1'b1; | 
|  | mem_req = 1'h0; | 
|  | mem_wr  = 1'h0; | 
|  | mem_addr = index_cnt[AddrW-1:0]; | 
|  | mem_wdata = {DataWidth{1'b1}}; | 
|  | end else begin | 
|  | st_next = StIdle; | 
|  | index_cnt_clr = 1'b1; | 
|  | end | 
|  | end | 
|  | StIdle: begin | 
|  | // host reads will always take priority over controller operations.  However ongoing | 
|  | // controller operations will not be interrupted | 
|  | if (host_req_i) begin | 
|  | // reads begin immediately | 
|  | hold_rd_cmd = 1'b1; | 
|  | mem_addr = host_addr_i; | 
|  | mem_req = 1'b1; | 
|  | time_cnt_inc = 1'b1; | 
|  | st_next = StHostRead; | 
|  | end else if (req_i && rd_i) begin | 
|  | st_next = StRead; | 
|  | end else if (req_i && prog_i) begin | 
|  | st_next = StRead; | 
|  | prog_pend_next = 1'b1; | 
|  | end else if (req_i && pg_erase_i) begin | 
|  | st_next = StErase; | 
|  | index_limit_next = WordsPerPage; | 
|  | time_limit_next = PgEraseCycles; | 
|  | end else if (req_i && bk_erase_i) begin | 
|  | st_next = StErase; | 
|  | index_limit_next = WordsPerBank; | 
|  | time_limit_next = BkEraseCycles; | 
|  | end | 
|  | end | 
|  | StHostRead: begin | 
|  | mem_addr = held_rd_addr; | 
|  | if (time_cnt < ReadCycles) begin | 
|  | mem_req = 1'b1; | 
|  | time_cnt_inc = 1'b1; | 
|  | host_req_rdy_o = 1'b0; | 
|  | end else begin | 
|  | host_req_done_o = 1'b1; //finish up transaction | 
|  |  | 
|  | // if another request already pending | 
|  | if (host_req_i) begin | 
|  | hold_rd_cmd = 1'b1; | 
|  | mem_addr = host_addr_i; | 
|  | mem_req = 1'b1; | 
|  | time_cnt_set1 = 1'b1; | 
|  | st_next = StHostRead; | 
|  | end else begin | 
|  | time_cnt_clr = 1'b1; | 
|  | st_next = StIdle; | 
|  | end | 
|  | end | 
|  | end | 
|  | StRead: begin | 
|  | host_req_rdy_o = 1'b0; | 
|  | mem_addr = addr_i; | 
|  | if (time_cnt < ReadCycles) begin | 
|  | mem_req = 1'b1; | 
|  | time_cnt_inc = 1'b1; | 
|  | end else begin | 
|  | prog_pend_next = 1'b0; | 
|  | rd_done_o  = 1'b1; | 
|  | time_cnt_clr = 1'b1; | 
|  | st_next = prog_pend ? StProg : StIdle; | 
|  | end | 
|  | end | 
|  | StProg: begin | 
|  | host_req_rdy_o = 1'b0; | 
|  | mem_addr = addr_i; | 
|  |  | 
|  | // if data is already 0, cannot program to 1 without erase | 
|  | mem_wdata = prog_data_i & held_data; | 
|  | if (time_cnt < ProgCycles) begin | 
|  | mem_req = 1'b1; | 
|  | mem_wr = 1'b1; | 
|  | time_cnt_inc = 1'b1; | 
|  | end else begin | 
|  | st_next = StIdle; | 
|  | prog_done_o  = 1'b1; | 
|  | time_cnt_clr = 1'b1; | 
|  | end | 
|  | end | 
|  | StErase: begin | 
|  | host_req_rdy_o = 1'b0; | 
|  |  | 
|  | // Actual erasing of the page | 
|  | if (index_cnt < index_limit || time_cnt < time_limit) begin | 
|  | mem_req = 1'b1; | 
|  | mem_wr = 1'b1; | 
|  | mem_wdata = {DataWidth{1'b1}}; | 
|  |  | 
|  | mem_addr = addr_i + index_cnt[AddrW-1:0]; | 
|  | time_cnt_inc = (time_cnt < time_limit); | 
|  | index_cnt_inc = (index_cnt < index_limit); | 
|  | end else begin | 
|  | st_next = StIdle; | 
|  | erase_done_o = 1'b1; | 
|  | time_cnt_clr = 1'b1; | 
|  | index_cnt_clr = 1'b1; | 
|  | end | 
|  | end | 
|  | default: begin | 
|  | host_req_rdy_o = 1'b0; | 
|  | st_next = StIdle; | 
|  | end | 
|  | endcase // unique case (st) | 
|  | end // always_comb | 
|  |  | 
|  | prim_ram_1p #( | 
|  | .Width(DataWidth), | 
|  | .Depth(WordsPerBank), | 
|  | .DataBitsPerMask(DataWidth) | 
|  | ) u_mem ( | 
|  | .clk_i, | 
|  | .rst_ni, | 
|  | .req_i    (mem_req), | 
|  | .write_i  (mem_wr), | 
|  | .addr_i   (mem_addr), | 
|  | .wdata_i  (mem_wdata), | 
|  | .wmask_i  ({DataWidth{1'b1}}), | 
|  | .rvalid_o (), | 
|  | .rdata_o  (rd_data_o) | 
|  | ); | 
|  |  | 
|  |  | 
|  |  | 
|  | endmodule // prim_generic_flash |