blob: cf2fa502b701f7d535f8dbe321644b0f7e524475 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Serial Peripheral Interface (SPI) Device Passthrough module.
//
/*
* # Passthrough
*
* The OpenTitan SPI Device has a feature for snooping the SPI transactions from
* a host system to a SPI flash device and for intervening against unauthorized
* traffic. It is mainly intended for blocking any harmful read/ write requests
* and for guaranteeing the return of genuine binary images to the host system.
*
* This module does two things:
*
* 1. It blocks any downstream commands if they are not permitted.
* 2. It swaps the address field for read commands to support an A/B scheme.
*
* ## Command Filter
*
* The Command Filter blocks incoming commands if they are in the block list CSR
* that SW has configured. When it blocks, it raises CSb (to high) at the 8th
* SPI_CLK edge and holds SPI_CLK to low until the host system releases its CSb.
*
* The module uses a clock gating cell to not propagate the SPI_CLK to the
* attached SPI Flash device, mainly in order to not introduce glitches on the
* CSb line. If the module does not hold the SPI_CLK, then CSb needs one more
* clock to remove the glitch. The timing delay means that this design will miss
* the tming to raise CSb in order to cancel the self-complete commands such as
* chip erase command.
*
* To make CSb de-assertion work, there are a few assumptions:
*
* 1. De-asserting CSb in the middle of transmit does not degrade the device
* quality.
* 2. If CSb is de-asserted prior to the 8th SPI_CLK, the SPI flash device
* cancels the process without making any assumptions about the expected
* behavior. For example, the SPI flash device does not charge the
* charge-pump in the 7th beat to prepare the chip for erasure.
*
* Based on the information given, it is expected that the assumptions above are
* satisfied.
*
* ## Address Manipulation
*
* SW can configure this module to swap the SPI line 0 to the downstream device
* for the first 32 beats of data after the SPI command. Currently the only
* supported commands are Read commands except for Dual IO and Quad IO commands.
*
* Due to the complexity of the design, this module does not support address
* data for the IO commands, as the address field comes from two or four SPI
* lines.
*
* SPI_DEVICE provides two programmable CSRs to support the address manipulation
* feature. One is the mask register and another is actual data sending to the
* downstream device.
*
* The mask register indicates the address bit to be swapped. If the mask bit
* corresponding to the address bit position is 1, then the value transferred to
* the downstream device is the value from the data register rather than the
* value from the host system.
*
* Both registers are 32-bit registers. If 4B address mode is not enabled, only
* lower 24-bit from the registers are used for the address manipulation.
*
* The usage of this feature may vary, but the main purpose is to provide an A/B
* binary image. The Root-of-Trust device verifies the binary image sitting
* inside the SPI flash device. If the image has been manipulated by malicious
* attacks or reliability issues, then SW in RoT can redirect the host system's
* requests to the other half partitions in the flash device.
*/
module spi_passthrough
import spi_device_pkg::*;
(
input clk_i, // SPI input clk
input rst_ni, // SPI reset
input clk_out_i, // SPI output clk
// Configurations
//
// Clock polarity
//
// CFG.CPOL informs the logic if the host sends inverted SCK or not. If
// CFG.CPOL is set, the filtering logic needs to account for the inverted
// clock. The logic gates the `host_inverted_sck_i` then sends out the
// inverted clock rather than `host_sck_i`.
input cfg_cpol_i,
// command filter information is given as 256bit register. It is subject to be
// changed if command config is stored in DPSRAM. If that is supported, the
// command config is valid at the 6th command cycle and given only 8 bits.
input [255:0] cfg_cmd_filter_i,
// address manipulation
input [31:0] cfg_addr_mask_i,
input [31:0] cfg_addr_value_i,
// Big-Endian of payload swap mask/data
// [31:24] => first byte
// [ 7: 0] => 4th byte
input [31:0] cfg_payload_mask_i,
input [31:0] cfg_payload_data_i,
// Address mode
input cfg_addr_4b_en_i,
input spi_mode_e spi_mode_i,
// Control: Passthrough block
// If passthrough_block_i is 1, the passthrough is turned off.
// The signal should be changed when CSb is high (SPI inactive).
input passthrough_block_i,
// Command Info structure
input cmd_info_t [NumTotalCmdInfo-1:0] cmd_info_i,
// SPI in
//
// Though it would be best if passthrough is able to re-use existing spi_s2p
// and cmdparse, but passthrough has to implement its own s2p and cmdparse to
// support the A/B binary scheme.
input host_sck_i,
input host_isck_i, // inverted SCK for CPOL:=1 case
input host_csb_i,
input [3:0] host_s_i,
output logic [3:0] host_s_o, // clk_out_i domain
output logic [3:0] host_s_en_o, // clk_out_i domain
// SPI to SPI_HOST and terminal to the downstream device
output passthrough_req_t passthrough_o,
input passthrough_rsp_t passthrough_i,
// event
// `cmd_filtered`: indicator of the incoming command filtered out
output event_cmd_filtered_o
);
/////////////////
// Definitions //
/////////////////
// State
typedef enum logic [2:0] {
// In Idle state, it forwards the incoming SPI to SPI_HOST and eventually
// to SPI Flash Device.
StIdle,
// When hitting the 8th beat (precisely 7 and half), the State machine
// checks incoming data and determine if blocks the command or keep forward
// to downstream device based on given config, cmd_filter.
StFilter,
// If a command filtered, the SCK to SPI_HOST Ip turned off and CSb
// deasserted in StWait. The SCK shall be turned off in StFilter. In this
// Wait state, the state machine waits for CSb de-assertion from the host
// system.
StWait,
// Output command handling.
//
// This is mostly duplicated code from SPI Flash mode, but exists here to
// control the output enable. In Passthrough mode, if the command is
// allowed, the data comes from the downstream device, but it doesn't give
// any hint of the output state (high-Z or driving).
//
// So, Passthrough module shall follow the SPI protocol to control `oe` of
// upstream pads. It follows the same protocol as described in Read Command
// module, Status Module, SFDP / JEDEC module.
//
// When St from StIdle or StAddress moves to StHighZ, it shall set wait
// timer. The wait timer expires, StHighZ moves to StDriving, which is
// dead-end until CSb de-asserted.
StDriving,
StHighZ,
// Address manipulation
//
// One special feature of SPI Passthrough is A/B binary image support. The
// logic can swap certain beats of data to pre-configured value. In this
// state, the logic sees addr_mask and data and swaps the line if necessary.
// After this, the ST moves to StDriving, or StHighZ.
//
// If the address hits the Mailbox region and the SW has enabled the
// mailbox, then ST cancels current transaction and moves to StWait state.
StAddress,
// MByte
StMByte
} passthrough_st_e;
passthrough_st_e st, st_d;
/* Not synthesizable in DC
localparam cmd_type_t PassThroughCmdInfoOld [256] = '{
// 8'h 00
'h 00: CmdInfoNone,
// 8'h 01 Write Status 1
'h 01: CmdInfoPayloadIn,
// 8'h 15 Write Statur 2
'h 31: CmdInfoPayloadIn,
// 8'h 11 Write Status 3
'h 11: CmdInfoPayloadIn,
// 8'h 02 Page Program
'h 02: CmdInfoAddrPayloadIn,
// 8'h 32 Quad Input Page Program : Expect to be filtered
'h 32: CmdInfoAddrPayloadInQuad,
// 8'h 03 Read Data
'h 03: CmdInfoAddrPayloadOut,
// 8'h 04 Write Disable
'h 04: CmdInfoNone,
// 8'h 05 Read Status 1
'h 05: CmdInfoPayloadOut,
// 8'h 35 Read Status 2
'h 35: CmdInfoPayloadOut,
// 8'h 15 Read Status 3
'h 15: CmdInfoPayloadOut,
// 8'h 06 Write Enable
'h 06: CmdInfoNone,
// 8'h 0B Fast Read
'h 0B: CmdInfoAddrDummyPayloadOut,
// 8'h 3B Fast Read Dual Output
'h 3B: CmdInfoAddrDummyPayloadOutDual,
// 8'h 6B Fast Read Quad Output
'h 6B: CmdInfoAddrDummyPayloadOutQuad,
// 8'h 20 Sector Erase (4kB)
'h 20: CmdInfoAddr,
// 8'h 52 Block Erase (32kB)
'h 52: CmdInfoAddr,
// 8'h D8 Block Erase (64kB)
'h D8: CmdInfoAddr,
// 8'h 36 Individual Block Lock
'h 36: CmdInfoAddr,
// 8'h 39 Individual Block Unlock
'h 39: CmdInfoAddr,
// 8'h 3D Read Block Lock
'h 3D: CmdInfoAddrPayloadOut,
// 8'h 38 Enter QPI : Expect to be filtered
'h 38: CmdInfoNone,
// 8'h 42 Program Security Register
// 8'h 44 Erase Security Register
// 8'h 48 Read Security Register
// 8'h 4B Read Unique ID
// 8'h 5A Read SFDP
'h 5A: CmdInfoAddr3BDummyPayloadOut,
// 8'h 90 Manufacture/Device ID
// 8'h 9F JEDEC ID
'h 9F: CmdInfoPayloadOut,
default: CmdInfoNone
};
*/
/////////////
// Signals //
/////////////
// internal clock
logic [3:0] host_s_en_inclk;
logic [3:0] device_s_en_inclk;
// Indicate Passthrough mode is enabled or not.
logic is_active;
assign is_active = (spi_mode_i == PassThrough);
logic [7:0] opcode, opcode_d;
logic unused_opcode_7;
assign unused_opcode_7 = opcode[7];
// If the filter becomes 1 on the 8th beat, it lowers SCK enable signal to CG
// cell then, at the 8th posedge of SCK, csb_deassert becomes 1.
// ____ ____ ____
// SCK ____/ 7 \____/ 8 \____/
// ___
// filter _______/XXXXXX/ \______________
// ______________
// sck_gate_en \__________________
// ______________
// csb_deassert __________________/
logic filter;
// If 1, SCK propagates to the downstream SPI Flash device.
logic sck_gate_en;
// CSb to the downstream device control If 1, CSb is de-asserted. This signal
// is glitch sensitive. This value is changed at SCK posedge. This does not
// drive CSb output directly. CSb to downstream is OR-ed with this and CSb
// from host system.
//
// `cdb_deassert` should be latched at the posedge of SCK. `filter` signal is
// computed in between the 7th posedge of SCK and the 8th posedge. As the
// command bit does not arrive until the 7th negedge of SCK, the filter signal
// is not valid in the first half of the period. By latching the filter signal
// at the posedge of the SCK, csb_deassert always shows correct value if the
// command needs to be filtered or not.
//
// However, the CSb output to the downstream flash device is better to be in
// the out clock domain. It helps the design constraints to be simpler. So,
// the `csb_deassert` is again latched at the outclk (which is the negedge of
// SCK).
logic csb_deassert;
logic csb_deassert_outclk;
// == BEGIN: Counters ======================================================
// bitcnt to count up to dummy
localparam int unsigned MetaMaxBeat = 8+32+8; // Cmd + Addr + Dummy
localparam int unsigned MetaBitCntW = $clog2(MetaMaxBeat);
logic [MetaBitCntW-1:0] bitcnt;
// Address or anything host driving after opcode counter
logic [AddrCntW-1:0] addrcnt, addrcnt_outclk;
// Dummy counter
logic [DummyCntW-1:0] dummycnt, dummycnt_d;
// -- END: Counters ------------------------------------------------------
// event
assign event_cmd_filtered_o = filter;
//////////////
// Datapath //
//////////////
// Opcode Latch
assign opcode_d = {opcode[6:0], host_s_i[0]};
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
opcode <= 8'h 00;
end else if (bitcnt < MetaBitCntW'(8)) begin
opcode <= opcode_d;
end
end
// Command Filter: CSb control
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) csb_deassert <= 1'b 0;
else if (filter) csb_deassert <= 1'b 1;
end
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) csb_deassert_outclk <= 1'b 0;
else csb_deassert_outclk <= csb_deassert;
end
// Look at the waveform above to see why sck_gate_en is inversion of fliter OR
// csb_deassert
assign sck_gate_en = ~(filter | csb_deassert);
// Bitcnt counter
/// Bitcnt increases until it hit the max value then wait reset.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
bitcnt <= '0;
end else if (bitcnt != '1) begin
bitcnt <= bitcnt + MetaBitCntW'(1);
end
end
// Latch only two bits at 7th cmd opcode
logic cmd_7th; // 7th beat of transaction
logic cmd_8th; // in 8th beat of transaction
logic [1:0] cmd_filter;
assign cmd_7th = (bitcnt == MetaBitCntW'(6));
assign cmd_8th = (bitcnt == MetaBitCntW'(7));
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cmd_filter <= 2'b 00;
end else if (cmd_7th) begin
// In this 7th beat, cmd_filter latches 2 bits from cfg_cmd_filter_i
// This reduces the last filter datapath from muxing 256 to just mux2
//
// If below syntax does not work, it can be replaced with
// for (int unsigned i = 0 ; i < 128 ; i++) begin
// it (i == opcode[6:0]) cmd_filter <= cfg_cmd_filter_i[2*i+:2];
// end
cmd_filter <= cfg_cmd_filter_i[{opcode_d[6:0],1'b0}+:2];
end
end
// Command Info Latch
cmd_info_t cmd_info, cmd_info_d;
cmd_info_t [1:0] cmd_info_7th, cmd_info_7th_d;
logic [AddrCntW-1:0] addr_size_d;
logic cmd_info_latch;
// Search opcode @ 7th opcode
always_comb begin
cmd_info_7th_d = {CmdInfoInput, CmdInfoInput};
if (cmd_7th) begin
for(int unsigned i = 0 ; i < NumTotalCmdInfo ; i++) begin
if (cmd_info_i[i].valid) begin
if (cmd_info_i[i].opcode == {opcode_d[6:0], 1'b1}) begin
cmd_info_7th_d[1] = cmd_info_i[i];
end else if (cmd_info_i[i].opcode == {opcode_d[6:0], 1'b0}) begin
cmd_info_7th_d[0] = cmd_info_i[i];
end
end // cmd_info_i[i].valid
end
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cmd_info_7th <= '0;
end else if (cmd_7th) begin
// Search two candidate among NumCmdInfo. If only one matched, other
// cmd_info is Always Input command.
cmd_info_7th <= cmd_info_7th_d;
//cmd_info_7th <= {PassThroughCmdInfo[{opcode_d[6:0],1'b1}],
// PassThroughCmdInfo[{opcode_d[6:0],1'b0}]};
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cmd_info <= '0;
end else if (cmd_info_latch) begin
// Latch only two cmd_info when the 7th bit arrives. Then select among two
// at the 8th beat for cmd_info_d to reduce timing.
cmd_info <= cmd_info_d;
end
end
// Some of the fields of cmd_info are used in the big FSM below. The opcode field and the addr_*
// fields are ignored because we pick them from cmd_info_d instead (in a different FSM state). We
// rely on the synthesis tool not to generate the unneeded flops but must explicitly waive lint
// warnings about unused fields.
logic unused_cmd_info_fields;
assign unused_cmd_info_fields = &{
1'b0,
cmd_info.valid, // valid bit is checked before latching into cmd_info
cmd_info.opcode,
cmd_info.addr_mode,
cmd_info.opcode,
cmd_info.upload,
cmd_info.busy
};
addr_mode_e cmdinfo7th_addr_mode;
assign cmdinfo7th_addr_mode = get_addr_mode(
cmd_info_7th[host_s_i[0]], cfg_addr_4b_en_i);
always_comb begin
cmd_info_d = '0;
addr_size_d = AddrCntW'(23);
if (cmd_8th) begin
// Latch only two cmd_info when the 7th bit arrives. Then select among two
// at the 8th beat for cmd_info_d to reduce timing.
cmd_info_d = cmd_info_7th[host_s_i[0]];
addr_size_d = (cmdinfo7th_addr_mode == Addr4B)
? AddrCntW'(31) : AddrCntW'(23);
cmd_info_d.opcode = opcode_d;
// dummy_size is set inside State Machine
end
end
// Address swap
logic addr_set;
logic addr_phase, addr_phase_outclk;
assign addr_phase = (st == StAddress);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
addrcnt <= '0;
end else if (addr_set) begin
// When addr_set is 1, cmd_info is not yet latched.
`ASSERT_I(AddrSetInStIdle_A, st == StIdle)
addrcnt <= addr_size_d;
end else if (addrcnt != '0) begin
addrcnt <= addrcnt - AddrCntW'(1);
end
end
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) addrcnt_outclk <= '0;
else addrcnt_outclk <= addrcnt;
end
// Based on AddrCnt, the logic swap.
// TODO: Handle the DualIO, QuadIO cases
logic addr_swap;
assign addr_swap = cfg_addr_mask_i[addrcnt_outclk]
? cfg_addr_value_i[addrcnt_outclk]
: host_s_i[0];
// Address swap should happen in outclk domain.
// The state machine operates in inclk domain.
// The state generates mux selection signal.
// The signal latched in outclk domain then activates the mux.
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) addr_phase_outclk <= 1'b 0;
else addr_phase_outclk <= addr_phase;
end
// Payload (4 bytes) swap
logic [4:0] payloadcnt, payloadcnt_outclk;
logic payload_replace, payload_replace_outclk;
logic payload_replace_set, payload_replace_clr;
// payload counter
//
// Reset to 'd31. decrease by 1 in every inclk. Stop at 0 then
// payload_replace is cleared.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
payloadcnt <= 5'h 1F;
end else if ((payloadcnt != '0) && payload_replace) begin
payloadcnt <= payloadcnt - 1'b 1;
end
end
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) payloadcnt_outclk <= 5'h 1F;
else payloadcnt_outclk <= payloadcnt;
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) payload_replace <= 1'b 0;
else if (payload_replace_set) payload_replace <= 1'b 1;
else if (payload_replace_clr) payload_replace <= 1'b 0;
end
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) payload_replace_outclk <= 1'b 0;
else payload_replace_outclk <= payload_replace;
end
assign payload_replace_clr = (payloadcnt == '0);
// FSM drives payload_replace_set : assert when st moves to StDriving
logic payload_swap;
assign payload_swap = cfg_payload_mask_i[payloadcnt_outclk]
? cfg_payload_data_i[payloadcnt_outclk]
: host_s_i[0] ;
// SPI swap (merging Addr & Payload)
logic swap_en, swap_data, addr_swap_en, payload_swap_en;
// Latch cmd_info config signals in output clock.
// cmd_info structure is latched in input clock. But addr_swap_en and
// payload_swap_en affects the output path (from host to the downstream flash
// device). as the output delay is bigger than the half of SCK, these paths
// violates timing regardless of the path delay. By latching the configs
// into output clock (inverted SCK), it alleviates the timing budget.
logic cmd_info_addr_swap_en_outclk, cmd_info_payload_swap_en_outclk;
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) begin
cmd_info_addr_swap_en_outclk <= 1'b 0;
cmd_info_payload_swap_en_outclk <= 1'b 0;
end else begin
cmd_info_addr_swap_en_outclk <= cmd_info.addr_swap_en;
cmd_info_payload_swap_en_outclk <= cmd_info.payload_swap_en;
end
end
assign addr_swap_en = addr_phase_outclk
& cmd_info_addr_swap_en_outclk;
assign payload_swap_en = payload_replace_outclk
& cmd_info_payload_swap_en_outclk;
assign swap_en = addr_swap_en | payload_swap_en ;
assign swap_data = (addr_swap_en) ? addr_swap : payload_swap ;
// Dummy Counter
logic dummy_set;
logic dummycnt_zero;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) dummycnt <= '0;
else if (dummy_set) begin
dummycnt <= dummycnt_d;
end else if (st == StHighZ) begin
dummycnt <= dummycnt - 1'b 1;
end
end
assign dummycnt_zero = (dummycnt == '0);
// MByte counter
logic mbyte_set;
logic mbytecnt_zero;
logic [1:0] mbyte_cnt;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
mbyte_cnt <= '0;
end else if (mbyte_set) begin
// TODO: check addr enable and update mbyte_cnt to 3 or 1
mbyte_cnt <= 2'h 3;
end else if (st == StMByte) begin
mbyte_cnt <= mbyte_cnt - 1'b 1;
end
end
assign mbytecnt_zero = (mbyte_cnt == '0);
// = BEGIN: Passthrough Mux (!important) ====================================
// As swap_en is in outclk domain, addr_swap can be directly used.
assign passthrough_o.s = (swap_en) ? {host_s_i[3:1], swap_data} : host_s_i ;
logic [3:0] passthrough_s_en;
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) passthrough_s_en <= 4'h 1; // S[0] active by default
else passthrough_s_en <= device_s_en_inclk;
end
assign passthrough_o.s_en = passthrough_s_en;
assign host_s_o = passthrough_i.s;
always_ff @(posedge clk_out_i or negedge rst_ni) begin
if (!rst_ni) host_s_en_o <= '0; // input mode
else host_s_en_o <= host_s_en_inclk;
end
logic pt_gated_sck, pt_gated_isck, pt_gated_isck_inv;
prim_clock_gating #(
.NoFpgaGate (1'b 0),
.FpgaBufGlobal (1'b 1) // Going outside of chip
) u_pt_sck_cg (
.clk_i (host_sck_i ),
.en_i (sck_gate_en ),
.test_en_i (1'b 0 ), // No FF connected to this gated SCK
.clk_o (pt_gated_sck)
);
prim_clock_gating #(
.NoFpgaGate (1'b 0),
.FpgaBufGlobal (1'b 1) // Going outside of chip
) u_pt_isck_cg (
.clk_i (host_isck_i ),
.en_i (sck_gate_en ),
.test_en_i (1'b 0 ), // No FF connected to this gated SCK
.clk_o (pt_gated_isck)
);
assign pt_gated_isck_inv = ~pt_gated_isck;
assign passthrough_o.sck = (cfg_cpol_i) ? pt_gated_isck_inv : pt_gated_sck;
assign passthrough_o.sck_en = 1'b 1;
// CSb propagation: csb_deassert signal should be an output of FF or latch to
// make CSb glitch-free.
assign passthrough_o.csb_en = 1'b 1;
assign passthrough_o.csb = host_csb_i | csb_deassert_outclk ;
// passthrough_en
assign passthrough_o.passthrough_en = is_active && !passthrough_block_i;
// - END: Passthrough Mux (!important) ------------------------------------
///////////////////
// State Machine //
///////////////////
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
st <= StIdle;
end else begin
st <= st_d;
end
end
always_comb begin
st_d = st;
// filter
filter = 1'b 0;
// Command Cfg Latch
cmd_info_latch = 1'b 0;
// addr_set
addr_set = 1'b 0;
// Payload swap control
payload_replace_set = 1'b 0;
// mbyte counter udpate
mbyte_set = 1'b 0;
// Dummy
dummy_set = 1'b 0;
dummycnt_d = '0;
// Output enable
host_s_en_inclk = 4'h 0;
device_s_en_inclk = 4'h 1; // S[0] MOSI active
unique case (st)
StIdle: begin
if (!is_active) begin
st_d = StIdle;
end else if (cmd_8th && cmd_filter[host_s_i[0]]) begin
st_d = StFilter;
filter = 1'b 1;
// Send notification event to SW
end else if (cmd_8th && cmd_info_d.valid) begin
cmd_info_latch = 1'b 1;
// Divert to multiple states.
//
// The following states are mainly for controlling the output enable
// signals. StAddress state, however, controls the SPI lines for
// address swap in case of Read Commands.
//
// Order: addr_mode , dummy_en, |payload_en
// Note that no direct transition to MByte state.
if (cmd_info_d.addr_mode != AddrDisabled) begin
st_d = StAddress;
addr_set = 1'b 1;
end else if (cmd_info_d.dummy_en) begin
st_d = StHighZ;
dummy_set = 1'b 1;
dummycnt_d = cmd_info_d.dummy_size;
end else if (cmd_info_d.payload_en != 0) begin
// Any input/output payload
if (cmd_info_d.payload_dir == PayloadOut) begin
st_d = StWait;
end else begin
st_d = StDriving;
payload_replace_set = 1'b 1;
end
end
end // cmd_8th && cmd_info_d.valid
else if (cmd_8th) begin
// cmd_info_d.valid is 0. Skip current transaction
st_d = StFilter;
filter = 1'b 1;
end
end
StMByte: begin
if (mbytecnt_zero) begin
st_d = StHighZ;
dummy_set = 1'b 1;
dummycnt_d = cmd_info_d.dummy_size;
end else begin
st_d = StMByte;
end
end
StFilter: begin
// Command is filtered. Wait until reset(CSb) comes.
st_d = StFilter;
host_s_en_inclk = 4'h 0; // explicit
device_s_en_inclk = 4'h 0;
end
StWait: begin
// Device Returns Data to host
st_d = StWait;
// output enable for host
host_s_en_inclk = cmd_info.payload_en;
device_s_en_inclk = 4'h 0;
end
StDriving: begin
// Host sends Data to device
st_d = StDriving;
// Output enable for device
host_s_en_inclk = 4'h 0; // explicit
device_s_en_inclk = cmd_info.payload_en;
end
StHighZ: begin
host_s_en_inclk = 4'h 0; // explicit
device_s_en_inclk = 4'h 0; // float
if (dummycnt_zero && (cmd_info.payload_dir == PayloadOut)) begin
// Assume payload_en not 0
st_d = StWait;
end else if (dummycnt_zero && (cmd_info.payload_dir == PayloadIn)) begin
st_d = StDriving;
payload_replace_set = 1'b 1;
end
end
StAddress: begin
// based on state, addr_phase is set. So just check if counter reaches 0
if (addrcnt == '0) begin
if (cmd_info.mbyte_en) begin
st_d = StMByte;
mbyte_set = 1'b 1;
end else if (cmd_info.dummy_en) begin
st_d = StHighZ;
dummy_set = 1'b 1;
dummycnt_d = cmd_info.dummy_size;
end else if (cmd_info.payload_en != 0
&& (cmd_info.payload_dir == PayloadOut)) begin
st_d = StWait;
end else if (cmd_info.payload_en != 0
&& (cmd_info.payload_dir == PayloadIn)) begin
st_d = StDriving;
payload_replace_set = 1'b 1;
end else begin
// Addr completed command. goto wait state
st_d = StWait;
end
end
end
default: begin
st_d = StIdle;
end
endcase
end
`ASSERT_KNOWN(PassThroughStKnown_A, st)
///////////////
// Assertion //
///////////////
// Assume when payload_swap_en is set, the direction is PayloadIn & only
// Single mode is used
`ASSUME(PayloadSwapConstraint_M,
cmd_info.payload_swap_en |-> (cmd_info.payload_en == 4'b 0001)
&& (cmd_info.payload_dir == PayloadIn))
endmodule: spi_passthrough