Home made SystemVerilog 3 word, Zero latency FIFO, documented for beginners. - Page 1
Forum section: electronics / general

author: BrianHG
author_rank: Super Contributor
timestamp: July 07, 2020, 03:50:00 am
content: Here is my home made 3 word, plus 1 extra reserve word (4 word total), Zero Latency FIFO, with 'look ahead' data and status flag outputs written in System Verilog.The Zero Latency means while the FIFO is empty, the 'shift_in' and 'data_in' are wired directly to the 'fifo_not_empty' and 'data_out' incurring 0 clock cycle delay when the FIFO is used as a small data command buffer.The outputs are also 'look ahead' which means, the data and flag outputs already have current valid data before you assert a clocked 'shift_out'. This means when you assert a shift out, on that cycle, you retrieve the output data and the FIFO prepares the next word on the next clock. You will then receive ahead of time the fifo_not_empty flag depending on the availability of more new data or not. This is also the reason for the +1 extra word in the FIFO, or, the FIFO full flag being returned 1 word early of it being truly full. This gives your other logic an extra clock transfer cycle to halt processes since it may take 1 clock for those modules to respond to the fifo_full flag.The 'FIFO_3word_0_latency.sv' has 2 parameters, bits which sets the width of the FIFO and zero_latency which enables/disables the 0 clock cycle delay on the output data.See this image for functionality:The source code has all the inputs, outputs & parameters well documented in the next post.

author: BrianHG
author_rank: Super Contributor
timestamp: July 07, 2020, 03:59:15 am
content: Get final version 3.1 here:V3.1 with 7 FIFO word mode and underflow/overflow protection.Obsolete V1.0 here:Code:// *****************************************************************// *** FIFO_3word_0_latency.sv V1.0, July 6, 2020// ***// *** This 3 word + 1 reserve word Zero latency FIFO// *** with look ahead data and status flags outputs was// *** written by Brian Guralnick.// ***// *** See the included 'FIFO_0_latency.png' simulation for functionality.// ***// *** Using System Verilog code which only uses synchronous logic.// *** Well commented for educational purposes.// *****************************************************************module FIFO_3word_0_latency (input wire clk, // CLK inputinput wire reset, // reset FIFOinput wire shift_in, // load a word into the FIFO.input wire shift_out, // shift data out of the FIFO.input wire [bits-1:0] data_in, // data word input.output wire fifo_not_empty, // High when there is data available.output wire fifo_full, // High when the FIFO is 3 words full.// *** Note the FIFO has 1 extra word of free space after// *** the fifo_full flag goes high, so it's actually a 4 word FIFO.output wire [bits-1:0] data_out // FIFO data word output);parameter bits = 8 ; // sets the width of the fifoparameter zero_latency = 1; // When set to 1, if the FIFO is empty, the data_out and fifo_empty flag will// immediately reflect the state of the inputs data_in and shift_in, 0 clock cycle delay.// When set to 0, like a normal synchronous FIFO, It will take 1 clock cycle before the// fifo_empty flag goes high and the data_out will have a copy of the data_in after a 'shift_in'.reg [bits-1:0] fifo_data_reg[3:0] ; // FIFO memoryreg [2:0] fifo_wr_pos, fifo_rd_pos, fifo_size ; // the amount of data stored in the fifoassign data_out = ( fifo_size == 0 ) && (zero_latency ) ? data_in : fifo_data_reg[fifo_rd_pos[1:0]] ; // When FIFO is empty and parameter zero_latency = 1,// bypass the memory registers and wire the output directly to the input.// Otherwise show the correct FIFO memory register once it is latched.assign fifo_not_empty = (fifo_size != 0) || (zero_latency && shift_in); // While the FIFO is empty and zero_latency = 1, directly wire// 'fifo_not_empty' output to the 'shift_in' input. Otherwise,assign fifo_full = (fifo_size >= 3'd3); // only set high once there is data in the FIFO.always @ (posedge clk) beginif (reset) beginfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_rd_pos <= 3'd0; // reset the FIFO memory counterfifo_wr_pos <= 3'd0; // reset the FIFO memory counterfifo_size <= 3'd0; // reset the FIFO memory counterend else beginif ( shift_in && ~shift_out ) fifo_size <= fifo_size + 1'b1; // Calculate the number of words stored in the FIFOelse if ( ~shift_in && shift_out ) fifo_size <= fifo_size - 1'b1;if ( shift_in ) beginfifo_wr_pos <= fifo_wr_pos + 1'b1 ;fifo_data_reg[fifo_wr_pos[1:0]] <= data_in ;endif ( shift_out ) beginfifo_rd_pos <= fifo_rd_pos + 1'b1 ;endend // ~resetend // always @ (posedge clk) beginendmodule

author: hamster_nz
author_rank: Super Contributor
timestamp: July 07, 2020, 11:10:32 am
content: Nice work!Must play havoc with your timing closure though?

author: BrianHG
author_rank: Super Contributor
timestamp: July 07, 2020, 12:20:48 pm
content: Quote from: hamster_nz on July 07, 2020, 11:10:32 amOnly if you use the FIFO wired directly to and from IO pins. If the FIFO is internal, the compiler would consider the 'WIRING' from it's input port to the output port just part of the logic gates, or a mux selection logic within your design. I still get an fmax in the 350MHz range on the slowest CycloneIV. This is the reason for making the FIFO only 4 bytes exactly. Anything width up to 36 bits will give me that theoretical 350MHz range. Or in other words, inserting such a FIFO anywhere inside my existing designs won't lower my design's current fmax.However, wired to IO pins, you need to take into account the input's tsu and un-latched data output registers behaving like a mux or gates, not clocked registers. Especially on the output side, FPGAs don't handle this quite well. Quartus gave me a 'restricted FMAX' of 250Mhz, but the data is mush, or, the data is just valid at the last few picoseconds. To be useful like this, and you want a valid data output window of say 10ns, you could not use this fifo faster than around 75MHz. I have not considered smaller PLD devices like MAX3000/MAX7000 series. With optimal chosen IOs, they might actually perform better as they were designed for glue logic / gate driven IO pins and the fabric is tiny compared to an FPGA.

