Managed Hardware FIFO

Typical FIFO circuit functions


A FIFO is a First-In, First-Out memory structure that is commonly used to decouple data producers and data consumers, so that data producers and consumers can use dissimilar time bases. For example, a data producer might quickly produce a collection of data points all at the same time, and deposit them in a FIFO where a consumer can access them one at a time, at a slower rate. As the FIFO nears empty, the producer can quickly load the FIFO with another batch of data, and again wait until the consumer finishes processing that batch. Likewise, a data producer might produce data points at a slower rate, and store them in a FIFO. Then when the FIFO is full (or nearly full), the consumer can retrieve the points all at once, and then wait again for the producer to refill the FIFO.

A FIFO is typically defined as a contiguous range of memory locations, from perhaps 16 bytes to 4Kbytes+, with memory address that seamlessly “roll over” so that the smallest memory address follows the largest memory address. Using a “circular” memory structure like this means there is no end to the FIFO, and it can be read and written endlessly. New data written into the FIFO will overwrite old data every N write cycles (where N is the number of FIFO locations), and data read from the FIFO will repeat every N cycles (unless it has been overwritten with new data).

A FIFO offers a single write port to data produces, and a single read port to data consumers. Producers can put data into the FIFO at whatever rate new data is available, and consumers can access the data at an unrelated rate. A FIFO management process can check fill levels, and alert producers and consumers when the FIFO is full, empty, or nearly full or empty. Using a FIFO results in a more modular and simpler programming model, since data producer and consumer processes aren’t burdened with managing addresses and status information.

In a hardware FIFO system (like those used with the UART, SPI and I2C controllers on ZYNQ), the FIFO is managed by a state machine that takes care of all overhead tasks, like storing and update addresses, performing compares to see if a FIFO is full or empty (or nearly so), and/or generating interrupts. Since no CPU time is needed for management tasks, a much more time-efficient solution, with a simpler programming model, results.

Some hardware FIFOs offer a single data port for writing data into the FIFO and reading data from the FIFO., and some use separate read and write ports (there is no real difference from a programmers perspective). The FIFO management state machine automatically maintains the read and write pointers, with the write pointer holding the address of the next FIFO location to be written, and the read pointer holding the address of the next location to be read. The pointers are incremented after each write or read, and they both seamlessly roll over to “0” immediately after the largest address is accessed. The state machine ensures that each newly written data point is placed in memory immediately after the previous point, and that each newly read point is sourced from the address immediately following the previously read point.

Because hardware FIFOs can be written and/or read endlessly, FIFO management state machine constantly compares the read and write pointers, and drives several status bits based on the results of the comparison. For example, one comparator sets a status bit if the read and write pointers match immediately after a write to indicate the FIFO is full, and another sets a different status bit if the pointers match directly after a read to indicate the FIFO is empty. Likewise, a programmable comparator can check to see when there are some number (user-defined) of write or read locations left in the FIFO, and drive FIFO “almost full” or “almost empty” status bits. Software can access these status bits to prevent overwriting data that hasn’t been consumed, and to prevent re-reading old data multiple times. In a typical FIFO system, these same status bits can also generate interrupt signals – since UART traffic is usually slow and intermittent, interrupts are often used.