blob: 0fd1171fe969146881e80918f834b41c962f4b9d [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.
//
module spi_fwm_rxf_ctrl #(
parameter int unsigned FifoDw = 8,
parameter int unsigned SramAw = 11,
parameter int unsigned SramDw = 32,
// Do not touch below
// SramDw should be multiple of FifoDw
localparam int unsigned NumBytes = SramDw/FifoDw, // derived parameter
localparam int unsigned SDW = $clog2(NumBytes), // derived parameter
localparam int unsigned PtrW = SramAw + SDW + 1 // derived parameter
) (
input clk_i,
input rst_ni,
input spi_device_pkg::spi_mode_e spi_mode_i,
// Configuration
input [SramAw-1:0] base_index_i,
input [SramAw-1:0] limit_index_i,
input [7:0] timer_v,
input [PtrW-1:0] rptr,
output logic [PtrW-1:0] wptr,
output logic [PtrW-1:0] depth,
output logic full,
input fifo_valid,
output logic fifo_ready,
input [FifoDw-1:0] fifo_rdata,
output logic sram_req,
output logic sram_write,
output logic [SramAw-1:0] sram_addr,
output logic [SramDw-1:0] sram_wdata,
output logic [SramDw-1:0] sram_wmask,
input sram_gnt,
input sram_rvalid,
input [SramDw-1:0] sram_rdata,
input [1:0] sram_error
);
logic active;
// Internal variable
logic [NumBytes-1:0] byte_enable;
logic [SDW-1:0] pos; // current byte position
logic [7:0] cur_timer;
logic [SramAw-1:0] sramf_limit;
// State input
logic sramf_full; // SRAM Fifo full
logic full_sramwidth; // Write data filled full SRAM
logic timer_expired;
// State output
logic update_wdata;
logic clr_byte_enable;
logic sram_req_d;
logic sram_write_d;
logic timer_rst;
logic update_wptr;
typedef enum logic [2:0] {
StIdle = 'h0,
StPop = 'h1,
StWait = 'h2,
StWrite = 'h3,
StUpdate = 'h4
} state_e;
state_e st_next, st;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) st <= StIdle;
else st <= st_next;
end
assign active = (spi_mode_i == spi_device_pkg::FwMode);
logic [PtrW-1:0] ptr_cmp;
assign ptr_cmp = rptr ^ wptr;
// The FIFO assumes the partial write as full word write. So, even 3B left,
// still the FIFO is full.
assign sramf_full = (ptr_cmp[PtrW-1] == 1'b1) && (ptr_cmp[PtrW-2:SDW] == '0);
assign full = sramf_full;
assign sramf_limit = limit_index_i - base_index_i;
// Write pointer update
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
wptr <= '0;
end else if (update_wptr) begin
if (byte_enable == '0) begin
// as byte enable is cleared, it means full write was done
if (wptr[PtrW-2:SDW] == sramf_limit) begin
wptr[PtrW-1] <= ~wptr[PtrW-1];
wptr[PtrW-2:0] <= '0;
end else begin
wptr[PtrW-2:SDW] <= wptr[PtrW-2:SDW] + 1'b1;
wptr[SDW-1:0] <= '0;
end
end else begin
wptr[SDW-1:0] <= pos;
end
end
end
// Full check
assign full_sramwidth = (1'b1 == &byte_enable);
// Depth
always_comb begin
if (wptr[PtrW-1] == rptr[PtrW-1]) begin
// Same phase
depth = {1'b0, wptr[PtrW-2:0]} - {1'b0, rptr[PtrW-2:0]};
end else begin
depth = {1'b0, wptr[PtrW-2:0]}
+ ({1'b0, sramf_limit,{SDW{1'b1}}} - {1'b0, rptr[PtrW-2:0]} + 1'b1);
end
end
//timer
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
cur_timer <= '1;
end else if (timer_rst) begin
cur_timer <= timer_v;
end else if (st == StWait) begin
if (cur_timer != '0) cur_timer <= cur_timer - 1'b1;
end
end
assign timer_expired = (cur_timer == '0);
// Data output
assign sram_addr = base_index_i + wptr[PtrW-2:SDW];
// Byte Enable control
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
byte_enable <= '0;
pos <= '0;
end else if (update_wdata) begin
byte_enable[pos] <= 1'b1;
if (pos == SDW'(NumBytes-1)) pos <= '0;
else pos <= pos + 1'b1;
end else if (clr_byte_enable) begin
byte_enable <= '0;
pos <= '0;
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
sram_wdata <= '0;
end else if (update_wdata) begin
sram_wdata[8*pos+:8] <= fifo_rdata;
end
end
assign sram_wmask = (byte_enable == '0) ? '1
: spi_device_pkg::sram_strb2mask(byte_enable);
logic unused_sram;
assign unused_sram = ^{sram_rvalid, sram_rdata, sram_error};
// If FIFO is not empty, initiate SRAM write.
// As FIFOWidth and SRAM Width are different, RMW is required.
// If host writes always DWord size, it is easy but it is not guaranteed.
//
// Next State & output logic
always_comb begin
// default output value
st_next = st;
fifo_ready = 1'b0;
update_wdata = 1'b0;
clr_byte_enable = 1'b0;
sram_req_d = 1'b0;
sram_write_d = 1'b0;
timer_rst = 1'b0;
update_wptr = 1'b0;
unique case (st)
StIdle: begin
// Out of reset state. If SRAM Fifo is not full and RX Fifo is not empty,
// state machine starts process incoming data
if (active && fifo_valid && !sramf_full) begin
st_next = StPop;
fifo_ready = 1'b1;
update_wdata = 1'b1;
end else begin
st_next = StIdle;
end
end
StPop: begin
// Pop entries from FIFO. It moves to WAIT if Fifo is empty and still not
// filled up full sram data width. If anytime while popping the entries
// and full sram data width is filled, it directly moves to Write state
if (fifo_valid && !full_sramwidth) begin
st_next = StPop;
fifo_ready = 1'b1;
update_wdata = 1'b1;
end else if (full_sramwidth) begin
st_next = StWrite;
clr_byte_enable = 1'b1;
sram_req_d = 1'b1;
sram_write_d = 1'b1;
end else begin
st_next = StWait;
timer_rst = 1'b1;
end
end
StWait: begin
// Wait up to X clocks. This state is useful to reduce traffic to SRAM.
// State machine gathers up to SramDw then tries to write at once.
// If not, it needs to Read-Modify-Write for every byte
if (fifo_valid) begin
st_next = StPop;
fifo_ready = 1'b1;
update_wdata = 1'b1;
end else if (!fifo_valid && timer_expired) begin
// Partial write
st_next = StWrite;
sram_req_d = 1'b1;
sram_write_d = 1'b1;
end else begin
st_next = StWait;
end
end
StWrite: begin
// Regardless of RMW or just full Words write, statemachine writes data
// into SRAM Fifo
if (sram_gnt) begin
st_next = StUpdate;
end else begin
st_next = StWrite;
sram_req_d = 1'b1;
sram_write_d = 1'b1;
end
end
StUpdate: begin
// Now, update write pointer then goes back to StIdle.
// It can goes to StPop state directly but doesn't have to as SPI is slower
st_next = StIdle;
update_wptr = 1'b1;
end
default: begin
st_next = StIdle;
end
endcase
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
sram_req <= 1'b0;
sram_write <= 1'b0;
end else begin
sram_req <= sram_req_d;
sram_write <= sram_write_d;
end
end
endmodule