author: NorthGuy
author_rank: Super Contributor
timestamp: July 07, 2020, 01:23:56 pm
content: Xilinx 7-series chips have this built-in in their hardware FIFOs. They call it "First Word Fall Through" (FWFT) mode.

author: asmi
author_rank: Super Contributor
timestamp: July 07, 2020, 01:29:10 pm
content: Quote from: NorthGuy on July 07, 2020, 01:23:56 pmThey have quite high minimum depth (16 IIRC), if you need less you will need to improvise.

author: BrianHG
author_rank: Super Contributor
timestamp: July 07, 2020, 01:35:54 pm
content: Same with Altera's internal IP FIFO, with an alleged warning that some of these mode settings may impact performance, however, my code has no vendor specific IP/module tied to it.

author: asmi
author_rank: Super Contributor
timestamp: July 07, 2020, 08:15:19 pm
content: Using blocking assignments inside clocked blocks is not a good idea in my opinion, I would also use actual SV constructs instead of legacy Verilog ones - like "always_ff", "logic" instead of regs (though I do still use wires to underscore that they are not FFs where this difference is important), using SV-style parameter declarations ("#( parameter X = 42)"), etc. But these things are mostly a matter of taste.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 07, 2020, 09:55:00 pm
content: Quote from: BrianHG on July 07, 2020, 12:20:48 pmDoesn't this trouble when zero_latency is 1, linking the timing paths leading up to "data_in" with those downstream from "data_out", with a couple of levels of logic added in?Code:assign data_out = ( fifo_size == 0 ) && (zero_latency ) ? data_in : fifo_data_reg[fifo_rd_pos[1:0]] ;As I count it, a bit of data_out depends on 9 bits of input (or maybe less, depending if fifo_data_reg is inferred as RAM)...

