Synchronous FIFO sizing issue - Page 1
Forum section: electronics / general

author: promach
author_rank: Frequent Contributor
timestamp: August 18, 2020, 02:58:14 am
content: Forthis synchronous fifo, how do I get around the limitation ofSIZE must be a power of two and greater than or equal to 4.?Code:// Credit : https://github.com/jbush001/NyuziProcessor/blob/master/hardware/core/sync_fifo.sv//// Copyright 2011-2015 Jeff Bush//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the 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.////// First-in, first-out queue, with synchronous read/write// - SIZE must be a power of two and greater than or equal to 4.// - almost_full asserts when there are ALMOST_FULL_THRESHOLD or more entries// queued.// - almost_empty asserts when there are ALMOST_EMPTY_THRESHOLD or fewer// entries queued.// - almost_full is still asserted when full is asserted, as is almost_empty// when empty is asserted.// - flush takes precedence over enqueue/dequeue if it is asserted// simultaneously. It is synchronous, unlike reset.// - It is not legal to assert enqueue when the FIFO is full or dequeue when it// is empty (The former is true even if there is a dequeue and enqueue in the// same cycle, which wouldn't change the count). Doing this will trigger an// error in the simulator and have incorrect behavior in synthesis.// - dequeue_value will contain the next value to be dequeued even if dequeue_en is// not asserted.//module sync_fifo#(parameter WIDTH = 4,parameter SIZE = 8//parameter ALMOST_FULL_THRESHOLD = SIZE,//parameter ALMOST_EMPTY_THRESHOLD = 1)(input clk,input reset,outputfull,//output reg almost_full,input enqueue_en,input [WIDTH - 1:0] enqueue_value,outputempty,//output reg almost_empty,input dequeue_en,output [WIDTH - 1:0]dequeue_value);parameter ADDR_WIDTH = $clog2(SIZE);// read and write pointers need one extra MSB bit to differentiate between empty and full// you can confirm this using count = wr_addr - rd_addr;reg[ADDR_WIDTH:0] rd_addr;reg[ADDR_WIDTH:0] wr_addr;reg[WIDTH - 1:0] data[SIZE - 1:0];`ifdef FORMALinitial rd_addr = 0;initial wr_addr = 0;`endifwire[ADDR_WIDTH:0] count = wr_addr - rd_addr;//assign almost_full = count >= (ADDR_WIDTH + 1)'(ALMOST_FULL_THRESHOLD);//assign almost_empty = count <= (ADDR_WIDTH + 1)'(ALMOST_EMPTY_THRESHOLD);assign full = (count == SIZE[ADDR_WIDTH:0]);assign empty = count == 0;assign dequeue_value = data[rd_addr[ADDR_WIDTH-1:0]]; // passed verilator width warninginteger index;always @(posedge clk)beginif (reset)beginrd_addr <= 0;wr_addr <= 0;for(index=0; index<SIZE; index=index+1)data <= 0;endelse begin// https://twitter.com/zipcpu/status/1143134086950789120// if enqueue_en and dequeue_en and full at the same time, nothing is added, one item is removed,// but count is not modified. Same for empty.// https://zipcpu.com/blog/2017/07/29/fifo.html// https://zipcpu.com/tutorial/lsn-10-fifo.pdfcase( {(dequeue_en && !empty), (enqueue_en && !full) })'b00 :beginwr_addr <= wr_addr;rd_addr <= rd_addr;end'b01 :beginwr_addr <= wr_addr + 1;data[wr_addr[ADDR_WIDTH-1:0]] <= enqueue_value; // passed verilator width warningrd_addr <= rd_addr;end'b10 :beginwr_addr <= wr_addr;rd_addr <= rd_addr + 1;end'b11 :beginwr_addr <= wr_addr + 1;data[wr_addr[ADDR_WIDTH-1:0]] <= enqueue_value; // passed verilator width warningrd_addr <= rd_addr + 1;enddefault: beginwr_addr <= wr_addr;rd_addr <= rd_addr;endendcaseendend// All the following formal proofs are modified from https://github.com/promach/afifo/blob/master/async_fifo.sv// and sfifo.v in http://zipcpu.com/tutorial/ex-10-fifo.zip/*See https://zipcpu.com/blog/2018/07/06/afifo.html for a formal proof of afifo in general*/`ifdef FORMALreg first_clock_had_passed;initial first_clock_had_passed = 0;always @(posedge clk)first_clock_had_passed <= 1;initial assume(reset);always @(posedge clk)beginif(first_clock_had_passed && $past(reset))beginassert(rd_addr == 0);assert(!full);assert(wr_addr == 0);assert(empty);endelse if(first_clock_had_passed)beginassert(count == (wr_addr - rd_addr));assert(count <= SIZE);assert(full == (count == SIZE));assert(empty == (count == 0));endendalways @(posedge clk)beginif (first_clock_had_passed)beginif($past(reset))beginassert(count == 0);assert(!full);assert(empty);assert(dequeue_value == 0);endendend`endif`ifdef FORMAL//////////////////////////////////////////////////////// Some cover statements, to make sure valuable states// are even reachable////////////////////////////////////////////////////////// Make sure a reset is possiblealways @(posedge clk)cover(reset);always @(posedge clk)if (first_clock_had_passed)cover((empty)&&(!$past(empty)));always @(*)if (first_clock_had_passed)cover(full);always @(posedge clk)if (first_clock_had_passed)cover($past(full)&&($past(enqueue_en))&&(full));always @(posedge clk)if (first_clock_had_passed)cover($past(full)&&(!full));always @(posedge clk)cover((full)&&(enqueue_en));always @(posedge clk)cover(enqueue_en);always @(posedge clk)cover((empty)&&(dequeue_en));always @(posedge clk)if (first_clock_had_passed)cover($past(!empty)&&($past(dequeue_en))&&(empty));`endif`ifdef FORMAL/* twin-write test */// write two pieces of different data into the synchronous fifo// then read them back from the synchronous fifowire [WIDTH - 1:0] first_data = $anyconst;wire [WIDTH - 1:0] second_data = $anyconst;always @(*) assume(first_data != 0);always @(*) assume(second_data != 0);always @(*) assume(first_data != second_data);// for induction verificationwire [ADDR_WIDTH : 0] f_first_addr = $anyconst;reg [ADDR_WIDTH : 0] f_second_addr;always @(*) f_second_addr <= f_first_addr + 1;wirewr = (enqueue_en && !full);wirerd = (dequeue_en && !empty);localparam IDLE = 0;localparam FIRST_DATA_IS_WRITTEN = 1;localparam SECOND_DATA_IS_WRITTEN = 2;localparam FIRST_DATA_IS_READ = 3;reg[1:0]f_state;initialf_state = IDLE;// See http://zipcpu.com/tutorial/lsn-10-fifo.pdf#page=21 for understanding the state machinealways @(posedge clk)beginif(reset) f_state <= IDLE;else begincase(f_state)IDLE:if (wr && (wr_addr == f_first_addr) && (enqueue_value == first_data))// Wrote first valuef_state <= FIRST_DATA_IS_WRITTEN;FIRST_DATA_IS_WRITTEN:if (rd && rd_addr == f_first_addr)// Test sprung earlyf_state <= IDLE;else if (wr)f_state <= (enqueue_value == second_data) ? SECOND_DATA_IS_WRITTEN : IDLE;SECOND_DATA_IS_WRITTEN:if (dequeue_en && rd_addr == f_first_addr)f_state <= FIRST_DATA_IS_READ;FIRST_DATA_IS_READ:if (dequeue_en) // second data is read, thus goes back idlingf_state <= IDLE;endcaseendendregf_first_addr_in_fifo, f_second_addr_in_fifo;reg[ADDR_WIDTH :0]f_distance_to_first, f_distance_to_second;always @(*)beginf_distance_to_first <= (f_first_addr - rd_addr);f_first_addr_in_fifo <= 0;if ((count != 0) && (f_distance_to_first < count))f_first_addr_in_fifo <= 1;elsef_first_addr_in_fifo <= 0;endalways @(*)beginf_distance_to_second <= (f_second_addr - rd_addr);if ((count != 0) && (f_distance_to_second < count))f_second_addr_in_fifo <= 1;elsef_second_addr_in_fifo <= 0;endalways @(posedge clk)begincase(f_state)IDLE:beginendFIRST_DATA_IS_WRITTEN:beginassert(f_first_addr_in_fifo);assert(data == first_data);assert(wr_addr == f_second_addr);endSECOND_DATA_IS_WRITTEN:beginassert(f_first_addr_in_fifo);assert(data == first_data);assert(f_second_addr_in_fifo);assert(data == second_data);if (dequeue_en && rd_addr == f_first_addr)assert(dequeue_value == first_data);endFIRST_DATA_IS_READ:beginassert(f_second_addr_in_fifo);assert(data == second_data);assert(dequeue_value == second_data);endendcaseend`endifendmodule

