Design of a Controller for the Sram Using Verilog

Apology for the lack of updates, but I have been on a rather long vacation to Asia and am slowly getting back into the rhythm of work and blogging. One of my first tasks after returning to work was to check over the RTL of an asynchronous FIFO in Verilog. What better way to relearn a topic than to write about it! Note that this asynchronous FIFO design is based entirely on Cliff Cumming's paper Simulation and Synthesis Techniques for Asynchronous FIFO Design. Please treat this as a concise summary of the design described his paper (specifically, FIFO style #1 with gray code counter style #2). This article assumes knowledge of basic synchronous FIFO concepts.

Metastability and synchronization is an extremely complex topic that has been the subject of over 60 years of research. There are many known design methods to safely pass data asynchronously from one clock domain to another, one of which is using an asynchronous FIFO. An asynchronous FIFO refers to a FIFO where data is written from one clock domain, read from a different clock domain, and the two clocks are asynchronous to each other.

Clock domain crossing logic is inherently difficult to design, and even more difficult to verify. An almost correct design may function 99% of the time, but the 1% failure will cost you countless hours of debugging, or worse, a respin. Therefore, it is imperative to design them correctly from the beginning! This article describes one proven method to design an asynchronous FIFO.

Asynchronous FIFO Pointers

In a synchronous FIFO design, one way to determine whether a FIFO is full or empty is to use separate count register to track the number of entries in the FIFO. This requires the ability to both increment and decrement the counter, potentially on the same clock. The same technique cannot be used in an asynchronous FIFO, however, because two different clocks will be needed to control the counter.

Instead, the asynchronous FIFO design uses a different technique (also derived from synchronous FIFO design) of using an additional bit in the FIFO pointers to detect full and empty. In this scheme, full and empty is determined by comparing the read and write pointers. The write pointer always points to the next location to be written; the read pointer always points to the current FIFO entry to be read. On reset, both pointers are reset to zero. The FIFO is empty when the two pointers (including the extra bit) are equal. It is full when the MSB of the pointers are different, but remaining bits are equal. This FIFO pointer convention has the added benefit of low access latency. As soon as data has been written, the FIFO drives read data to the FIFO data output port, hence the receive side does not need to use two clocks (first set a read enable, then read the data) to read out the data.

Synchronizing Pointers Across Clock Domains

Synchronizing a binary count (pointer) across clock domains is going to pose a difficulty, however. All bits of a binary counter can change simultaneously, for example a 4-bit count changing from 7->8 (4'b0111->4'b1000). To pass this value safely across a clock domain crossing requires careful synchronization and handshaking such that all bits are sampled and synchronized on the same edge (otherwise the value will be incorrect). It can be done with some difficulty, but a simpler method that bypasses this problem altogether is to use Gray code to encode the pointers.

Gray codes are named after Frank Gray, who first patented the codes. The code distance between any two adjacent Gray code words is 1, which means only 1 bit changes from one Gray count to the next. Using Gray code to encode pointers eliminates the problem of synchronizing multiple changing bits on a clock edge. The most common Gray code is a reflected code where the bits in any column except the MSB are symmetrical about the sequence mid-point. An example 4-bit Gray code counter is show below. Notice the MSB differs between the first and 2nd half, but otherwise the remaining bits are mirrored about the mid-point. The Gray code never changes by more than 1 bit in a transition.

Decimal Count Binary Count Gray Code Count
0 4'b0000 4'b0000
1 4'b0001 4'b0001
2 4'b0010 4'b0011
3 4'b0011 4'b0010
4 4'b0100 4'b0110
5 4'b0101 4'b0111
6 4'b0110 4'b0101
7 4'b0111 4'b0100
8 4'b1000 4'b1100
9 4'b1001 4'b1101
10 4'b1010 4'b1111
11 4'b1011 4'b1110
12 4'b1100 4'b1010
13 4'b1101 4'b1011
14 4'b1110 4'b1001
15 4'b1111 4'b1000

Gray Code Counter