author: BrianHG
author_rank: Super Contributor
timestamp: July 07, 2020, 11:02:26 pm
content: Quote from: hamster_nz on July 07, 2020, 09:55:00 pmWith zero_latency set to 0, the downstream logic will always be a 4 position mux selecting 1 of the 4 fifo_data_regs selected by the 2 bit address fifo_rd_pos.With zero_latency set to 1, we now have a 3 bit address. The MSB address is tied to the output of a 3 input OR gate whose inputs are tied to the 3 bit counter fifo_size. The inputs of that now 8:1 mux selector have the first 4 mux inputs tied in parallel to the data coming data_in (basically a register from somewhere in you existing design) while the top 4 inputs of that 8:1 mux are tied to the 4 fifo_data_regs.Since you will be feeding the the data in from another register in your FPGA anyways, just like the 4 registers fifo_data_reg, the penalty between going from zero_latency off to on is equivalent to switching between a 4:1 mux memory register bank to a 8:1 mux memory register bank, with the first 4 of those registers tied together from with your data_in (again, still another reg in your FPGA design) and the MSB addr of the mux selector if tied to the output of a 3 input OR gate whose inputs come from the 3 bit counter fifo_size.Dam it hamster_nz (I don't know why I didn't realize it myself), I just thought of a way to eliminate that 3 input OR gate making it a single register bit just like the first 2 bits fifo_rd_pos, hence shaving off a nanosecond. As fast as the current design is, it would still be an improvement for slower FPGAs or old PLDs.Updated V2 code coming with timing comparison if possible (meaning the current code is already so fast).Note that I will code it so that the 8:1 mux becomes obvious & I will adapt my simulation test bench to reflect the FIFO being inside a design where it is being fed by a set of registers, and the outputs will be latched by another set of registers. The compiler will then provide a proper valid FMAX reading and penalty between zero_latency on and off when using the FIFO buried inside a design in this manner.Note: zero_latency doesn't count as an input as it's a fixed parameter removed at compile time.

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 03:40:43 am
content: Get final version 3.1 here:V3.1 with 7 FIFO word mode and underflow/overflow protection.Here is obsolete version 2.0. I've also added an overflow and underflow protection feature to prevent FIFO corruption if too many reads or writes are requested.This version has only a single 2 input OR gate driving the 'fifo_not_empty' flag when in zero_latency mode called "First Word Fall Through" (FWFT) mode while the output data mux selector no longer has a 3 input OR gate to select the data_in in FWFT mode. The timing improvement will be shown in the next post.Code:// *****************************************************************// *** FIFO_3word_0_latency.sv V2.0, July 7, 2020// ***// *** This 3 word + 1 reserve word Zero latency FIFO// *** with look ahead data and status flags outputs was// *** written by Brian Guralnick.// ***// *** See the included 'FIFO_0_latency.png' simulation for functionality.// ***// *** Using System Verilog code which only uses synchronous logic.// *** Well commented for educational purposes.// *****************************************************************module FIFO_3word_0_latency (input wire clk, // CLK inputinput wire reset, // reset FIFOinput wire shift_in, // load a word into the FIFO.input wire shift_out, // shift data out of the FIFO.input wire [bits-1:0] data_in, // data word input.output wire fifo_not_empty, // High when there is data available.output wire fifo_full, // High when the FIFO is 3 words full.// *** Note the FIFO has 1 extra word of free space after// *** the fifo_full flag goes high, so it's actually a 4 word FIFO.output wire [bits-1:0] data_out // FIFO data word output);//*************************************************************************************************************************************parameter bits = 8 ; // sets the width of the fifoparameter zero_latency = 1; // When set to 1, if the FIFO is empty, the data_out and fifo_empty flag will// immediately reflect the state of the inputs data_in and shift_in, 0 clock cycle delay.// When set to 0, like a normal synchronous FIFO, It will take 1 clock cycle before the// fifo_empty flag goes high and the data_out will have a copy of the data_in after a 'shift_in'.// Enabling the overflow/underflow protection features may lower top FMAX.parameter overflow_protection = 0; // Prevents internal write position and writing if the fifo is full past the 1 extra reserve wordparameter underflow_protection = 0; // Prevents internal position position increment if the fifo is empty//*************************************************************************************************************************************reg [bits-1:0] fifo_data_reg[3:0] ; // FIFO memoryreg [1:0] fifo_wr_pos, fifo_rd_pos; // read and write memory pointersreg [7:0] fifo_position = 8'b11100001 ; // The fifo empty locationwire [2:0] read_pointer ; // read data mux pointerwire [bits-1:0] fifo_data_mux[7:0] ; // the mux data inputsassign fifo_data_mux[3:0] = fifo_data_reg[3:0]; // mux selection from fifo register data.assign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign read_pointer[1:0] = fifo_rd_pos[1:0]; // adress the 4 fifo wordsassign read_pointer = ~fifo_position && zero_latency ; // when high, address the data_in on the top 4 mux positions.assign data_out = fifo_data_mux; // mux select the data output.//assign data_out = ( ~fifo_position && zero_latency ) ? data_in : fifo_data_reg[fifo_rd_pos[1:0]] ; // When FIFO is empty and parameter zero_latency = 1,// bypass the memory registers and wire the output directly to the input.// Otherwise show the correct FIFO memory register once it is latched.assign fifo_not_empty = fifo_position || (zero_latency && shift_in); // While the FIFO is empty and zero_latency = 1, directly wire// 'fifo_not_empty' output to the 'shift_in' input. Otherwise,assign fifo_full = fifo_position ; // only set high once there is data in the FIFO.wire shift_in_protect, shift_out_protect;assign shift_in_protect = shift_in && ( ~fifo_position || (overflow_protection !=1) );// Do not allow a shift_in if the FIFO is full past the fourth reserve wordassign shift_out_protect = shift_out && ( fifo_not_empty || (underflow_protection !=1) );// Do not allow a shift_out if the FIFO is emptyalways @ (posedge clk) beginif (reset) beginfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_rd_pos <= 2'd0; // reset the FIFO memory counterfifo_wr_pos <= 2'd0; // reset the FIFO memory counterfifo_position <= 8'b11100001; // The fifo empty locationend else beginif ( shift_in_protect && ~shift_out_protect ) fifo_position[7:0] <= {fifo_position[6:0],fifo_position}; // Rotate the FIFO position leftelse if ( ~shift_in_protect && shift_out_protect ) fifo_position[7:0] <= {fifo_position,fifo_position[7:1]}; // Rotate the FIFO position rightif ( shift_in_protect ) beginfifo_wr_pos <= fifo_wr_pos + 1'b1 ;fifo_data_reg[fifo_wr_pos[1:0]] <= data_in ;endif ( shift_out_protect ) beginfifo_rd_pos <= fifo_rd_pos + 1'b1 ;endend // ~resetend // always @ (posedge clk) beginendmodule

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 04:03:42 am
content: This simulation shows the response improvement of the 'fifo_not_empty' flag in FWFT mode with a shift in:Here is a view of then improvement of the data_out timing when shifting out the last word in the FIFO as it enters FWFT mode passing through the data_in:These timing simulations were done from IO pins to IO pins on a CycloneIII -8 FPGA. They do not represent the authentic performance when using the FIFO internally in a system design as the compiler simplifies out and re-times registers to achieve full timing closure. However, embedded in a design, the V1 had an FMAX of 308MHz & the V2 had an FMAX of 290MHz. Though it sounds off that the V2 has a lower FMAX, it is because the V2 has an 8 bit bi-directional shift register in place of a 3 bit counter in V1. If you do not need the above 290MHz performance, V2's improved FWFT MUX response will make it easier for your compiler to achieve it's timing closure goals with less register re-timing during fitting.