author: BrianHG
author_rank: Super Contributor
timestamp: August 18, 2020, 03:25:45 am
content: NOTE: If you are using a FIFO in an FPGA and going for anything more than 8 or 16 words, just use the FPGA Vendor's built in FIFO as they will use the FPGA's ram blocks automatically with integrated dedicated logic to do the job utilizing the FPGA fabric in a far superior way.

author: promach
author_rank: Frequent Contributor
timestamp: August 18, 2020, 05:54:28 am
content: QuoteLook at my original question again.

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2020, 06:17:33 am
content: You start by defining ADDR_WIDTH explicitly instead of deriving it from SIZE.But FIFO with size < 4 makes very little sense to me. At that size you are probably better off coding something directly at the place of use.You have not really specified which way you want to avoid it. Do you need it bigger than 4 and not power of 2? Or what do you want. You really leave out critical information in your questions.Also, this is a trivial FIFO implementation. You may want to train on simpler examples if you don't yet understand how this works.

author: promach
author_rank: Frequent Contributor
timestamp: August 18, 2020, 06:27:26 am
content: QuoteI want SIZE=1,2,3By the way, Look atparameter ADDR_WIDTH = $clog2(SIZE);and code that usesADDR_WIDTH

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2020, 06:45:59 am
content: I looked at it. What do you want me to see there?FIFO of smaller sizes often easier to just describe directly. And FIFO of size 1 is just a register with a busy flag.

