[spi_device] Re-write SRAM access in Readcmd This commit re-writes the SRAM access logic in `spi_readcmd` module. It is split into a new submodule `spid_readsram`. The logic receives a request from the Read command FSM and fills the internal FIFO for the Read command logic to return the data through SPI lines. Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/spi_device/lint/spi_device.waiver b/hw/ip/spi_device/lint/spi_device.waiver index dfd06fd..a89b32a 100644 --- a/hw/ip/spi_device/lint/spi_device.waiver +++ b/hw/ip/spi_device/lint/spi_device.waiver
@@ -10,6 +10,9 @@ waive -rules {ARITH_CONTEXT} -location {spi_readcmd.sv} \ -regexp {'addr_q\[31:2\] \+ 1'b1' is self-determined} \ -comment "Leave it as it is for readability" +waive -rules {ARITH_CONTEXT} -location {spid_readsram.sv} \ + -regexp {'current_address_i\[31:2\] \+ 1'b1' is self-deter} \ + -comment "Leave it as it is for readability" waive -rules HIER_NET_NOT_READ -location {spi_device.sv} -regexp {[nN]et.*a_(address|param|user).*not read from} \ -comment "several TLUL signals are not used by register file"
diff --git a/hw/ip/spi_device/rtl/spi_readcmd.sv b/hw/ip/spi_device/rtl/spi_readcmd.sv index dc6648e..ce3f6f9 100644 --- a/hw/ip/spi_device/rtl/spi_readcmd.sv +++ b/hw/ip/spi_device/rtl/spi_readcmd.sv
@@ -276,7 +276,7 @@ ///////////// // Address shift & latch - logic addr_ready_in_word; + logic addr_ready_in_word, addr_ready_in_halfword; logic addr_latched; logic addr_shift_en; logic addr_latch_en; @@ -320,17 +320,12 @@ // bit count within a word logic bitcnt_update; logic bitcnt_dec; - logic [4:0] bitcnt; // count down from 31 or partial for first unaligned + logic [2:0] bitcnt; // count down from 7 or partial for first unaligned // FIFO logic unused_fifo_rvalid, fifo_pop; - sram_data_t fifo_rdata; + spi_byte_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; @@ -370,18 +365,23 @@ 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_ready_in_halfword) begin + // When addr is a cycle earlier than full addr, sram req sent in + // spid_readsram + addr_d = {addr_q[31:8], s2p_byte_i[6:0], 1'b 0}; 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_d = addr_q[31:0] + 1'b1; addr_latch_en = 1'b 1; end end - assign addr_ready_in_word = (addr_cnt_d == 5'd 2); + assign addr_ready_in_word = (addr_cnt_d == 5'd 2); + assign addr_ready_in_halfword = (addr_cnt_d == 5'd 1); // TODO: Check if addr_cnt_d or addr_cnt_q ? assign addr_latched = (addr_cnt_d == 5'd 0); @@ -406,6 +406,8 @@ addr_cnt_d = (cmd_info_i.addr_4b_affected && addr_4b_en_i) ? 5'd 30 : 5'd 22; // TODO: Dual IO/ Quad IO case + + // TODO: Force 4B mode end else if (addr_cnt_q == '0) begin addr_cnt_d = addr_cnt_q; end else if (addr_shift_en) begin @@ -432,15 +434,19 @@ 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; + unique case (cmd_info_i.payload_en) + 4'b 0010: bitcnt <= 3'h 7; + 4'b 0011: bitcnt <= 3'h 6; + 4'b 1111: bitcnt <= 3'h 4; + default: bitcnt <= 3'h 7; endcase end else if (bitcnt_dec) begin - bitcnt <= bitcnt - 1'b 1; + unique case (cmd_info_i.payload_en) + 4'b 0010: bitcnt <= bitcnt - 3'h 1; + 4'b 0011: bitcnt <= bitcnt - 3'h 2; + 4'b 1111: bitcnt <= bitcnt - 3'h 4; + default: bitcnt <= bitcnt - 3'h 1; + endcase end end @@ -451,7 +457,6 @@ // Convert into masked address localparam int unsigned MailboxAw = $clog2(MailboxDepth); localparam logic [31:0] MailboxMask = {{30-MailboxAw{1'b1}}, {2+MailboxAw{1'b0}}}; - localparam int unsigned SfdpAw = $clog2(SfdpDepth); assign mailbox_masked_addr = addr_d & MailboxMask; @@ -463,47 +468,10 @@ // manages the address to follow. logic sram_req; - logic [SramAw-1:0] sram_addr; - - always_comb begin - sram_addr = '0; - if (sel_dp_i == DpReadSFDP) begin - // SFDP Read command. Upper address is swapped to SFDP Base Addr - sram_addr = SfdpBaseAddr | sram_addr_t'(addr_d[2+:SfdpAw]); - end else 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.data[7:0], fifo_rdata.data[15:8], - fifo_rdata.data[23:16], fifo_rdata.data[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 + assign p2s_byte = fifo_rdata; // outclk latch // can't put async fifo. DC constraint should have half clk datapath @@ -571,8 +539,6 @@ 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; @@ -592,8 +558,9 @@ MainAddress: begin addr_shift_en = 1'b 1; + // TODO: DualIO/ QuadIO case if (addr_ready_in_word) begin - // TODO: Send Address request. No need to move the state + sram_req = 1'b 1; end if (addr_latched) begin @@ -601,9 +568,6 @@ // could be 23, 15, or 7 bitcnt_update = 1'b 1; - // latch addr_d[1:0] to fifo_byteoffset; - offset_update = 1'b 1; - // Next state: // MByte if mbyte enabled // Dummy if mbyte = 0 and dummy_en = 1 @@ -666,14 +630,7 @@ 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 + if (bitcnt == 3'h 0) begin // sent all words bitcnt_update = 1'b 1; // TODO: FIFO pop here? @@ -696,50 +653,42 @@ // 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 ( + spid_readsram #( + .ReadBaseAddr (ReadBaseAddr), + .ReadBufferDepth (ReadBufferDepth), + .MailboxBaseAddr (MailboxBaseAddr), + .MailboxDepth (MailboxDepth), + .SfdpBaseAddr (SfdpBaseAddr), + .SfdpDepth (SfdpDepth) + ) u_readsram ( .clk_i, .rst_ni, - .clr_i ( 1'b0), + .sram_read_req_i (sram_req), + .addr_latched_i (addr_latched), + .current_address_i (addr_d), // TODO: Change it - .wvalid_i (sram_rvalid_i), - .wready_o (), // not used - .wdata_i (sram_rdata_i), + .mailbox_en_i, + .mailbox_hit_i (addr_in_mailbox), + .sfdp_hit_i (sel_dp_i == DpReadSFDP), - .rvalid_o (unused_fifo_rvalid), - .rready_i (fifo_pop), - .rdata_o (fifo_rdata), + .sram_req_o, + .sram_we_o, + .sram_addr_o, + .sram_wdata_o, + .sram_rvalid_i, + .sram_rdata_i, + .sram_rerror_i, - .full_o (unused_full), - .depth_o (unused_depth) + // FIFO + .fifo_rvalid_o (unused_fifo_rvalid), + .fifo_rready_i (fifo_pop), + .fifo_rdata_o (fifo_rdata) ); - // TODO: Handle SRAM integrity errors - sram_err_t unused_sram_rerror; - assign unused_sram_rerror = sram_rerror_i; - + //////////////// // 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)) + //////////////// // `addr_inc` should not be asserted in Address phase `ASSERT(AddrIncNotAssertInAddressState_A, addr_inc |-> main_st != MainAddress)
diff --git a/hw/ip/spi_device/rtl/spid_readsram.sv b/hw/ip/spi_device/rtl/spid_readsram.sv new file mode 100644 index 0000000..9ca420e --- /dev/null +++ b/hw/ip/spi_device/rtl/spid_readsram.sv
@@ -0,0 +1,358 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// SPI Device Flash Read command SRAM request/rsp + +/* + spid_readsram module is a submodule of the read command process block. Its + main purpose is to manage the SRAM request address and returning of the data + in a spi_byte_t size. + + The sequence is: + + 1. read FSM sends the buffer fill request. + 2. this logic sends the SRAM request based on the current address. + 3. when it returns, based on the current address, the internal FSM pushes + data into the FIFO. It means, the FIFO push happens when addr[0] is + received. + 4. If the available depth becomes 4, the logic increases the address + ({current_address_i[31:2] + 1'b 1, 2'b00}) and sends another SRAM read + request. +*/ +`include "prim_assert.sv" + +module spid_readsram + 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. + parameter sram_addr_t ReadBaseAddr = spi_device_pkg::SramReadBufferIdx, + parameter int unsigned ReadBufferDepth = spi_device_pkg::SramMsgDepth, + + // Mailbox Base Addr: Beginning Index of Mailbox in DPSRAM + // Mailbox Size: Mailbox buffer size (# of indices) + parameter sram_addr_t MailboxBaseAddr = spi_device_pkg::SramMailboxIdx, + parameter int unsigned MailboxDepth = spi_device_pkg::SramMailboxDepth, + + // SFDP Base Addr: the beginning index of the SFDP region in DPSRAM + // SFDP Depth: The size of the SFDP buffer (64 fixed in the spi_device_pkg) + parameter sram_addr_t SfdpBaseAddr = spi_device_pkg::SramSfdpIdx, + parameter int unsigned SfdpDepth = spi_device_pkg::SramSfdpDepth +) ( + input clk_i, // SCK + input rst_ni, // CSb + + input sram_read_req_i, // from FSM + + // addr_latched_i + // _________ _________ + // ADDR: X addr[1] X addr[0] >---- + // _________ + // sig: _____/ \_________ + input addr_latched_i, // indicator of addr[1] -> addr[0] + + // current_address_i is the address pointer the byte sent to the host system. + // If it is the first request of the command, then the current_address_i is + // the latched address field. + input [31:0] current_address_i, + + input mailbox_en_i, + input mailbox_hit_i, // the received address field hits the mailbox + input sfdp_hit_i, // selected DP is DpReadSFDP + + // SRAM request and response + 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, + + // FIFO output (spi_byte_t) + output logic fifo_rvalid_o, + input fifo_rready_i, + output spi_byte_t fifo_rdata_o +); + + + //////////////// + // Definition // + //////////////// + + // States + typedef enum logic [1:0] { + StIdle, // Reset state, waits sram_read_req + StPush, // With returned data, push the data into spi_byte_t fifo + StActive // Now in the middle of the process. Sits here + } st_e; + st_e st_q, st_d; + + typedef enum logic { + AddrInput, // Received command address + AddrContinuous // Continuous Read + } addr_sel_e; + addr_sel_e addr_sel; + + localparam int unsigned ReadBufAw = $clog2(ReadBufferDepth); + localparam int unsigned MailboxAw = $clog2(MailboxDepth); + localparam int unsigned SfdpAw = $clog2(SfdpDepth); + + //////////// + // Signal // + //////////// + + // State Machine output + + // data_inc: incoming data is word size (sram_data_t). + // FIFO size is spi_byte_t. So, only a byte can be pushed to the FIFO at + // a time. data_inc to increase the index to next + logic data_inc; + + // strb_set: When addr[0] phase, strb_set is 1 (by FSM). This is to latch + // the offset of the received address. The offset is used to push to the + // FIFO only for the required bytes. + logic strb_set; + assign strb_set = addr_latched_i; + + logic [31:0] next_address; + + logic sram_req; + assign sram_req_o = sram_req; + assign sram_we_o = 1'b 0; + assign sram_wdata_o = '0; + + sram_addr_t sram_addr; + assign sram_addr_o = sram_addr; + + sram_data_t sram_data; + + // FIFO + logic fifo_wvalid, fifo_wready; + spi_byte_t fifo_wdata; + + logic sram_d_valid, sram_d_ready; + logic sram_fifo_full; + logic unused_sram_depth; + + // Unused + logic unused_fifo_full; + logic [1:0] unused_fifo_depth; + + logic unused_next_address; + assign unused_next_address = ^{next_address[31:$bits(sram_addr_t)+1],next_address[1:0]}; + + ////////////// + // Datapath // + ////////////// + + logic sram_latched; // sram request sent + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) sram_latched <= 1'b 0; + else if (sram_req_o) sram_latched <= 1'b 1; + end + + logic [1:0] strb; + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) strb <= 2'b 00; + else if (data_inc) strb <= strb + 1'b 1; // Overflow is OK + else if (strb_set) strb <= current_address_i[1:0]; + end + + // fifo_wdata + always_comb begin + unique case (strb) + 2'b 00: fifo_wdata = sram_data.data[ 7: 0]; + 2'b 01: fifo_wdata = sram_data.data[15: 8]; + 2'b 10: fifo_wdata = sram_data.data[23:16]; + 2'b 11: fifo_wdata = sram_data.data[31:24]; + default: fifo_wdata = '0; + endcase + end + + // Address calculation + assign next_address = (addr_sel == AddrContinuous) + ? {current_address_i[31:2] + 1'b1, 2'b00} + : current_address_i; + + // Sram Address + always_comb begin + if (sfdp_hit_i) begin + sram_addr = SfdpBaseAddr | sram_addr_t'(next_address[2+:SfdpAw]); + end else if (mailbox_en_i && mailbox_hit_i) begin + sram_addr = MailboxBaseAddr | sram_addr_t'(next_address[2+:MailboxAw]); + end else begin + sram_addr = ReadBaseAddr | sram_addr_t'(next_address[2+:ReadBufAw]); + end + end + + /////////////////// + // State Machine // + /////////////////// + + /* + _ _ _ _ _ + SCK | |_| |_| |_| |_| |_| + ___ ___ ___ ___ + ADDR X___X___X___X___X + 3 2 1 0 + ___ + S.RE / \__________ + ___ + S.R.V _____/ \______ + + STRB / V \ + + State Idle X P X Push + */ + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) st_q <= StIdle; + else st_q <= st_d; + end + + always_comb begin + st_d = StIdle; + + fifo_wvalid = 1'b 0; + + addr_sel = AddrInput; + data_inc = 1'b 0; + + sram_req = 1'b 0; + sram_d_ready = 1'b 0; + + unique case (st_q) + StIdle: begin + if (sram_read_req_i) begin // TODO: Change to STRB set + sram_req = 1'b 1; + addr_sel = AddrInput; + end + + if ((sram_req_o || sram_latched) && strb_set) begin + // Only when both are valid + st_d = StPush; + end else begin + st_d = StIdle; + end + end + + StPush: begin + if (sram_d_valid) begin + fifo_wvalid = 1'b 1; // push to FIFO + end + + if (fifo_wready) data_inc = 1'b 1; + + if (strb == 2'b 11 && fifo_wready) begin + // pushed all bytes to FIFO + st_d = StActive; + + sram_d_ready = 1'b 1; + end else begin + st_d = StPush; + end + end + + StActive: begin + if (!sram_fifo_full) begin + st_d = StPush; + + sram_req = 1'b 1; + addr_sel = AddrContinuous; + end else begin + st_d = StActive; + end + end + + default: begin + st_d = StIdle; + end + endcase + end + + ////////////// + // Instance // + ////////////// + prim_fifo_sync #( + .Width ($bits(sram_data_t)), + .Pass (1'b 1), + .Depth (1), + .OutputZeroIfEmpty (1'b 0) + ) u_sram_fifo ( + .clk_i, + .rst_ni, + + .clr_i (1'b 0), + + .wvalid_i (sram_rvalid_i), + .wready_o (), + .wdata_i (sram_rdata_i), + + .rvalid_o (sram_d_valid), + .rready_i (sram_d_ready), + .rdata_o (sram_data), + + .full_o (sram_fifo_full), + .depth_o (unused_sram_depth) + ); + + prim_fifo_sync #( + .Width ($bits(spi_byte_t)), + .Pass (1'b1), + .Depth (2), + .OutputZeroIfEmpty (1'b0) + ) u_fifo ( + .clk_i, + .rst_ni, + + .clr_i (1'b 0), + + .wvalid_i (fifo_wvalid), + .wready_o (fifo_wready), // always assume empty space + .wdata_i (fifo_wdata ), + + .rvalid_o (fifo_rvalid_o), + .rready_i (fifo_rready_i), + .rdata_o (fifo_rdata_o ), + + .full_o (unused_fifo_full ), + .depth_o (unused_fifo_depth) + ); + + // TODO: Handle SRAM integrity errors + sram_err_t unused_sram_rerror; + assign unused_sram_rerror = sram_rerror_i; + + + //////////////// + // Assertions // + //////////////// + + // sfdp_hit keeps high until CSb de-assertion + + // mailbox_hit keeps high until CSb de-assertion + + // current_address keeps increasing (by word? or by byte) after receiving + // sram_req + + // 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 |-> !sram_fifo_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) + + // in fifo_pop, FIFO should not be empty. + `ASSERT(FifoNotEmpty_A, fifo_rready_i |-> unused_fifo_depth != 0) + + // strb_set is asserted together with sram_req or follows the req + `ASSUME(ReqStrbRelation_M, sram_read_req_i |-> ##[0:2] addr_latched_i) + +endmodule : spid_readsram
diff --git a/hw/ip/spi_device/spi_device.core b/hw/ip/spi_device/spi_device.core index b6b4828..3cd7197 100644 --- a/hw/ip/spi_device/spi_device.core +++ b/hw/ip/spi_device/spi_device.core
@@ -23,6 +23,7 @@ - rtl/spi_fwm_txf_ctrl.sv - rtl/spi_fwmode.sv - rtl/spi_cmdparse.sv + - rtl/spid_readsram.sv - rtl/spi_readcmd.sv - rtl/spi_passthrough.sv - rtl/spi_s2p.sv