blob: a7b3705eeda24ab6d5bf05db11f340739cb2a601 [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
}
) (
input clk_i,
input rst_ni,
input clk_out_i, // Output clock (inverted SCK)
input sys_clk_i, // Handling STATUS CSR (ext type)
input sys_rst_ni,
input csb_i, // CSb as a signal (not as a reset)
// status register from CSR: sys_clk domain
// bit [ 0]: RW0C by SW / W1S by HW
// bit [23:1]: RW
input sys_status_we_i,
input logic [23:0] sys_status_i,
output logic [23: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
// indicator of busy for other HW. Mainly to block passthrough
output logic inclk_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;
////////////
// Signal //
////////////
logic [23:0] status_sck;
logic unused_status_sck;
assign unused_status_sck = ^status_sck;
logic p2s_valid_inclk;
spi_byte_t p2s_byte_inclk;
////////////////////////////
// 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.
logic reg_en; // If 1, register in bus clock domain can be updated by SW
logic reg_update; // indicator of HW update (when CSb is de-asserted)
// Register interface update in bus clock domain
logic [23:0] status;
// BUSY
always_ff @(posedge sys_clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
status[0] <= 1'b 0;
end else if (reg_en && sys_status_we_i && (1'b0 == sys_status_i[0])) begin
status[0] <= 1'b 0;
end else if (reg_update) begin
status[0] <= status_sck[0];
end
end
// rest of STATUS
always_ff @(posedge sys_clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
status[23:1] <= '0;
end else if (reg_en && sys_status_we_i) begin
status[23:1] <= sys_status_i[23:1];
end
end
assign sys_status_o = status;
// busy_broadcast
prim_flop_2sync #(
.Width (1),
.ResetValue (1'b 0)
) u_busy_sync (
.clk_i,
.rst_ni,
.d_i (status[0]),
.q_o (inclk_busy_broadcast_o)
);
// CSb pulse
logic csb_sync_d, csb_sync_q, csb_asserted_pulse;
prim_flop_2sync #(
.Width (1),
.ResetValue (1'b 1)
) u_csb_sync (
.clk_i,
.rst_ni, //Use CSb as a reset
.d_i (1'b 0),
.q_o (csb_sync_d)
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) csb_sync_q <= 1'b 1;
else csb_sync_q <= csb_sync_d;
end
assign csb_asserted_pulse = csb_sync_q && !csb_sync_d;
// CSb de-asserted pulse is used as reg_update.
// TODO: merge this to the top then receive the signal through the module
// port?
logic reg_en_q;
prim_flop_2sync #(
.Width (1),
.ResetValue (1'b 1)
) u_csb_sync_sysclk (
.clk_i (sys_clk_i),
.rst_ni (sys_rst_ni),
.d_i (csb_i),
.q_o (reg_en)
);
always_ff @(posedge sys_clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) reg_en_q <= 1'b 1;
else reg_en_q <= reg_en;
end
assign reg_update = !reg_en_q && reg_en;
// Status in SCK
always_ff @(posedge clk_i or negedge sys_rst_ni) begin
if (!sys_rst_ni) begin
status_sck <= 24'h 0;
end else if (csb_asserted_pulse) begin
status_sck <= status;
end else if (inclk_busy_set_i) begin
status_sck[0] <= 1'b 1;
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) ? status_sck[8*byte_sel_d+:8]
: status_sck[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