blob: 83c7df3508b432c54a98e06c4f4e585fcc830899 [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 module.
//
`include "prim_assert.sv"
module spi_device
import spi_device_reg_pkg::NumAlerts;
#(
parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
) (
input clk_i,
input rst_ni,
// Register interface
input tlul_pkg::tl_h2d_t tl_i,
output tlul_pkg::tl_d2h_t tl_o,
// Alerts
input prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,
output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o,
// SPI Interface
input cio_sck_i,
input cio_csb_i,
output logic [3:0] cio_sd_o,
output logic [3:0] cio_sd_en_o,
input [3:0] cio_sd_i,
input cio_tpm_csb_i,
// Passthrough interface
output spi_device_pkg::passthrough_req_t passthrough_o,
input spi_device_pkg::passthrough_rsp_t passthrough_i,
// Interrupts
// INTR: Generic mode
output logic intr_generic_rx_full_o, // RX FIFO Full
output logic intr_generic_rx_watermark_o, // RX FIFO above level
output logic intr_generic_tx_watermark_o, // TX FIFO below level
output logic intr_generic_rx_error_o, // RX Frame error
output logic intr_generic_rx_overflow_o, // RX Async FIFO Overflow
output logic intr_generic_tx_underflow_o, // TX Async FIFO Underflow
// INTR: Flash mode
output logic intr_upload_cmdfifo_not_empty_o,
output logic intr_upload_payload_not_empty_o,
output logic intr_upload_payload_overflow_o,
output logic intr_readbuf_watermark_o,
output logic intr_readbuf_flip_o,
// INTR: TPM mode
output logic intr_tpm_header_not_empty_o, // TPM Command/Address buffer
// Memory configuration
input prim_ram_2p_pkg::ram_2p_cfg_t ram_cfg_i,
// External clock sensor
output logic sck_monitor_o,
// DFT related controls
input mbist_en_i,
input scan_clk_i,
input scan_rst_ni,
input prim_mubi_pkg::mubi4_t scanmode_i
);
import spi_device_pkg::*;
localparam int FifoWidth = $bits(spi_byte_t);
localparam int FifoDepth = 8; // 2 DWords
localparam int SDW = $clog2(SramDw/FifoWidth);
localparam int PtrW = SramAw + 1 + SDW;
localparam int AsFifoDepthW = $clog2(FifoDepth+1);
localparam int unsigned ReadBufferDepth = spi_device_pkg::SramMsgDepth;
localparam int unsigned BufferAw = $clog2(ReadBufferDepth);
localparam int unsigned TpmRdFifoWidth = spi_device_reg_pkg::TpmRdFifoWidth;
localparam int unsigned TpmWrFifoDepth = 64; // 64B
localparam int unsigned TpmRdFifoDepth = 16;
localparam int unsigned TpmWrFifoPtrW = $clog2(TpmWrFifoDepth+1);
`ASSERT_INIT(TpmWrPtrMatch_A,
TpmWrFifoPtrW == spi_device_reg_pkg::TpmWrFifoPtrW)
// Derived parameters
logic clk_spi_in, clk_spi_in_muxed, clk_spi_in_buf; // clock for latch SDI
logic clk_spi_out, clk_spi_out_muxed, clk_spi_out_buf; // clock for driving SDO
logic clk_csb, clk_csb_muxed; // CSb as a clock source to latch BUSY
spi_device_reg_pkg::spi_device_reg2hw_t reg2hw;
spi_device_reg_pkg::spi_device_hw2reg_t hw2reg;
tlul_pkg::tl_h2d_t tl_sram_h2d;
tlul_pkg::tl_d2h_t tl_sram_d2h;
// Dual-port SRAM Interface: Refer prim_ram_2p_wrapper.sv
logic sram_clk;
logic sram_clk_en;
logic sram_clk_ungated;
logic sram_clk_muxed;
logic sram_rst_n;
logic sram_rst_n_noscan;
logic mem_a_req;
logic mem_a_write;
logic [SramAw-1:0] mem_a_addr;
logic [SramDw-1:0] mem_a_wdata;
logic [SramDw-1:0] mem_a_wmask;
logic mem_a_rvalid;
logic [SramDw-1:0] mem_a_rdata;
logic [1:0] mem_a_rerror;
sram_l2m_t mem_b_l2m;
sram_m2l_t mem_b_m2l;
logic mem_b_req;
logic mem_b_write;
logic [SramAw-1:0] mem_b_addr;
logic [SramDw-1:0] mem_b_wdata;
logic [SramDw-1:0] mem_b_wmask;
logic mem_b_rvalid;
logic [SramDw-1:0] mem_b_rdata;
logic [1:0] mem_b_rerror;
// Submoule SRAM Requests
sram_l2m_t flash_sram_l2m;
sram_m2l_t flash_sram_m2l;
sram_l2m_t sub_sram_l2m [IoModeEnd];
sram_m2l_t sub_sram_m2l [IoModeEnd];
// Host return path mux
logic [3:0] internal_sd, internal_sd_en;
logic [3:0] passthrough_sd, passthrough_sd_en;
// Upload related interfaces (SRAM, FIFOs)
// Initially, SysSramEnd was the end of the enum variable. But lint tool
// raises errors the value being used in the parameter. So changed to
// localparam
typedef enum int unsigned {
SysSramFw = 0,
SysSramCmdFifo = 1,
SysSramAddrFifo = 2,
SysSramEnd = 3
} sys_sram_e;
sram_l2m_t sys_sram_l2m [SysSramEnd]; // FW, CMDFIFO, ADDRFIFO
sram_m2l_t sys_sram_m2l [SysSramEnd];
// Arbiter among Upload CmdFifo/AddrFifo & FW access
logic [SysSramEnd-1:0] sys_sram_req ;
logic [SysSramEnd-1:0] sys_sram_gnt ;
logic sys_sram_fw_gnt ;
logic [SramAw-1:0] sys_sram_addr [SysSramEnd];
logic [SysSramEnd-1:0] sys_sram_write ;
logic [SramDw-1:0] sys_sram_wdata [SysSramEnd];
logic [SramDw-1:0] sys_sram_wmask [SysSramEnd];
logic [SysSramEnd-1:0] sys_sram_rvalid ;
logic [SramDw-1:0] sys_sram_rdata [SysSramEnd];
logic [1:0] sys_sram_rerror [SysSramEnd];
logic cmdfifo_rvalid, cmdfifo_rready;
logic [7:0] cmdfifo_rdata;
logic cmdfifo_notempty;
logic cmdfifo_set_pulse;
logic addrfifo_rvalid, addrfifo_rready;
logic [31:0] addrfifo_rdata;
logic addrfifo_notempty;
logic payload_notempty;
logic payload_overflow;
localparam int unsigned CmdFifoPtrW = $clog2(SramCmdFifoDepth+1);
localparam int unsigned AddrFifoPtrW = $clog2(SramAddrFifoDepth+1);
localparam int unsigned PayloadByte = SramPayloadDepth * (SramDw/$bits(spi_byte_t));
localparam int unsigned PayloadDepthW = $clog2(PayloadByte+1);
localparam int unsigned PayloadIdxW = $clog2(PayloadByte);
logic [CmdFifoPtrW-1:0] cmdfifo_depth;
logic [AddrFifoPtrW-1:0] addrfifo_depth;
logic [PayloadDepthW-1:0] payload_depth;
logic [PayloadIdxW-1:0] payload_start_idx;
assign payload_notempty = payload_depth != '0;
/////////////////////
// Control signals //
/////////////////////
logic cpol; // Clock polarity
logic cpha; // Phase : Not complete
logic txorder; // TX bitstream order: 0(bit 7 to 0), 1(bit 0 to 7)
logic rxorder; // RX bitstream order: 0(bit 7 to 0), 1(bit 0 to 7)
logic abort; // Abort current operations (txf only at this time)
// Think how FW knows abort is done.
//logic abort_done; // TODO: Not implemented yet
logic sys_csb_syncd;
logic rst_txfifo_n, rst_rxfifo_n;
logic rst_txfifo_reg, rst_rxfifo_reg;
//spi_addr_size_e addr_size; // Not used in fwmode
spi_mode_e spi_mode;
//spi_byte_t fw_dummy_byte;
logic cfg_addr_4b_en;
// Address 3B/ 4B tracker related signals
//
// EN4B/ EX4B change internal status by HW. If SW is involved into the
// process, the latency is long. As EN4B/ EX4B commands do not assert BUSY
// bit, the host system issues next read commands without any delays. SW
// process latency cannot meet the requirement.
//
// `spid_addr_4b` submodule processes the broadcasting signal
// `cfg_addr_4b_en`. The command parser recognizes the commands and triggers
// the `spid_addr_4b` submodule to change the internal status.
//
// The opcodes of the commands SW may configure via CMD_INFO_EN4B,
// CMD_INFO_EX4B.
logic cmd_en4b_pulse, cmd_ex4b_pulse;
logic intr_sram_rxf_full, intr_fwm_rxerr;
logic intr_fwm_rxlvl, rxlvl, rxlvl_d, intr_fwm_txlvl, txlvl, txlvl_d;
logic intr_fwm_rxoverflow, intr_fwm_txunderflow;
logic rxf_overflow, txf_underflow;
logic [7:0] timer_v; // Wait timer inside rxf control
logic [PtrW-1:0] sram_rxf_rptr, sram_rxf_wptr;
logic [PtrW-1:0] sram_txf_rptr, sram_txf_wptr;
logic [PtrW-1:0] sram_rxf_depth, sram_txf_depth;
logic [SramAw-1:0] sram_rxf_bindex, sram_txf_bindex;
logic [SramAw-1:0] sram_rxf_lindex, sram_txf_lindex;
logic [AsFifoDepthW-1:0] as_txfifo_depth, as_rxfifo_depth;
logic rxf_empty, rxf_full, txf_empty, txf_full;
logic rxf_full_syncd, txf_empty_syncd; // sync signals
// SPI S2P signals
// io_mode: Determine s2p/p2s behavior. As of now, only fwmode exists.
// TODO: Add FlashMode IO, passthrough IO
// based on the SPI protocol, the mode should be changed at the negedge of
// SPI_CLK. The sub_iomode value is changed based on the input of SPI,
// it is latched by clk_spi_out.
// TODO: Add this path to DC constraint
io_mode_e io_mode, io_mode_outclk;
io_mode_e sub_iomode[IoModeEnd];
logic s2p_data_valid;
spi_byte_t s2p_data;
logic p2s_valid;
spi_byte_t p2s_data;
logic p2s_sent;
logic sub_p2s_valid[IoModeEnd];
spi_byte_t sub_p2s_data[IoModeEnd];
logic sub_p2s_sent[IoModeEnd];
// Read commands related signals
logic [31:0] readbuf_addr_sck;
logic [31:0] readbuf_addr_busclk;
// CMD interface
sel_datapath_e cmd_dp_sel, cmd_dp_sel_outclk;
// Mailbox in Passthrough needs to take SPI if readcmd hits mailbox address
logic intercept_en;
logic cfg_mailbox_en;
logic [31:0] mailbox_addr;
// Intercept
typedef struct packed {
logic status;
logic jedec;
logic sfdp;
logic mbx;
} intercept_t;
intercept_t cfg_intercept_en;
intercept_t intercept; // Assume signals
// Threshold value of a buffer in bytes
logic [BufferAw:0] readbuf_threshold;
// Passthrouth config signals
logic [255:0] cmd_filter;
logic [31:0] addr_swap_mask;
logic [31:0] addr_swap_data;
logic [31:0] payload_swap_mask;
logic [31:0] payload_swap_data;
// Command Info structure
cmd_info_t [NumTotalCmdInfo-1:0] cmd_info;
// Broadcasted cmd_info. cmdparse compares the opcode up to CmdInfoReadCmdEnd
// and latches the cmd_info and broadcast to submodules
cmd_info_t cmd_info_broadcast;
logic [CmdInfoIdxW-1:0] cmd_info_idx_broadcast;
// CSb edge detector in the system clock and SPI input clock
// SYS clock assertion can be detected but no usage for the event yet.
// SPI clock de-assertion cannot be detected as no SCK at the time is given.
logic sys_csb_deasserted_pulse;
logic sck_csb_asserted_pulse ;
// Read Status input and broadcast
logic sck_status_busy_set; // set by HW (upload)
logic csb_status_busy_broadcast; // from spid_status
// WREN / WRDI HW signal
logic sck_status_wr_set;
logic sck_status_wr_clr;
// Jedec ID
jedec_cfg_t jedec_cfg;
// Interrupts in Flash mode
logic intr_upload_cmdfifo_not_empty, intr_upload_payload_not_empty;
logic intr_upload_payload_overflow;
logic intr_readbuf_watermark, intr_readbuf_flip;
logic flash_sck_readbuf_watermark, flash_sck_readbuf_flip;
// TPM ===============================================================
// Interface
logic tpm_mosi, tpm_miso, tpm_miso_en;
assign tpm_mosi = cio_sd_i[0];
// Return-by-HW registers
logic [8*spi_device_reg_pkg::NumLocality-1:0] tpm_access;
logic [31:0] tpm_int_enable;
logic [7:0] tpm_int_vector;
logic [31:0] tpm_int_status;
logic [31:0] tpm_intf_capability;
logic [31:0] tpm_status;
logic [31:0] tpm_did_vid;
logic [7:0] tpm_rid;
// Buffer and FIFO signals
logic tpm_cmdaddr_rvalid, tpm_cmdaddr_rready;
logic [31:0] tpm_cmdaddr_rdata;
logic tpm_wrfifo_rvalid, tpm_wrfifo_rready;
logic [7:0] tpm_wrfifo_rdata;
logic tpm_rdfifo_wvalid, tpm_rdfifo_wready;
logic [TpmRdFifoWidth-1:0] tpm_rdfifo_wdata;
tpm_cap_t tpm_cap;
// TPM CFG
logic cfg_tpm_en, cfg_tpm_mode, cfg_tpm_hw_reg_dis;
logic cfg_tpm_invalid_locality, cfg_tpm_reg_chk_dis;
// TPM_STATUS
logic tpm_status_cmdaddr_notempty;
logic [TpmWrFifoPtrW-1:0] tpm_status_wrfifo_depth;
// TPM ---------------------------------------------------------------
/////////////////
// CSb Buffers //
/////////////////
// Split the CSB into multiple explicit buffers. One for reset, two for each
// clock domains.
logic clk_csb_buf, rst_csb_buf, sys_csb, sck_csb;
prim_buf #(
.Width (4)
) u_csb_buf (
.in_i ({4{cio_csb_i}}),
.out_o ({clk_csb_buf, rst_csb_buf, sys_csb, sck_csb})
);
// Split TPM CSB into explicit reset and data.
logic rst_tpm_csb_buf, sys_tpm_csb_buf, sck_tpm_csb_buf;
logic sys_tpm_csb_syncd; // synchronized prior to be connected to reg
prim_buf #(
.Width (3)
) u_tpm_csb_buf (
.in_i ({3{cio_tpm_csb_i}}),
.out_o ({rst_tpm_csb_buf, sys_tpm_csb_buf, sck_tpm_csb_buf})
);
//////////////////////////////////////////////////////////////////////
// Connect phase (between control signals above and register module //
//////////////////////////////////////////////////////////////////////
assign cpol = reg2hw.cfg.cpol.q;
assign cpha = reg2hw.cfg.cpha.q;
assign txorder = reg2hw.cfg.tx_order.q;
assign rxorder = reg2hw.cfg.rx_order.q;
assign rst_txfifo_reg = reg2hw.control.rst_txfifo.q;
assign rst_rxfifo_reg = reg2hw.control.rst_rxfifo.q;
assign sram_clk_en = reg2hw.control.sram_clk_en.q;
assign timer_v = reg2hw.cfg.timer_v.q;
//assign cfg_addr_4b_en = reg2hw.cfg.addr_4b_en.q;
assign sram_rxf_bindex = reg2hw.rxf_addr.base.q[SDW+:SramAw];
assign sram_rxf_lindex = reg2hw.rxf_addr.limit.q[SDW+:SramAw];
assign sram_txf_bindex = reg2hw.txf_addr.base.q[SDW+:SramAw];
assign sram_txf_lindex = reg2hw.txf_addr.limit.q[SDW+:SramAw];
assign sram_rxf_rptr = reg2hw.rxf_ptr.rptr.q[PtrW-1:0];
assign hw2reg.rxf_ptr.wptr.d = {{(16-PtrW){1'b0}}, sram_rxf_wptr};
assign hw2reg.rxf_ptr.wptr.de = 1'b1;
assign sram_txf_wptr = reg2hw.txf_ptr.wptr.q[PtrW-1:0];
assign hw2reg.txf_ptr.rptr.d = {{(16-PtrW){1'b0}}, sram_txf_rptr};
assign hw2reg.txf_ptr.rptr.de = 1'b1;
assign abort = reg2hw.control.abort.q;
assign hw2reg.status.abort_done.d = 1'b1;
assign hw2reg.status.rxf_empty.d = rxf_empty;
assign hw2reg.status.txf_full.d = txf_full;
// SYNC logic required
assign hw2reg.status.rxf_full.d = rxf_full_syncd;
assign hw2reg.status.txf_empty.d = txf_empty_syncd;
// CSb : after 2stage synchronizer
assign hw2reg.status.csb.d = sys_csb_syncd;
assign hw2reg.status.tpm_csb.d = sys_tpm_csb_syncd;
logic rxf_full_q, txf_empty_q;
always_ff @(posedge clk_spi_in_buf or negedge rst_ni) begin
if (!rst_ni) rxf_full_q <= 1'b0;
else rxf_full_q <= rxf_full;
end
always_ff @(posedge clk_spi_out_buf or negedge rst_ni) begin
if (!rst_ni) txf_empty_q <= 1'b1;
else txf_empty_q <= txf_empty;
end
prim_flop_2sync #(.Width(1)) u_sync_rxf (
.clk_i,
.rst_ni,
.d_i(rxf_full_q),
.q_o(rxf_full_syncd)
);
prim_flop_2sync #(.Width(1), .ResetValue(1'b1)) u_sync_txe (
.clk_i,
.rst_ni,
.d_i(txf_empty_q),
.q_o(txf_empty_syncd)
);
assign spi_mode = spi_mode_e'(reg2hw.control.mode.q);
// Async FIFO level
// rx rdepth, tx wdepth to be in main clock domain
assign hw2reg.async_fifo_level.txlvl.d = {{(8-AsFifoDepthW){1'b0}}, as_txfifo_depth};
assign hw2reg.async_fifo_level.rxlvl.d = {{(8-AsFifoDepthW){1'b0}}, as_rxfifo_depth};
// Interrupt
// Edge
logic sram_rxf_full_q, fwm_rxerr_q;
logic sram_rxf_full , fwm_rxerr ;
// ICEBOX(#10058): Check if CE# deasserted in the middle of bit transfer
assign fwm_rxerr = 1'b0;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
sram_rxf_full_q <= 1'b0;
fwm_rxerr_q <= 1'b0;
end else begin
sram_rxf_full_q <= sram_rxf_full;
fwm_rxerr_q <= fwm_rxerr;
end
end
// Interrupt
assign intr_sram_rxf_full = ~sram_rxf_full_q & sram_rxf_full;
assign intr_fwm_rxerr = ~fwm_rxerr_q & fwm_rxerr;
assign rxlvl_d = (sram_rxf_depth >= reg2hw.fifo_level.rxlvl.q[PtrW-1:0]) ;
assign txlvl_d = (sram_txf_depth < reg2hw.fifo_level.txlvl.q[PtrW-1:0]) ;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rxlvl <= 1'b0;
txlvl <= 1'b0;
end else begin
rxlvl <= rxlvl_d;
txlvl <= txlvl_d;
end
end
assign intr_fwm_rxlvl = ~rxlvl && rxlvl_d;
assign intr_fwm_txlvl = ~txlvl && txlvl_d;
// rxf_overflow
// Could trigger lint error for input clock.
// It's unavoidable due to the characteristics of SPI intf
prim_pulse_sync u_rxf_overflow (
.clk_src_i (clk_spi_in_buf ),
.rst_src_ni (rst_ni ),
.src_pulse_i (rxf_overflow ),
.clk_dst_i (clk_i ),
.rst_dst_ni (rst_ni ),
.dst_pulse_o (intr_fwm_rxoverflow)
);
// txf_underflow
// Could trigger lint error for input clock.
// It's unavoidable due to the characteristics of SPI intf
prim_pulse_sync u_txf_underflow (
.clk_src_i (clk_spi_out_buf ),
.rst_src_ni (rst_ni ),
.src_pulse_i (txf_underflow ),
.clk_dst_i (clk_i ),
.rst_dst_ni (rst_ni ),
.dst_pulse_o (intr_fwm_txunderflow)
);
prim_intr_hw #(.Width(1)) u_intr_rxf (
.clk_i,
.rst_ni,
.event_intr_i (intr_sram_rxf_full ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.generic_rx_full.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.generic_rx_full.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.generic_rx_full.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.generic_rx_full.q ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.generic_rx_full.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.generic_rx_full.d ),
.intr_o (intr_generic_rx_full_o )
);
prim_intr_hw #(.Width(1)) u_intr_rxlvl (
.clk_i,
.rst_ni,
.event_intr_i (intr_fwm_rxlvl ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.generic_rx_watermark.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.generic_rx_watermark.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.generic_rx_watermark.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.generic_rx_watermark.q ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.generic_rx_watermark.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.generic_rx_watermark.d ),
.intr_o (intr_generic_rx_watermark_o )
);
prim_intr_hw #(.Width(1)) u_intr_txlvl (
.clk_i,
.rst_ni,
.event_intr_i (intr_fwm_txlvl ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.generic_tx_watermark.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.generic_tx_watermark.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.generic_tx_watermark.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.generic_tx_watermark.q ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.generic_tx_watermark.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.generic_tx_watermark.d ),
.intr_o (intr_generic_tx_watermark_o )
);
prim_intr_hw #(.Width(1)) u_intr_rxerr (
.clk_i,
.rst_ni,
.event_intr_i (intr_fwm_rxerr ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.generic_rx_error.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.generic_rx_error.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.generic_rx_error.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.generic_rx_error.q ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.generic_rx_error.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.generic_rx_error.d ),
.intr_o (intr_generic_rx_error_o )
);
prim_intr_hw #(.Width(1)) u_intr_rxoverflow (
.clk_i,
.rst_ni,
.event_intr_i (intr_fwm_rxoverflow ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.generic_rx_overflow.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.generic_rx_overflow.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.generic_rx_overflow.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.generic_rx_overflow.q ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.generic_rx_overflow.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.generic_rx_overflow.d ),
.intr_o (intr_generic_rx_overflow_o )
);
prim_intr_hw #(.Width(1)) u_intr_txunderflow (
.clk_i,
.rst_ni,
.event_intr_i (intr_fwm_txunderflow ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.generic_tx_underflow.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.generic_tx_underflow.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.generic_tx_underflow.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.generic_tx_underflow.q ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.generic_tx_underflow.de),
.hw2reg_intr_state_d_o (hw2reg.intr_state.generic_tx_underflow.d ),
.intr_o (intr_generic_tx_underflow_o )
);
prim_edge_detector #(
.Width (2),
.EnSync(1'b 0)
) u_intr_upload_edge (
.clk_i,
.rst_ni,
.d_i ({payload_notempty, payload_overflow}),
.q_sync_o (),
.q_posedge_pulse_o ({intr_upload_payload_not_empty,
intr_upload_payload_overflow}),
.q_negedge_pulse_o ()
);
assign intr_upload_cmdfifo_not_empty = cmdfifo_set_pulse;
prim_intr_hw #(.Width(1)) u_intr_cmdfifo_not_empty (
.clk_i,
.rst_ni,
.event_intr_i (intr_upload_cmdfifo_not_empty ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.upload_cmdfifo_not_empty.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.upload_cmdfifo_not_empty.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.upload_cmdfifo_not_empty.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.upload_cmdfifo_not_empty.q ),
.hw2reg_intr_state_d_o (hw2reg.intr_state.upload_cmdfifo_not_empty.d ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.upload_cmdfifo_not_empty.de),
.intr_o (intr_upload_cmdfifo_not_empty_o )
);
prim_intr_hw #(.Width(1)) u_intr_payload_not_empty (
.clk_i,
.rst_ni,
.event_intr_i (intr_upload_payload_not_empty ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.upload_payload_not_empty.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.upload_payload_not_empty.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.upload_payload_not_empty.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.upload_payload_not_empty.q ),
.hw2reg_intr_state_d_o (hw2reg.intr_state.upload_payload_not_empty.d ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.upload_payload_not_empty.de),
.intr_o (intr_upload_payload_not_empty_o )
);
prim_intr_hw #(.Width(1)) u_intr_payload_overflow (
.clk_i,
.rst_ni,
.event_intr_i (intr_upload_payload_overflow ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.upload_payload_overflow.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.upload_payload_overflow.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.upload_payload_overflow.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.upload_payload_overflow.q ),
.hw2reg_intr_state_d_o (hw2reg.intr_state.upload_payload_overflow.d ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.upload_payload_overflow.de),
.intr_o (intr_upload_payload_overflow_o )
);
prim_pulse_sync u_flash_readbuf_watermark_pulse_sync (
.clk_src_i (clk_spi_in_buf ),
.rst_src_ni (rst_ni ),
.src_pulse_i (flash_sck_readbuf_watermark),
.clk_dst_i (clk_i ),
.rst_dst_ni (rst_ni ),
.dst_pulse_o (intr_readbuf_watermark )
);
prim_intr_hw #(.Width(1)) u_intr_readbuf_watermark (
.clk_i,
.rst_ni,
.event_intr_i (intr_readbuf_watermark ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.readbuf_watermark.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.readbuf_watermark.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.readbuf_watermark.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.readbuf_watermark.q ),
.hw2reg_intr_state_d_o (hw2reg.intr_state.readbuf_watermark.d ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.readbuf_watermark.de),
.intr_o (intr_readbuf_watermark_o )
);
prim_pulse_sync u_flash_readbuf_flip_pulse_sync (
.clk_src_i (clk_spi_in_buf ),
.rst_src_ni (rst_ni ),
.src_pulse_i (flash_sck_readbuf_flip),
.clk_dst_i (clk_i ),
.rst_dst_ni (rst_ni ),
.dst_pulse_o (intr_readbuf_flip )
);
prim_intr_hw #(.Width(1)) u_intr_readbuf_flip (
.clk_i,
.rst_ni,
.event_intr_i (intr_readbuf_flip ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.readbuf_flip.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.readbuf_flip.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.readbuf_flip.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.readbuf_flip.q ),
.hw2reg_intr_state_d_o (hw2reg.intr_state.readbuf_flip.d ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.readbuf_flip.de),
.intr_o (intr_readbuf_flip_o )
);
// cmdaddr_notempty is a level signal. Issue has been discussed in
// https://github.com/lowRISC/opentitan/issues/15282.
//
// TODO: Remove `prim_intr_hw` and ditect connect from status(level)
// assign intr_o = (status | test) & enable;
prim_intr_hw #(
.Width (1 ),
.IntrT ("Status")
) u_intr_tpm_cmdaddr_notempty (
.clk_i,
.rst_ni,
.event_intr_i (tpm_status_cmdaddr_notempty ),
.reg2hw_intr_enable_q_i (reg2hw.intr_enable.tpm_header_not_empty.q),
.reg2hw_intr_test_q_i (reg2hw.intr_test.tpm_header_not_empty.q ),
.reg2hw_intr_test_qe_i (reg2hw.intr_test.tpm_header_not_empty.qe ),
.reg2hw_intr_state_q_i (reg2hw.intr_state.tpm_header_not_empty.q ),
.hw2reg_intr_state_d_o (hw2reg.intr_state.tpm_header_not_empty.d ),
.hw2reg_intr_state_de_o (hw2reg.intr_state.tpm_header_not_empty.de),
.intr_o (intr_tpm_header_not_empty_o )
);
// SPI Flash commands registers
assign cfg_intercept_en = '{
status: reg2hw.intercept_en.status.q,
jedec: reg2hw.intercept_en.jedec.q,
sfdp: reg2hw.intercept_en.sfdp.q,
mbx: reg2hw.intercept_en.mbx.q
};
logic unused_cfg_intercept_en;
assign unused_cfg_intercept_en = ^cfg_intercept_en;
assign hw2reg.last_read_addr.d = readbuf_addr_busclk;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
readbuf_addr_busclk <= '0;
end else if (sys_csb_deasserted_pulse) begin
readbuf_addr_busclk <= readbuf_addr_sck;
end
end
// Jedec ID
assign jedec_cfg = '{ num_cc: reg2hw.jedec_cc.num_cc.q,
cc: reg2hw.jedec_cc.cc.q,
jedec_id: reg2hw.jedec_id.mf.q,
device_id: reg2hw.jedec_id.id.q
};
assign readbuf_threshold = reg2hw.read_threshold.q[BufferAw:0];
localparam int unsigned MailboxAw = $clog2(SramMailboxDepth*SramDw/8);
assign cfg_mailbox_en = reg2hw.cfg.mailbox_en.q;
assign mailbox_addr = { reg2hw.mailbox_addr.q[31:MailboxAw],
{MailboxAw{1'b0}}
};
logic unused_mailbox_addr;
assign unused_mailbox_addr = ^reg2hw.mailbox_addr.q[MailboxAw-1:0];
// Passthrough config: value shall be stable while SPI transaction is active
//assign cmd_filter = reg2hw.cmd_filter.q;
always_comb begin
for (int unsigned i = 0 ; i < 256 ; i++) begin
cmd_filter[i] = reg2hw.cmd_filter[i].q;
end
end
assign addr_swap_mask = reg2hw.addr_swap_mask.q;
assign addr_swap_data = reg2hw.addr_swap_data.q;
// payload_swap_mask and _data are big-endian to calculate easily.
assign payload_swap_mask = {<<8{reg2hw.payload_swap_mask.q}};
assign payload_swap_data = {<<8{reg2hw.payload_swap_data.q}};
// Connect command info
always_comb begin
for (int unsigned i = 0 ; i < spi_device_reg_pkg::NumCmdInfo ; i++) begin
cmd_info[i] = '{
valid: reg2hw.cmd_info[i].valid.q,
opcode: reg2hw.cmd_info[i].opcode.q,
addr_mode: addr_mode_e'(reg2hw.cmd_info[i].addr_mode.q),
addr_swap_en: reg2hw.cmd_info[i].addr_swap_en.q,
mbyte_en: reg2hw.cmd_info[i].mbyte_en.q,
dummy_en: reg2hw.cmd_info[i].dummy_en.q,
dummy_size: reg2hw.cmd_info[i].dummy_size.q,
payload_en: reg2hw.cmd_info[i].payload_en.q,
payload_dir: payload_dir_e'(reg2hw.cmd_info[i].payload_dir.q),
payload_swap_en: reg2hw.cmd_info[i].payload_swap_en.q,
upload: reg2hw.cmd_info[i].upload.q,
busy: reg2hw.cmd_info[i].busy.q
};
end
// Manual addition to cmd_info list
// Default Input mode
for (int unsigned i = CmdInfoReserveEnd + 1; i < NumTotalCmdInfo; i++) begin
cmd_info[i] = CmdInfoInput;
end
// Hand crafted command information slots
cmd_info[CmdInfoEn4B].valid = reg2hw.cmd_info_en4b.valid.q;
cmd_info[CmdInfoEn4B].opcode = reg2hw.cmd_info_en4b.opcode.q;
cmd_info[CmdInfoEx4B].valid = reg2hw.cmd_info_ex4b.valid.q;
cmd_info[CmdInfoEx4B].opcode = reg2hw.cmd_info_ex4b.opcode.q;
cmd_info[CmdInfoWrEn].valid = reg2hw.cmd_info_wren.valid.q;
cmd_info[CmdInfoWrEn].opcode = reg2hw.cmd_info_wren.opcode.q;
cmd_info[CmdInfoWrDi].valid = reg2hw.cmd_info_wrdi.valid.q;
cmd_info[CmdInfoWrDi].opcode = reg2hw.cmd_info_wrdi.opcode.q;
end
//////////////////////////////
// // Clock & reset control //
//////////////////////////////
// clk_spi cannot use glitch-free clock mux as clock switching in glitch-free
// requires two clocks to propagate clock selection and enable but SPI clock
// doesn't exist until it transmits data through SDI
logic sck_n;
logic rst_spi_n;
prim_mubi_pkg::mubi4_t [ScanModeUseLast-1:0] scanmode;
prim_mubi4_sync #(
.NumCopies(int'(ScanModeUseLast)),
.AsyncOn(0) // clock/reset below is only used for SVAs.
) u_scanmode_sync (
.clk_i,
.rst_ni,
.mubi_i(scanmode_i),
.mubi_o(scanmode)
);
prim_clock_inv #(
.NoFpgaBufG(1'b1)
) u_clk_spi (
.clk_i(cio_sck_i),
.clk_no(sck_n),
.scanmode_i(prim_mubi_pkg::mubi4_test_true_strict(scanmode[ClkInvSel]))
);
assign sck_monitor_o = cio_sck_i;
assign clk_spi_in = (cpha ^ cpol) ? sck_n : cio_sck_i ;
assign clk_spi_out = (cpha ^ cpol) ? cio_sck_i : sck_n ;
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_clk_spi_in_mux (
.clk0_i(clk_spi_in),
.clk1_i(scan_clk_i),
.sel_i(prim_mubi_pkg::mubi4_test_true_strict(scanmode[ClkMuxSel])),
.clk_o(clk_spi_in_muxed)
);
prim_clock_buf #(
.RegionSel(1'b1)
) u_clk_spi_in_buf(
.clk_i (clk_spi_in_muxed),
.clk_o (clk_spi_in_buf)
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_clk_spi_out_mux (
.clk0_i(clk_spi_out),
.clk1_i(scan_clk_i),
.sel_i(prim_mubi_pkg::mubi4_test_true_strict(scanmode[ClkMuxSel])),
.clk_o(clk_spi_out_muxed)
);
prim_clock_buf #(
.RegionSel(1'b1)
) u_clk_spi_out_buf(
.clk_i (clk_spi_out_muxed),
.clk_o (clk_spi_out_buf)
);
// CSb muxed to scan clock
prim_clock_mux2 #(
.NoFpgaBufG(1'b 1)
) u_clk_csb_mux (
.clk0_i (clk_csb_buf),
.clk1_i (scan_clk_i ),
.sel_i (prim_mubi_pkg::mubi4_test_true_strict(scanmode[ClkMuxSel])),
.clk_o (clk_csb_muxed)
);
prim_clock_buf #(
.NoFpgaBuf (1'b 1)
) u_clk_csb_buf (
.clk_i (clk_csb_muxed),
.clk_o (clk_csb )
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_csb_rst_scan_mux (
.clk0_i(rst_ni & ~rst_csb_buf),
.clk1_i(scan_rst_ni),
.sel_i(prim_mubi_pkg::mubi4_test_true_strict(scanmode[CsbRstMuxSel])),
.clk_o(rst_spi_n)
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_tx_rst_scan_mux (
.clk0_i(rst_ni & ~rst_txfifo_reg),
.clk1_i(scan_rst_ni),
.sel_i(prim_mubi_pkg::mubi4_test_true_strict(scanmode[TxRstMuxSel])),
.clk_o(rst_txfifo_n)
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_rx_rst_scan_mux (
.clk0_i(rst_ni & ~rst_rxfifo_reg),
.clk1_i(scan_rst_ni),
.sel_i(prim_mubi_pkg::mubi4_test_true_strict(scanmode[RxRstMuxSel])),
.clk_o(rst_rxfifo_n)
);
logic tpm_rst_n, sys_tpm_rst_n;
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_tpm_csb_rst_scan_mux (
.clk0_i (rst_ni & ~rst_tpm_csb_buf),
.clk1_i (scan_rst_ni),
.sel_i (prim_mubi_pkg::mubi4_test_true_strict(scanmode[TpmRstSel])),
.clk_o (tpm_rst_n)
);
// TPM Read FIFO uses TPM CSb as a reset.
// The write port is clocked at SYS_CLK. Metastability may occur as CSb may
// be asserted, de-asserted independent of SYS_CLK. This reset synchronizer
// (sync to SYS_CLK), may delay the reset signal by 2 SYS_CLK when TPM_CSb
// is de-asserted. However, Read FIFO is being used after TPM module
// receives the command (8 SPI_CLK), then 24bit address (24 SPI_CLK). So,
// there are plenty of time to synced reset being released unless SYS_CLK:
// SPI_CLK is <1:12.
prim_rst_sync #(
.ActiveHigh (1'b 0),
.SkipScan (1'b 0)
) u_tpm_csb_rst_sync (
.clk_i (clk_i),
.d_i (tpm_rst_n),
.q_o (sys_tpm_rst_n),
.scan_rst_ni,
.scanmode_i (scanmode[TpmRstSel])
);
// SRAM clock
// If FwMode, SRAM clock for B port uses peripheral clock (clk_i)
// If FlashMode or PassThrough, SRAM clock for B port uses SPI_CLK
// To remove glitch, CG cell is put after clock mux
// The enable signal is not synchronized to SRAM_CLK when clock is
// switched into SPI_CLK. So, change the clock only when SPI_CLK is
// not toggle.
//
// Programming sequence:
// Change to SPI_CLK
// 1. Check if SPI line is idle.
// 2. Clear sram_clk_en to 0.
// 3. Change mode to FlashMode or PassThrough
// 4. Set sram_clk_en to 1.
// Change to peripheral clk
// 1. Check if SPI_CLK is idle
// 2. Clear sram_clk_en to 0.
// 3. Change mode to FwMode
// 4. Set sram_clk_en to 1.
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_sram_clk_sel (
.clk0_i (clk_spi_in_buf),
.clk1_i (clk_i),
.sel_i (spi_mode == FwMode),
.clk_o (sram_clk_ungated)
);
prim_clock_mux2 #(
.NoFpgaBufG(1'b1)
) u_sram_clk_scan (
.clk0_i (sram_clk_ungated),
.clk1_i (scan_clk_i),
.sel_i ((prim_mubi_pkg::mubi4_test_true_strict(scanmode[ClkSramSel]) | mbist_en_i)),
.clk_o (sram_clk_muxed)
);
prim_clock_gating #(
.FpgaBufGlobal(1'b0)
) u_sram_clk_cg (
.clk_i (sram_clk_muxed),
.en_i (sram_clk_en),
.test_en_i ((prim_mubi_pkg::mubi4_test_true_strict(scanmode[ClkSramSel]) | mbist_en_i)),
.clk_o (sram_clk)
);
prim_clock_mux2 #(
.NoFpgaBufG (1'b1)
) u_sram_rst_sel (
.clk0_i (rst_spi_n),
.clk1_i (rst_ni),
.sel_i (spi_mode == FwMode),
.clk_o (sram_rst_n_noscan)
);
prim_clock_mux2 #(
.NoFpgaBufG (1'b1)
) u_sram_rst_scanmux (
.clk0_i (sram_rst_n_noscan),
.clk1_i (scan_rst_ni),
.sel_i (prim_mubi_pkg::mubi4_test_true_strict(scanmode[RstSramSel])),
.clk_o (sram_rst_n)
);
// CSb edge on the system clock
prim_edge_detector #(
.Width (1 ),
.ResetValue(1'b 1),
.EnSync (1'b 1)
) u_csb_edge_sysclk (
.clk_i,
.rst_ni,
.d_i (sys_csb ),
.q_sync_o (sys_csb_syncd),
// sys_csb_assertion can be detected but no usage
.q_posedge_pulse_o (sys_csb_deasserted_pulse),
.q_negedge_pulse_o ( )
);
// CSb edge on the SPI input clock
prim_edge_detector #(
.Width (1 ),
.ResetValue (1'b 1),
.EnSync (1'b 1)
) u_csb_edge_spiclk (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.d_i (sck_csb),
.q_sync_o ( ),
// posedge(deassertion) cannot be detected as clock could be absent.
.q_posedge_pulse_o ( ),
.q_negedge_pulse_o (sck_csb_asserted_pulse)
);
// TPM CSb 2FF sync to SYS_CLK
prim_flop_2sync #(
.Width (1 ),
.ResetValue (1'b 1)
) u_sys_tpm_csb_sync (
.clk_i,
.rst_ni,
.d_i (sys_tpm_csb_buf),
.q_o (sys_tpm_csb_syncd)
);
///////////////////////////////////////////////////////////////////////////
// CDC
// 1. csb_i pulse is detected by asynchronous reset in spi clk
// 2. Detected edge pulse is converted to toggle level in spi clk
// 3. The spi toggle level goes to 2nd edge detector in sys clk
// => sys_csb_pos_pulse_stretch is used for capturing SW data on sys_clk
// 4. To clear the toggle level in spi clk, we create a ack pulse from
// sys_csb_pos_pulse_stretch using 3rd edge detector
// PR# : #13859 and #13586
///////////////////////////////////////////////////////////////////////////
logic spi_clk_csb_rst_pulse;
logic spi_clk_csb_rst_toggle;
logic sys_csb_pos_pulse_stretch;
logic spi_clk_ack;
// spi_clk csb_rst_asserted_pulse
prim_edge_detector #(
.Width (1),
.ResetValue (1'b 1),
.EnSync (1'b 1)
) u_sck_csb_edge (
.clk_i (clk_spi_in_buf),
.rst_ni (tpm_rst_n),
.d_i (1'b0), // tpm_rst_n has CSb assertion
.q_sync_o (),
.q_posedge_pulse_o (),
.q_negedge_pulse_o (spi_clk_csb_rst_pulse)
);
always_ff @(posedge clk_spi_in_buf or negedge tpm_rst_n) begin
if (!tpm_rst_n) spi_clk_csb_rst_toggle <= 1'b0;
else if (spi_clk_csb_rst_pulse)
spi_clk_csb_rst_toggle <= !spi_clk_csb_rst_toggle;
else if (spi_clk_ack)
spi_clk_csb_rst_toggle <= 1'b0;
end
// sys_clk csb_rst_asserted_pulse with pulse stretch
prim_edge_detector #(
.Width (1),
.ResetValue (1'b 0),
.EnSync (1'b 1)
) u_clk_csb_edge_0 (
.clk_i (clk_i),
.rst_ni (rst_ni),
.d_i (spi_clk_csb_rst_toggle), // tpm_rst_n has CSb assertion
.q_sync_o (),
.q_posedge_pulse_o (sys_csb_pos_pulse_stretch),
.q_negedge_pulse_o ()
);
logic sys_clk_tog;
logic spi_clk_pos_edge;
logic spi_clk_neg_edge;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) sys_clk_tog <= 1'b0;
else if (sys_csb_pos_pulse_stretch)
sys_clk_tog <= !sys_clk_tog ;
end
// spi_clk csb_rst_asserted_pulse
prim_edge_detector #(
.Width (1),
.ResetValue (1'b 0),
.EnSync (1'b 1)
) u_sck_tog_edge (
.clk_i (clk_spi_in_buf),
.rst_ni (tpm_rst_n),
.d_i (sys_clk_tog), // tpm_rst_n has CSb assertion
.q_sync_o (),
.q_posedge_pulse_o (spi_clk_pos_edge),
.q_negedge_pulse_o (spi_clk_neg_edge)
);
assign spi_clk_ack = spi_clk_pos_edge | spi_clk_neg_edge;
// spi_clk_csb_rst_toggle should already have = 0 before tpm_rst_n
`ASSERT(CsPulseWidth_A, !tpm_rst_n |-> spi_clk_csb_rst_toggle == '0)
//////////////////////////////
// SPI_DEVICE mode selector //
//////////////////////////////
// This logic chooses appropriate signals based on input SPI_DEVICE mode.
// e.g) If FwMode is selected. all data connected to spi_fwmode logic
// Assume spi_mode does not change dynamically
// io_mode to spi_s2p io_mode should be affected at the negedge of SPI_CLK
// based on SPI protocol. the internal io_mode signal is generated by SPI
// input signals. So, the io_mode should be latched at clk_spi_out to not
// introduce the timing loop.
//
// example: cmdparse triggers sel_dp at 8th beat of CMD bit.
// -> readcmd activates, it also changes IoMode if opcode is DualIO
// or QuadIO commands
// -> changed io_mode affects spi_s2p module, which again affects
// cmdparse module.
always_ff @(posedge clk_spi_out_buf or negedge rst_spi_n) begin
if (!rst_spi_n) io_mode_outclk <= SingleIO;
else io_mode_outclk <= io_mode;
end
always_ff @(posedge clk_spi_out_buf or negedge rst_spi_n) begin
if (!rst_spi_n) cmd_dp_sel_outclk <= DpNone;
else cmd_dp_sel_outclk <= cmd_dp_sel;
end
// SCK clock domain MUX for SRAM access for Flash and Passthrough
always_comb begin
flash_sram_l2m = '{ default: '0 };
for (int unsigned i = IoModeCmdParse ; i < IoModeEnd ; i++) begin
sub_sram_m2l[i] = '{
rvalid: 1'b 0,
rdata: '0,
rerror: '{uncorr: 1'b 0, corr: 1'b 0}
};
end
unique case (cmd_dp_sel)
DpReadCmd, DpReadSFDP: begin
// SRAM:: Remember this has glitch
// switch should happen only when clock gate is disabled.
flash_sram_l2m = sub_sram_l2m[IoModeReadCmd];
sub_sram_m2l[IoModeReadCmd] = flash_sram_m2l;
end
DpUpload: begin
flash_sram_l2m = sub_sram_l2m[IoModeUpload];
sub_sram_m2l[IoModeUpload] = flash_sram_m2l;
end
default: begin
// DpNone, DpReadStatus, DpReadJEDEC
flash_sram_l2m = '{default: '0 };
end
endcase
end
// inverted SCK clock domain MUX for IO Mode and P2S
always_comb begin
io_mode = SingleIO;
p2s_valid = 1'b 0;
p2s_data = 8'h 0;
sub_p2s_sent = '{default: 1'b 0};
mem_b_l2m = '{ default: '0 };
sub_sram_m2l[IoModeFw] = '{
rvalid: 1'b 0,
rdata: '0,
rerror: '{uncorr: 1'b 0, corr: 1'b 0}
};
flash_sram_m2l = '{
rvalid: 1'b 0,
rdata: '0,
rerror: '{uncorr: 1'b 0, corr: 1'b 0}
};
unique case (spi_mode)
FwMode: begin
io_mode = sub_iomode[IoModeFw];
p2s_valid = sub_p2s_valid[IoModeFw];
p2s_data = sub_p2s_data[IoModeFw];
sub_p2s_sent[IoModeFw] = p2s_sent;
// SRAM:: Remember this has glitch
// switch should happen only when clock gate is disabled.
mem_b_l2m = sub_sram_l2m[IoModeFw];
sub_sram_m2l[IoModeFw] = mem_b_m2l;
end
FlashMode, PassThrough: begin
// SRAM comb logic is in SCK clock domain
mem_b_l2m = flash_sram_l2m;
flash_sram_m2l = mem_b_m2l;
unique case (cmd_dp_sel_outclk)
DpNone: begin
io_mode = sub_iomode[IoModeCmdParse];
sub_p2s_sent[IoModeCmdParse] = p2s_sent;
end
DpReadCmd, DpReadSFDP: begin
io_mode = sub_iomode[IoModeReadCmd];
p2s_valid = sub_p2s_valid[IoModeReadCmd];
p2s_data = sub_p2s_data[IoModeReadCmd];
sub_p2s_sent[IoModeReadCmd] = p2s_sent;
end
DpReadStatus: begin
io_mode = sub_iomode[IoModeStatus];
p2s_valid = sub_p2s_valid[IoModeStatus];
p2s_data = sub_p2s_data[IoModeStatus];
sub_p2s_sent[IoModeStatus] = p2s_sent;
end
DpReadJEDEC: begin
io_mode = sub_iomode[IoModeJedec];
p2s_valid = sub_p2s_valid[IoModeJedec];
p2s_data = sub_p2s_data[IoModeJedec];
sub_p2s_sent[IoModeJedec] = p2s_sent;
end
DpUpload: begin
io_mode = sub_iomode[IoModeUpload];
p2s_valid = sub_p2s_valid[IoModeUpload];
p2s_data = sub_p2s_data[IoModeUpload];
sub_p2s_sent[IoModeUpload] = p2s_sent;
end
// DpUnknown:
default: begin
io_mode = sub_iomode[IoModeCmdParse];
sub_p2s_sent[IoModeCmdParse] = p2s_sent;
end
endcase
end
default: begin
io_mode = SingleIO;
end
endcase
end
`ASSERT_KNOWN(SpiModeKnown_A, spi_mode)
always_comb begin
cio_sd_o = internal_sd;
cio_sd_en_o = internal_sd_en;
if (cfg_tpm_en && !sck_tpm_csb_buf) begin : miso_tpm
// TPM transaction is on-going. MOSI, MISO is being used by TPM
cio_sd_o = {2'b 00, tpm_miso, 1'b 0};
cio_sd_en_o = {2'b 00, tpm_miso_en, 1'b 0};
end else begin : spi_out_flash_passthrough
// SPI Generic, Flash, Passthrough modes
unique case (spi_mode)
FwMode, FlashMode: begin
cio_sd_o = internal_sd;
cio_sd_en_o = internal_sd_en;
end
PassThrough: begin
if (intercept_en) begin
cio_sd_o = internal_sd;
cio_sd_en_o = internal_sd_en;
end else begin
cio_sd_o = passthrough_sd;
cio_sd_en_o = passthrough_sd_en;
end
end
default: begin
cio_sd_o = internal_sd;
cio_sd_en_o = internal_sd_en;
end
endcase
end
end
// Assume `intercept` is registered (SPI_IN).
// passthrough assumed signal shall be registered in (SPI_OUT)
always_ff @(posedge clk_spi_out_buf or negedge rst_spi_n) begin
if (!rst_spi_n) intercept_en <= 1'b 0;
else intercept_en <= |intercept;
end
// intercept_en shall not be de-asserted except mailbox
`ASSUME(InterceptLevel_M,
$rose(|{intercept.status, intercept.jedec, intercept.sfdp}) |=>
##1 $stable(intercept_en) until !rst_spi_n,
clk_spi_out_buf, !rst_spi_n)
////////////////////////////
// SPI Serial to Parallel //
////////////////////////////
spi_s2p u_s2p (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
// SPI interface
.s_i (cio_sd_i),
.data_valid_o (s2p_data_valid),
.data_o (s2p_data ),
// Config (changed dynamically)
.order_i (rxorder),
.io_mode_i (io_mode_outclk)
);
spi_p2s u_p2s (
.clk_i (clk_spi_out_buf),
.rst_ni (rst_spi_n),
.data_valid_i (p2s_valid),
.data_i (p2s_data),
.data_sent_o (p2s_sent),
.csb_i (sck_csb),
.s_en_o (internal_sd_en),
.s_o (internal_sd),
.cpha_i (cpha),
.order_i (txorder),
.io_mode_i (io_mode_outclk)
);
/////////////
// FW Mode //
/////////////
spi_fwmode #(
.FifoWidth (FifoWidth),
.FifoDepth (FifoDepth)
) u_fwmode (
.clk_i (sram_clk),
.rst_ni (sram_rst_n),
.clk_spi_in_i (clk_spi_in_buf),
.rst_rxfifo_ni (rst_rxfifo_n),
.clk_spi_out_i (clk_spi_out_buf),
.rst_txfifo_ni (rst_txfifo_n),
// Mode
.spi_mode_i (spi_mode),
.rxf_overflow_o (rxf_overflow),
.txf_underflow_o (txf_underflow),
// SRAM interface
.fwm_req_o (sub_sram_l2m[IoModeFw].req ),
.fwm_write_o (sub_sram_l2m[IoModeFw].we ),
.fwm_addr_o (sub_sram_l2m[IoModeFw].addr ),
.fwm_wdata_o (sub_sram_l2m[IoModeFw].wdata ),
.fwm_wstrb_o (sub_sram_l2m[IoModeFw].wstrb ),
.fwm_rvalid_i (sub_sram_m2l[IoModeFw].rvalid ),
.fwm_rdata_i (sub_sram_m2l[IoModeFw].rdata ),
.fwm_rerror_i (sub_sram_m2l[IoModeFw].rerror ),
// Input from S2P
.rx_data_valid_i (s2p_data_valid),
.rx_data_i (s2p_data),
// Output to S2P (mode select)
.io_mode_o (sub_iomode[IoModeFw]),
// P2S
.tx_wvalid_o (sub_p2s_valid [IoModeFw]),
.tx_data_o (sub_p2s_data [IoModeFw]),
.tx_wready_i (sub_p2s_sent [IoModeFw]),
// CSRs
.timer_v_i (timer_v),
.sram_rxf_bindex_i (sram_rxf_bindex),
.sram_txf_bindex_i (sram_txf_bindex),
.sram_rxf_lindex_i (sram_rxf_lindex),
.sram_txf_lindex_i (sram_txf_lindex),
.abort_i (abort),
.sram_rxf_rptr_i (sram_rxf_rptr ),
.sram_rxf_wptr_o (sram_rxf_wptr ),
.sram_txf_rptr_o (sram_txf_rptr ),
.sram_txf_wptr_i (sram_txf_wptr ),
.sram_rxf_depth_o (sram_rxf_depth),
.sram_txf_depth_o (sram_txf_depth),
.sram_rxf_full_o (sram_rxf_full ),
.as_txfifo_depth_o (as_txfifo_depth),
.as_rxfifo_depth_o (as_rxfifo_depth),
.rxf_empty_o (rxf_empty),
.rxf_full_o (rxf_full),
.txf_empty_o (txf_empty),
.txf_full_o (txf_full)
);
////////////////////
// SPI Flash Mode //
////////////////////
spi_cmdparse u_cmdparse (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.data_valid_i (s2p_data_valid),
.data_i (s2p_data),
.spi_mode_i (spi_mode),
.cmd_info_i (cmd_info),
.io_mode_o (sub_iomode[IoModeCmdParse]),
.sel_dp_o (cmd_dp_sel),
.cmd_info_o (cmd_info_broadcast),
.cmd_info_idx_o (cmd_info_idx_broadcast),
.cfg_intercept_en_status_i (cfg_intercept_en.status),
.cfg_intercept_en_jedec_i (cfg_intercept_en.jedec),
.cfg_intercept_en_sfdp_i (cfg_intercept_en.sfdp),
.intercept_status_o (intercept.status),
.intercept_jedec_o (intercept.jedec),
.intercept_sfdp_o (intercept.sfdp),
// Not used for now
.cmd_config_req_o (),
.cmd_config_idx_o ()
);
spi_readcmd u_readcmd (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.clk_out_i (clk_spi_out_buf),
.sys_rst_ni (rst_ni),
.sel_dp_i (cmd_dp_sel),
// SRAM interface
.sram_l2m_o (sub_sram_l2m[IoModeReadCmd]),
.sram_m2l_i (sub_sram_m2l[IoModeReadCmd]),
// S2P
.s2p_valid_i (s2p_data_valid),
.s2p_byte_i (s2p_data),
// P2S
.p2s_valid_o (sub_p2s_valid [IoModeReadCmd]),
.p2s_byte_o (sub_p2s_data [IoModeReadCmd]),
.p2s_sent_i (sub_p2s_sent [IoModeReadCmd]),
.spi_mode_i (spi_mode),
.cmd_info_i (cmd_info_broadcast),
.cmd_info_idx_i (cmd_info_idx_broadcast),
.readbuf_threshold_i (readbuf_threshold),
.addr_4b_en_i (cfg_addr_4b_en),
.mailbox_en_i (cfg_mailbox_en ),
.cfg_intercept_en_mbx_i (cfg_intercept_en.mbx),
.mailbox_addr_i (mailbox_addr ),
.mailbox_assumed_o (intercept.mbx ),
.readbuf_address_o (readbuf_addr_sck),
.io_mode_o (sub_iomode [IoModeReadCmd]),
.sck_read_watermark_o (flash_sck_readbuf_watermark),
.sck_read_flip_o (flash_sck_readbuf_flip)
);
// Begin: Read Status ==============================================
logic readstatus_qe;
logic [23:0] readstatus_q;
logic [23:0] readstatus_d;
assign readstatus_qe = reg2hw.flash_status.busy.qe
&& reg2hw.flash_status.status.qe;
assign readstatus_q = { reg2hw.flash_status.status.q,
reg2hw.flash_status.busy.q
};
assign hw2reg.flash_status.busy.d = readstatus_d[0];
assign hw2reg.flash_status.status.d = readstatus_d[23:1];
assign sck_status_wr_set = (cmd_dp_sel == DpWrEn);
assign sck_status_wr_clr = (cmd_dp_sel == DpWrDi);
spid_status u_spid_status (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.clk_out_i (clk_spi_out_buf),
.clk_csb_i (clk_csb),
.sys_clk_i (clk_i),
.sys_rst_ni (rst_ni),
.sys_csb_deasserted_pulse_i (sys_csb_deasserted_pulse),
.sys_status_we_i (readstatus_qe),
.sys_status_i (readstatus_q),
.sys_status_o (readstatus_d),
.sel_dp_i (cmd_dp_sel),
.cmd_info_i (cmd_info_broadcast),
.cmd_info_idx_i (cmd_info_idx_broadcast),
.outclk_p2s_valid_o (sub_p2s_valid[IoModeStatus]),
.outclk_p2s_byte_o (sub_p2s_data[IoModeStatus]),
.outclk_p2s_sent_i (sub_p2s_sent[IoModeStatus]),
.io_mode_o (sub_iomode[IoModeStatus]),
.inclk_busy_set_i (sck_status_busy_set), // SCK domain
.inclk_we_set_i (sck_status_wr_set),
.inclk_we_clr_i (sck_status_wr_clr),
.csb_busy_broadcast_o (csb_status_busy_broadcast) // SCK domain
);
// Tie unused
logic unused_sub_sram_status;
assign unused_sub_sram_status = ^{
sub_sram_l2m[IoModeStatus],
sub_sram_m2l[IoModeStatus]
};
assign sub_sram_l2m[IoModeStatus] = '0;
// End: Read Status ------------------------------------------------
spid_jedec u_jedec (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.clk_out_i (clk_spi_out_buf),
.inclk_csb_asserted_pulse_i (sck_csb_asserted_pulse),
.sys_jedec_i (jedec_cfg),
.io_mode_o (sub_iomode[IoModeJedec]),
.sel_dp_i (cmd_dp_sel),
.cmd_info_i (cmd_info_broadcast),
.cmd_info_idx_i (cmd_info_idx_broadcast),
.outclk_p2s_valid_o (sub_p2s_valid[IoModeJedec]),
.outclk_p2s_byte_o (sub_p2s_data[IoModeJedec]),
.outclk_p2s_sent_i (sub_p2s_sent[IoModeJedec])
);
// Tie unused
logic unused_sub_sram_jedec;
assign unused_sub_sram_jedec = ^{
sub_sram_l2m[IoModeJedec],
sub_sram_m2l[IoModeJedec]
};
assign sub_sram_l2m[IoModeJedec] = '0;
// Begin: Upload ===================================================
spid_upload #(
.CmdFifoBaseAddr (SramCmdFifoIdx),
.CmdFifoDepth (SramCmdFifoDepth),
.AddrFifoBaseAddr (SramAddrFifoIdx),
.AddrFifoDepth (SramAddrFifoDepth),
.PayloadBaseAddr (SramPayloadIdx),
.PayloadDepth (SramPayloadDepth),
.SpiByte ($bits(spi_byte_t))
) u_upload (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.sys_clk_i (clk_i),
.sys_rst_ni (rst_ni),
.clk_csb_i (clk_csb),
.sck_csb_asserted_pulse_i (sck_csb_asserted_pulse),
.sys_csb_deasserted_pulse_i (sys_csb_deasserted_pulse),
.sel_dp_i (cmd_dp_sel),
.sck_sram_o (sub_sram_l2m[IoModeUpload]),
.sck_sram_i (sub_sram_m2l[IoModeUpload]),
.sys_cmdfifo_sram_o (sys_sram_l2m[SysSramCmdFifo]),
.sys_cmdfifo_sram_i (sys_sram_m2l[SysSramCmdFifo]),
.sys_cmdfifo_gnt_i (sys_sram_gnt[SysSramCmdFifo]),
.sys_addrfifo_sram_o (sys_sram_l2m[SysSramAddrFifo]),
.sys_addrfifo_sram_i (sys_sram_m2l[SysSramAddrFifo]),
.sys_addrfifo_gnt_i (sys_sram_gnt[SysSramAddrFifo]),
// SYS clock FIFO interface
.sys_cmdfifo_rvalid_o (cmdfifo_rvalid),
.sys_cmdfifo_rready_i (cmdfifo_rready),
.sys_cmdfifo_rdata_o (cmdfifo_rdata),
.sys_addrfifo_rvalid_o (addrfifo_rvalid),
.sys_addrfifo_rready_i (addrfifo_rready),
.sys_addrfifo_rdata_o (addrfifo_rdata),
// Interface: SPI to Parallel
.s2p_valid_i (s2p_data_valid),
.s2p_byte_i (s2p_data),
// Interface: Parallel to SPI
.p2s_valid_o (sub_p2s_valid[IoModeUpload]),
.p2s_data_o (sub_p2s_data [IoModeUpload]),
.p2s_sent_i (sub_p2s_sent [IoModeUpload]),
.spi_mode_i (spi_mode),
.cfg_addr_4b_en_i (cfg_addr_4b_en),
.cmd_info_i (cmd_info_broadcast),
.cmd_info_idx_i (cmd_info_idx_broadcast),
.io_mode_o (sub_iomode[IoModeUpload]),
.set_busy_o (sck_status_busy_set),
.sys_cmdfifo_set_o (cmdfifo_set_pulse),
.sys_cmdfifo_notempty_o (cmdfifo_notempty),
.sys_cmdfifo_full_o (), // not used
.sys_addrfifo_notempty_o (addrfifo_notempty),
.sys_addrfifo_full_o (), // not used
.sys_payload_overflow_o (payload_overflow),
.sys_cmdfifo_depth_o (cmdfifo_depth),
.sys_addrfifo_depth_o (addrfifo_depth),
.sys_payload_depth_o (payload_depth),
.sys_payload_start_idx_o (payload_start_idx)
);
// FIFO connect
assign cmdfifo_rready = reg2hw.upload_cmdfifo.re;
assign hw2reg.upload_cmdfifo.d = cmdfifo_rdata;
logic unused_cmdfifo_q;
assign unused_cmdfifo_q = ^{reg2hw.upload_cmdfifo.q, cmdfifo_rvalid};
assign addrfifo_rready = reg2hw.upload_addrfifo.re;
assign hw2reg.upload_addrfifo.d = addrfifo_rdata;
logic unused_addrfifo_q;
assign unused_addrfifo_q = ^{reg2hw.upload_addrfifo.q, addrfifo_rvalid};
// Connect UPLOAD_STATUS
assign hw2reg.upload_status.cmdfifo_depth.de = 1'b1;
assign hw2reg.upload_status.cmdfifo_depth.d = cmdfifo_depth;
assign hw2reg.upload_status.cmdfifo_notempty.de = 1'b1;
assign hw2reg.upload_status.cmdfifo_notempty.d = cmdfifo_notempty;
assign hw2reg.upload_status.addrfifo_depth.de = 1'b 1;
assign hw2reg.upload_status.addrfifo_depth.d = addrfifo_depth;
assign hw2reg.upload_status.addrfifo_notempty.de = 1'b 1;
assign hw2reg.upload_status.addrfifo_notempty.d = addrfifo_notempty;
assign hw2reg.upload_status2.payload_depth.de = 1'b 1;
assign hw2reg.upload_status2.payload_depth.d = payload_depth;
assign hw2reg.upload_status2.payload_start_idx.de = 1'b 1;
assign hw2reg.upload_status2.payload_start_idx.d = payload_start_idx;
`ASSERT_INIT(PayloadStartIdxWidthMatch_A,
$bits(hw2reg.upload_status2.payload_start_idx.d) == PayloadIdxW)
// End: Upload ---------------------------------------------------
// Begin: Address 3B/4B Tracker ====================================
assign cmd_en4b_pulse = cmd_dp_sel == DpEn4B;
assign cmd_ex4b_pulse = cmd_dp_sel == DpEx4B;
spid_addr_4b u_spid_addr_4b (
.sys_clk_i (clk_i ),
.sys_rst_ni (rst_ni),
.spi_clk_i (clk_spi_in_buf),
.spi_csb_asserted_pulse_i (sck_csb_asserted_pulse ),
.sys_csb_deasserted_pulse_i(sys_csb_deasserted_pulse),
// Assume CFG.addr_4b_en is not external register.
// And has the permissions as below:
// swaccess: "rw"
// hwaccess: "hrw"
.reg2hw_cfg_addr_4b_en_q_i (reg2hw.cfg.addr_4b_en.q ), // registered input
.hw2reg_cfg_addr_4b_en_de_o (hw2reg.cfg.addr_4b_en.de),
.hw2reg_cfg_addr_4b_en_d_o (hw2reg.cfg.addr_4b_en.d ),
.spi_cfg_addr_4b_en_o (cfg_addr_4b_en), // broadcast
.spi_addr_4b_set_i (cmd_en4b_pulse), // EN4B command
.spi_addr_4b_clr_i (cmd_ex4b_pulse) // EX4B command
);
// End: Address 3B/4B Tracker ------------------------------------
/////////////////////
// SPI Passthrough //
/////////////////////
// Passthrough block
// signal: sys_csb_syncd -> sysclock 2FF CSb
// signal: sys_busy -> output of u_status readstatus_d[0]
// set by CSb deassertion pulse & BUSY(SCK)
// clr by CSb = 1 & SW writing 0
//
// NOTE: there will be a gap between the actual assertion of CSb and the CSb
// syncd event visible in the u_status BUSY logic (2FF @ SYS_CLK). So,
// there's chance that the SW may clear the BUSY right at the CSb
// assertion event. If that happens, passthrough block may set during SPI
// transaction. The behavior of the SPI_DEVICE in this scenario is
// undeterminstic.
logic passthrough_block;
assign passthrough_block = csb_status_busy_broadcast;
spi_passthrough u_passthrough (
.clk_i (clk_spi_in_buf),
.rst_ni (rst_spi_n),
.clk_out_i (clk_spi_out_buf),
// Configurations
.cfg_cpol_i (cpol),
.cfg_cmd_filter_i (cmd_filter),
.cfg_addr_mask_i (addr_swap_mask),
.cfg_addr_value_i (addr_swap_data),
.cfg_payload_mask_i (payload_swap_mask),
.cfg_payload_data_i (payload_swap_data),
.cfg_addr_4b_en_i (cfg_addr_4b_en),
.cmd_info_i (cmd_info),
.spi_mode_i (spi_mode),
// Control: BUSY block
.passthrough_block_i (passthrough_block),
// Host SPI
.host_sck_i (cio_sck_i),
.host_isck_i (sck_n ), // inverted cio_sck_i
.host_csb_i (cio_csb_i),
.host_s_i (cio_sd_i),
.host_s_o (passthrough_sd),
.host_s_en_o (passthrough_sd_en),
// Passthrough to SPI_HOST HWIP
.passthrough_o,
.passthrough_i,
.event_cmd_filtered_o ()
);
//////////////////
// TPM over SPI //
//////////////////
// Instance of spi_tpm
spi_tpm #(
// CmdAddrFifoDepth
.WrFifoDepth (TpmWrFifoDepth),
.RdFifoDepth (TpmRdFifoDepth),
.RdFifoWidth (TpmRdFifoWidth),
.EnLocality (1)
) u_spi_tpm (
.clk_in_i (clk_spi_in_buf ),
.clk_out_i (clk_spi_out_buf),
.sys_clk_i (clk_i),
.sys_rst_ni (rst_ni ),
.rst_n (tpm_rst_n ),
.sys_sync_rst_ni (sys_tpm_rst_n),
.csb_i (sck_tpm_csb_buf), // used as data only
.mosi_i (tpm_mosi ),
.miso_o (tpm_miso ),
.miso_en_o (tpm_miso_en ),
.tpm_cap_o (tpm_cap),
.sys_csb_pulse_stretch (sys_csb_pos_pulse_stretch),
.cfg_tpm_en_i (cfg_tpm_en ),
.cfg_tpm_mode_i (cfg_tpm_mode ),
.cfg_tpm_hw_reg_dis_i (cfg_tpm_hw_reg_dis ),
.cfg_tpm_reg_chk_dis_i (cfg_tpm_reg_chk_dis ),
.cfg_tpm_invalid_locality_i (cfg_tpm_invalid_locality),
.sys_access_reg_i (tpm_access ),
.sys_int_enable_reg_i (tpm_int_enable ),
.sys_int_vector_reg_i (tpm_int_vector ),
.sys_int_status_reg_i (tpm_int_status ),
.sys_intf_capability_reg_i (tpm_intf_capability),
.sys_status_reg_i (tpm_status ),
.sys_id_reg_i (tpm_did_vid ),
.sys_rid_reg_i (tpm_rid ),
.sys_cmdaddr_rvalid_o (tpm_cmdaddr_rvalid),
.sys_cmdaddr_rdata_o (tpm_cmdaddr_rdata ),
.sys_cmdaddr_rready_i (tpm_cmdaddr_rready),
.sys_wrfifo_rvalid_o (tpm_wrfifo_rvalid),
.sys_wrfifo_rdata_o (tpm_wrfifo_rdata ),
.sys_wrfifo_rready_i (tpm_wrfifo_rready),
.sys_rdfifo_wvalid_i (tpm_rdfifo_wvalid),
.sys_rdfifo_wdata_i (tpm_rdfifo_wdata ),
.sys_rdfifo_wready_o (tpm_rdfifo_wready),
.sys_cmdaddr_notempty_o (tpm_status_cmdaddr_notempty),
.sys_wrfifo_depth_o (tpm_status_wrfifo_depth )
);
// Register connection
// TPM_CAP:
assign hw2reg.tpm_cap = '{
rev: '{ de: 1'b 1, d: tpm_cap.rev },
locality: '{ de: 1'b 1, d: tpm_cap.locality },
max_wr_size: '{ de: 1'b 1, d: tpm_cap.max_wr_size },
max_rd_size: '{ de: 1'b 1, d: tpm_cap.max_rd_size }
};
// CFG:
assign cfg_tpm_en = reg2hw.tpm_cfg.en.q;
assign cfg_tpm_mode = reg2hw.tpm_cfg.tpm_mode.q;
assign cfg_tpm_hw_reg_dis = reg2hw.tpm_cfg.hw_reg_dis.q;
assign cfg_tpm_reg_chk_dis = reg2hw.tpm_cfg.tpm_reg_chk_dis.q;
assign cfg_tpm_invalid_locality = reg2hw.tpm_cfg.invalid_locality.q;
// STATUS:
assign hw2reg.tpm_status = '{
cmdaddr_notempty: '{ de: 1'b 1, d: tpm_status_cmdaddr_notempty },
wrfifo_depth: '{ de: 1'b 1, d: tpm_status_wrfifo_depth }
};
// Return-by-HW registers:
// TPM_ACCESS_x, TPM_STS_x, TPM_INT_ENABLE, TPM_INT_VECTOR,
// TPM_INT_STATUS, TPM_INTF_CAPABILITY, TPM_DID_VID, TPM_RID
for (genvar i = 0 ; i < spi_device_reg_pkg::NumLocality ; i++) begin : g_tpm_access
assign tpm_access[8*i+:8] = reg2hw.tpm_access[i].q;
end : g_tpm_access
assign tpm_int_enable = reg2hw.tpm_int_enable.q;
assign tpm_int_vector = reg2hw.tpm_int_vector.q;
assign tpm_int_status = reg2hw.tpm_int_status.q;
assign tpm_intf_capability = reg2hw.tpm_intf_capability.q;
assign tpm_status = reg2hw.tpm_sts.q;
assign tpm_did_vid = { reg2hw.tpm_did_vid.did.q ,
reg2hw.tpm_did_vid.vid.q };
assign tpm_rid = reg2hw.tpm_rid.q;
// Command / Address Buffer
logic unused_tpm_cmdaddr;
assign unused_tpm_cmdaddr = ^{tpm_cmdaddr_rvalid, reg2hw.tpm_cmd_addr};
assign tpm_cmdaddr_rready = reg2hw.tpm_cmd_addr.cmd.re;
assign hw2reg.tpm_cmd_addr = '{
addr: tpm_cmdaddr_rdata[23: 0],
cmd: tpm_cmdaddr_rdata[31:24]
};
// Write FIFO (read by SW)
logic unused_tpm_wrfifo;
assign unused_tpm_wrfifo= ^{tpm_wrfifo_rvalid, reg2hw.tpm_write_fifo};
assign tpm_wrfifo_rready = reg2hw.tpm_write_fifo.re;
assign hw2reg.tpm_write_fifo.d = tpm_wrfifo_rdata;
// Read FIFO (write by SW)
logic unused_tpm_rdfifo;
assign unused_tpm_rdfifo= tpm_rdfifo_wready;
assign tpm_rdfifo_wvalid = reg2hw.tpm_read_fifo.qe;
assign tpm_rdfifo_wdata = reg2hw.tpm_read_fifo.q;
`ASSUME(TpmRdfifoNotFull_A, tpm_rdfifo_wvalid |-> tpm_rdfifo_wready)
// END: TPM over SPI --------------------------------------------------------
////////////////////
// Common modules //
////////////////////
logic [SramDw-1:0] sys_sram_l2m_fw_wmask;
tlul_adapter_sram #(
.SramAw (SramAw),
.SramDw (SramDw),
.Outstanding (1),
.ByteAccess (0)
) u_tlul2sram (
.clk_i,
.rst_ni,
.tl_i (tl_sram_h2d),
.tl_o (tl_sram_d2h),
.en_ifetch_i (prim_mubi_pkg::MuBi4False),
.req_o (sys_sram_l2m[SysSramFw].req),
.req_type_o (),
.gnt_i (sys_sram_fw_gnt),
.we_o (sys_sram_l2m[SysSramFw].we),
.addr_o (sys_sram_l2m[SysSramFw].addr),
.wdata_o (sys_sram_l2m[SysSramFw].wdata),
.wmask_o (sys_sram_l2m_fw_wmask), // Not used
.intg_error_o(),
.rdata_i (sys_sram_m2l[SysSramFw].rdata),
.rvalid_i (sys_sram_m2l[SysSramFw].rvalid),
.rerror_i (sys_sram_m2l[SysSramFw].rerror)
);
assign sys_sram_l2m[SysSramFw].wstrb = sram_mask2strb(sys_sram_l2m_fw_wmask);
for (genvar i = 0 ; i < SysSramEnd ; i++) begin : g_sram_connect
if (i == SysSramFw) begin : g_sram_req_sw
// Fixed low priority. (Discussed in #10065)
// When HW requests the SRAM access, lower the SW requests (and grant)
always_comb begin
sys_sram_req[i] = sys_sram_l2m[i].req;
sys_sram_fw_gnt = sys_sram_gnt[i];
for (int unsigned j = 0; j < SysSramEnd ; j++) begin
if (i != j && sys_sram_l2m[j].req) begin
sys_sram_req[i] = 1'b 0;
// lower the grant
sys_sram_fw_gnt = 1'b 0;
end
end
end
end else begin : g_sram_req_hw
assign sys_sram_req[i] = sys_sram_l2m[i].req;
end
assign sys_sram_addr [i] = sys_sram_l2m[i].addr;
assign sys_sram_write [i] = sys_sram_l2m[i].we;
assign sys_sram_wdata [i] = sys_sram_l2m[i].wdata;
assign sys_sram_wmask [i] = sram_strb2mask(sys_sram_l2m[i].wstrb);
assign sys_sram_m2l[i].rvalid = sys_sram_rvalid[i];
assign sys_sram_m2l[i].rdata = sys_sram_rdata[i];
assign sys_sram_m2l[i].rerror = sys_sram_rerror[i];
// Other than SYS access (tlul), other requesters can't wait the grant.
// It should be instantly available to not introduce the latency.
// If prim_sram_arbiter has fixed arbitration, then FW access should be
// lowest priority.
//
// ICEBOX(#10065): Implement grant in upload module and sram interface
`ASSERT(ReqAlwaysAccepted_A, sys_sram_req[i] |-> sys_sram_gnt[i])
end : g_sram_connect
prim_sram_arbiter #(
.N (SysSramEnd),
.SramDw (SramDw),
.SramAw (SramAw),
.EnMask (1'b 1)
) u_sys_sram_arbiter (
.clk_i,
.rst_ni,
.req_i (sys_sram_req),
.req_addr_i (sys_sram_addr),
.req_write_i (sys_sram_write),
.req_wdata_i (sys_sram_wdata),
.req_wmask_i (sys_sram_wmask),
.gnt_o (sys_sram_gnt),
.rsp_rvalid_o (sys_sram_rvalid),
.rsp_rdata_o (sys_sram_rdata),
.rsp_error_o (sys_sram_rerror),
.sram_req_o (mem_a_req),
.sram_addr_o (mem_a_addr),
.sram_write_o (mem_a_write),
.sram_wdata_o (mem_a_wdata),
.sram_wmask_o (mem_a_wmask),
.sram_rvalid_i (mem_a_rvalid),
.sram_rdata_i (mem_a_rdata),
.sram_rerror_i (mem_a_rerror)
);
// SRAM Wrapper
assign mem_b_req = mem_b_l2m.req;
assign mem_b_write = mem_b_l2m.we;
assign mem_b_addr = mem_b_l2m.addr;
assign mem_b_wdata = mem_b_l2m.wdata;
assign mem_b_wmask = sram_strb2mask(mem_b_l2m.wstrb);
assign mem_b_m2l.rvalid = mem_b_rvalid;
assign mem_b_m2l.rdata = mem_b_rdata;
assign mem_b_m2l.rerror = mem_b_rerror;
prim_ram_2p_async_adv #(
.Depth (SramDepth),
.Width (SramDw), // 32 x 512 --> 2kB
.DataBitsPerMask (8),
.EnableECC (0),
.EnableParity (1),
.EnableInputPipeline (0),
.EnableOutputPipeline(0)
) u_memory_2p (
.clk_a_i (clk_i),
.rst_a_ni (rst_ni),
.clk_b_i (sram_clk),
.rst_b_ni (sram_rst_n),
.a_req_i (mem_a_req),
.a_write_i (mem_a_write),
.a_addr_i (mem_a_addr),
.a_wdata_i (mem_a_wdata),
.a_wmask_i (mem_a_wmask),
.a_rvalid_o (mem_a_rvalid),
.a_rdata_o (mem_a_rdata),
.a_rerror_o (mem_a_rerror),
.b_req_i (mem_b_req),
.b_write_i (mem_b_write),
.b_addr_i (mem_b_addr),
.b_wdata_i (mem_b_wdata),
.b_wmask_i (mem_b_wmask),
.b_rvalid_o (mem_b_rvalid),
.b_rdata_o (mem_b_rdata),
.b_rerror_o (mem_b_rerror),
.cfg_i (ram_cfg_i)
);
// Register module
logic [NumAlerts-1:0] alert_test, alerts;
spi_device_reg_top u_reg (
.clk_i,
.rst_ni,
.tl_i (tl_i),
.tl_o (tl_o),
.tl_win_o (tl_sram_h2d),
.tl_win_i (tl_sram_d2h),
.reg2hw,
.hw2reg,
// SEC_CM: BUS.INTEGRITY
.intg_err_o (alerts[0]),
.devmode_i (1'b1)
);
// Alerts
assign alert_test = {
reg2hw.alert_test.q &
reg2hw.alert_test.qe
};
for (genvar i = 0; i < NumAlerts; i++) begin : gen_alert_tx
prim_alert_sender #(
.AsyncOn(AlertAsyncOn[i]),
.IsFatal(1'b1)
) u_prim_alert_sender (
.clk_i,
.rst_ni,
.alert_test_i ( alert_test[i] ),
.alert_req_i ( alerts[0] ),
.alert_ack_o ( ),
.alert_state_o ( ),
.alert_rx_i ( alert_rx_i[i] ),
.alert_tx_o ( alert_tx_o[i] )
);
end
// make sure scanmode_i is never X (including during reset)
`ASSERT_KNOWN(scanmodeKnown, scanmode_i, clk_i, 0)
`ASSERT_KNOWN(CioSdoEnOKnown, cio_sd_en_o)
`ASSERT_KNOWN(IntrRxfOKnown, intr_generic_rx_full_o )
`ASSERT_KNOWN(IntrRxlvlOKnown, intr_generic_rx_watermark_o)
`ASSERT_KNOWN(IntrTxlvlOKnown, intr_generic_tx_watermark_o)
`ASSERT_KNOWN(IntrRxerrOKnown, intr_generic_rx_error_o )
`ASSERT_KNOWN(IntrRxoverflowOKnown, intr_generic_rx_overflow_o )
`ASSERT_KNOWN(IntrTxunderflowOKnown, intr_generic_tx_underflow_o)
`ASSERT_KNOWN(IntrUploadCmdfifoNotEmptyOKnown,
intr_upload_cmdfifo_not_empty_o)
`ASSERT_KNOWN(IntrUploadPayloadNotEmptyOKnown,
intr_upload_payload_not_empty_o)
`ASSERT_KNOWN(IntrUploadPayloadOverflowOKnown,
intr_upload_payload_overflow_o)
`ASSERT_KNOWN(IntrReadbufWatermarkOKnown, intr_readbuf_watermark_o)
`ASSERT_KNOWN(IntrReadbufFlipOKnown, intr_readbuf_flip_o)
`ASSERT_KNOWN(IntrTpmHeaderNotEmptyOKnown, intr_tpm_header_not_empty_o)
`ASSERT_KNOWN(AlertKnownO_A, alert_tx_o)
// Assume the tpm_en is set when TPM transaction is idle.
`ASSUME(TpmEnableWhenTpmCsbIdle_M, $rose(cfg_tpm_en) |-> cio_tpm_csb_i)
// When CSBs are inactive, spi_device shouldn't drive the CIO
`ASSERT(CioSdoEnOffWhenInactive, cio_csb_i && cio_tpm_csb_i -> cio_sd_en_o === 0)
// Alert assertions for reg_we onehot check
`ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A, u_reg, alert_tx_o[0])
endmodule