blob: 76ad1cd02ee27c0ab4ea7746fe42de62f9b35e50 [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 Device Flash/ Passthrough mode Command Parser
//
// This module decodes the incoming SPI Flash commands. Then activates proper
// downstream module.
`include "prim_assert.sv"
module spi_cmdparse
import spi_device_pkg::*;
(
input clk_i,
input rst_ni,
// Data from spi_s2p
input data_valid_i,
input spi_byte_t data_i,
// Configurations
input spi_mode_e spi_mode_i,
// 256b indicator determines which commands to be uploaded
// If 1 and the command does not fall into other modules
// (SFDP/JEDEC/Read/etc), Command Processor triggers upload module.
// Other upload related configurations (Address/ Payload) are used
// in upload module.
//
// If Command Config (from DPSRAM) is implemented, this signal shall be
// changed to 8bit width.
input logic [255:0] upload_mask_i,
// Command info slot
//
// cmdparse uses the command info slot to activate sub-datapath. It uses
// pre-assigned index and search opcode. e.g) if cmdslot[0].opcode == 'h03,
// then if received opcode matches to the cmdslot[0] opcode, then it activates
// Read Status module as Index 0 is pre-assigned to Read Status.
input cmd_info_t [spi_device_reg_pkg::NumCmdInfo-1:0] cmd_info_i,
// control to spi_s2p
output io_mode_e io_mode_o,
// Activate downstream modules
output sel_datapath_e sel_dp_o,
output spi_byte_t opcode_o,
// Command Config is not implemented yet.
// Indicator of command config. The pulse is generated at 3rd bit position
// of Opcode. The upper 5 bits are used as address to fetch command configs
// from DPSRAM.
output logic cmd_config_req_o,
output logic [4:0] cmd_config_idx_o
);
///////////////
// Temporary //
///////////////
assign io_mode_o = SingleIO;
assign cmd_config_req_o = 1'b 0;
assign cmd_config_idx_o = data_i[4:0];
// among the command slots, Passthrough related slots are not used. So tie them down here.
logic unused_cmdinfo;
assign unused_cmdinfo = &{1'b0,
cmd_info_i[CmdInfoPassthroughEnd:CmdInfoPassthroughStart]};
// Only opcode in the cmd_info is used. Tie the rest of the members.
logic unused_cmdinfo_members;
always_comb begin
unused_cmdinfo_members = 1'b 0;
for (int unsigned i = 0 ; i <= CmdInfoPassthroughEnd ; i++) begin
unused_cmdinfo_members &= &{ cmd_info_i[i].addr_4b_affected,
cmd_info_i[i].addr_en,
cmd_info_i[i].addr_swap_en,
cmd_info_i[i].dummy_en,
&cmd_info_i[i].dummy_size,
cmd_info_i[i].payload_dir,
&cmd_info_i[i].payload_en};
end
end
// Unnecessary but for ascentlint error only
logic unused_cmdinfo_opcode;
always_comb begin
unused_cmdinfo_opcode = 1'b 0;
for (int unsigned i = CmdInfoPassthroughStart ; i <= CmdInfoPassthroughEnd ; i++) begin
unused_cmdinfo_opcode &= &cmd_info_i[i].opcode;
end
end
////////////////
// Definition //
////////////////
typedef enum logic [2:0] {
// At Idle, FSM waits command valid signal.
// The last 8th bit in the command byte determines the last two bit from
// `upload_mask`. Then triggers proper downstream modules based on
// predefined opcode and `upload_mask`.
StIdle,
// State machine has state for each downstream module, to track and select
// proper io_mode and signals
StStatus,
StSfdp,
StJedec,
// Mailbox is processed in Read Command block.
StReadCmd,
StUpload
} st_e;
st_e st, st_d;
// spi_flash_cmd_e defines HW supported (TBD for IO) commands.
// If received SPI Flash command falls into one of these commands, the module
// processes the command without SW intervention.
typedef enum logic [7:0] {
OpReadStatus1 = 'h 05,
OpReadStatus2 = 'h 35,
OpReadStatus3 = 'h 15,
OpReadJEDEC = 'h 9F,
OpReadSfdp = 'h 5A,
OpReadNormal = 'h 03,
OpReadFast = 'h 0B,
OpReadDual = 'h 3B,
OpReadQuad = 'h 6B,
// Supporting DualIO/ QuadIO is TBD.
OpReadDualIO = 'h BB,
OpReadQuadIO = 'h EB
} spi_flash_cmd_e;
////////////
// Signal //
////////////
sel_datapath_e sel_dp;
assign sel_dp_o = sel_dp;
// the logic operates only when module_active condition is met
logic module_active;
logic in_flashmode, in_passthrough;
// Opcode out
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
opcode_o <= spi_byte_t'(0);
end else if (st == StIdle && data_valid_i) begin
opcode_o <= data_i;
end
end
// below signals are used in the FSM to determine to activate a certain
// datapath based on the received input (opcode). The opcode is the SW
// configurable CSRs `cmd_info_i`.
logic opcode_readstatus, opcode_readjedec, opcode_readsfdp, opcode_readcmd;
assign opcode_readstatus = (data_i == cmd_info_i[CmdInfoReadStatus1].opcode)
| (data_i == cmd_info_i[CmdInfoReadStatus2].opcode)
| (data_i == cmd_info_i[CmdInfoReadStatus3].opcode);
assign opcode_readjedec = (data_i == cmd_info_i[CmdInfoReadJedecId].opcode);
assign opcode_readsfdp = (data_i == cmd_info_i[CmdInfoReadSfdp].opcode);
always_comb begin
opcode_readcmd = 1'b 0;
for (int unsigned i = CmdInfoReadCmdStart ; i <= CmdInfoReadCmdEnd ; i++) begin
if (data_i == cmd_info_i[i].opcode) opcode_readcmd = 1'b 1;
end
end
///////////////////
// State Machine //
///////////////////
assign in_flashmode = spi_mode_i == FlashMode ;
assign in_passthrough = spi_mode_i == PassThrough ;
assign module_active = in_flashmode || in_passthrough ;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
st <= StIdle;
end else if (module_active) begin
// TODO: What condition should this to be activated?
st <= st_d;
end
end
always_comb begin
st_d = st;
sel_dp = DpNone;
unique case (st)
StIdle: begin
if (module_active && data_valid_i) begin
// 8th bit is valid here
priority case (1'b 1)
opcode_readstatus: begin
st_d = StStatus;
end
opcode_readjedec: begin
if (in_flashmode) begin
st_d = StJedec;
end else begin
// TODO: Passthrough ?
st_d = StIdle;
end
end
opcode_readsfdp: begin
if (in_flashmode) begin
st_d = StSfdp;
end else begin
// TODO: Passthrough? Cannot stay in the Idle as it will compare at the next byte
st_d = StIdle;
end
end
opcode_readcmd: begin
// Let it move to ReadCmd regardless of the modes
// Then, ReadCmd will handle Mailbox command if received address
// falls into Mailbox address range
st_d = StReadCmd;
end
default: begin
// If Command Config in DPSRAM, need to change as below
// if (upload_mask_i[data_i[2:0]]) begin
if (upload_mask_i[data_i]) begin
st_d = StUpload;
// Reason to select dp here is for Opcode-only commands such as
// ChipErase. As no further SCK is given after 8th bit, need to
// write the command to FIFO at the same cycle.
//
// May sel_dp have glitch. As Opcode isn't yet fully stable, it
// has a chance to select datapath to Upload and back to None.
// If we can write opcode in negedge of SCK, then selecting DP
// at 8th posedge of SCK is also possible.
//
// If not, then we may end up latch `sel_dp` at negedge of SCK.
// Then when 8th bit is visible here (`data_valid_i`), the
// sel_dp may change. But the output of sel_dp affects only when
// 8th negedge of SCK.
sel_dp = DpUpload;
end else begin
st_d = StIdle;
// DpNone
end
end
endcase
end
end
// dead-end states below. Reset (CSb de-assertion) let SM back to Idle
StStatus: sel_dp = DpReadStatus;
StJedec: sel_dp = DpReadJEDEC;
StSfdp: sel_dp = DpReadSFDP;
StReadCmd: sel_dp = DpReadCmd;
StUpload: sel_dp = DpUpload;
default: begin
sel_dp = DpNone;
st_d = StIdle;
end
endcase
end
`ASSERT_KNOWN(StKnown_A, st)
///////////////
// Assertion //
///////////////
// at the first byte, only one datapath shall be active or stay silent.
`ASSERT(OnlyOneDatapath_A, module_active && data_valid_i && (st == StIdle)
|-> $onehot0({opcode_readstatus, opcode_readjedec, opcode_readsfdp,
opcode_readcmd}))
endmodule