The Gray code counter used in this design is "Style #2" as described in Cliff Cumming's paper. The FIFO counter consists of an n-bit binary counter, of which bits [n-2:0] are used to address the FIFO memory, and an n-bit Gray code register for storing the Gray count value to synchronize to the opposite clock domain. One important aspect about a Gray code counter is they generally must have power-of-2 counts, which means a Gray code pointer FIFO will have power-of-2 number of entries. The binary count value can be used to implement FIFO "almost full" or "almost empty" conditions.

Asynchronous FIFO in SystemVerilog binary and gray pointers

Converting Binary to Gray

To convert a binary number to Gray code, notice that the MSB is always the same. All other bits are the XOR of pairs of binary bits:

logic [3:0] gray; logic [3:0] binary;  // Convert gray to binary assign gray[0] = binary[1] ^ binary[0]; assign gray[1] = binary[2] ^ binary[1]; assign gray[2] = binary[3] ^ binary[2]; assign gray[3] = binary[3];  // Convert binary to Gray code the concise way assign gray = (binary >> 1) ^ binary;

Converting Gray to Binary

To convert a Gray code to a binary number, notice again that the MSB is always the same. Each other binary bit is the XOR of all of the more significant Gray code bits:

logic [3:0] gray; logic [3:0] binary;  // Convert Gray code to binary assign binary[0] = gray[3] ^ gray[2] ^ gray[1] ^ gray[0]; assign binary[1] = gray[3] ^ gray[2] ^ gray[1]; assign binary[2] = gray[3] ^ gray[2]; assign binary[3] = gray[3];  // Convert Gray code to binary the concise way for (int i=0; i<4; i++)   binary[i] = ^(gray >> i);

Generating full & empty conditions

The FIFO is empty when the read pointer and the synchronized write pointer, including the extra bit, are equal. In order to efficiently register the rempty output, the synchronized write pointer is actually compared against the rgraynext (the next Gray code to be registered into rptr).

The full flag is trickier to generate. Dissecting the Gray code sequence, you can come up with the following conditions that all need to be true for the FIFO to be full:

  1. MSB of wptr and synchronized rptr are not equal
  2. Second MSB of wptr and synchronized rptr are not equal
  3. Remaining bits of wptr and synchronized rptr are equal

Similarly, in order to efficiently register the wfull output, the synchronized read pointer is compared against the wgnext (the next Gray code that will be registered in the wptr).

Asynchronous FIFO (Style #1) – Putting It Together

Here is the complete asynchronous FIFO put together in a block diagram.

Asynchronous FIFO in SystemVerilog block diagram

The design is partitioned into the following modules.

  • fifo1 – top level wrapper module
  • fifomem – the FIFO memory buffer that is accessed by the write and read clock domains
  • sync_r2w – 2 flip-flop synchronizer to synchronize read pointer to write clock domain
  • sync_w2r – 2 flip-flop synchronizer to synchronize write pointer to read clock domain
  • rptr_empty – synchronous logic in the read clock domain to generate FIFO empty condition
  • wptr_full – synchronous logic in the write clock domain to generate FIFO full condition

Sample source code can be downloaded at the end of this article.

Conclusion

An asynchronous FIFO is a proven design technique to pass multi-bit data across a clock domain crossing. This article describes one known good method to design an asynchronous FIFO by synchronizing Gray code pointers across the clock domain crossing to determine full and empty conditions.

Whew! This has been one of the longer articles. I'm simultaneously surprised that 1) this article took 1300 words, and that 2) it only took 1300 words to explain an asynchronous FIFO. Do you have other asynchronous FIFO design techniques? Please share in the comments below!

References

  • Simulation and Synthesis Techniques for Asynchronous FIFO Design
  • Frank Gray, "Pulse Code Communication." United States Patent Number 2,632,058. March 17, 1953

Sample Source Code

The accompanying source code for this article is the dual-clock asynchronous FIFO design with testbench, which generates the following waveform when run. Download and run the code to see how it works!

Asynchronous FIFO 1 waveform

Download Article Companion Source Code

Get the FREE, EXECUTABLE test bench and source code for this article, notification of new articles, and more!

.

  • Author
  • Recent Posts

Jason Yu

Design of a Controller for the Sram Using Verilog

Source: https://www.verilogpro.com/asynchronous-fifo-design/

0 Response to "Design of a Controller for the Sram Using Verilog"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel