FPGA (mis)adventures: Part 1

Yahya GedikYahya Gedik
4 min read

I had a basic understanding of FPGAs, but I never found a compelling reason to purchase an FPGA development board. However, after watching this intriguing FPGA project video, I still didn't have a concrete reason, yet I decided to buy one anyway, and here we are.

I specifically bought a Tang Nano 20K, mainly because no local sellers in my country offered a larger model, and it seemed like a great starting point.

I should explain what an FPGA is probably, at least briefly. An FPGA is like a “programmable circuit”, instead of writing code to run on a CPU (or microcontroller), we “program” a “board” into doing what we want it to do. You have a module (or bunch of modules in most cases, a module is a single piece of circuitry in a sense, sort of like a class) and constraint files. A constraint file is a file that provides the modules with pin information, such as where the pin physically is on the board, what type it is etc.

Think of it like this, when writing a code for a microcontroller we could just do (simplified):

pinMode(10, OUTPUT);

// in main loop
digitalWrite(10, HIGH);
delay(1000);
digitalWrite(10, LOW);

For a basic, 1sec blinking LED.

When working with an FPGA you typically have a so called “top” module (Main.v in this context):

module Main (input Clk, output reg LED0);

reg [32:0] ClockCounter = 0; // way too big for our case, but anyway

always @(posedge Clk) begin
    ClockCounter <= ClockCounter + 1;
    if (ClockCounter == 27_000_000) begin
        ClockCounter <= 0;
        LED0 <= ~LED0;
    end
end

endmodule

And a constraints file (Constraints.cst file):

IO_LOC "Clk" 4;
IO_PORT "Clk" PULL_MODE=UP IO_TYPE=LVCMOS33;

IO_LOC "LED0" 15;
IO_PORT "LED0" IO_TYPE=LVCMOS33;

This might seem daunting at first, it was for me. But after looking at it, it makes sense.

For our Main module, we have input Clk and output reg LED0, simple enough. We take a clock signal, and we want to set the state of a pin called LED0.

Line below that we define a reg, 32 bits in size called ClockCounter.

And the main loop of our program: the always block. Basically we say when Clk is rising (posedge means Positive Edge of the signal, in this case Clk) we want to do the following:

  • Add 1 to the ClockCounter

  • Check if ClockCounter is 27 million (The clock we selected is 27Mhz), effectively every second

    • Reset the ClockCounter back to zero

    • Whatever was inside the LED0, invert it.

Effectively we have a 1 second blinking LED.

In the constraints file:

  • We define Clk as pin 4, 3.3V

  • We define LED0 as pin 15, 3.3V

Thats it.

Synthesize, Place & Route and voila, we have a 1 second blinking LED.

When I did this I had a question to myself, why are we counting to 27 million, is there no delay? This seems so ineffective.

Answer is: literally nothing exists on the FPGA until you make it, well, thats a lie, there actually is something flashed onto the integrated flash chip for tutorial purposes, but you get the idea. An FPGA is not a CPU, is a cleverly laid out blocks of lookup tables and complex circuitry, that normally does absolutely nothing, dead board.

In the Verilog example I provided, we have a always block, you could have multiple of these blocks, running at same speeds in parallel to each other. We pretty much didn’t lose any performance doing this. Instead we programmed a “circuit”, flashed that onto the board and the configurator takes that and does some ✨circuit magic✨ to make our program into a real circuit, via cleverly engineered flip flops, gates and whatnot.

This was magical for me, I always worked with OS development, compiler design and other low level concepts and this hit a spot in my brain. Now I could make my own CPU and run my own OS on it! Hell, it might even run DOOM (Did I mention that the board has a HDMI output?).

So the next logical step was: programming an UART module so I can read and write over serial and make it easier to debug.

As I continue my FPGA journey, I've already ventured into developing a UART module, which has been an intriguing challenge. In the upcoming entries, I'll be sharing my experiences, insights, and the various gotchas I encountered during the development process. This exploration has deepened my understanding of FPGA capabilities, and I'm eager to discuss the solutions and strategies that helped me navigate the complexities of UART implementation. Stay tuned for a detailed look into this aspect of my FPGA (mis)adventures.

0
Subscribe to my newsletter

Read articles from Yahya Gedik directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yahya Gedik
Yahya Gedik