author: asmi
author_rank: Super Contributor
timestamp: July 08, 2020, 04:18:03 am
content: Here is a fifo I use in my 64bit RISC-V core (it's a FWFT fifo which I called elastic_fifo for some reason which I don't remember anymore):Code:module elastic_fifo #(parameter type data_type = bit[31:0],parameter MAX_DEPTH = 4,parameter ALMOST_FULL_THRESHOLD = 2)(input sysclk,input reset,elastic_fifo_write_intf.fifo producer,elastic_fifo_read_intf.fifo consumer);localparam PTR_WIDTH = $clog2(MAX_DEPTH);localparam DEFAULT_PTR = {PTR_WIDTH{1'b0}};//http://billauer.co.il/reg_fifo.htmldata_type mem[0:MAX_DEPTH-1];bit [PTR_WIDTH:0] write_ptr = 0;bit [PTR_WIDTH:0] read_ptr = 0;wire empty_int = (write_ptr == read_ptr);wire full_or_empty = (write_ptr[PTR_WIDTH-1:0] == read_ptr[PTR_WIDTH-1:0]);assign producer.is_full = full_or_empty & ~empty_int;wire is_empty = full_or_empty & empty_int;wire [PTR_WIDTH:0] diff = write_ptr - read_ptr;assign producer.is_almost_full = diff > (MAX_DEPTH - ALMOST_FULL_THRESHOLD);bit dout_valid = 0;wire fifo_rd_en = ~is_empty && (!dout_valid || consumer.read_en);assign consumer.is_empty = ~dout_valid;assign consumer.data_valid = dout_valid;data_type dout;assign consumer.data = dout;always_ff @(posedge sysclk) beginif(reset) begindout_valid <= 1'b0;end else beginif (fifo_rd_en)dout_valid <= 1'b1;else if (consumer.read_en)dout_valid <= 1'b0;endendalways_ff @(posedge sysclk) beginif(reset) beginwrite_ptr <= DEFAULT_PTR;read_ptr <= DEFAULT_PTR;dout <= '{default:'0};end else beginif (producer.write_en && ~producer.is_full ) beginmem[write_ptr[PTR_WIDTH-1 -:PTR_WIDTH]] <= producer.data;write_ptr <= write_ptr + 1;endif (fifo_rd_en) begindout <= mem[read_ptr[PTR_WIDTH-1 -:PTR_WIDTH]];read_ptr <= read_ptr + 1;endendendInterfaces are defined as follows:Code:// Interface: elastic_fifo_write_intf//interface elastic_fifo_write_intf #(parameter type data_type = logic);data_type data;logic write_en;logic is_full;logic is_almost_full;modport producer(input is_full, is_almost_full, output data, write_en);modport fifo(output is_full, is_almost_full, input data, write_en);endinterface: elastic_fifo_write_intf// Interface: elastic_fifo_read_intf//interface elastic_fifo_read_intf #(parameter type data_type = logic);data_type data;logic data_valid;logic read_en;logic is_empty;modport consumer(input is_empty, data, data_valid, output read_en);modport fifo(output is_empty, data, data_valid, input read_en);endinterface: elastic_fifo_read_intfI use it in fetch module to store code data that comes from the memory, it stores 130-bit wide structures (64bit address, 64bit data, 2bit epoch). Works fine at least at 181 MHz clock (it's limited by a different block of CPU, so not sure how much higher can it go) on Artix-7/Spartan-7 fabric. On Kintex-7 fabric the same code goes above 300 MHz IIRC.

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 04:50:26 am
content: Get final version 3.1 here:V3.1 with 7 FIFO word mode and underflow/overflow protection.Obsolete 7 word version:Code:// *****************************************************************// *** FIFO_7word_0_latency.sv V2.0, July 8, 2020// ***// *** This 7 word + 1 reserve word Zero latency FIFO// *** with look ahead data and status flags outputs was// *** written by Brian Guralnick.// ***// *** See the included 'FIFO_0_latency.png' simulation for functionality.// ***// *** Using System Verilog code which only uses synchronous logic.// *** Well commented for educational purposes.// *****************************************************************module FIFO_7word_0_latency (input wire clk, // CLK inputinput wire reset, // reset FIFOinput wire shift_in, // load a word into the FIFO.input wire shift_out, // shift data out of the FIFO.input wire [bits-1:0] data_in, // data word input.output wire fifo_not_empty, // High when there is data available.output wire fifo_full, // High when the FIFO is 7 words full.// *** Note the FIFO has 1 extra word of free space after// *** the fifo_full flag goes high, so it's actually a 8 word FIFO.output wire [bits-1:0] data_out // FIFO data word output);//*************************************************************************************************************************************parameter bits = 8 ; // sets the width of the fifoparameter zero_latency = 1; // When set to 1, if the FIFO is empty, the data_out and fifo_empty flag will// immediately reflect the state of the inputs data_in and shift_in, 0 clock cycle delay.// When set to 0, like a normal synchronous FIFO, It will take 1 clock cycle before the// fifo_empty flag goes high and the data_out will have a copy of the data_in after a 'shift_in'.// Enabling the overflow/underflow protection features may lower top FMAX.parameter overflow_protection = 0; // Prevents internal write position and writing if the fifo is full past the 1 extra reserve wordparameter underflow_protection = 0; // Prevents internal position position increment if the fifo is empty//*************************************************************************************************************************************reg [bits-1:0] fifo_data_reg[7:0] ; // FIFO memoryreg [2:0] fifo_wr_pos, fifo_rd_pos; // read and write memory pointersreg [15:0] fifo_position = 16'b1111111000000001 ; // The fifo empty locationwire [3:0] read_pointer ; // read data mux pointerwire [bits-1:0] fifo_data_mux[15:0] ; // the mux data inputsassign fifo_data_mux[7:0] = fifo_data_reg[7:0]; // mux selection from fifo register data.assign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign fifo_data_mux = data_in ; // mux selection from data inputassign read_pointer[2:0] = fifo_rd_pos[2:0]; // adress the 4 fifo wordsassign read_pointer = ~fifo_position && zero_latency ; // when high, address the data_in on the top 4 mux positions.assign data_out = fifo_data_mux; // mux select the data output.//assign data_out = ( ~fifo_position && zero_latency ) ? data_in : fifo_data_reg[fifo_rd_pos[1:0]] ; // When FIFO is empty and parameter zero_latency = 1,// bypass the memory registers and wire the output directly to the input.// Otherwise show the correct FIFO memory register once it is latched.assign fifo_not_empty = fifo_position || (zero_latency && shift_in); // While the FIFO is empty and zero_latency = 1, directly wire// 'fifo_not_empty' output to the 'shift_in' input. Otherwise,assign fifo_full = fifo_position ; // only set high once there is data in the FIFO.wire shift_in_protect, shift_out_protect;assign shift_in_protect = shift_in && ( ~fifo_position || (overflow_protection !=1) );// Do not allow a shift_in if the FIFO is full past the fourth reserve wordassign shift_out_protect = shift_out && ( fifo_not_empty || (underflow_protection !=1) );// Do not allow a shift_out if the FIFO is emptyalways @ (posedge clk) beginif (reset) beginfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_rd_pos <= 3'd0; // reset the FIFO memory counterfifo_wr_pos <= 3'd0; // reset the FIFO memory counterfifo_position <= 16'b1111111000000001; // The fifo empty locationend else beginif ( shift_in_protect && ~shift_out_protect ) fifo_position[15:0] <= {fifo_position[14:0],fifo_position }; // Rotate the FIFO position leftelse if ( ~shift_in_protect && shift_out_protect ) fifo_position[15:0] <= {fifo_position ,fifo_position[15:1]}; // Rotate the FIFO position rightif ( shift_in_protect ) beginfifo_wr_pos <= fifo_wr_pos + 1'b1 ;fifo_data_reg[fifo_wr_pos[2:0]] <= data_in ;endif ( shift_out_protect ) beginfifo_rd_pos <= fifo_rd_pos + 1'b1 ;endend // ~resetend // always @ (posedge clk) beginendmoduleCycloneIV-8 3word x 36bit FMAX 245MHz, 7word x 36bit FMAX 222MHz.

