09 December 2007

Let us have a small recap of asynchronous FIFO working and then we will go to new asynchronous FIFO design.

The general block diagram of asynchronous FIFO is shown in Figure (1). Functionality wise mainly we can distinguish four blocks in this diagram. They are: dual port RAM, read pointer logic, write pointer logic and synchronizer. Dual port RAM has two ports-one is for reading and the other one is for writing operation. These two accesses of the FIFO are independent of each other and are completely controlled by read pointer logic and write pointer logic. Number of memory locations of the FIFO varies from 8 locations to some kilobytes. The data width of each location is also varying from one to 256 bits depending on the applications and technology. Modern day FIFOs provide options to program both of the above parameters as per requirements.

Data is written sequentially into the FIFO and read sequentially such that the first data written is the first data read out and so on with the remaining sequential data. Thus architecture of FIFO is completely characterized by these two independent operations.Dual port RAM and read-write logic circuits with synchronizers accomplish this task. Read port has its associated memory addressing logic called as ‘read pointer’ logic and write port has ‘write pointer’ logic. When FIFO is reset both read and write pointers point to first memory location of the FIFO. As and when data is written to FIFO write pointer gets incremented and points to next memory location. Similarly when read operation takes place read pointer gets incremented for every read. Both pointer works in circular fashion i.e. after reaching the last position it will jump to first location of the FIFO.

Figure (1) General approach of FIFO design [1]

‘Full flag’ and ‘empty flag’ are used to detect the status of the FIFO. These two flags are generated depending on the comparison result of FIFO pointers. Full flag is asserted when FIFO is completely full. Empty flag is asserted when FIFO is empty. Assertion of full flag indicates that no data can be written further unless at least one data is read out of the FIFO. Assertion of empty flag indicates the condition that no more data can be read from the FIFO unless until at least one data is written to the FIFO.

Even after the assertion of full flag, if data is written to FIFO ‘overflow’ condition occurs. Similarly after the assertion of empty flag if read operation is performed then ‘underflow’ occurs. Either overflow or underflow condition causes the data corruption or data loss. Safe and reliable FIFO designs always avoid both extreme conditions.

New asynchronous FIFO Design

A new asynchronous FIFO design is presented here. The concept of using pointer difference for determining the FIFO status is already used in synchronous FIFO designs. [3]. Here same concept is extended to asynchronous FIFO. The corresponding block diagram is shown in the Figure (2). The block diagram consists of a dual port RAM, two 4 bit binary up-counters, address pointer gap generation logic, full and empty condition generation logic, next read control logic and next write control logic.

Figure (2) new proposed asynchronous FIFO

The naming convention used is explained below:

d_in: input data; 8 bit width is considered

d_out: output data; 8 bit width is considered

w_en: write enable signal

r_en: read enable signal

r_next_en: read next enable

w_next_en: write next enable

w_clk: write clock; 10 MHz for this design

r_clk: read clock; 50 MHz for this design

w_ptr: write address pointer; 4 bit to address depth of 16

r_ptr: read address pointer; 4 bit to address depth of 16

ptr_diff: address pointer difference; 4 bit width

f_full_flag: FIFO full flag; asserted when FIFO is full

f_empty_flag: FIFO empty flag; asserted when FIFO is empty

f_half_full_flag: FIFO half full flag; asserted when FIFO is half full

f_almost_full_flag: FIFO almost full flag; asserted when FIFO is almost full(configurable)

f_almost_empty_flag: FIFO almost empty flag; asserted when FIFO is almost empty (configurable)

Dual port RAM

For this design depth of the RAM is considered to be 16 and the width is 8. . The RTL code synthesizes to a distributed dual port RAM as shown in Figure (3). LUTs are used as dual port RAM.

Data is written to FIFO memory only if FIFO is not full and write enable signal w_en is enabled. Similarly data is read out of memory location only if FIFO is not empty and read enable signal r_en is active

Binary Counters

Four bit binary up counters are used to generate address for read and write port. These address generators have external reset and enable signals called as w_next_en and r_next_en which are generated and controlled by next write control logic and next read control logic. Presently single reset signal resets the both counters. Individual resets also can be easily provided which is elaborated below.

