Skip to content

dehre/fpga-spi-fifo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

The FPGA-Based FIFO

All in all, it's a FIFO queue.
But it's implemented on an FPGA, and has a cool display showing its count.

Useful? Not that much...
Interesting? You bet!


Index


Overview

This design implements a Block RAM based FIFO on an FPGA board, capable of holding up to 99 bytes.

At its core, it's a state machine that handles user commands -- FIFO operations -- received via the SPI interface.

To add a visual touch, the item count is displayed in real time on the two onboard 7-segment displays.


SPI Interface

The FPGA module acts as an SPI Slave device in Mode 0 (CPOL=0, CPHA=0).

It recognizes these commands:

  • CMD_COUNT -> 0xF0: Read the item count
  • CMD_WRITE -> 0xF1: Write bytes to the FIFO
  • CMD_READ -> 0xF2: Read bytes from the FIFO

The module replies with:

  • ACK -> 0xFA: Command acknowledged
  • FIFO_EMPTY -> 0xFE: Command acknowledged, FIFO is empty
  • FIFO_FULL -> 0xFF: Command acknowledged, FIFO is full
  • NACK -> 0xFB: Command not acknowledged

Timing diagram:

Note

On CMD_WRITE, the device replies to each DATA byte with:

  • ACK (0xFA): Byte written successfully
  • FIFO_FULL (0xFF): Byte written successfully, but the FIFO became full
  • NACK (0xFB): Write failed (FIFO was already full)

Example SPI Session

Sometimes a picture an example is worth a thousand words.

This logic analyzer session captures the communication between the Arduino Nano and the FPGA board, with the SPI clock slowed to 5 kHz for easier debugging.

To view it:

  1. Install Logic2 from Saleae (it's free!)

  2. Inspect this file: readme-assets/saleae-logic2-session.sal


The following program was running on the Arduino:

ExtSerial.printf("Push 50 items, Count, Pop 10 items...");
uint8_t data_a[50];
for (size_t i = 0; i < sizeof(data_a); ++i)
    data_a[i] = 1 + i;
fpga::write(data_a, sizeof(data_a));
fpga::count();
fpga::read(10);

ExtSerial.printf("Push 60 items, Count, Pop 100 items...");
uint8_t data_b[60];
for (size_t i = 0; i < sizeof(data_b); ++i)
    data_b[i] = 51 + i;
fpga::write(data_b, sizeof(data_b));
fpga::count();
fpga::read(100);

Want to see it in action? Check it out on YouTube!


Hardware and Schematic

Hardware components used:


Schematic:


Breadboarding

The project kicked off with an Arduino Uno and long jumper wires connecting the components -- the quickest way to get started.

And it worked!
Until SPI came into play.

That’s when issues with data integrity started creeping in.

Quoting:

Breadboards are great for low speed analog stuff. However, when you're working on digital circuits, you really need a lot of bandwidth. Realize that a square wave is made up from not only the fundamental frequency, but ALL of the odd harmonics of the fundamental.

In order to get a really decent 10kHz square wave, you really need around 200kHz of bandwidth. Breadboards have quite a bit of parasitic capacitance and inductance; and the interconnecting jumpers add inductance at a rate of 15nH per 10mm, or roughly 0.9uH per foot. It adds up very quickly, and can wreak havoc with your circuit.


Determined to stick with that breadboard setup, I initially patched things up by:

  • Slowing the SPI clock to 5 kHz

  • Adding small resistors (<50Ω) in series with the SPI lines to dampen high-frequency noise

  • Setting unused PMOD pins to output-low to reduce interference

These tweaks kept things running, but as the project progressed, I became more dissatisfied with the limitations.

So I switched to the Arduino Nano, removed the resistors, and reorganized the breadboard into a cleaner, more reliable layout à la Ben Eater.

Result?
I could boost the SPI clock speed to 2 MHz!


Development Environment

To set up the development environment for the Go Board, I recommend going through these two articles:

Both the iCEcube2 IDE and the Diamond Programmer are free at the time of writing.

Tip

Note for Linux users:
Only RHEL is supported, up to version 6 😒.
Just run Windows on a virtual machine and call it a day.


Do you own a Go Board, but don't want to build the project yourself?
Here's the bitstream file for the Diamond Programmer.

Curious about the resource utilization report?
Take a look here.


Coding Style

VHDL is fairly liberal when it comes to style.

To keep things consistent and maintainable, I followed this VHDL code guideline.


Git

The git history for this project is structured into stages, with each commit on the main branch representing a self-contained step in development:

  • stage-1: count rising edges on PMOD pin 1 using onboard LEDs
  • stage-2: count rising edges on PMOD pin 1 using 7-segment displays
  • stage-3: create an spi slave loopback device using PMOD pins
  • stage-4: create fifo queue and design state machine around it
  • stage-5: remove unused entities and show fifo-count on the 7-segment displays
  • stage-6: write additional testbenches

This allowed me to revisit any stage where a feature was complete and perfectly reproduce that setup by checking out the corresponding stage in the related Arduino project.

If you're curious about how the project actually evolved, check the git history in the feature branches (e.g. stage-4--fifo): I simply squash-merged them into main when they were ready.


Credits

Primary inspiration came from Russell Merrick's book.

Simulations have been run on EDA Playground.

KiCad has been used to draw the schematic and WaveDrom to create the timing diagram.

About

The FPGA-Based FIFO

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published