author: NorthGuy
author_rank: Super Contributor
timestamp: July 08, 2020, 02:06:33 pm
content: The performance loss will not be seen until you put it into operation. All the logic which the writer uses to produce shift_in and data_in, all the logic within fifo which produces fifo_not_empty and data_out, all the logic the reader uses to analyze fifo_not_empty, process data and set shift_out, will have to be evaluated consecutively during the same clock cycle. Thus, it may be fast when writer and reader are simple test entities, but if they become more complex, the max clock frequency will drop.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: July 08, 2020, 04:34:50 pm
content: SystemVerilog, but you're still using antique reg and wire types and not using always_ff? Why?

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 04:39:48 pm
content: Quote from: NorthGuy on July 08, 2020, 02:06:33 pmYes, that performance drop, now with V2, is the addition of 1 additional input to a preceding gates which derived the control/enable of your next stage. And for the data, the speed of the mux pass through. Depending on your compiler's ability to restructure multiplexers and feed data through them, enabling the zero_latency will may slow down your FMAX if this FIFO module is the most complex bottleneck part of your design.Enabling "First Word Fall Through" (FWFT) mode in Intel's internal FIFO has an equivalent performance hit (Worse than my version, but they can go up to kilobyte size FIFOs using ram blocks). I cannot speak for Xilinx.Example not empty in zero_latency mode:fifo_not_empty = fifo_position || shift_in;That clocked 1 synchronous register output OR'd with the shift_in. And if you know your compiler, it will push back the that shift in to you previous logic which would meet the next part of your design if you did not have the FIFO and what you would call the shift in would transparently go onto the next stage in your design.Like I said, V2 got rid of all the gates involved the comparing a 3 bit number to a size quantity, then ORing that compare result with your fed shift_in input, then that would feed your next stage. You cannot go faster than my current V2 design. The official FWFT FIFOs form Xilinx and Intel arent as efficient as my trick to get rid of counters and associated logic to determine the amount of data inside the FIFO becomes absurd beyond an 8 word FIFO. Though, with some smart coding, the trick can be implemented in a different way.

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 04:40:19 pm
content: Quote from: Sal Ammoniac on July 08, 2020, 04:34:50 pmSorry, bad habit. I started thin in regular Verilog, then, wanting to address arrays, .sv just made that easy to add the [x:x] after the registers.Fixed in version 3.0 here:V3.0 with 7 FIFO word mode and underflow/overflow protection.

author: NorthGuy
author_rank: Super Contributor
timestamp: July 08, 2020, 05:40:08 pm
content: Quote from: BrianHG on July 08, 2020, 04:39:48 pmThere will be entities which use your fifo. They will have their own logic, which may or may not be complex. With zero latency you get the following combinatory chain:fifo_full -> writer's logic outside of your FIFO -> shift_in -> fifo_not_empty -> reader's logic outside of your FIFO -> shift_outWithout zero latency you get two parallel chains:fifo_full -> writer's logic outside of your FIFO -> shift_in -> fifo_not_emptyfifo_not_empty -> reader's logic outside of your FIFO -> shift_outFMax decreases not because you add a mux inside FIFO, but because you combine writer's and reader's logic dealing with FIFO into a single combinatory chain. Without zero latency, the FIFO pipelines this chain into two - you get the data a clock later, but you can run the clock faster. Which one is better depends on the circumstances.

author: asmi
author_rank: Super Contributor
timestamp: July 08, 2020, 05:53:23 pm
content: Quote from: BrianHG on July 08, 2020, 04:39:48 pmI can't speak for Intel, but Xilinx has FWFT mode implemented in silicon, so zero logic is required to use it. And it works in asynchronous mode (when read and write side are clocked by two unrelated clocks) too. What about yours?

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 06:20:56 pm
content: Quote from: asmi on July 08, 2020, 05:53:23 pmMy design is a single clock FIFO for linking 2 parts of your design together where 1 may build up data or commands faster than the execution unit on the output side. It should work predictably across Intel, Lattice, Xilinx, or even a custom ASIC you might be considering. It was not designed to take up any dedicated ram blocks or eat excessive amounts of logic other than the data registers.A true FWFT mode still needs to pass data from the input to output. Does Xilinx specify this transmission of delay in ns? What happens when you cross clock domains?At least with mine, your compiler's reported FMAX means that the data driven through is ready by the next clock cycle.Q: Is the Xilinx FWFT mode a functional feature, regardless of the delay so if you use the same CLK in and CLK out, will the output data and flags be all valid before the next clock cycle? Since you say this is a hardware block, it should have a predictable fixed delay. I am not questioning whether it is implemented in dedicated hardware or not.