author: promach
author_rank: Frequent Contributor
timestamp: August 18, 2020, 07:02:28 am
content: Do you know how to modify bothreg[ADDR_WIDTH:0] rd_addr;andreg[ADDR_WIDTH:0] wr_addr;such that they do not rely on integer overflow to wrap around ?

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2020, 07:08:13 am
content: Instead of "wr_addr <= wr_addr + 1;" you wrote something like "wr_addr <= (wr_addr == (SIZE-1)) ? 0 : (wr_addr + 1);"But in FPGA this will probably use up more logic than if you were to just round up to the closest power of two and sacrifice some FFs for that.But again, if this is not obvious, you should probably start with simpler projects. You can't survive on copying random code from the internet. It is somewhat possible in programming, not so much in hardware design.

author: promach
author_rank: Frequent Contributor
timestamp: August 18, 2020, 07:48:37 am
content: QuoteThe above coding only uses address maximum limit, but it still does not solve the resource usage issue of non-power-of-two FIFO size

author: ataradov
author_rank: Super Contributor
timestamp: August 18, 2020, 07:50:33 am
content: What do you mean? This will wrap around the address at any SIZE value. SIZE can be non-power of two. That's why you need to replace clog2() with the actual hand calculated value or come up with a better formula.

author: BrianHG
author_rank: Super Contributor
timestamp: August 18, 2020, 05:53:38 pm
content: @promach, you would have gone and done much further here if instead of copying someone else's code you found online and asking us to try to help you modify it instead of asking us to help you design and code simple FIFO from scratch. You would have learned more and exercised your mind in coding something from scratch. A far more valuable and fulfilling experience which would help you in many more future endeavors instead of the art of patching other peoples code.

author: asmi
author_rank: Super Contributor
timestamp: August 18, 2020, 06:51:47 pm
content: Quote from: BrianHG on August 18, 2020, 05:53:38 pmJudging by some questions, apparently some people assume that this forum is just a variant of Stack Overflow, which is full of questions that have answers on the first page of Google search results. I generally avoid such threads, and generally expect the person asking a question to do some basic research first.Also in my experience HDLs are unique such that reading someone else's code is much harder than writing your own, so I often prefer writing my own as opposed to reusing something existing, as this way you will learn much more about whatever the code is doing.

author: hamster_nz
author_rank: Super Contributor
timestamp: August 18, 2020, 09:58:24 pm
content: Quote from: promach on August 18, 2020, 06:27:26 amCheat and tweak this line:assign full = (count == SIZE[ADDR_WIDTH:0]);Assert full when you have 1, 2 or 3 items in the FIFO.As others have pointed out, there really isn't much point is having tiny FIFOs, as the "distributed memory" that is used to implement the storage has an optimal size, usually 16, 32 or 64 entries depending on the FPGA architecture. If you desire a FIFO smaller than that you are best off just capping the number of items that can be stored to a "less optimal" number.

author: promach
author_rank: Frequent Contributor
timestamp: August 19, 2020, 01:56:21 am
content: See mylatest modification to non-power-of-two fifo codingIt is still going through formal verification, so the logic modification might not be entirely correct yet.

author: promach
author_rank: Frequent Contributor
timestamp: August 22, 2020, 08:09:58 am
content: Did anyone see anything wrong with the following simulation waveform ?