Four bit up counter is designed as a separate module. This is instantiated in main module to realize read binary counter (i.e. read address generator) r_b_counter and write binary counter (i.e. write address generator) w_b_counter. Single reset input is mapped for both counters which resets both read and write pointers. If individual read reset and write reset are required in the design then simply map these separate reset inputs to instantiated counter reset. RTL code for the counter is given below.RTL schematic is showed in the Figure (4).

Binary counter is instantiated in the a_fifo5.v file to obtain read address generator r_b_counter and write address generator w_b_counter. The instantiation code is given below.//-------------------------------------b_counter//instantiation in main code r_b_counter(.c_out(r_ptr),.c_reset(reset),.c_clk(r_clk),.en(r_next_en));

b_counter//instantiation in main code w_b_counter(.c_out(w_ptr),.c_reset(reset),.c_clk(w_clk),.en(w_next_en));

//-------------------------------------

Address pointer gap generation logic

The blocks compare the read and write addresses and gives out the difference of two address pointers. If w_ptr>r_ptr, it finds the gap using relation, ptr_diff=w_ptr-r_ptr. If w_ptr Then ptr_diff=10-5=5. If w_ptr=5 and r_ptr=10, then ptr_diff = (16-10) + 5=11. From the RTL schematic we can find that this block consists of comparators and adder-subtractor. These modules always calculate the difference between r_ptr and w_ptr whenever there is a change in r_ptr and w_ptr. Thus ptr_diff dynamically reflects the status of the FIFO. This flexibility of the design allows us to use any read and write clock frequencies, within the maximum operating frequency.

//-------------------------------------

always @(*)//pointer difference is evaluated for both clock edges

begin

if(w_ptr>r_ptr)

ptr_diff<=w_ptr-r_ptr;

else if(w_ptr)

ptr_diff<=((f_depth-r_ptr)+w_ptr);

else ptr_diff<=0; end

//-------------------------------------

Figure (5) RTL schematic of adder-subtracter and comparators

The above given RTL code synthesizes into adder-subtracter and two comparators. Maximum operating frequency of the design is limited by the carry propagation delay of the adder-sub tractor. Keeping ‘speed’ as the optimization goal, maximum achievable operating frequency is limited to 112.3MHz. This is one of the main drawbacks of this design compared to the Figure (1) implementation of the FIFO.

Full and Empty Generation logic

This logic takes ptr_diff as input and generates the required condition. If ptr_diff=0, empty condition is generated and if ptr_diff =15 full condition is generated. Similarly almost full, almost empty, half full conditions are generated for required values. Since pointer differences is calculated with respect to both read and write clocks, FIFO status assertion is immediate with zero clock delay.

Figure (6) to Figure (9) shows the FIFO status flag generation logic. Since the ptr_diff is calculated, generation of FIFO status condition becomes very easy with AND and NOT gates.

Next read and write control logic

These control blocks decide the enabling of read and write once the empty and full conditions are asserted. After the assertion of f_empty_flag r_ptr should not increment unless and until there is at least one data is written. Thus once empty flag is asserted next read control logic looks into the write domain for any write activity. It disables r_next_en signal till it finds that data has been written to FIFO. Immediately after the write operation (i.e. posedge of w_clk) control logic enables r_next_en.

Similarly after the assertion of f_full_flag, w_ptr should not increment unless and until there is at least one data has been read. Once f_full_flag is asserted write next control logic looks into the read domain for any read activity. It disables w_next_en signal till it finds that data has been read out from FIFO. Immediately after the read operation (i.e. posedge of r_clk) control logic enables w_next_en.

Next article will discuss about simulation and synthesis of asynchronous FIFO.

8 comments:

The reason the 'normal' async fifo is done with all the registers at the bottom of the diagram, is this: you can't compare two counters with gates, when the two counters are running on different clocks. The results from the comparison will not always be stable on any given clock edge, due to possible recent count on the edge of the other clock.The standard async design has a 'full' decision which compares the current write counter to a synchronized and possibly stale read counter; and the 'empty' decision compares the current read counter to a synchronized and possibly stale version of the write counter. So there are 'fall through' delays: 'full' really means 'possibly full' and 'empty' means 'possibly empty'; but, on the other hand it actually works. Your design will work in a functional simulation but will not be reliable in an actual circuit with independent clocks.

Using gray counters and dual flop synchronizers to avoid metastability, and then comparing the pointer differences for full and empty flags, will cost us an extra overhead! Better to go with an extra bit for pointer address.