author: NorthGuy
author_rank: Super Contributor
timestamp: July 08, 2020, 07:14:13 pm
content: Quote from: BrianHG on July 08, 2020, 06:20:56 pmThe FWFT mode is for dual clocks. The writer uses his clock to write FIFO. The data will appear on the reader's end without waiting for the reader's read strobe. This adds 1 to the depth of the FIFO. The asymchronous more requires internal synchronization, so the latency is rather high.They have synchronous mode too. It requires a special EN_SYNC flag, so it probably ensures better latency. I don't know what is the latency through synchronous FIFO.It is not combinatorial like yours (if I understand Verilog of your V2 correctly), where the reader can process the data and capture them on the very first clock edge - the same clock edge which is used by writer to write the data into the memory.

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 07:27:28 pm
content: You understand mine fine. The combinational logic structure is taken into account by the compiler during compile and for many apps, you can completely shave off a clock cycle.This can only work with a good FMAX this way so because the data is going through a small 5:1 mux, and the MUX selector is a registered 2 bit counter plus 1 bit registered fifo__not_empty flag without any combinational logic gates to derive that MUX selection, unlike my V1 which had an added almost 1ns added penalty when switching the MUX into fall through mode as seen in my second timing diagram.Thinking things out further, there is 1 last improvement I can make. If it improves FMAX, I'll update to V3 code.

author: NorthGuy
author_rank: Super Contributor
timestamp: July 08, 2020, 09:31:38 pm
content: Quote from: BrianHG on July 08, 2020, 07:27:28 pmSure you can improve your combinatorial delay by a lot. All you need on your critical path (if you can call it that) is a single mux which selects either data_in or some temporary register prefetched from the FIFO's memory on the previous clock. The selector for the mux can also be pre-calculated on the previous clock. So, it's only one logic level you need.I don't think Fmax is a meaningful term here. The path for Fmax calculation is measured from flop to flop. Both launching and receiving flops are outside of your module. If you just put the registers before and after your FIFO, the path from the in register to the out register will be faster than internal paths inside your module, so the tools will not even try to optimize it. Your Fmax will rather reflect the performance of the internal paths (which start and end withing your FIFO). On the other hand, when you connect your FIFO module to other modules (or to package IO pins for that matter, as you have already tried), the in-to-out path will be much slower than your internal paths, so the performance of internal paths will not be important any more. Thus, what you want to optimize is the combinatorial belay from data_in to data_out, not the Fmax of your internal paths.

author: BrianHG
author_rank: Super Contributor
timestamp: July 08, 2020, 10:17:52 pm
content: Quote from: NorthGuy on July 08, 2020, 09:31:38 pmAgreed with point #1 as I was also going also going to dedicate separate pre-calculated registers to feed that mux and fifo_not_empty flag.As for the FMAX, I did not cheat here. I did not get Quartus to generate that figure from the simulation of tying the FIFO to IO pins. Doing so made Quartus give me an FMAX of 372MHz, not the 260MHz I reported where I placed the FIFO in an actual circuit being fed by logic module A, through FIFO, to logic module B. When compiling with this test fixture, Quartus must take into account the complete combinational path from logic module A through the FIFO to logic module B ensuring data setup & hold integrity which is why the FMAX suffered to such a degree. I understand the issue well and made sure my report offered an actual usage case scenario.The IO pin timing simulation was strictly to evaluate performance difference where trying to Signal tap post fitting my actual use case scenario could not tell me what's going on inside the FPGA fabric since all my net names vanished and were broken down and changed into many signals like multiple oe_product terms and luts.

author: asmi
author_rank: Super Contributor
timestamp: July 09, 2020, 12:26:31 am
content: I think what he's trying to say is that since you don't have a register right at the input and output, the Fmax is meaningless because you can have over 9000 logic levels on the input and over 9000 at the output, and this will trash your Fmax down the toilet. If you'd have inputs and outputs registered (which is the gold standard I'm trying to follow whenever I can), then your module's combinatorial paths are not going to be affected in any way by whatever connects to your module because all of them are going to be 100% internal to the module.

author: NorthGuy
author_rank: Super Contributor
timestamp: July 09, 2020, 12:39:47 am
content: Quote from: BrianHG on July 08, 2020, 10:17:52 pmSorry, I didn't mean to accuse you of cheating. I think the opposite concern is more important. As you improve your data_in to data_out path, you will get to the point where the Fmax will not show any further improvements because other things inside the FIFO (such as fetching from memory/array) may be slower than the path you're working on.

author: BrianHG
author_rank: Super Contributor
timestamp: July 09, 2020, 03:13:31 am
content: Quote from: asmi on July 09, 2020, 12:26:31 amIf you want that true 0 latency un-clocked fall through, no matter what I do, the data coming into my module needs to get out. The fasted way I can implement this is narrowing down that side to an AB mux between data_in and a registered fifo ram word. Same goes for the fifo_not_empty, though the question here is what's faster, using an AB mux, or a single 2 input OR gate. Everything else in my design is already registered. My current output is a 5:1 mux which selects the 1 of 4 registered fifo memory words or have the data_in fall through to the data_out. I'll be improving that to an AB mux design which only selects between 1 registered word or the data_in. This change will also vastly improve the 7 word fifo.Even with so called hardware implemented FIFO, that fall through word needs to get from in to out somehow, whether through a static ram cell or a mux selection at the output, even if that mux is implemented as part of the dedicated fifo hardware, it's still incurs a delay.The purpose of my fifo is not to create a bridge between 2 clock domains, it's to be small and fast all within 1 system clock domain. Now, I do have a another 4 word FIFO designed to switch between 2 clock domains and avoid meta-stability issues with only 4 words, but that is a different thread topic.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 09, 2020, 10:08:30 am
content: Possibly dumb question.... Did you consider in the original code that:Code:assign fifo_full = (fifo_size >= 3'd3);Could be:Code:assign fifo_full = (fifo_size >= 3'd3) || shift_out;...as when the consumer is pulling data from the FIFO you should always be able to insert a new item?Also, it occurred to me that with such a small FIFO you could consider just a 6-input lookup table finite state machine - four bits of state with the shift_in and shift_out signals indexing a table of the next state (so 4 x LUT6s) and seven or so LUT4s to MUX control signals and outputs, along with the regs to hold the data? With a little bit of care in selecting the state so that two of the bits corresponded with the read index it seems could be implemented in two levels of logic on a LUT6 architecture.

