blob: d194e614f070a56e25014404e936e96bf31dfa67 [file] [log] [blame]
// 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