DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Cover image for Making fancy FPGA projects with external I/O using the GPIO
Lena
Lena

Posted on • Updated on

Making fancy FPGA projects with external I/O using the GPIO

Hello everyone!

In the last post, Getting started with FPGA projects on Intelยฎ Quartus Prime, I went through the basics of creating FPGA projects and digital circuits.

However, the lack of inputs and outputs on the FPGA really limits the digital circuit's complexity and creativity.
To overcome this constraint, I decided to use the external GPIO pins on my DE10 Nano FPGA. Thus in this tutorial, I will be going through the steps required to use external inputs and outputs for the FPGA. If at any stage you get lost with the Quartus operations during this tutorial, visit my previous post for details on how to use Quartus.

Table Of Contents

Finite State Machine with external inputs and outputs

I will be using the Intel Laboratory Exercise 7, Finite State Machines. Here, we would like to implement Part I of Intel's Exercise 7, where the FSM looks like the following.

Alt Text

Assuming that you have familiarised yourself with Verilog HDL, the Verilog HDL code I wrote for this exercise is shown below, and can also be found on my Github.

module FSM1(clock, reset, w, z, stateLED);
    input clock;
    input reset;
    input w;
    output reg z;
    output[8:0] stateLED;

    //the following state registers are the circles on state diagram
    reg [8:0] present;
    reg [8:0] next;

    //One hot code encoded
    parameter A = 9'b000000001;
    parameter B = 9'b000000010;
    parameter C = 9'b000000100;
    parameter D = 9'b000001000;
    parameter E = 9'b000010000;
    parameter F = 9'b000100000;
    parameter G = 9'b001000000;
    parameter H = 9'b010000000;
    parameter I = 9'b100000000;

    parameter w0 = 1'b0;
    parameter w1 = 1'b1;

    assign stateLED = present;

    always @(present, w)
    begin

        case(present)

        A:
        begin
            case(w)
            w0: next = B;
            w1: next = F;
            endcase
        end

        B:
        begin
            case(w)
            w0: next = C;
            w1: next = F;
            endcase
        end

        C:
        begin
            case(w)
            w0: next = D;
            w1: next = F;
            endcase
        end

        D:
        begin
            case(w)
            w0: next = E;
            w1: next = F;
            endcase
        end

        E:
        begin
            case(w)
            w0: next = E;
            w1: next = F;
            endcase
        end

        F:
        begin
            case(w)
            w0: next = B;
            w1: next = G;
            endcase
        end

        G:
        begin
            case(w)
            w0: next = B;
            w1: next = H;
            endcase
        end

        H:
        begin
            case(w)
            w0: next = B;
            w1: next = I;
            endcase
        end

        I:
        begin
            case(w)
            w0: next = B;
            w1: next = I;
            endcase
        end

        endcase
    end

    //output signal depending on state
    //signals in states E and I will be 1, otherwise 0

    always @(posedge clock)
    begin
        if(present==E || present==I)
            z = 1'b1;
        else
            z = 1'b0;
    end

    //moves next to present on posedge clock,this is transition in state diagram
    always @(posedge clock)
    begin
        if (reset==1'b0) //active low synchronous reset
            present = A; //if resetted, go to state A
        else
            present = next;
    end

endmodule
Enter fullscreen mode Exit fullscreen mode

Before uploading the digital circuit to the FPGA, we would like to test if the state transitions and signals show up as expected.

We will be using a simulator called ModelSim, and will require the use of a test bench file. This part is optional and can be skipped, however, it is really difficult to pinpoint errors in the logic without ModelSim, so I recommend using it for complex logic circuits.

This test bench part was tricky when I started the FPGA projects, so I highly recommend watching Intel's Tutorial on generating a test bench on ModelSim to become familiar with ModelSim and test benches.

I wrote the test bench file in Verilog HDL, and is shown here in my Github.

`timescale 1ns / 1ns

module FSM_tb();
    reg clock_tb;
    reg reset_tb;
    reg w_tb;
    wire z_tb;
    wire stateLED_tb;

    initial
    begin: CLOCK_GENERATOR
    clock_tb = 0;
        forever
        begin
            #10 clock_tb = ~clock_tb;
        end
    end

    initial
    begin
        reset_tb <= 0; w_tb <= 0;
        #10 reset_tb <= 1; w_tb <= 1;
        #90 w_tb <= 0;
        #40 w_tb <= 1;
        #100 w_tb <= 0;
        #80 w_tb <= 1;
        #100 reset_tb <= 0; w_tb <= 0;
        #80 w_tb <= 1;
    end

    FSM1 DUT(clock_tb, reset_tb, w_tb, z_tb, stateLED_tb);
endmodule
Enter fullscreen mode Exit fullscreen mode

If everything goes as expected, the signals should look something like this.
Alt Text

Now comes the fun part, uploading the digital circuit onto the FPGA. I used the following inputs and outputs for this project,

  1. DE10 Nano User button for the clock
  2. External Slide Switch for reset
  3. External Slide Switch for input w Outputs:
  4. External LED0-LED8 for showing state code
  5. External LED9 for output z

For 2-3, I got a SS12D00G5 slide switch. For 4, I got a 3mm 20 mA White LED. For 5, I got a 3mm 20 mA red LED. You don't have to worry too much about the LED's voltage as long as it is within the 1-5 V range. A higher voltage may result in dimmer light, but should still be visible. I also got myself a breadboard, male-female jumper wires, and a few small resistors (not really necessary, just there to control the brightness of the LED). For the reset, I have decided to use an active low synchronous reset (which means simply sliding the switch does not reset it, but will reset when I press the clock button).

Use the male-female connectors to wire the GPIO pins to the breadboard. The GPIO pin names should be available in your FPGA's manual, and try to keep track of them while you are wiring your breadboard. I am using the DE10 Nano, and my circuit ended up looking like this.
Alt Text
Alt Text
Also, make sure to connect the ground to the GND GPIO. After finishing the circuit construction, double click on Analysis & Synthesis. Upon completion, go to the Pin Planner, and assign the pins accordingly. The only thing that you need to input in the Pin Planner window would be the pin names. Don't worry about the voltages shown there, as we only care about whether the signals fed into the electric components are on or off.

Alt Text

Compile the Design, and once everything succeeds, go to the Programmer and press Start. If successful, the circuit should behave somewhat similar to the demo shown below.

Image description
In the demo, the state transition is A > B > C > D > E > reset > A > F > G > H > I. The red LED z lights up if 0 or 1 is inputted from the slide switch 4 or more times consecutively, thus the red LED lights up on state E and I. For the clock, I am manually pressing the user button on the DE10 nano. Also, ignore the 8 slide switches on the left because they are not used in this project.

Troubleshooting
If you have verified that the digital circuit works as expected on ModelSim but not on the physical circuit, there may be some problems in the circuit and its connections.

If the LED is not lighting up, try the following:

  • Make sure the anode (longer end) and cathode (shorter end) on the LED are correctly wired.
  • Make sure the voltage requirement of the LED is not too high, and try to use a LED within the 1-5 V range. Using a LED with a higher voltage will result in a dim light that may not be visible
  • Make sure the ground is properly connected to the GND GPIO pin
  • Lower the resistance by removing the resistors.
  • Replace wires

If the LED lights up, but the circuit does not behave as expected, try the following:

  • Make sure the pin assignments are correct
  • Make sure the slide switch's three terminals are connected properly, and the middle terminal should be connected to GND

Morse Code Translator with external inputs and outputs

I will go through another FPGA project I worked on a few years ago while ago, which is the Morse Code Translator. I implemented a subset of morse codes as shown below.
Alt Text

And I came up with the following state transition diagram.
Alt Text

For this project, I used a 7 segment display. As I could not find the specifications for this component, I manually tested the 7 segment display to find out which signals give which letters/numbers.
Alt Text

To make the morse code translator as realistic as possible, I decided to distinguish between the dot and dash depending on how long the button is pressed. A button that is pressed and released instantaneously would be registered as a dot, while a button that is pressed and held on for more than a second will be registered as a dash. To implement a time-dependent digital circuit, I made use of a Clock Divider, which divides the FPGA's Clock (50 MHz for a DE10 Nano) to count time in the millisecond range.
A portion of the Morse Verilog Code looks like the following. (You can see the full code on my Github)

module morse(in, clock50, reset, dash, dot, dotdash, seg7, stateLED);
    input in;
    input clock50;
    input reset;

    output dash;
    output dot;
    output [1:0] dotdash;
    output reg[7:0] seg7 = 8'b00000000;
    output [4:0] stateLED; //this shows the statecode on LED

    reg[7:0] present = s0;
    reg[7:0] next;

    reg dash = 0;
    reg dot = 0;
    reg clockout;
    reg [9:0] count = 10'b0;
    reg [22:0] c=23'd0;
    parameter divide = 23'b10111110101111000010000;
    reg[1:0] dotdash =2'b00;

    //statecodes
    parameter A = 5'b00001;
    parameter B = 5'b00010;
    parameter C = 5'b00011;
    parameter D = 5'b00100;
    parameter E = 5'b00101;
    parameter F = 5'b00110;
    parameter G = 5'b00111;
    parameter H = 5'b01000;
    parameter s0= 5'b01001;
    parameter s1= 5'b01010;
    parameter s2= 5'b01011;
    parameter s3= 5'b01100;
    parameter s4= 5'b01101;
    parameter s5= 5'b01110;
    parameter s6= 5'b01111;
    parameter s7= 5'b10000;
    parameter x = 5'b10001;
    ...
    ...
    //signals for the 7 segment display
    always @(present)
    begin
        case(present)
        A: seg7 = 8'b11101110;
        B: seg7 = 8'b00101111;
        C: seg7 = 8'b10000111;
        D: seg7 = 8'b01101011;
        E: seg7 = 8'b10001111;
        F: seg7 = 8'b10001110;
        G: seg7 = 8'b10101111;
        H: seg7 = 8'b01101110;
        s0:seg7 = 8'b11100111;
        s1:seg7 = 8'b01100000;
        s2:seg7 = 8'b11001011;
        s3:seg7 = 8'b11101001;
        s4:seg7 = 8'b01101100;
        s5:seg7 = 8'b10101101;
        s6:seg7 = 8'b00101111;
        s7:seg7 = 8'b11100000;
        x: seg7 = 8'b10001001;

        default: seg7 = 8'b10001001;
        endcase
    end

endmodule
Footer
Enter fullscreen mode Exit fullscreen mode

For the circuit, I used two segment displays, one to show whether the button press registered as a dot or dash, and another to display the translated character or an intermediate state. The circuit diagram I used looks like the following.

Alt Text

And my breadboard circuit looks like this.
Alt Text

In addition to the external inputs and outputs, I used the DE10 Nano's Clock, LEDs and Buttons, so the pin planner should look something like the following.

Alt Text

In this first demo, the state transitions {s0 > E > s5 > s7 > H >(reset) s0} takes place.

Image description

E: .
s5: ..
s7: ...
H: .

In this second demo, the state transitions {s0 > E > s5 > s6 > F} takes place.

Image description

E: .
s5: ..
s6: ..-
F: ..-.

In this third demo, the state transitions {s0 > s1 > s3 > s4 > C} takes place.

Image description
s1: -
s3: -.
s4: -.-
C: -.-.

Troubleshooting

If the 7 segment display does not light up at all, try the following:

  • Check the specification for the 7 segment display, as there are different types such as the common anode and cathode. My wire connections for the 7 segment display is not universal, and differs across manufacturers.
  • Make sure the voltage requirement for the 7 segment display is not too high, and is within the 1-5 V range.

If the 7 segment display does not show the desired characters, try the following:

  • If the 7 segment display's specification does not include details about the input signals and their corresponding output, try experimenting with the signals to see which segment lights up. Write them down and create a truth table like the one shown previously.

What's next?

Being able to use external inputs and outputs allows for fancy FPGA projects, and also helps with debugging. Back in 2020 for my 2nd year undergraduate project, I made a MIPS 5-Stage Pipeline CPU that accepts user inputs in Verilog HDL, and ran a Fibonacci Program. The CPU uses the design from "Digital Design and Computer Architecture" by David Harris & Sarah Harris.

The use of external inputs and outputs made the debugging process much easier, as I was able to see what was going on inside the CPU, as well as its memory and register contents.

I may write a tutorial on creating a CPU in Verilog HDL that accepts user inputs, and in the meanwhile, I highly suggest reading through Digital Design and Computer Architecture by David Harris & Sarah Harris as it explains the construction of a single cycle MIPS CPU in Verilog HDL.

Thank you for reading! Good luck with your FPGA projects, and have fun creating your circuits!

I would love to see your creations on the FPGAs, so please feel free to post them here!

Also, special thanks to my advisor and the lab members for guiding me through the process!

Top comments (1)

๐ŸŒš Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.