A FIFO is a First In First Out memory. You can think of data being shifted in one end and shifted out the other, with the amount of data in the FIFO being allowed to grow up to some maximum limit.
However, actually shifting data around in memory is costly to do in hardware. A better way is to use a memory more normally but make it look like a circular buffer by manipulation of the next address to write to and read from. These addresses live in separate registers, and are often called the read pointer and the write pointer.
Here is a illustration of the state for a simple FIFO using a 8-word memory:
The next incoming word will be written to the empty word at address 1 (the value of the write pointer), then the write pointer incremented to 2. The next read request will fetch the word at 5 (the value of the read pointer), then the read pointer is incremented by 1. In this example, the addresses are automatically circular if the pointers are 3 bits wide. Adding 1 to 7 yields 0 in 3-bit unsigned math.
Complete FIFO systems need ways to indentify the full and empty conditions. There are various schemes for this. A separate register could be used to keep track of how many words are in the FIFO, which is 4 in the snapshot shown above.
A useful scheme for a firmware implementation is to compare the read and write pointers. You could, for example, decide that both pointers equal is FIFO empty, and write one behind read (after wrapping) is FIFO full. Note that such schemes will leave one word of the FIFO unused. You end up spending a piece of state somewhere to allow detection of full and empty, whether that's a separate register or a unusable word in the FIFO. The advantage of this scheme is that reads and writes can happen independently without conflict, so such a FIFO doesn't need a mutex between reading and writing. For example, you don't have to disable interrupts when reading because no harm is done if a interrupt routine pushes a word onto the FIFO while foreground code is trying to read.