| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // SPI Flash Read Commands Processing block |
| // |
| // This module processes read commands and Mailbox address region. |
| // |
| /* |
| Details: |
| |
| # Process |
| |
| Command Parser sends command at 7th bit. The data valids at the negedge of SCK |
| or later. But the data valid in the posedge of 8th SCK. |
| |
| Read command process block sees the opcode and transits to Output mode or IO |
| mode depends on the opcode. If Fast Dual I/O or Fast Quad I/O, it moves to |
| I/O states to receive address in dual or quad lines. In any of those two, the |
| protocol assumes M byte and dummy cycle in between. |
| |
| When moves to the address states regardless of Output command or I/O command, |
| based on 3B/4B config, the address count register is reset to 23 (for 3B) or |
| 31 (for 4B). |
| |
| ## Address handling |
| |
| ### Address for Output commands |
| |
| As address comes through IO0 only, the state machine shifts the address |
| one-by-one and decrement the address counter register by 1. |
| |
| When it reaches to the 4B address (addr[2]), it triggers DPSRAM state machine |
| to fetch data from DPSRAM. And when it receives addr[0], at posedge the state |
| machine moves to appropriate command state. |
| |
| ### Address for I/O commands |
| |
| Address comes through IO0,1 in dual I/O command, or IO0:3 in quad I/O command. |
| The logic latches 2 or 4bit at a time and when it reaches valid 4B address, it |
| triggers DPSRAM state machine to fetch data from DPSRAM. It, then, moves to |
| M byte state. The value is not used in current design. |
| |
| ### Mailbox when the mode is enabled |
| |
| If the address falls into mailbox address range while stacking address into a |
| register, the state turns on mailbox selection bit. Then all out-going |
| requests to DPSRAM for read point to Mailbox section not Read Buffer section. |
| |
| ## Normal command |
| |
| Normal command sends read data right after the address phase. Right after |
| moved to the Normal command state, state machine push proper byte into |
| synchronous FIFO. The FIFO has pass parameter enabled to get the data within |
| a cycle. |
| |
| ## Fast Read command |
| |
| Moving into Fast Read command state is identical to the Normal read command |
| state. The address state sends read request when it reaches 4B granularity. |
| Then when the state moves to this Fast Read command state, the machine waits |
| preconfigured dummy cycle. The default dummy cycle is 8. Then it follows same |
| step as Normal read command state. |
| |
| ## Dual/ Quad Output command |
| |
| The state is similar to the Fast Read command. The dummy cycle for Dual/ Quad |
| are 4 and 2 by default. But they can be configured by the software. The states |
| send DPSRAM read request at every 2nd cycle in a byte for Dual command, 1st |
| cycle in a byte (two beats) for Quad command. |
| |
| ## Dual/ Quad I/O command |
| |
| I/O commands have M byte prior to the dummy cycles. Read command module does |
| not support continuous read feature that is described in M byte. So, Dual & |
| Quad I/O commands state waits M byte then moves to Dual/ Quad output command |
| states. |
| |
| */ |
| |
| `include "prim_assert.sv" |
| |
| module spi_readcmd |
| import spi_device_pkg::*; |
| #( |
| // Read command configurations |
| // Base address: Index of DPSRAM |
| // Buffer size: # of indices assigned to Read Buffer. |
| // This is used to calculate double buffering and threshold. |
| // Mailbox Base Addr: Beginning Index of Mailbox in DPSRAM |
| // Mailbox Size: Mailbox buffer size (# of indices) |
| parameter sram_addr_t ReadBaseAddr = spi_device_pkg::SramReadBufferIdx, |
| parameter int unsigned ReadBufferDepth = spi_device_pkg::SramMsgDepth, |
| parameter sram_addr_t MailboxBaseAddr = spi_device_pkg::SramMailboxIdx, |
| parameter int unsigned MailboxDepth = spi_device_pkg::SramMailboxDepth |
| ) ( |
| input clk_i, |
| input rst_ni, |
| |
| input clk_out_i, // Output clock (inverted SPI_CLK) |
| |
| input sys_rst_ni, // System reset for buffer tracking pointers |
| |
| // From Command Parser |
| // DpReadCmd activates readcmd module, and it sees opcode to determine its |
| // mode inside (Normal/ Fast/ Dual/ Quad/ DualIO/ QuadIO) |
| input sel_datapath_e sel_dp_i, |
| input spi_byte_t opcode_i, |
| |
| // SRAM access |
| output logic sram_req_o, |
| output logic sram_we_o, |
| output sram_addr_t sram_addr_o, |
| output sram_data_t sram_wdata_o, |
| input sram_rvalid_i, |
| input sram_data_t sram_rdata_i, |
| input sram_err_t sram_rerror_i, |
| |
| // Interface: SPI to Parallel |
| input s2p_valid_i, |
| input spi_byte_t s2p_byte_i, |
| input [BitCntW-1:0] s2p_bitcnt_i, |
| |
| // Interface: Parallel to SPI |
| // Should be latched to clk_out_i |
| output logic p2s_valid_o, |
| output spi_byte_t p2s_byte_o, |
| input logic p2s_sent_i, |
| |
| // Configurations: |
| // All configs come from peripheral clock domain. |
| // No CDC exists in between. |
| input spi_mode_e spi_mode_i, |
| input [2:0] fastread_dummy_i, |
| input [2:0] dualread_dummy_i, // Dual I/O |
| input [2:0] quadread_dummy_i, // Quad I/O |
| |
| // Double buffering |
| input [$clog2(ReadBufferDepth)-2:0] readbuf_threshold_i, |
| |
| // The command mode is 4B mode. Every read command receives 4B address |
| input addr_4b_en_i, |
| |
| // Features |
| input mailbox_en_i, |
| |
| // Mailbox Address base address |
| // Only allow compile-time fixed size (1kB by default) |
| input [31:0] mailbox_addr_i, |
| //input [31:0] mailbox_mask_i, |
| |
| // Indicator to take SPI line in Passthrough mode |
| output mailbox_assumed_o, |
| |
| output io_mode_e io_mode_o, |
| |
| // Read watermark event occurs when current address exceeds the threshold cfg |
| // value for the first time after hit the current read buffer (1kB granularity). |
| output read_watermark_o |
| |
| ); |
| |
| logic unused_threshold; |
| assign unused_threshold = ^readbuf_threshold_i; |
| assign read_watermark_o = 1'b 0; |
| |
| logic unused_p2s_sent ; |
| assign unused_p2s_sent = p2s_sent_i; |
| |
| spi_mode_e unused_spi_mode ; // will be used for passthrough for output enable |
| assign unused_spi_mode = spi_mode_i; |
| |
| // TODO: Implement |
| assign mailbox_assumed_o = 1'b 0; |
| |
| sram_err_t unused_sram_rerr; |
| assign unused_sram_rerr = sram_rerror_i; |
| |
| ///////////////// |
| // Definitions // |
| ///////////////// |
| typedef enum logic [3:0] { |
| // Reset: Wait until the datapath for Read command is activated by cmdparse |
| MainReset, |
| |
| // Address |
| // |
| // In this state, the FSM stacks incoming byte from s2p up to 3B or 4B |
| // based on the config by SW. When FSM moves from Reset to this state, |
| // it sets the IO mode to Single/ Dual/ Quad depending on the incoming |
| // SPI command. |
| // |
| // In this state, when it stacks addr[2] bit, which is 3rd from last in |
| // Single IO, 2nd from the last in Dual IO, and the last in Quad IO, it |
| // triggers Fetch State Machine to fetch data from Dual Port SRAM in |
| // SPI_DEVICE. |
| // |
| // For I/O commands, the state moves to MByte state. |
| // For Fast commands, the state moves to Dummy state. |
| // For Normal Read command, the state moves to Output state directly. |
| MainAddress, |
| |
| // MByte |
| // |
| // IO commands (Dual I/O, Quad I/O) has M byte following Address. It is |
| // normally used for "continuous read" operation. This logic does not |
| // support the feature, so ignoring it then moves to Dummy state. |
| MainMByte, |
| |
| // Dummy: Wait until it reaches dummy cycles, then moves to Output state |
| MainDummy, |
| |
| // Output |
| // |
| // After passing the dummy cycle or skipping it for Normal Read command, |
| // The state drives SPI interface to send data. This state always assumes |
| // the data is ready in the sync fifo (see instance below). The Fetch |
| // Machine always prepares the data in advance up to designated level. |
| // The prefetch does not affect the watermark that SW has configured. The |
| // event occurs only when the byte is indeed sent through SPI lines. |
| // |
| // This state machine watches the sent address and raises an event if it |
| // exceeds the level. This feature is not turned on for Mailbox address. |
| MainOutput, |
| |
| // Error: Wait until CSb deasserted |
| MainError |
| } main_st_e; |
| main_st_e main_st, main_st_d; |
| |
| ///////////// |
| // Signals // |
| ///////////// |
| |
| // Address shift & latch |
| logic addr_ready_in_word; |
| logic addr_latched; |
| logic addr_shift_en; |
| logic addr_latch_en; |
| logic addr_inc; // increase address by 1 word |
| |
| logic [31:0] addr_q, addr_d; |
| // Indicator of 3B or 4B mode: addr_4b_en_i |
| |
| // Dummy counter |
| logic dummycnt_eq_zero; |
| logic load_dummycnt; |
| logic [2:0] dummycnt; |
| |
| // IO Mode selection |
| |
| // SRAM signals |
| // Compare addr_d[SramAw-1:2] and mailbox_addr_i with mailbox_mask_i. If the |
| // value falls into mailbox, set this. Even this is set, it only uses when |
| // sram address is sent. |
| logic addr_in_mailbox; |
| |
| logic [31:0] mailbox_masked_addr, buffer_masked_addr; |
| |
| // Double buffering signals |
| logic readbuf_idx; // 0 or 1 |
| logic readbuf_flip; // if this is asserted, readbuf_idx flips. |
| |
| // bit count within a word |
| logic bitcnt_update; |
| logic bitcnt_dec; |
| logic [4:0] bitcnt; // count down from 31 or partial for first unaligned |
| |
| // FIFO |
| logic unused_fifo_rvalid, fifo_pop; |
| sram_data_t fifo_rdata; |
| |
| // offset_update: latch addr_d[1:0] into fifo_byteoffset when the statemachine |
| // moves to Output stage. |
| logic offset_update; |
| logic [1:0] fifo_byteoffset; // the byte position in SRAM word |
| logic [7:0] fifo_byte [4]; // converting word sram data into a SPI byte |
| logic [7:0] p2s_byte; |
| logic p2s_valid_inclk; |
| |
| ////////////// |
| // Datapath // |
| ////////////// |
| |
| // Address Shifting. |
| // the data comes from S2P module, which sends valid signal with spi_byte_t. |
| // So, if the logic sees `valid` only, it can't latch the exact word address. |
| // This logic latches in byte based until the 2nd last byte. Then merging |
| // the incoming byte (premature) to organize 4B address. |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| addr_q <= '0; |
| end else if (addr_latch_en) begin |
| addr_q <= addr_d; |
| end |
| end |
| |
| always_comb begin |
| addr_d = '0; // default value. In 3B mode, upper most byte is 0 |
| addr_latch_en = 1'b0; |
| |
| // TODO: Handle the case of IO command |
| if (addr_ready_in_word) begin |
| // Return word based address, but should not latch |
| addr_d = {addr_q[31:8], s2p_byte_i[5:0], 2'b00}; |
| end else if (addr_shift_en && s2p_valid_i) begin |
| // Latch |
| addr_d = {addr_q[23:0], s2p_byte_i[7:0]}; |
| addr_latch_en = 1'b 1; |
| end else if (addr_inc) begin |
| // Increase the address to next |
| addr_d = {addr_q[31:2] + 1'b1, 2'b00}; |
| addr_latch_en = 1'b 1; |
| end |
| end |
| |
| // Check bitcnt and assert addr_ready_in_word |
| always_comb begin |
| addr_ready_in_word = 1'b 0; |
| |
| // TODO: handle the QuadIO case. `+6` should be `+8`. |
| // meaning the SRAM request only can be sent at the end of address beat |
| if (addr_4b_en_i) begin |
| /// 8bit for Opcode + 24 bit for upper 3Byte + 6bit for addr[7:2] |
| addr_ready_in_word = (s2p_bitcnt_i == BitCntW'(8+24+6)) ? 1'b 1 : 1'b 0; |
| end else begin |
| // 3B |
| addr_ready_in_word = (s2p_bitcnt_i == BitCntW'(8+16+6)) ? 1'b 1 : 1'b 0; |
| end |
| end |
| |
| always_comb begin |
| addr_latched = 1'b 0; |
| |
| // TODO: handle the QuadIO case. |
| if (addr_4b_en_i) begin |
| addr_latched = (s2p_bitcnt_i == BitCntW'(8+32)) ? 1'b 1 : 1'b 0; |
| end else begin |
| addr_latched = (s2p_bitcnt_i == BitCntW'(8+24)) ? 1'b 1 : 1'b 0; |
| end |
| end |
| |
| // Dummy Counter |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| dummycnt <= '0; |
| end else if (load_dummycnt) begin |
| // load quad_io |
| if (opcode_i == CmdReadQuadIO) begin |
| dummycnt <= quadread_dummy_i; |
| end else if (opcode_i == CmdReadDualIO) begin |
| dummycnt <= dualread_dummy_i; |
| end else begin |
| // Using normal fastread dummy cycle |
| dummycnt <= fastread_dummy_i; |
| end |
| end else if (!dummycnt_eq_zero) begin |
| dummycnt <= dummycnt - 1'b 1; |
| end |
| end |
| assign dummycnt_eq_zero = ~|dummycnt; |
| |
| // FIFO bit count |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| bitcnt <= '0; |
| end else if (bitcnt_update) begin |
| unique case (addr_d[1:0]) |
| 2'b 00: bitcnt <= 5'h 1f; |
| 2'b 01: bitcnt <= 5'h 17; |
| 2'b 10: bitcnt <= 5'h 0f; |
| 2'b 11: bitcnt <= 5'h 07; |
| default: bitcnt <= 5'h 1f; |
| endcase |
| end else if (bitcnt_dec) begin |
| bitcnt <= bitcnt - 1'b 1; |
| end |
| end |
| |
| //= BEGIN: SRAM Datapath ==================================================== |
| // Main FSM trigger this datapath. This logic calculates the correct address |
| |
| // Address conversion |
| // Convert into masked address |
| localparam int unsigned MailboxAw = $clog2(MailboxDepth); |
| localparam logic [31:0] MailboxMask = {{30-MailboxAw{1'b1}}, {2+MailboxAw{1'b0}}}; |
| |
| assign mailbox_masked_addr = addr_d & MailboxMask; |
| |
| // Only valid when logic sends SRAM request |
| assign addr_in_mailbox = (mailbox_masked_addr == mailbox_addr_i); |
| |
| // internal addr is the address that the logic tracks. |
| // the first address comes from host system and then the internal logic |
| // manages the address to follow. |
| |
| logic sram_req; |
| logic [SramAw-1:0] sram_addr; |
| |
| always_comb begin |
| sram_addr = '0; |
| if (mailbox_en_i && addr_in_mailbox) begin |
| sram_addr = MailboxBaseAddr | sram_addr_t'(addr_d[2+:MailboxAw]); |
| end else begin |
| // Read Buffer Address |
| // TODO: Double buffering |
| sram_addr = ReadBaseAddr | sram_addr_t'({readbuf_idx, addr_d[2+:$clog2(ReadBufferDepth)]}); |
| end |
| end |
| |
| assign sram_addr_o = sram_addr; |
| |
| assign sram_req_o = sram_req; |
| assign sram_we_o = 1'b 0; // always read |
| assign sram_wdata_o = '0; // always read |
| |
| //- END: SRAM Datapath ---------------------------------------------------- |
| |
| //= BEGIN: FIFO to P2S datapath ============================================= |
| assign fifo_byte = '{fifo_rdata[7:0], fifo_rdata[15:8], |
| fifo_rdata[23:16], fifo_rdata[31:24]}; |
| // TODO: addr_inc should not affect this until it is accepted. |
| assign p2s_byte = fifo_byte[fifo_byteoffset]; |
| |
| // TODO: fifo_byteoffset |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| fifo_byteoffset <= '0; |
| end else if (offset_update) begin |
| fifo_byteoffset <= addr_d[1:0]; |
| end else if (p2s_valid_inclk && bitcnt[2:0] == 0) begin |
| // at the MainOutput state, when it sends a byte, increase offset |
| fifo_byteoffset <= fifo_byteoffset + 1'b 1; |
| end |
| end |
| |
| // outclk latch |
| // can't put async fifo. DC constraint should have half clk datapath |
| always_ff @(posedge clk_out_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| p2s_valid_o <= 1'b 0; |
| p2s_byte_o <= '0 ; |
| end else begin |
| p2s_valid_o <= p2s_valid_inclk; |
| p2s_byte_o <= p2s_byte; |
| end |
| end |
| //- END: FIFO to P2S datapath --------------------------------------------- |
| |
| //= BEGIN: Double Buffering ================================================= |
| |
| // Readbuf Index: |
| // |
| // this signal is not reset by CSb. The value should be alive throughout the |
| // CSb event. (last_access_addr too) |
| |
| always_ff @(posedge clk_i or negedge sys_rst_ni) begin |
| if (!sys_rst_ni) begin |
| readbuf_idx <= 1'b 0; |
| end else if (readbuf_flip) begin |
| // readbuf_flip happens when the module completes the second to the last |
| // byte of a buffer through SPI. There will be a chance that will be |
| // cancelled by de-asserting CSb. This logic does not guarantee to cover |
| // that corner case. It expects to complete a byte transfer if it sends |
| // the first beat of the byte. |
| readbuf_idx <= ~readbuf_idx; |
| end |
| end |
| |
| // readbuf_flip |
| // TODO: implement. Below is temporary. |
| assign readbuf_flip = (main_st == MainOutput && addr_q[9:0] == '1); |
| |
| //- END: Double Buffering ------------------------------------------------- |
| |
| /////////////////// |
| // State Machine // |
| /////////////////// |
| |
| //= BEGIN: Main state machine =============================================== |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| main_st <= MainReset; |
| end else begin |
| main_st <= main_st_d; |
| end |
| end |
| |
| always_comb begin |
| main_st_d = main_st; |
| |
| load_dummycnt = 1'b 0; |
| |
| // address control |
| addr_inc = 1'b 0; |
| sram_req = 1'b 0; |
| addr_shift_en = 1'b 0; |
| |
| p2s_valid_inclk = 1'b 0; |
| fifo_pop = 1'b 0; |
| |
| offset_update = 1'b 0; |
| |
| bitcnt_update = 1'b 0; |
| bitcnt_dec = 1'b 0; |
| |
| io_mode_o = SingleIO; |
| |
| unique case (main_st) |
| MainReset: begin |
| if (sel_dp_i == DpReadCmd) begin |
| // Any readcommand goes to MainAddress state to latch address |
| // 3B, 4B handles inside address |
| main_st_d = MainAddress; |
| end |
| end |
| |
| MainAddress: begin |
| addr_shift_en = 1'b 1; |
| |
| if (addr_ready_in_word) begin |
| // TODO: Send Address request. No need to move the state |
| end |
| |
| if (addr_latched) begin |
| // update bitcnt. If input address is not word aligned, bitcnt |
| // could be 23, 15, or 7 |
| bitcnt_update = 1'b 1; |
| |
| // latch addr_d[1:0] to fifo_byteoffset; |
| offset_update = 1'b 1; |
| |
| // Move to next based on Commands |
| unique case (opcode_i) inside |
| // Move to Output command |
| // Assume FIFO entry is not empty in this case |
| CmdReadData: main_st_d = MainOutput; |
| |
| // Dummy cycle for commands other than IO |
| CmdReadFast, CmdReadDual, CmdReadQuad: begin |
| main_st_d = MainDummy; |
| // TODO: Reset dummy counter |
| load_dummycnt = 1'b 1; |
| end |
| |
| // MByte for IO commands |
| CmdReadDualIO, CmdReadQuadIO: begin |
| main_st_d = MainMByte; |
| end |
| |
| default: begin |
| // Error if opcode does not match |
| main_st_d = MainError; |
| |
| // TODO: Error push? |
| // TODO: Define Error handling in Programmer's Guide |
| end |
| |
| endcase |
| end |
| end |
| |
| MainMByte: begin |
| if (s2p_valid_i) begin |
| main_st_d = MainDummy; |
| |
| load_dummycnt = 1'b 1; |
| end |
| end |
| |
| MainDummy: begin |
| if (dummycnt_eq_zero) begin |
| main_st_d = MainOutput; |
| end |
| end |
| |
| MainOutput: begin |
| bitcnt_dec = 1'b 1; |
| |
| // Note: p2s accepts the byte and latch inside at the first beat. |
| // So, it is safe to change the data at the next cycle. |
| p2s_valid_inclk = 1'b 1; |
| |
| // DeadEnd until CSb deasserted |
| // Change Mode based on the input opcode |
| // Return data from FIFO |
| unique case (opcode_i) inside |
| CmdReadData, CmdReadFast: io_mode_o = SingleIO; |
| CmdReadDual, CmdReadDualIO: io_mode_o = DualIO; |
| CmdReadQuad, CmdReadQuadIO: io_mode_o = QuadIO; |
| default: io_mode_o = SingleIO; |
| endcase |
| |
| // if 2 bits left, increase the address |
| // TODO: Dual or Quad output I/O handling |
| if (bitcnt == 5'h 2) begin |
| addr_inc = 1'b 1; |
| sram_req = 1'b 1; |
| end |
| |
| if (bitcnt == 5'h 0) begin |
| // sent all words |
| bitcnt_update = 1'b 1; |
| // TODO: FIFO pop here? |
| fifo_pop = 1'b 1; |
| end |
| end |
| |
| MainError: begin |
| main_st_d = MainError; |
| end |
| |
| default: begin |
| main_st_d = MainReset; |
| end |
| endcase |
| end |
| //- END: Main state machine ----------------------------------------------- |
| |
| /////////////// |
| // Instances // |
| /////////////// |
| |
| // FIFO for read data from DPSRAM |
| logic unused_full; |
| logic [1:0] unused_depth; |
| prim_fifo_sync #( |
| .Width ($bits(sram_data_t)), |
| .Pass (1'b1), |
| .Depth (2), |
| .OutputZeroIfEmpty (1'b0) |
| ) u_fifo ( |
| .clk_i, |
| .rst_ni, |
| |
| .clr_i ( 1'b0), |
| |
| .wvalid_i (sram_rvalid_i), |
| .wready_o (), // not used |
| .wdata_i (sram_rdata_i), |
| |
| .rvalid_o (unused_fifo_rvalid), |
| .rready_i (fifo_pop), |
| .rdata_o (fifo_rdata), |
| |
| .full_o (unused_full), |
| .depth_o (unused_depth) |
| ); |
| |
| // Assertions // |
| // FIFO should not overflow. The Main state machine shall send request only |
| // when it needs the data within 2 cycles |
| `ASSERT(NotOverflow_A, sram_req_o && !sram_we_o |-> !unused_full) |
| |
| // SRAM access always read |
| `ASSERT(SramReadOnly_A, sram_req_o |-> !sram_we_o) |
| |
| // SRAM data should return in next cycle |
| `ASSUME(SramDataReturnRequirement_M, sram_req_o && !sram_we_o |=> sram_rvalid_i) |
| |
| // TODO: When main state machine returns data to SPI (via p2s), the FIFO shall |
| // not be empty |
| `ASSERT(NotEmpty_A, p2s_valid_inclk |-> (unused_depth != 0)) |
| |
| // There's chance to addr_latched and addr_Ready_in_word happens at the same cycle. |
| // That should be QuadIO command only. |
| `ASSUME(QuadIOLatchAndWordReady_M, |
| addr_latched && addr_ready_in_word |-> opcode_i == CmdReadQuadIO) |
| |
| // `addr_inc` should not be asserted in Address phase |
| `ASSERT(AddrIncNotAssertInAddressState_A, addr_inc |-> main_st != MainAddress) |
| |
| // Assume mailbox addr config is mailbox size. |
| // As depth is # of entries and the entry is word, the size should add 2bits |
| `ASSUME(MailboxSizeMatch_M, mailbox_addr_i[$clog2(MailboxDepth)+1:0] == '0) |
| |
| |
| endmodule : spi_readcmd |