blob: 9ed71220d49b6b115cc03444414cbb19a7b4c684 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// SPI Flash Read Status handler
/*
*/
`include "prim_assert.sv"
module spid_status
import spi_device_pkg::*;
#(
// Read Status module recognizes the command by the cmd_info_idx from the
// cmdparse logic as the Opcode of the command could vary. Use the index and
// determines the return order of the status register.
parameter int unsigned StatusCmdIdx[3] = '{
spi_device_pkg::CmdInfoReadStatus1,
spi_device_pkg::CmdInfoReadStatus2,
spi_device_pkg::CmdInfoReadStatus3
},
parameter int unsigned StatusW = 24
) (
input clk_i,
input rst_ni,
input clk_out_i, // Output clock (inverted SCK)
input clk_csb_i, // CSb clock source
input sys_clk_i, // Handling STATUS CSR (ext type)
input sys_rst_ni,
input sys_csb_deasserted_pulse_i, // to latch committed status
// status register from CSR: sys_clk domain
// bit [ 0]: RW0C by SW / W1S by HW
// bit [StatusW-1:1]: RW
input sys_status_we_i,
input logic [StatusW-1:0] sys_status_i,
output logic [StatusW-1:0] sys_status_o, // sys_clk domain
// from cmdparse
input sel_datapath_e sel_dp_i,
input cmd_info_t cmd_info_i,
input logic [CmdInfoIdxW-1:0] cmd_info_idx_i,
output logic outclk_p2s_valid_o,
output spi_byte_t outclk_p2s_byte_o,
input logic outclk_p2s_sent_i,
output io_mode_e io_mode_o,
// receives the busy from other HW
input inclk_busy_set_i, // SCK domain
// WREN set/ clear
input inclk_we_set_i,
input inclk_we_clr_i,
// indicator of busy for other HW. Mainly to block passthrough
output logic csb_busy_broadcast_o // SCK domain
);
///////////////
// Temporary //
///////////////
logic unused_cmd_info;
assign unused_cmd_info = ^cmd_info_i;
logic unused_p2s_sent;
assign unused_p2s_sent = outclk_p2s_sent_i;
assign io_mode_o = SingleIO;
typedef enum logic {
StIdle,
StActive
} st_e;
st_e st_q, st_d;
typedef enum int unsigned {
BitBusy = 0, // BUSY bit [0]
BitWe = 1 // WEL bit [1]
} status_bit_e;
////////////
// Signal //
////////////
logic [StatusW-1:0] sck_status_committed;
logic [StatusW-1:0] sck_status_staged;
logic [StatusW-1:0] sck_sw_status;
logic p2s_valid_inclk;
spi_byte_t p2s_byte_inclk;
// FIFO rvalid , rready
logic sck_sw_we, sck_sw_ack;
////////////////////////////
// Status CSR (incl. CDC) //
////////////////////////////
//
// Flash mode STATUS register is implemented in this module rather than
// relying on the regtool. The reason is that the STATUS read by the SPI
// host system. The value should be propagated into SCK domain. Due to the
// lack of SCK while CSb is de-asserted, it needs special cares to safely
// used in SCK.
//
// Before returning the STATUS register value to the host system
// corresponding to the Read Status commands (05h, 35h, 15h), the logic can
// get 8 SCK clock edges. The logic synchronizes CSb into SCK domain first.
// Then create a pulse to latch the STATUS register in SCK domain.
//
// If a command is uploaded (handled by spid_upload), it sets BUSY bit to 1.
// The value is latched in the SCK domain first. Then, when CSb is
// de-asserted, the logic synchronizes CSb into the bus clock domain to
// create a pulse signal. That pulse signal will latch the STATUS register
// from SCK domain into the bus clock domain.
//
// The STATUS register in the bus clock domain can be updated only when CSb
// is not asserted in order to prevent any CDC issue. The safest way is to
// hand the busclock synched CSb signal over to SCK clock domain again but
// it may not be possible to latch the register within the 8th posedge of
// the SCK if the bus clock is slow.
//
// BUSY is set by HW. The value is not directly broadcasted to the
// passthrough module. It is, first, applied into the bus clock domain. Then
// the signal is broadcasted to Passthrough to filter-out the following
// commands until the BUSY signal is released.
// Design Doc
// https://docs.google.com/document/d/1wUIynMYVfVg9HmCL0q5-6r9BuN-XM0z--wGqU0bXRQ0
// assumes BUSY bit as 0
`ASSERT_INIT(BusyBitZero_A, BitBusy == 0)
// BUSY status in SCK
always_ff @(posedge clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
sck_status_staged[BitBusy] <= 1'b 0;
end else if (sck_sw_we && (sck_sw_status[BitBusy] == 1'b 0)) begin
sck_status_staged[BitBusy] <= 1'b 0;
end else if (inclk_busy_set_i) begin
sck_status_staged[BitBusy] <= 1'b 1;
end
end
// WEL handling
always_ff @(posedge clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
sck_status_staged[BitWe] <= 1'b 0;
end else if (inclk_we_set_i) begin
sck_status_staged[BitWe] <= 1'b 1;
end else if (inclk_we_clr_i) begin
sck_status_staged[BitWe] <= 1'b 0;
end else if (sck_sw_we) begin
sck_status_staged[BitWe] <= sck_sw_status[BitWe];
end
end
// Rest of Status
always_ff @(posedge clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
sck_status_staged[StatusW-1:BitWe+1] <= '0;
end else if (sck_sw_we) begin
sck_status_staged[StatusW-1:BitWe+1] <= sck_sw_status[StatusW-1:BitWe+1];
end
end
// Staged to Comitted at CSb de-assertion
prim_flop #(
.Width ($bits(sck_status_staged)),
.ResetValue ('0)
) u_stage_to_commit (
.clk_i (clk_csb_i),
.rst_ni (sys_rst_ni),
.d_i (sck_status_staged),
.q_o (sck_status_committed)
);
// busy_broadcast
assign csb_busy_broadcast_o = sck_status_committed[BitBusy];
// Status in SCK
assign sck_sw_ack = 1'b 1; // always accept when clock is valid.
prim_fifo_async #(
.Width (StatusW),
.Depth (2),
.OutputZeroIfEmpty (1'b 1)
) u_sw_status_update_sync (
.clk_wr_i (sys_clk_i ),
.rst_wr_ni (sys_rst_ni ),
.wvalid_i (sys_status_we_i),
.wready_o ( ), // ignore
.wdata_i (sys_status_i ),
.wdepth_o ( ),
.clk_rd_i (clk_i ),
.rst_rd_ni (sys_rst_ni ),
.rvalid_o (sck_sw_we ),
.rready_i (sck_sw_ack ),
.rdata_o (sck_sw_status),
.rdepth_o ( )
);
// Committed to SYS clk
// Update with csb release event (pulse), which always will be delayed two
// SYS cycles. Then it is safe to use comitted register.
always_ff @(posedge sys_clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
sys_status_o <= '0;
end else if (sys_csb_deasserted_pulse_i) begin
sys_status_o <= sck_status_committed;
end
end
/////////////////
// Data Return //
/////////////////
// Latch in clk_out
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) begin
outclk_p2s_valid_o <= 1'b 0;
outclk_p2s_byte_o <= '0;
end else begin
outclk_p2s_valid_o <= p2s_valid_inclk;
outclk_p2s_byte_o <= p2s_byte_inclk;
end
end
// cmd_idx to data selector
logic [1:0] byte_sel_d, byte_sel_q;
logic byte_sel_update, byte_sel_inc;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
byte_sel_q <= 2'b 00;
end else begin
byte_sel_q <= byte_sel_d;
end
end
always_comb begin : byte_sel_input
byte_sel_d = byte_sel_q;
if (byte_sel_update) begin
// Check input command index and assign initial byte_sel
byte_sel_d = 2'b 00; // default value
for (int unsigned i = 0 ; i <= 2 ; i++) begin
if (cmd_info_idx_i == CmdInfoIdxW'(StatusCmdIdx[i])) begin
byte_sel_d = i;
end
end
end else if (byte_sel_inc) begin
unique case (byte_sel_q)
2'b 00: byte_sel_d = 2'b 01;
2'b 01: byte_sel_d = 2'b 10;
2'b 10: byte_sel_d = 2'b 00;
default: byte_sel_d = 2'b 00;
endcase
end
end : byte_sel_input
assign p2s_byte_inclk = (st_q == StIdle)
? sck_status_committed[8*byte_sel_d+:8]
: sck_status_committed[8*byte_sel_q+:8];
// State Machine
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) st_q <= StIdle;
else st_q <= st_d;
end
always_comb begin
st_d = st_q;
byte_sel_update = 1'b 0;
byte_sel_inc = 1'b 0;
p2s_valid_inclk = 1'b 0;
unique case (st_q)
StIdle: begin
if (sel_dp_i == DpReadStatus) begin
st_d = StActive;
// dp asserted after 8th SCK. Should send out the data right away.
byte_sel_update = 1'b 1;
p2s_valid_inclk = 1'b 1;
end
end
StActive: begin
p2s_valid_inclk = 1'b 1;
// deadend state
// Everytime a byte sent out, shift to next.
// Check if the byte_sel_inc to be delayed a cycle
// p2s_sent is asserted at 7th beat not 8th beat.
// But the spi_p2s module stores prev data into its 8bit register.
// So increasing the selection signal does not affect current SPI byte.
if (outclk_p2s_sent_i) byte_sel_inc = 1'b 1;
end
default: begin
st_d = StIdle;
end
endcase
end
endmodule : spid_status