blob: 46d86c4f1c5879867c0613eeef358393d73a04d3 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Faux Flash Controller Module
//
//
module flash_ctrl (
input clk_i,
input rst_ni,
// Bus Interface
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// Flash Interface
input flash_ctrl_pkg::flash_m2c_t flash_i,
output flash_ctrl_pkg::flash_c2m_t flash_o,
// Interrupts
output logic intr_prog_empty_o, // Program fifo is empty
output logic intr_prog_lvl_o, // Program fifo is empty
output logic intr_rd_full_o, // Read fifo is full
output logic intr_rd_lvl_o, // Read fifo is full
output logic intr_op_done_o, // Requested flash operation (wr/erase) done
output logic intr_op_error_o // Requested flash operation (wr/erase) done
);
import flash_ctrl_pkg::*;
import flash_ctrl_reg_pkg::*;
localparam int NumBanks = top_pkg::FLASH_BANKS;
localparam int PagesPerBank = top_pkg::FLASH_PAGES_PER_BANK;
localparam int WordsPerPage = top_pkg::FLASH_WORDS_PER_PAGE;
localparam int BankW = top_pkg::FLASH_BKW;
localparam int PageW = top_pkg::FLASH_PGW;
localparam int WordW = top_pkg::FLASH_WDW;
localparam int AllPagesW = BankW + PageW;
localparam int AddrW = top_pkg::FLASH_AW;
localparam int DataWidth = top_pkg::FLASH_DW;
localparam int DataBitWidth = $clog2(DataWidth/8);
localparam int EraseBitWidth = $bits(flash_erase_op_e);
localparam int FifoDepth = 16;
localparam int FifoDepthW = $clog2(FifoDepth+1);
localparam int MpRegions = 8;
flash_ctrl_reg2hw_t reg2hw;
flash_ctrl_hw2reg_t hw2reg;
tlul_pkg::tl_h2d_t tl_fifo_h2d [2];
tlul_pkg::tl_d2h_t tl_fifo_d2h [2];
// Register module
flash_ctrl_reg_top u_reg (
.clk_i,
.rst_ni,
.tl_i,
.tl_o,
.tl_win_o (tl_fifo_h2d),
.tl_win_i (tl_fifo_d2h),
.reg2hw,
.hw2reg,
.devmode_i (1'b1)
);
// FIFO Connections
logic prog_fifo_wready;
logic prog_fifo_rvalid;
logic prog_fifo_req;
logic prog_fifo_wen;
logic prog_fifo_ren;
logic [DataWidth-1:0] prog_fifo_wdata;
logic [DataWidth-1:0] prog_fifo_rdata;
logic [FifoDepthW-1:0] prog_fifo_depth;
logic rd_fifo_wready;
logic rd_fifo_rvalid;
logic rd_fifo_wen;
logic rd_fifo_ren;
logic [DataWidth-1:0] rd_fifo_wdata;
logic [DataWidth-1:0] rd_fifo_rdata;
logic [FifoDepthW-1:0] rd_fifo_depth;
// Program Control Connections
logic prog_flash_req;
logic prog_flash_ovfl;
logic [AddrW-1:0] prog_flash_addr;
// Read Control Connections
logic rd_flash_req;
logic rd_flash_ovfl;
logic [AddrW-1:0] rd_flash_addr;
// Erase Control Connections
logic erase_flash_req;
logic [AddrW-1:0] erase_flash_addr;
logic [EraseBitWidth-1:0] erase_flash_type;
// Done / Error signaling from ctrl modules
logic [2:0] ctrl_done, ctrl_err;
// Flash Memory Protection Connections
logic flash_req;
logic flash_rd_done, flash_prog_done, flash_erase_done;
logic flash_error;
logic [AddrW-1:0] flash_addr;
logic [DataWidth-1:0] flash_prog_data;
logic [DataWidth-1:0] flash_rd_data;
logic init_busy;
logic rd_op;
logic prog_op;
logic erase_op;
logic [AllPagesW-1:0] err_page;
logic [BankW-1:0] err_bank;
assign rd_op = reg2hw.control.op.q == FlashRead;
assign prog_op = reg2hw.control.op.q == FlashProg;
assign erase_op = reg2hw.control.op.q == FlashErase;
// Program FIFO
// Since the program and read FIFOs are never used at the same time, it should really be one
// FIFO with muxed inputs and outputs. This should be addressed once the flash integration
// strategy has been identified
tlul_adapter_sram #(
.SramAw(1), //address unused
.SramDw(DataWidth),
.ByteAccess(0), //flash may not support byte access
.ErrOnRead(1) //reads not supported
) u_to_prog_fifo (
.clk_i,
.rst_ni,
.tl_i (tl_fifo_h2d[0]),
.tl_o (tl_fifo_d2h[0]),
.req_o (prog_fifo_req),
.gnt_i (prog_fifo_wready),
.we_o (prog_fifo_wen),
.addr_o (),
.wmask_o (),
.wdata_o (prog_fifo_wdata),
.rdata_i (DataWidth'(0)),
.rvalid_i (1'b0),
.rerror_i (2'b0)
);
prim_fifo_sync #(
.Width(DataWidth),
.Depth(FifoDepth)
) u_prog_fifo (
.clk_i,
.rst_ni (rst_ni & ~reg2hw.control.fifo_rst.q),
.wvalid (prog_fifo_req & prog_fifo_wen),
.wready (prog_fifo_wready),
.wdata (prog_fifo_wdata),
.depth (prog_fifo_depth),
.rvalid (prog_fifo_rvalid),
.rready (prog_fifo_ren),
.rdata (prog_fifo_rdata)
);
// Program handler is consumer of prog_fifo
flash_prog_ctrl #(
.DataW(DataWidth),
.AddrW(AddrW)
) u_flash_prog_ctrl (
.clk_i,
.rst_ni,
// Software Interface
.op_start_i (reg2hw.control.start.q & prog_op),
.op_num_words_i (reg2hw.control.num.q),
.op_done_o (ctrl_done[0]),
.op_err_o (ctrl_err[0]),
.op_addr_i (reg2hw.addr.q[DataBitWidth +: AddrW]),
// FIFO Interface
.data_i (prog_fifo_rdata),
.data_rdy_i (prog_fifo_rvalid),
.data_rd_o (prog_fifo_ren),
// Flash Macro Interface
.flash_req_o (prog_flash_req),
.flash_addr_o (prog_flash_addr),
.flash_ovfl_o (prog_flash_ovfl),
.flash_data_o (flash_prog_data),
.flash_done_i (flash_prog_done),
.flash_error_i (flash_error)
);
// Read FIFO
logic adapter_rvalid;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
adapter_rvalid <= 1'b0;
end else begin
adapter_rvalid <= rd_fifo_ren && rd_fifo_rvalid;
end
end
tlul_adapter_sram #(
.SramAw(1), //address unused
.SramDw(DataWidth),
.ByteAccess(0), //flash may not support byte access
.ErrOnWrite(1) //writes not supported
) u_to_rd_fifo (
.clk_i,
.rst_ni,
.tl_i (tl_fifo_h2d[1]),
.tl_o (tl_fifo_d2h[1]),
.req_o (rd_fifo_ren),
.gnt_i (rd_fifo_rvalid),
.we_o (),
.addr_o (),
.wmask_o (),
.wdata_o (),
.rdata_i (rd_fifo_rdata),
.rvalid_i (adapter_rvalid),
.rerror_i (2'b0)
);
prim_fifo_sync #(
.Width(DataWidth),
.Depth(FifoDepth)
) u_rd_fifo (
.clk_i,
.rst_ni (rst_ni & ~reg2hw.control.fifo_rst.q),
.wvalid (rd_fifo_wen),
.wready (rd_fifo_wready),
.wdata (rd_fifo_wdata),
.depth (rd_fifo_depth),
.rvalid (rd_fifo_rvalid),
//adapter_rvalid is used here because adapter_sram does not accept data the same cycle.
//It expects an sram like interface where data arrives during the next cycle
.rready (adapter_rvalid),
.rdata (rd_fifo_rdata)
);
// Read handler is consumer of rd_fifo
flash_rd_ctrl #(
.DataW(DataWidth),
.AddrW(AddrW)
) u_flash_rd_ctrl (
.clk_i,
.rst_ni,
// Software Interface
.op_start_i (reg2hw.control.start.q & rd_op),
.op_num_words_i (reg2hw.control.num.q),
.op_done_o (ctrl_done[1]),
.op_err_o (ctrl_err[1]),
.op_addr_i (reg2hw.addr.q[DataBitWidth +: AddrW]),
// FIFO Interface
.data_rdy_i (rd_fifo_wready),
.data_o (rd_fifo_wdata),
.data_wr_o (rd_fifo_wen),
// Flash Macro Interface
.flash_req_o (rd_flash_req),
.flash_addr_o (rd_flash_addr),
.flash_ovfl_o (rd_flash_ovfl),
.flash_data_i (flash_rd_data),
.flash_done_i (flash_rd_done),
.flash_error_i (flash_error)
);
// Erase handler does not consume fifo
flash_erase_ctrl #(
.AddrW(AddrW),
.PagesPerBank(PagesPerBank),
.WordsPerPage(WordsPerPage),
.EraseBitWidth(EraseBitWidth)
) u_flash_erase_ctrl (
// Software Interface
.op_start_i (reg2hw.control.start.q & erase_op),
.op_type_i (reg2hw.control.erase_sel.q),
.op_done_o (ctrl_done[2]),
.op_err_o (ctrl_err[2]),
.op_addr_i (reg2hw.addr.q[DataBitWidth +: AddrW]),
// Flash Macro Interface
.flash_req_o (erase_flash_req),
.flash_addr_o (erase_flash_addr),
.flash_op_o (erase_flash_type),
.flash_done_i (flash_erase_done),
.flash_error_i (flash_error)
);
// Final muxing to flash macro module
always_comb begin
unique case (reg2hw.control.op.q)
FlashRead: begin
flash_req = rd_flash_req;
flash_addr = rd_flash_addr;
end
FlashProg: begin
flash_req = prog_flash_req;
flash_addr = prog_flash_addr;
end
FlashErase: begin
flash_req = erase_flash_req;
flash_addr = erase_flash_addr;
end
default: begin
flash_req = 1'b0;
flash_addr = '0;
end
endcase // unique case (flash_op_e'(reg2hw.control.op.q))
end
// extra region is the default region
flash_mp_region_t region_cfgs[MpRegions+1];
logic [NumBanks-1:0] bank_cfgs;
// TODO: reuse generated type from reg_pkg?
for (genvar k = 0; k < NumBanks; k++) begin : gen_mp_bank_cfgs
assign bank_cfgs[k] = reg2hw.mp_bank_cfg[k].q;
end
for (genvar k = 0; k <= MpRegions; k++) begin : gen_mp_region_cfg
if (k == MpRegions) begin : gen_last_region
assign region_cfgs[k].base_page = '0;
assign region_cfgs[k].size = {AllPagesW{1'b1}};
assign region_cfgs[k].en = 1'b1;
assign region_cfgs[k].rd_en = reg2hw.default_region.rd_en.q;
assign region_cfgs[k].prog_en = reg2hw.default_region.prog_en.q;
assign region_cfgs[k].erase_en = reg2hw.default_region.erase_en.q;
end else begin : gen_region
assign region_cfgs[k].base_page = reg2hw.mp_region_cfg[k].base.q;
assign region_cfgs[k].size = reg2hw.mp_region_cfg[k].size.q;
assign region_cfgs[k].en = reg2hw.mp_region_cfg[k].en.q;
assign region_cfgs[k].rd_en = reg2hw.mp_region_cfg[k].rd_en.q;
assign region_cfgs[k].prog_en = reg2hw.mp_region_cfg[k].prog_en.q;
assign region_cfgs[k].erase_en = reg2hw.mp_region_cfg[k].erase_en.q;
end
end
// Flash memory protection
flash_mp #(
.MpRegions(MpRegions),
.NumBanks(NumBanks),
.AllPagesW(AllPagesW)
) u_flash_mp (
.clk_i,
.rst_ni,
// sw configuration
.region_cfgs_i(region_cfgs),
.bank_cfgs_i(bank_cfgs),
// read / prog / erase controls
.req_i(flash_req),
.req_addr_i(flash_addr[WordW +: AllPagesW]),
.addr_ovfl_i(rd_flash_ovfl | prog_flash_ovfl),
.req_bk_i(flash_addr[WordW + PageW +: BankW]),
.rd_i(rd_op),
.prog_i(prog_op),
.pg_erase_i(erase_op & (erase_flash_type == PageErase)),
.bk_erase_i(erase_op & (erase_flash_type == BankErase)),
.rd_done_o(flash_rd_done),
.prog_done_o(flash_prog_done),
.erase_done_o(flash_erase_done),
.error_o(flash_error),
.err_addr_o(err_page),
.err_bank_o(err_bank),
// flash phy interface
.req_o(flash_o.req),
.rd_o(flash_o.rd),
.prog_o(flash_o.prog),
.pg_erase_o(flash_o.pg_erase),
.bk_erase_o(flash_o.bk_erase),
.rd_done_i(flash_i.rd_done),
.prog_done_i(flash_i.prog_done),
.erase_done_i(flash_i.erase_done)
);
assign hw2reg.op_status.done.d = 1'b1;
assign hw2reg.op_status.done.de = |ctrl_done;
assign hw2reg.op_status.err.d = 1'b1;
assign hw2reg.op_status.err.de = |ctrl_err;
assign hw2reg.status.rd_full.d = ~rd_fifo_wready;
assign hw2reg.status.rd_empty.d = ~rd_fifo_rvalid;
assign hw2reg.status.prog_full.d = ~prog_fifo_wready;
assign hw2reg.status.prog_empty.d = ~prog_fifo_rvalid;
assign hw2reg.status.init_wip.d = init_busy;
assign hw2reg.status.error_page.d = err_page;
assign hw2reg.status.error_bank.d = err_bank;
assign hw2reg.control.start.d = 1'b0;
assign hw2reg.control.start.de = |ctrl_done;
// Flash Interface
assign flash_o.addr = flash_addr;
assign flash_o.prog_data = flash_prog_data;
assign flash_rd_data = flash_i.rd_data;
assign init_busy = flash_i.init_busy;
// Interrupts
// Generate edge triggered signals for sources that are level
logic [3:0] intr_src;
logic [3:0] intr_src_q;
logic [3:0] intr_assert;
assign intr_src = { ~prog_fifo_rvalid,
reg2hw.fifo_lvl.prog.q == prog_fifo_depth,
~rd_fifo_wready,
reg2hw.fifo_lvl.rd.q == rd_fifo_depth
};
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
intr_src_q <= 'h0;
end else begin
intr_src_q <= intr_src;
end
end
assign intr_assert = ~intr_src_q & intr_src;
assign intr_prog_empty_o = reg2hw.intr_enable.prog_empty.q & reg2hw.intr_state.prog_empty.q;
assign intr_prog_lvl_o = reg2hw.intr_enable.prog_lvl.q & reg2hw.intr_state.prog_lvl.q;
assign intr_rd_full_o = reg2hw.intr_enable.rd_full.q & reg2hw.intr_state.rd_full.q;
assign intr_rd_lvl_o = reg2hw.intr_enable.rd_lvl.q & reg2hw.intr_state.rd_lvl.q;
assign intr_op_done_o = reg2hw.intr_enable.op_done.q & reg2hw.intr_state.op_done.q;
assign intr_op_error_o = reg2hw.intr_enable.op_error.q & reg2hw.intr_state.op_error.q;
assign hw2reg.intr_state.prog_empty.d = 1'b1;
assign hw2reg.intr_state.prog_empty.de = intr_assert[3] |
(reg2hw.intr_test.prog_empty.qe &
reg2hw.intr_test.prog_empty.q);
assign hw2reg.intr_state.prog_lvl.d = 1'b1;
assign hw2reg.intr_state.prog_lvl.de = intr_assert[2] |
(reg2hw.intr_test.prog_lvl.qe &
reg2hw.intr_test.prog_lvl.q);
assign hw2reg.intr_state.rd_full.d = 1'b1;
assign hw2reg.intr_state.rd_full.de = intr_assert[1] |
(reg2hw.intr_test.rd_full.qe &
reg2hw.intr_test.rd_full.q);
assign hw2reg.intr_state.rd_lvl.d = 1'b1;
assign hw2reg.intr_state.rd_lvl.de = intr_assert[0] |
(reg2hw.intr_test.rd_lvl.qe &
reg2hw.intr_test.rd_lvl.q);
assign hw2reg.intr_state.op_done.d = 1'b1;
assign hw2reg.intr_state.op_done.de = |ctrl_done |
(reg2hw.intr_test.op_done.qe &
reg2hw.intr_test.op_done.q);
assign hw2reg.intr_state.op_error.d = 1'b1;
assign hw2reg.intr_state.op_error.de = |ctrl_err |
(reg2hw.intr_test.op_error.qe &
reg2hw.intr_test.op_error.q);
// Unused bits
logic [DataBitWidth-1:0] unused_byte_sel;
logic [31-AddrW:0] unused_higher_addr_bits;
logic [31:0] unused_scratch;
// Unused signals
assign unused_byte_sel = reg2hw.addr.q[DataBitWidth-1:0];
assign unused_higher_addr_bits = reg2hw.addr.q[31:AddrW];
assign unused_scratch = reg2hw.scratch;
// Assertions
`ASSERT_KNOWN(TlDValidKnownO_A, tl_o.d_valid, clk_i, !rst_ni)
`ASSERT_KNOWN(TlAReadyKnownO_A, tl_o.a_ready, clk_i, !rst_ni)
`ASSERT_KNOWN(FlashKnownO_A, flash_o, clk_i, !rst_ni)
`ASSERT_KNOWN(IntrProgEmptyKnownO_A, intr_prog_empty_o, clk_i, !rst_ni)
`ASSERT_KNOWN(IntrProgLvlKnownO_A, intr_prog_lvl_o, clk_i, !rst_ni)
`ASSERT_KNOWN(IntrProgRdFullKnownO_A, intr_rd_full_o, clk_i, !rst_ni)
`ASSERT_KNOWN(IntrRdLvlKnownO_A, intr_rd_lvl_o, clk_i, !rst_ni)
`ASSERT_KNOWN(IntrOpDoneKnownO_A, intr_op_done_o, clk_i, !rst_ni)
`ASSERT_KNOWN(IntrOpErrorKnownO_A, intr_op_error_o, clk_i, !rst_ni)
endmodule