FIFO
FIFO
1. If else logic we may get setup and hold. In case statements we may not get setup and hold. If
else is a priority logic with multiple muxes. Case is a single mux of a larger size.
2. Hash, heap and tree are there in system Verilog and not in Verilog for verification purposes.
Verilog is pure HDL.
3. Stack and queue are the basic data structures for LIFO and FIFO. They use insert and remove.
4. We used circular queues for FIFO, where we can keep writing and reading around the queue.
LIFO are non circular. The size of stack reduces as we read.
5. FIFO Basics: put write get read, full_bar when fifo full empty_bar when empty
6. We use a write pointer and read pointer to know which point we are at. Write pointer points at
next empty location. Read pointer points at last current row to read. It is circular so after read
finishes reading, write can occur at location 4 rather than location 0. Queue pointers always
starts at the bottom of the FIFO.
7. If reader is ahead of the writer, it means the writer wrapped around the memoery.
8. FIFO ports: we add data_in, data_out, wen , ren, F and E. Also we keep wptr and rptr internal
which is a better option. Also it is sequential so we need a clock and reset. Combinational is
difficult for a fifo. It is not easy to control.
9. Multiple read ports: It is a little confusing. One write pointer and many read pointers.
10. When rdptr reaches wtptr then it is empty.
11. When wtptr reaches rdptr it is full.
12. To understand full and empty we have various solutions:
1) Unallocated slots: We leave one slot empty. Wtptr = rdptr -1 buffer is full
Wptr = rdptr then buffer is empty.
2) Fillcount: If 8 depth FIFO, if fillcount = 8 full = 1 fillcount = 0 empty = 1 Here we increment
fillcount for write and decrement for read. It is the best solution.
3) Write and read counts: we have separate counts and wt count rd count if equal to 0 then it
is empty. If 8 for a 8 depth FIFO then full. It is not better than fillcount as we need two
counters.
4) Absolute indices: indices are used instead of pointers. It is confusing.
5) We used use AE and AF. Almost empty and almost full. When 6 or 7 is fillcount then AF = 1.
When 1 or 2 is fillcount then AE = 1. Here we dont need fillcount it will be just wtptr rd ptr
mod 8.
6) Mirroring, we add extra bit to wtptr and rdptr. If the extra bit matches means the FIFO is
empty. If it is different then FIFO is full. It is also called wrap bit.
13. Synchronous same clock. Asynchronous we find it more challenging. We coulf do CDC(clock
domain processing), DVFS(Dynamic voltage frequency scaling) and DPM.
14. FIFO with 2 different clocks will be communicating asynchronously. Any data coming into write
clk from reader side has to be sample and vice versa. The more the sampling less the chance of
metastability(when setup and hold may occur). Using two flip flops is the best option. When one
of the clock domain has high frequency chances of error increase. Then we sample it thrice.
15. Double sampling works as getting two consequent setup violations usually doesnt happen.
16. On reader side we check write ptr sampled. On writer side we check read ptr samples. (wtptrss
and rdptrss).
17. Gray coding is the safest way to ensure less errors as less transitions.
18. To convert binary to gray say 1011: 1 1^ 0 0^1 1^1 which make gray as 1110. So we xor all the
bits except MSB to get gray. For gray: 1110: 1 1^1 1^1^1 1^1^1^0 which makes binary 1011.
So for gray to binary we keep xoring the bit with all the previous bits. Bin[0] =
gray[3]^gray[2]^gray[1]^gray[0] and Bin[1] = gray[3]^gray[2]^gray[1] Only MSB is as it is.
19. FIFO width expansion: here full = F1 or F2. Implementation is about data arrangement.
20. FIFO depth expansion: We could do token passing where we finish one FIFO and then go to next.
Full = Full 1 and Full 2. Ping pong is where we keep going back and forth both the FIFOs.
21. We could have globally asynchronous and locally synchronous.
FIFO questions:
FIFO 1:
1. First mistake which you do is using blocking statements in a sequential logic. Always, always use
non-blocking statements in sequential logic.
2. The Verilog file is not parameter based. To make it parameter based,
Module FIFO1 #(parameter FIFO_depth = 4, POINTER_depth = 2,FIFO_width = 4) (clk,reset, ..);
FIFO1 testbench also has to use parameter so it is
FIFO1 #(.FIFO_depth(4),.POINTER_depth(2),.FIFO_width(4)) FF1(clk,reset..);
3. Here we use FIFO_width*2 because our data is 4 valued logic. {0,1,D,Dbar}. POINTER_depth is
log2 (FIFO_depth). FIFO1 uses fillcount.
4. Here we declare input [FIFO_width*2-1:0] data_in; And same value register size for output
data_out. We also output empty_bar, full_bar and [POINTER_depth:0] fillcount. Fillcount is
always one bit extra. The other varianles are [POINTER_depth-1:0] wptr,rptr. And a
[FIFO_width*2-1:0] FIFO [0:FIFO_depth-1];
5. Then we start always @ (posedge clk)
If reset then we set wptr and rptr as 0 and fillcount is 0. Data out is x and emptybar and fullbar is
1. Else if reset = 0 then we do four if begin end statements. First we check if put = 1 and full_bar
= 1, if so then FIFO[wptr] <= data_in. wptr and fillcount is incremented by 1.
If fillcount is equal to FIFO_depth fullbar is 0 else 1.
Then we check if get =1 and empty_bar = 1, if so then data_out = FIFO[rptr]. rptr is incremented
by 1 and fillcount is decremented by 1.
If fillcount is 0, emptybar is 0 else 1.
6. So the basic idea here is fillcount is incremented when write occurs and decremented when read
occurs. Fillcount = fifo depth then full is 1. Fillcount = 0 then empty is 1. Write pointer
incremented when we write. Read pointer incremented when we read.
7. In testbench we check read, write. In synthesis we get one module as there only one always
block. It completely contains gates and wires. In the same cycle we can have put = 1 and get = 1
sp read and write can occur in the same clock.
FIFO2:
1. We have the same parameters, inputs and outputs here as FIFO1 except we dont have fillcount.
We have AE and AF which are almost empty and almost full.
2. At reset = 1, we set AE and AF as 0. If reset is 0,
if put = 1 and full_bar = 1, if so then FIFO[wptr] <= data_in. wptr is incremented by 1.
If wptr-rptr == 0 and AF = 1, then full_bar 0 else 1.
If (wptr rptr == FIFOdepth-1 or FIFOdepth-2) then AF is 1 else 0. (here we use FIFOdepth
parameter as the module has varying parameters)
if get =1 and empty_bar = 1, if so then data_out = FIFO[rptr]. rptr is incremented by 1.
If wptr-rptr == 0 and AE = 1, then empty_bar 0 else 1.
If (wptr rptr == 1 or 2) then AE is 1 else 0.
3. The main concept here is that AE and AF indicate we have reached one end. So if wptr and rptr
reach each other either AE or AF will be 1 and that will indicate that fifo is empty or full.
4. This is a good method as we need just one bit regs for AE and AF. But we compare here so
comparison units may add to area. But we also compare fillcount so area difference may not be
a lot. We need a subtractor for wptr and rdptr.
FIFO3:
1. We have the same parameters, inputs and outputs here as FIFO1 except we use mirroring
technique here. Here [POINTER_depth:0] wptr,rptr instead of [Pointer_depth-1:0]. No additional
fillcount or AE AF.
2. At reset = 1, we set the usual. If reset is 0,
if put = 1 and full_bar = 1, if so then FIFO[wptr[POINTERdepth-1:0]]<= data_in. wptr is
incremented by 1.
If (wptr[pointerdepth] != rptr[pointerdepth] and wptr[pointerdepth-1:0] == rptr[pointerdepth-
1:0] then fullbar is 0 else 1
if get =1 and empty_bar = 1, if so then data_out = FIFO[rptr[POINTERdepth-1:0]]. rptr is
incremented by 1.
If wptr == rptr then emptybar is 0 else 1.
3. So here the method is to check the extra bit to know if it is full or empty. Extra bit if it doesnt
match for wptr and rptr then full else if the extra bit matches then empty.
FIFO 2 clocks:
1. First lets understand the concept. We want to double sample the pointers because we have two
different clocks. Before sampling a pointer we first convert it to gray. After sampling, we need to
check for empty and full flags. To check those we need to convert the sampled pointer from gray
back to binary. In read clock we sample read pointer. In write clock we sample write pointer.
2. Lets call FIFO depth: FD Pointer depth: PD FIFO width: FW
3. Here we assume a 2 value system.
4. We have all [PD:0] wptr,wptr1,wptrs.wptrss1,wptrss2,
[PD:0] rptr,rptr1,rptrs,rptrss1,rptrss2
We are using the n+1 method. This is the mirroring method.
5. We have two clks, rclk and wclk. We sample rptr in wclk and wptr in rclk.
In posedge wclk,
If (reset == 1) wptr =0 rptrs = 0 rptrss = 0
Else
Rptrss1 <= rptrs
Rptrs <= rptr1
if put = 1 and full_bar = 1, if so then FIFO[wptr[POINTERdepth-1:0]]<= data_in. wptr is
incremented by 1.
In posedge rclk,
If (reset == 1) rptr =0 wptrs = 0 wptrss = 0 data_out = 0
Else
wptrss1 <= wptrs
wptrs <= wptr1
if get= 1 and empty_bar = 1, if so then data_out <= FIFO[rptr[POINTERdepth-1:0]]. rptr is
incremented by 1.
6. Empty_bar, full_bar are combinational and dont depend on clk. Empty_bar depends on rclk so
we check with rptr and wptrss2. Rptr is ptr before converting to gray. Wptrss2 is ptr after
sampled is converted to binary.
Empty_bar = (wptrss2 == rptr) ? 0:1;
Full_bar depends on wptr and rptrss2.
Full_bar = ((wptr[3]!= rptrss2[3]) && (wptr[2:0] == rptr[2:0])) ? 0:1;
7. Then we assign
Wptr1 = btog(wptr)
Rptr1 = btog(rptr)
Wptrss2 = gtob(wptrss1)
Rptrss2 = gtob(rptrss1)
8. We then write the functions to be used binary to gray and vice versa
Function [2:0] btog;
Input [2:0] ptr;
Begin
Btog[0] = ptr[0] ^ ptr[1];
Btog[1] = ptr[1] ^ ptr[2];
Btog[2] = ptr[2];
End
endfunction
LIFO:
1. LIFO basic parameters are like FIFO. Here we use pushptr and popptr. reg [LIFO_WIDTH - 1:0]
LIFO [0:LIFO_DEPTH - 1];
2. If reset = 1, full = 0 and empty = 1. Pushptr and popptr are 0.
If reset = 0,
If push is 1 and pushptr<LIFOWIDTH-1 and full == 0
Then full = 0 empty = 0, LIFO[pushptr] = din. Pushptr is incremented by 1. And popptr = pushptr
Else if push is 1 pushptr is LIFOWIDTH -1 and full is 0,
Then full = 1 empty = 0, LIFO[pushptr] = din. And popptr = pushptr. We cant further increase
pushptr.
Else we do nothing
If pop is 1 and popptr>0 and empty == 0
Then full = 0 empty = 0, dout = LIFO[popptr] . Popptr is decremented by 1. Pushptr = popptr + 1
Else If pop is 1 and popptr==0 and empty == 0,
Then full = 0 empty = 1, dout = LIFO[popptr] . Popptr is decremented by 1. Pushptr = popptr
Else we do nothing
3. popptr = popptr - 1; pushptr = popptr + 1; uses blocking statements but if we want non blocking
statements we can make it as popptr <= popptr - 1; pushptr <= popptr; Then we can make all
non blocking statements in the sequential logic block.
4. LIFO is the simplest. Pushptr is always ahead of popptr unless at the beginning or end. Always
use non blocking for sequential logic.
1. $datawidth = $ARGV[0];
Outfile = fifowidth.v;
$datawidth = 2 * $datawidth;
It is a 4 valued logic.
$n = $datawidth;
2. We make an array. Here we use dynamic programming. We have the widths 5,12 and 16. So
array contains no of redundant or extra bits we have if we use these FIFO widths.
Array[0] = 0 (as we are using none) also for calculations it helps to have array[0] as 0.
Array[1] = 4 = 5-1 (we need 1 FIFO5)
Array[2] = 3 = 5-2 (we need 1 FIFO5)
Array[3] = 2 = 5-3 (we need 1 FIFO5)
Array[4] = 1 = 5-4 (we need 1 FIFO5)
Array[5] = 0 = 5-5 (we need 1 FIFO5)
Array[6] = 4 = 10 6 (we need 2 FIFO5)
Array[7] = 3 = 10 7 (we need 2 FIFO5)
Array[8] = 2 = 10 8 (we need 2 FIFO5)
Array[9] = 1 = 10 9 (we need 2 FIFO5)
Array[10] = 0 = 10 10 (we need 2 FIFO5)
Array[11] = 1 = 12-1(we use FIFO 12)
Array[12] = 0 (we use FIFO12)
Array[13] = 2 = 15 -13 (we use 3 FIFO5)
Array[14] = 1 = 15-14 (we use 3 FIFO5)
Array[15] = 0 = 15-15 (we use 3 FIFO5)
Array[16] = 0 (we use 1 FIFO16)
After the array is made we can use it to do more.
3. If data width < 1 print INVALID else
4. Print module input,output.
In this instead of wire data_in is input and data_out is output.
5. We start the code, we first need to get all the array values for 17..$n-1. Which is if datawidth is
say 19, we run a for loop for 17..18
$k = $m. We run a while loop ($k > 0)
We see three ways of moving:
If (k-15) > 0 (We consider one value lesser because if k = 16 we want it to go into k-15 > 0)
Else (k-11) > 0
Else (k-4) > 0
Inside them we have 3 paths. We want to go to the path which gives us the least redundant bits.
Initially when we get $k we want to check if it is >=16, >=12, >=5 or <5.
Inside each of them we need to again check.
If (k>=16) => (k-15) >0
Initially flag 5 = 1
If array(k-12) < = array(k-5) flag12 =1
Because that means 12 offers a better path. If array(k-16) <= array(k-12) and <= array(k-5) then
flag16=1. Means 16 offers a better path.
Now we check if flag5 = 1 else flag12 = 1 else flag16 = 1
Whichever flag is set k=k-5,k-12,k-16 respectively.
If k=0, array[$m] = 0. We write this in each k-15 k-11 etc. as when k=0 we quit the loop and we
wouldnt get array[$m] value.
That was for k-15 >0
For k-11>0
We test flag 12 conditon
If (k=0) condition
For k-5>0
No test flag5=1
K=k-5
If (k=0) condition
Else
Flag5=1
And if (k=5) array[$m] = 0
Else array[$m] = 5-$k;
6. After everything we repeat same method for $n. $k =$n. Except for one difference: based on flag
amd (k-11),(k-15), (k-4) and else etc. We make another array arraynos with $l = 0 initially.
If flag16 =1 arraynos[$l] = 16
$l = $l + 1
Else if flag 12 = 1 arraynos[$l] = 12
$l=$l + 1
7. Now that we have figured out the redundant bits and what FIFOs to use, we do datawidthminus
which datawidth + array[n] -1 (array[n] gives us the redundant bits) Here we print input and
output with datawidthminus:0 size.
8. $l gives us the no of FIFOs we have.
print $of "wire ";
foreach my $i (0..$l-1){
if ($i < ($l-1)){
print $of "full_bar",$i,",";
}
else {
print $of "full_bar",$i,";\n";
}
} // For printing wire full_bar1,full_bar0 etc. Same method for empty bar.