author: BrianHG
author_rank: Super Contributor
timestamp: July 09, 2020, 02:24:02 pm
content: Quote from: hamster_nz on July 09, 2020, 10:08:30 amCorrection:Code:assign fifo_full = (fifo_size >= 3'd3) && ~shift_out;Thanks. I completely missed that. With that 1 extra word in my fifo, I was expecting the module feeding the fifo to have a delayed response to the full flag, however, you are absolutely right.Will be added to V3.As for your second half, my V3 single 2 input mux for the data_out which will be the fastest possible. All other regs have solely a 1 or 2 input source control while the 4/8word array will be left to the compiler how it feeds my new 1 register before that final output 2:1 AB mux. The output flags will be a single logic cell, no additional fanout other than the fifo_not_empty being a 2 input OR gate, 1 input coming again from a single logic cell with no additional fanout (now for fifo_full as well).I'm writing it in a way which it still appears like the fifo memory is a normal array, but separated that array by an additional register like what you see in dual-port rams where you get a performance boost by registering the data outs.The final 2:1 AB mux will only have 1 control registered logic cell with no additional fanouts. 1 side of the mux, connected to my fifo memories new register output will have no additional fanouts on that reg's data output. The only thing I cannot control is where I'm receiving the data_in from the user who is feeding me.The end goal is to eliminate every bottleneck possible. Just drop in my fifo and forget as there would be no faster way to get that 0 clk first word fall through capability without any determent to the user's existing design.As for the finite state machine, if your logic has few enough bits that it will fit, the compiler does coverts your logic to a state machine if will improve design efficiency and it can.

author: BrianHG
author_rank: Super Contributor
timestamp: July 11, 2020, 07:42:22 pm
content: Here is Version 3.1.My attempt at the AB-MUX trick didn't improve performance, so here is what I've done:1. Converted the code to proper System Verilog removing the old fashioned Verilog block/wire/reg assignments.2. Added a parameter 'size7_ena' which will change the FIFO from 3 words to 7 words.3. Added hamster_nz patch to the 'fifo_full' flag.Code:// *****************************************************************// *** FIFO_3word_0_latency.sv V3.1, July 11, 2020// ***// *** This 3 word + 1 reserve word Zero latency FIFO// *** with look ahead data and status flags outputs was// *** written by Brian Guralnick.// ***// *** See the included 'FIFO_0_latency.png' simulation for functionality.// ***// *** Using System Verilog code which only uses synchronous logic.// *** Well commented for educational purposes.// *****************************************************************module FIFO_3word_0_latency (input logic clk, // CLK inputinput logic reset, // reset FIFOinput logic shift_in, // load a word into the FIFO.input logic shift_out, // shift data out of the FIFO.input logic [bits-1:0] data_in, // data word input.output logic fifo_not_empty, // High when there is data available.output logic fifo_full, // High when the FIFO is 3 words full.// *** Note the FIFO has 1 extra word of free space after// *** the fifo_full flag goes high, so it's actually a 4 word FIFO.output logic [bits-1:0] data_out // FIFO data word output);//*************************************************************************************************************************************parameter int bits = 8 ; // sets the width of the fifoparameter bit zero_latency = 1; // When set to 1, if the FIFO is empty, the data_out and fifo_empty flag will// immediately reflect the state of the inputs data_in and shift_in, 0 clock cycle delay.// When set to 0, like a normal synchronous FIFO, It will take 1 clock cycle before the// fifo_empty flag goes high and the data_out will have a copy of the data_in after a 'shift_in'.// Enabling the overflow/underflow protection features may lower top FMAX.parameter bit overflow_protection = 0; // Prevents internal write position and writing if the fifo is full past the 1 extra reserve wordparameter bit underflow_protection = 0; // Prevents internal position position increment if the fifo is emptyparameter bit size7_ena = 0; // Set to 0 for 3 words, set to 1 for 7 words.localparam int add_words = size7_ena * 4;//*************************************************************************************************************************************logic [bits-1:0] fifo_data_reg[(3 + add_words):0] ; // FIFO memorylogic [(1 + size7_ena):0] fifo_wr_pos, fifo_rd_pos; // read and write memory pointerslogic [(2 + size7_ena):0] fifo_words ; // The number of words in the FIFOlogic fifo_not_empty_r = 1'b0 ; // The fifo is not empty registerlogic fifo_full_r = 1'b0 ; // The fifo is the normal +3 word full registerlogic fifo_full_exr = 1'b0 ; // The fifo is at the true +4 word full registerlogic [(2 + size7_ena):0] read_pointer ; // read data mux pointerlogic [bits-1:0] fifo_data_mux[(7 + (add_words * 2)):0]; // the mux data inputslogic shift_in_protect, shift_out_protect;always_comb beginfor (int i=0 ; i<(4 + add_words) ; i++) fifo_data_mux = fifo_data_reg; // mux selection from fifo register data.for (int i=(4 + add_words) ; i<(8 + (add_words * 2)) ; i++) fifo_data_mux = data_in ; // mux selection from data inputread_pointer[(1 + size7_ena):0] = fifo_rd_pos[(1 + size7_ena):0]; // adress the 4 fifo wordsread_pointer[(2 + size7_ena)] = !fifo_not_empty_r && zero_latency ; // when high, address the data_in on the top 4 mux positions.data_out = fifo_data_mux; // mux select the data output.fifo_not_empty = fifo_not_empty_r || (zero_latency && shift_in); // While the FIFO is empty and zero_latency = 1, directly wire// 'fifo_not_empty' output to the 'shift_in' input. Otherwise,// only set high once there is data in the FIFO.fifo_full = fifo_full_r && !(shift_out && zero_latency) ; // Goes high when the FIFO has 3 words in storage and shift_out// isn't currently being requested while in zero_latency mode.shift_in_protect = shift_in && ( !(fifo_full_exr && !(shift_out && zero_latency)) || !overflow_protection ); // Do not allow a shift_in if the FIFO is full past the fourth reserve wordshift_out_protect = shift_out && ( fifo_not_empty || !underflow_protection ); // Do not allow a shift_out if the FIFO is emptyend // always_combalways_ff @(posedge clk) beginif (reset) beginfor (int i=0 ; i<(4 + add_words) ; i++) fifo_data_reg <= 0 ; // clear the FIFO's memory contentsfifo_rd_pos <= 0; // reset the FIFO memory counterfifo_wr_pos <= 0; // reset the FIFO memory counterfifo_words <= 0; // The fifo's number of stored wordsfifo_not_empty_r <= 0 ; // The fifo is not empty registerfifo_full_r <= 0 ; // The fifo is the normal +3 word full registerfifo_full_exr <= 0 ; // The fifo is at the true +4 word full registerend else beginif ( shift_in_protect && !shift_out_protect ) beginfifo_words <= fifo_words + 1'b1; // increment the fifo's number of stored words/*if (fifo_words==0)*/ fifo_not_empty_r <= 1;if (fifo_words==(2 + add_words)) fifo_full_r <= 1;if (fifo_words==(3 + add_words)) fifo_full_exr <= 1;end else if ( !shift_in_protect && shift_out_protect ) beginfifo_words <= fifo_words - 1'b1; // decrement the fifo's number of stored wordsif (fifo_words==1) fifo_not_empty_r <= 0;if (fifo_words==(3 + add_words)) fifo_full_r <= 0;if (fifo_words==(4 + add_words)) fifo_full_exr <= 0;endif ( shift_in_protect ) beginfifo_wr_pos <= fifo_wr_pos + 1'b1 ;fifo_data_reg[fifo_wr_pos[(1 + size7_ena):0]] <= data_in ;endif ( shift_out_protect ) beginfifo_rd_pos <= fifo_rd_pos + 1'b1 ;endend // !resetend // always_ffendmoduleQuote from: Sal Ammoniac on July 08, 2020, 04:34:50 pmFixed! Including 'kfnight's comments in the next post.

author: kfnight
author_rank: Regular Contributor
timestamp: July 12, 2020, 12:28:26 am
content: Some last items to embrace SV. Get rid ofCode:integer i;and 1) use int 2) declare it in the for-loopCode:for (int i=(4 + add_words)Also add types for your parameters. For example you have some boolean parameters (can only be 0 or 1) but they'll probably default to ints if you don't provide a type. So you can haveCode:parameter bit overflow_protection = 0;andCode:shift_in_protect = shift_in && ( !(fifo_full_exr && !(shift_out && zero_latency)) || !overflow_protection;Note also that you are mixing logical and bit-wise boolean operators, so all of your ~'s should be !'s.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 12, 2020, 05:40:59 am
content: Is it expected that data_out to remain valid when shift_out is not asserted? (by valid, I mean presenting the last value that passed through the FIFO).Just wondering if values that shortcut through the FIFO need to be written to the registers at all...

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: July 12, 2020, 07:18:07 am
content: Thanks for sharing, your V3.1 would get 410MHz on a gowin FPGAAlso it would use these LUT and FF resources, I wonder how fast Quartus or Vivado or ISE would synthesize and place and route this! Gowin is surprisingly fastThe worst thing about Gowin is the lack of simulation for it's IP cores

author: NorthGuy
author_rank: Super Contributor
timestamp: July 12, 2020, 12:38:15 pm
content: Quote from: hamster_nz on July 12, 2020, 05:40:59 amdata_in is written into the array anyway (at least how it was in V2). If shift_out is not asserted, next clock you'll get the same data, but from the array. Presumably, BrianHG has tested everything before posting.

author: BrianHG
author_rank: Super Contributor
timestamp: July 12, 2020, 02:32:17 pm
content: Quote from: NorthGuy on July 12, 2020, 12:38:15 pmRemember, when starting with an empty FIFO, the data falls through. It takes a 'shift_in' when the the data is comited to an array and the data_out is automatically swapped from the data_in to that appropriate array retaining a solid output. This is exactly what happens in my simulation in post #1.If you 'shift_out', it means you have taken the current array and requesting the next array will be prepared by the next clock. If the FIFO is on it's last word, on the next clock, the FIFO will switch to fall through mode the data_in.Without any 'shift_in', no data will be written to any FIFO registers.Yes, we are currently using the FIFO in a functional design right now as well.

author: BrianHG
author_rank: Super Contributor
timestamp: July 12, 2020, 02:39:26 pm
content: Quote from: ali_asadzadeh on July 12, 2020, 07:18:07 amTo get a FMAX in a typical use scenario, you should bury all the FIFO's IO's inside D'latch flipflops with the same clock. That 410MHz may drop as the compiler will now need to consider the combinational path between the D'latch flipflops feeding the inputs through the fifo to the D'latch flipflops you have at the outputs.Disabling the 0 latency will give you a true FMAX in almost any scenario. This means the data still falls through, but the 'shift_in' will require 1 clock to affect the data_out (which will now be a register always) and fifo_not_empty flag.