// Copyright 2018 ETH Zurich and University of Bologna. // Copyright 2024 - PlanV Technologies for additionnal contribution. // Copyright and related rights are licensed under the Solderpad Hardware // License, Version 0.51 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law // or agreed to in writing, software, hardware and materials distributed under // this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // Author: Florian Zaruba // Additional contributions by: // Angela Gonzalez - PlanV Technologies module cva6_fifo_v3 #( parameter bit FALL_THROUGH = 1'b0, // fifo is in fall-through mode parameter bit FPGA_ALTERA = 1'b0, // FPGA Altera optimizations enabled parameter int unsigned DATA_WIDTH = 32, // default data width if the fifo is of type logic parameter int unsigned DEPTH = 8, // depth can be arbitrary from 0 to 2**32 parameter type dtype = logic [DATA_WIDTH-1:0], parameter bit FPGA_EN = 1'b0, // DO NOT OVERWRITE THIS PARAMETER parameter int unsigned ADDR_DEPTH = (DEPTH > 1) ? $clog2(DEPTH) : 1 ) ( input logic clk_i, // Clock input logic rst_ni, // Asynchronous reset active low input logic flush_i, // flush the queue input logic testmode_i, // test_mode to bypass clock gating // status flags output logic full_o, // queue is full output logic empty_o, // queue is empty output logic [ADDR_DEPTH-1:0] usage_o, // fill pointer // as long as the queue is not full we can push new data input dtype data_i, // data to push into the queue input logic push_i, // data is valid and can be pushed to the queue // as long as the queue is not empty we can pop new elements output dtype data_o, // output data input logic pop_i // pop head from queue ); // local parameter // FIFO depth - handle the case of pass-through, synthesizer will do constant propagation localparam int unsigned FifoDepth = (DEPTH > 0) ? DEPTH : 1; // clock gating control logic gate_clock; // pointer to the read and write section of the queue logic [ADDR_DEPTH - 1:0] read_pointer_n, read_pointer_q, write_pointer_n, write_pointer_q; // keep a counter to keep track of the current queue status // this integer will be truncated by the synthesis tool logic [ADDR_DEPTH:0] status_cnt_n, status_cnt_q; // actual memory dtype [FifoDepth - 1:0] mem_n, mem_q; dtype data_ft_n, data_ft_q; logic first_word_n, first_word_q; // fifo ram signals for fpga target logic fifo_ram_we; logic [ADDR_DEPTH-1:0] fifo_ram_read_address; logic [ADDR_DEPTH-1:0] fifo_ram_write_address; logic [$bits(dtype)-1:0] fifo_ram_wdata; logic [$bits(dtype)-1:0] fifo_ram_rdata; assign usage_o = status_cnt_q[ADDR_DEPTH-1:0]; if (DEPTH == 0) begin : gen_pass_through assign empty_o = ~push_i; assign full_o = ~pop_i; end else begin : gen_fifo assign full_o = (status_cnt_q == FifoDepth[ADDR_DEPTH:0]); assign empty_o = (status_cnt_q == 0) & ~(FALL_THROUGH & push_i); end // status flags // read and write queue logic always_comb begin : read_write_comb // default assignment read_pointer_n = read_pointer_q; write_pointer_n = write_pointer_q; status_cnt_n = status_cnt_q; if (FPGA_EN && FPGA_ALTERA) data_ft_n = data_ft_q; if (FPGA_EN && FPGA_ALTERA) first_word_n = first_word_q; if (FPGA_EN) begin fifo_ram_we = '0; fifo_ram_write_address = '0; fifo_ram_wdata = '0; if (DEPTH == 0) begin data_o = data_i; end else begin if (FPGA_ALTERA) data_o = first_word_q ? data_ft_q : fifo_ram_rdata; else data_o = fifo_ram_rdata; end end else begin data_o = (DEPTH == 0) ? data_i : mem_q[read_pointer_q]; mem_n = mem_q; gate_clock = 1'b1; end // push a new element to the queue if (push_i && ~full_o) begin if (FPGA_EN) begin fifo_ram_we = 1'b1; fifo_ram_write_address = write_pointer_q; fifo_ram_wdata = data_i; if (FPGA_ALTERA) first_word_n = first_word_q && pop_i; end else begin // push the data onto the queue mem_n[write_pointer_q] = data_i; // un-gate the clock, we want to write something gate_clock = 1'b0; end // increment the write counter if (write_pointer_q == FifoDepth[ADDR_DEPTH-1:0] - 1) write_pointer_n = '0; else write_pointer_n = write_pointer_q + 1; // increment the overall counter status_cnt_n = status_cnt_q + 1; end if (pop_i && ~empty_o) begin data_ft_n = data_i; if (FPGA_EN && FPGA_ALTERA) first_word_n = first_word_q && push_i; // read from the queue is a default assignment // but increment the read pointer... if (read_pointer_n == FifoDepth[ADDR_DEPTH-1:0] - 1) read_pointer_n = '0; else read_pointer_n = read_pointer_q + 1; // ... and decrement the overall count status_cnt_n = status_cnt_q - 1; end // keep the count pointer stable if we push and pop at the same time if (push_i && pop_i && ~full_o && ~empty_o) status_cnt_n = status_cnt_q; // FIFO is in pass through mode -> do not change the pointers if ((FALL_THROUGH || (FPGA_EN && FPGA_ALTERA)) && (status_cnt_q == 0) && push_i) begin if (FALL_THROUGH) data_o = data_i; if (FPGA_EN && FPGA_ALTERA) begin data_ft_n = data_i; first_word_n = '1; end if (pop_i) begin first_word_n = '0; status_cnt_n = status_cnt_q; read_pointer_n = read_pointer_q; write_pointer_n = write_pointer_q; end end if (FPGA_EN) fifo_ram_read_address = (FPGA_ALTERA == 1) ? read_pointer_n : read_pointer_q; else fifo_ram_read_address = '0; end // sequential process always_ff @(posedge clk_i or negedge rst_ni) begin if (~rst_ni) begin read_pointer_q <= '0; write_pointer_q <= '0; status_cnt_q <= '0; if (FPGA_ALTERA) first_word_q <= '0; if (FPGA_ALTERA) data_ft_q <= '0; end else begin if (flush_i) begin read_pointer_q <= '0; write_pointer_q <= '0; status_cnt_q <= '0; if (FPGA_ALTERA) first_word_q <= '0; if (FPGA_ALTERA) data_ft_q <= '0; end else begin read_pointer_q <= read_pointer_n; write_pointer_q <= write_pointer_n; status_cnt_q <= status_cnt_n; if (FPGA_ALTERA) data_ft_q <= data_ft_n; if (FPGA_ALTERA) first_word_q <= first_word_n; end end end if (FPGA_EN) begin : gen_fpga_queue if (FPGA_ALTERA) begin SyncDpRam_ind_r_w #( .ADDR_WIDTH(ADDR_DEPTH), .DATA_DEPTH(DEPTH), .DATA_WIDTH($bits(dtype)) ) fifo_ram ( .Clk_CI (clk_i), .WrEn_SI (fifo_ram_we), .RdAddr_DI(fifo_ram_read_address), .WrAddr_DI(fifo_ram_write_address), .WrData_DI(fifo_ram_wdata), .RdData_DO(fifo_ram_rdata) ); end else begin AsyncDpRam #( .ADDR_WIDTH(ADDR_DEPTH), .DATA_DEPTH(DEPTH), .DATA_WIDTH($bits(dtype)) ) fifo_ram ( .Clk_CI (clk_i), .WrEn_SI (fifo_ram_we), .RdAddr_DI(fifo_ram_read_address), .WrAddr_DI(fifo_ram_write_address), .WrData_DI(fifo_ram_wdata), .RdData_DO(fifo_ram_rdata) ); end end else begin : gen_asic_queue always_ff @(posedge clk_i or negedge rst_ni) begin if (~rst_ni) begin mem_q <= '0; end else if (!gate_clock) begin mem_q <= mem_n; end end end // pragma translate_off initial begin assert (DEPTH > 0) else $error("DEPTH must be greater than 0."); end full_write : assert property (@(posedge clk_i) disable iff (~rst_ni) (full_o |-> ~push_i)) else $fatal(1, "Trying to push new data although the FIFO is full."); empty_read : assert property (@(posedge clk_i) disable iff (~rst_ni) (empty_o |-> ~pop_i)) else $fatal(1, "Trying to pop data although the FIFO is empty."); // pragma translate_on endmodule // fifo_v3