Pages

Sunday, February 23, 2025

Designing Binary to Seven Segment for FPGA

In this post, I will be teaching you how to build a binary to seven segment Verilog module and download it into the Tangnano 9k FPGA board. If you don't have much background on Verilog or FPGA, I suggest reading my post A Beginner's Guide to FPGA Programming using Tang Nano 9k Board and OSS CAD Suite. That will give you enough background so you can easily grasp the concept and follow the steps I will discuss in this post.



Specification


The Verilog module that I'm going to design accepts 4-bit binary input and drives the seven segment LED with the corresponding hex value. The input to output mapping should be as follows:

  • Input: 0000 Output: 0
  • Input: 0001 Output: 1
  • Input: 0010 Output: 2
  • Input: 0011 Output: 3
  • Input: 0100 Output: 4
  • Input: 0101 Output: 5
  • Input: 0110 Output: 6
  • Input: 0111 Output: 7
  • Input: 1000 Output: 8
  • Input: 1001 Output: 9
  • Input: 1010 Output: A
  • Input: 1011 Output: b
  • Input: 1100 Output: C
  • Input: 1101 Output: d
  • Input: 1110 Output: E
  • Input: 1111 Output: F

I will also add another 4-bit output to serve as an indicator to reflect the value of the input. The Verilog module should look like the diagram below.




In terms of the decoding logic, the seven segment LED that I have is a common cathode, which means I need to drive logic 1 to the segment I want to turn on. I will discuss the implementation of the decoding logic in the coding portion of this post.


Tang nano 9k has only 2 on-board pushbuttons, therefore it is not enough to be used as our input mechanism. Because of that I will be using the IO pins and connect pushbuttons via breadboard. For the output, I will be using the on-board LEDs for led_ind and IO pins for driving the seven segment LED. The schematic diagram is shown below:




Designing the Verilog Module


Let us first create the module name bin_to_seven_segment and declare the input and output ports. The code should be like this:




Then let us connect the led_ind to bin. By doing this, we will be able to see, the value of bin being displayed using the on-board LEDs. Take note that the on-board LEDs are reversed bias. This means that when the bin is at logic low (pushbutton is not pressed) , the LED will turn on. Subsequently, when we press the pushbutton, the LED on the board will turn off. With this we can easily see the value of bin in the physical board without the need for multimeter  or logic analyzer. The code should look like this:




Now, let us code the decoding logic. Let us first create an always block. In Verilog, always block executes the code inside it whenever the trigger is activated. The syntax for the always block is shown below:



In our case, we will make our input port named bin to act as the trigger in the always block. By doing this, every time the value of bin changes the always block will get executed. The code should be like this:




After creating the always block, we will now use the Verilog case statement to implement the decoding logic. The syntax for case statement is as follows:




We use bin as the input to our case statement, this means that we are checking the value of bin and will execute the corresponding action for that specific value. Let us say for example, the value of bin is 4'b0000, we will set seg to be 7'b0111111. If the value of bin is not in the choices, it will go to default. The complete code can be found here.


Running Simulations


I created a simple Verilog testbench to check whether the bin_to_seven_segment module is working. The testbench code is shown below.





Looking into the test bench code, you can see line at line 15, there is a for loop that assigns the value of i to bin. That will enumerate all the possible values of bin starting from 4'b0000 to 4'b1111.  Line 18 just prints the value of the input and outputs of the module. If you ran the test bench you can see the following output in your terminal. If you do not know how to run simulations, I suggest reading this first: A Beginner's Guide to FPGA Programming using Tang Nano 9k Board and OSS CAD Suite.






You can use the GTKWave to open the VCD file. In my case, I downloaded a VSCode plugin called surfer to open vcd files directly on the VSCode so it will be seamless. The VCD waveform should look like this:




The value shown in the waveform should be the same of what is on the terminal. The only difference is that the waveform. shows the hex value, you can easily change that in the waveform GUI. Right click on the signal name then change format to binary. If you do that, your waveform will be like this:




Now that we can see that the module is working properly in simulations, we can now define the IO pins we will be going to use and create a CST file. For me, I wired everything up on the breadboard, then identify the FPGA pins I want to use based on the connections I made. You can follow the connections I made or you can choose your own IO pins. The resulting CST file that I have is shown here:





Implementing the Design to FPGA


To implement the design and download the bit file to the tang nano 9k board, you need to run the following commands:

yosys -p "read_verilog bin_to_seven_segment.v; synth_gowin -json bin_to_seven_segment.json"

nextpnr-himbaechel --json bin_to_seven_segment.json --write pnr.json\
--device GW1NR-LV9QN88PC6/I5\
--vopt family=GW1N-9C --vopt\
cst=bin_to_seven_segment.cst

gowin_pack -d GW1N-9C -o pack.fs pnr.json
openFPGALoader -b tangnano9k pack.fs


Validating the Design


Once you downloaded the design, you can check if it is working as expected by pressing the pushbuttons and observing both the seven segment LED and the on-board LED. You can take a look on the video below to see how it would look like:








Saturday, February 15, 2025

A Beginner's Guide to FPGA Programming using Tang Nano 9k Board and OSS CAD Suite

Introduction



This tutorial will teach you how to create a simple Verilog module and implement it into Sipeed Tang Nano 9k, a low cost FPGA board. This tutorial is ideal for students or someone who have zero or very little knowledge on Verilog or FPGA but has some knowledge on digital design (logic gates, etc.). We will use the OSS CAD Suite, a collection of open source software to run simulation, synthesis, pnr, generate and download bitstream into our target fpga board. Don't worry about the terms I just mentioned, we will go through them one by one throughout this tutorial.




What is Verilog?


Verilog is a Hardware Description Language (HDL). From the name itself, it is used to design a hardware specifically digital hardware (those that processes binary data such as processors). I know it is hard to understand what designing a hardware means if you just started learning these things, I have been there myself so I will try my best to explain it to you.


I am assuming you have some background with digital design, if not it is good to have some reading on that topic first before jumping into Verilog. To understand better the use case of Verilog let us have the following example: Imagine your professor ask you to create a CMOS inverter using transistors. With the knowledge you gained from his/her lectures, you will be able to design the inverter by connecting an NMOS and a PMOS transistors together. Easy right? How about he asked you to create a processor? Although it is possible to do that, it will be difficult to design the processor (the hardware) by just using individual transistors and stitching them together to produce your desired functionality. This problem is being resolved by using HDLs like Verilog. Instead of doing transistor level design for digital circuits, you can describe the behavior of the circuit at a higher level of abstraction just like coding software (programming in C but a little different). 



What is FPGA?


FPGA stands for Field Programmable Gate Arrays. Let us focus first on the "field programmable" portion and understand what it means. Field programmable means that it is programmable even it is already deployed in the field (customers). To understand this better let us have the following example. A company designs a TV (with all the functionalities of a typical TV) then ships it to stores, then ends up to the customers' home (field). The functionalities of the TV is fixed, you cannot reprogram it to be a video game console, you can of course use that to display the output of the console or install apps but it this functionality will still be fixed from what the manufacturer set. Now, try to imagine you can reconfigure the TV to become a PC, a gaming console, or a logic gate 😅. That is what field programmable means. 


The FPGA is field programmable because it has "the gate arrays. You can think of it as collection of gates, which you can wire up (using the programmable interconnects) like the one shown in the picture below. With that, you can design and implement a custom digital circuit from a simple logic gate to complex processors as long as your FPGA has enough physical resources to do it. 



This is a very simplified explanation of FPGA just to give you a general idea as a beginner and is not very accurate. For more details you can browse the web to understand better the FPGA architecture, like the control logic blocks, Look Up Tables and many more.



Design Flow


The diagram below shows the steps on how you can design in FPGA. This is a simplified flow which you can follow to develop your design as you start playing with FPGAs. 



Specification


The first step in your design process is to specify what your design should do. Having a clear set of requirements will help you a lot in the design process.


Develop HDL

In this stage, you will now use HDL like Verilog to design a digital circuit based on the specification. Just a quick side note, there are other HDLs apart from Verilog such as System Verilog, which is a superset of Verilog. You can think of it as Verilog in steroids, it added a lot of functionality targeted for design verification such as object oriented programming, assertions and many more. The other popular HDL is VHDL, I do not have enough knowledge on it, so I will not able to share much so just search the web if you are interested on it. 


The main reason I chose Verilog for coding the examples is because of the tool support. Although it is better to design with System Verilog as it is the latest standard compared to Verilog, most of the open source tools does not have extensive support on the language so to avoid possible issues I will just code the examples using Verilog-2005. Take note that there are also different versions of  Verilog like Verilog-95, Verilog-2001 and so on. Each version has a difference compared to the other such as using a certain coding style for declaring ports in Verilog-2005 will not work inVerilog-95. Don't worry about this too much, as the tools will give you error messages with explanations just like typical programming.


I think the question now running in your mind, is where do you develop the HDL code. Is there an special IDE to it? The answer to that is HDLs are just text files. You can code it using normal text editors. I personally suggest using VS Code and install a Verilog plugin or even Git Hub Copilot. That will help you with the syntax or even ask copilot to explain portions of your code to check if whatever you have on your mind, you have properly implemented it in your code.  


Simulate


Simulation is an important process because at this stage you are verifying the designs correctness prior to implementing the design into the FPGA. In this stage we are creating a test bench, using another Verilog module to provide stimuli to the design and check whether the output of the design is correct or not. The complexity of the test bench scales with the design, if the design is very complex, it is often good practice to do an exhaustive verification and use good methodologies like UVM. For someone, who is just starting out, my personal suggestion is to start with simple Verilog test benches. I will give you some examples how to do it on the later portion of this tutorial


Synthesize


Synthesis is the process by which a tool converts the HDL code like Verilog into logic gates and other basic digital circuits like flip flops. Synthesis is also a broad topic and is dependent on the cell libraries design constraints like power and speed and many more. For now, just think of it as conversion from code to gates. The output of the synthesis is called netlist.


Place and Route (PnR)


This process involves getting the netlist generated from synthesis and mapping it into the actual physical hardware blocks inside the FPGA. Remember, the gate array example earlier, the netlist describes what gates are there, in our example above, one AND gate connected to two inverters. The PnR is the process how you placed them on the boxes and how you route the wires. I just made it sound simple but this process like synthesis and verification can become more complex.


Bit File Generation


This process converts the result of PnR into streams of 1s and 0s (that is why it is called bit file). This is the format used to configure the FPGA.

Bit File Download


This is the process in which the bit file is downloaded into the physical FPGA chip. This can be down using the specific download cable (JTAG) that came with your board. Other boards also have dedicated IC on the board so you only need a USB cable to download the bit file from your PC into the FPGA.

Validation


This process involves testing the downloaded design to make sure that all functionalities are correct.


Tooling


Different manufacturers like Xilinx/AMD, Altera, Lattice, Gowin and many others have different tools that is used to program their FPGAs. Normally, these tools have some free version you can use with some limitations. For Tang Nano 9k FPGA, the tool that needs to be used is the Gowin IDE, since the FPGA chip installed in the board is from Gowin. However, I will not be using that in this tutorial but instead I will be using OSS CAD Suite which is a collection of open source tools developed by the open source community. 

Installation


To install the tool suite, you can follow their official tutorial in this link: https://github.com/YosysHQ/oss-cad-suite-build


When using the tool, I encountered some issue downloading the bit file so I searched the web and found the following solution I encountered when using Windows PC: https://github.com/trabucayre/openFPGALoader/issues/245#issuecomment-1336159690.
When I tried the same exercise using a Linux based (Ubuntu) Raspberry Pi, I encountered similar issue and the solution that worked for me is this: https://github.com/trabucayre/openFPGALoader/issues/245#issuecomment-1213510964.
I have not tested on Mac OS, but there is a note in their official instruction to enable execution of quarantined files.

Depending what operating system you have in your PC, the commands or the steps will have slight differences but overall they should be pretty much similar.

I have created this diagram below to help you visualize the process as well as to have idea what tools we well going to use for each process. More details on tools usage will be discussed in the succeeding portions of this tutorial.






Coding Exercise


In this exercise, I will be creating a very simple  design which is a NOT gate or an inverter. If you have zero knowledge on Verilog, do not worry, as I will discuss the code in a detailed manner.

Step1: Specification


A NOT gate is a circuit that accepts a 1 bit input and produces an inverted value of the input. To summarize it:
  • Input logic 1 (HIGH) will result to Output logic 0 (LOW)
  • Input logic 0 (LOW) will result to Output logic 1 (HIGH)
The onboard push button should be used to get the user input and use one of the onboard LEDs to show the output.

Step2: Develop HDL


In Verilog, the basic building blocks is called a module. If you are familiar with LEGO, you can think of it as a single brick. It has a unique shape and has interface so that other bricks can connect to it. If you are an experienced software engineer you can think Verilog modules are similar to classes. It has certain functionality and has interface to connect to other classes. It has major differences compared to classes though, as it is static in nature and does not support that much of inheritance and polymorphism. I don't intend to discuss classes vs modules in details in this tutorial maybe on my succeeding blog post but I think it is helpful to have that kind of analogy.

The syntax for a Verilog module is as follows:




The terms module and endmodule are reserved keywords to create a module. The term my_not_gate, is a user defined name for that module, you can name it anything you want like your_not_gate , as long as it complies with the Verilog naming rules. One of those rules is you cannot use an operator, like + or used a reserved keyword like module. If you do that, an error will be flagged during compilation. I will show you later how it looks like.


If we imagine what the code above looks like, it will be something like this. An empty module named my_not_gate.



The next step is to add input and output ports to the following module. The code for that is as follows:




The syntax is straight forward, input and output are Verilog keywords for declaring port direction whether it is an input or an output of the module. Port names a and b are user defined, you can name it anything you want as long as it complies to Verilog naming rules.


The equivalent diagram of that code is something like this:



Since the example module has now ports, it can now receive an input and provide an output. The next thing we need to do is to add the functionality. To do that, I will be using Verilog bitwise invert operator ~ . I will also use Verilog assign keyword to assign value to the output port. The corresponding code is as follows:



The equivalent diagram of the code above is shown below:


Step3: Simulate


The next step is to simulate the design. Simulation means we will create a test bench that will provide inputs to the design and also will check the correctness of its output. I have created another module named my_not_gate_tb which is shown below.



Inside this module I will create two internal nets, reg tb_a, and wire tb_b




The names tb_a and tb_b are user defined, you can give it any name just like the input and output ports. The terms reg and wire are Verilog keywords that specifies the type of the net. The net named tb_a is declared as a reg which means register and tb_b is wire which means it is a wire. You might be wondering, what is the difference between the two, why not declare wire for both of them. The reason for that is wire can only be assigned or connected once. Just like when you are designing a schematic, if you connect wires together, the connection will persist unless you deleted it. In Verilog, it is the same thing, wire connection should only be made once and it will stay throughout the simulation lifetime. You can terminate the simulation, then re-wire things and run again. You cannot re-wire at the middle or when the simulation is running. You will also be flagged with compilation issue when you used the wire keyword incorrectly. Registers on the other hand, are like cash registers, you can put and retrieve values anytime in the simulation lifetime. 


I will now instantiate my_not_gate (design) inside the my_not_gate_tb (testbench). The corresponding code is shown below:




The term dut is called instance name, it is user defined so you can name it based on your preference but I chose dut to indicate design under test. The equivalent diagram of the code above is as follows:





Now we, will connect tb_a to a and tb_b to b. The code is as follows:




The equivalent diagram is as follows:





Now, that we successfully instantiated and connected our design to the test bench we can now create our test sequence. The test sequence will contain the code for driving the inputs to exercise the design. We will be using Verilog initial block, the initial block works very similar to typical programming like C, in which all the code inside it will be executed line by line. The example code is shown below:




The example code above initially sets tb_a to be zero then waits for 1 time unit and checks if tb_b is 1. It also checks the condition with tb_a is 1. The time unit is define on line 1, this means #1 means 1 ns.


Another thing that we can add in out test bench is the waveform dumping. Our example code is very simple and does not involved a lot of logic so it is easy to debug. However, if we have complicated design or test bench, dumping waves is valuable to analyze overall design and root cause any encountered issue. To to this, we can add the another initial block with waveform dumping commands as follows:





The $dumpfile specifies the filename of the output waveform file. The file extension is .vcd (value change dump). The $dumpvars specifies what variables to dump. In this example, it is all variables of  my_not_gate_tb including the variables of the modules instantiated inside it. 


Now, that we have a complete test bench, we can now run it. I'm using a Windows PC, so the instruction to run will be based on that. If you have other OS, it may have some difference. 

Open the start.bat file inside the oss-cad-suite folder




You should see something like this:





Go to the folder where you created your .v files by using the cd <location> command. 



Then do the iverilog command like this (don't forget to hit Enter). The iverilog command compiles the verilog files and outputs an executable file named a.out. The a.out file can be run using the vvp command. 



You can use vvp command like this. The vvp command will run the simulation.



As you can see, we did not see any "Test failed" printed in the terminal, which means our design is behaving as expected. One thing you can try out to ensure that your test bench is not giving you false pass, you can try deliberately making a mistake in your design, you can remove the ~ in the my_not_gate module and rerun the iverilog and vvp command. This time you should get the test failed print. Do not forget to revert the design to the correct version if you made changes for testing for false pass.

Apart from the message prints in the terminal we can also use the waveform viewer gtkwave, which is also a part of the oss-cad-suite, to open it, you can do the following command:



It will open a GUI like this:



On the left side, click on my_not_gate_tb and you will see the signals inside that module on the side bar of the GUI like the one below. It shows the signal name and the corresponding type (reg or wire).




Double click on the name to plot the waveform of the signal. It will show the value of signal across simulation time. In our example, we set  tb_a to 0 at time 0ns then after 1 ns we changed it to 1, then finally after another 1 ns we called the $finish to end the simulation. The tb_b which is a test bench wire that is directly connected to the output port b of dut can also be plotted in the waveform and you should see that it is just the inverse of the input tb_a. The waveform produced by the simulation should be like this:





In the waveform above, we just plotted the test bench signals, in some cases it is also helpful when debugging to view the signals of the design itself . We can do that by, clicking and expanding the my_not_gate_tb on the left side bar. You will see that there will be an item named dut under it. You will also see in the sidebar the signals of the dut named a and b. You can plot it on the waveform just like you did for the test bench signals. You should get something like this:





Step4: Synthesize


In this step we will use yosys to synthesize our design. First close the GTK Wave GUI. You should see something like this:




Enter the following command in the terminal: yosys -p "read_verilog my_not_gate.v; synth_gowin -json my_not_gate.json"


It should be completed very quickly and you should have logs displayed in terminal like this:




The yosys command is easy to understand. The command read_verilog reads the Verilog file supplied as argument, in our case my_not_gate.v. It is worth noting that we only need to synthesize design files and not test bench files. Test bench files is only for simulation purpose to verify your design's correctness with respect to its specification. The synth_gowin , is the specific command to use to synthesize for Gowin-based FPGAs. The -json , indicates the output file will be in json format and will take the name we supplied after it, in our case my_not_gate.json.

You can open the my_not_gate.json if you see what it looks like but I will not go into  the details of the file itself because it is not very easy to understand for beginners. What is important is you are able to synthesize the design and generated the .json file without any errors.




Step5: Place and Route


In this step we will be mapping the results of the synthesis into the physical device and therefore we will set configurations specific to Tang nano 9k FPGA board.  


First let us create a constraint file. We can create my_not_gate.cst. Type the following lines on the file and save it. It should look like this:



The IO_LOC maps the specified port to the specific pin number in the FPGA board. In our example, the port of our design is mapped into pin 10 of FPGA which is connected to an onboard LED. The port a is connected to pin 3 which is connected to the on-board push button. FPGA constraint files are device specific and it should be provided by the manufacturer of the FPGA board. For Tang Nano 9k, you can get it on the Sipeed website. 


We will use the nextpnr-himbaechel tool to do the place and route. To do this, in your terminal, you can type the following command: nextpnr-himbaechel --json my_not_gate.json --write pnr.json --device GW1NR-LV9QN88PC6/I5 --vopt family=GW1N-9C --vopt cst=my_not_gate.cst


You should see similar output like this:




The breakdown of the command is as follows:
  • nextpnr-himbaechel is the tool you are calling to do PnR
  • --json my_not_gate.json specifies the input .json file (output from synthesis)
  • --write pnr.json specifies the output json file of the nextpnr-himbaechel
  • --device GW1NR-LV9QN88PC6/I5 specifies the target FPGA chip 
  • --vopt family=GW1N-9C specifies the family of the target FPGA chip 
  • --vopt cst=my_not_gate.cst specifies the constraint file


Step6: Bit file Generation


In this step, we will get the output of the PnR and build a bit file. To do this, you should enter the following command in your terminal: gowin_pack -d GW1N-9C -o pack.fs pnr.json



The breakdown of the command is as follows:
  • gowin_pack is the tool for building bit file
  • -d GW1N-9C is the FPGA family
  • -o pack.fs specifies the output .fs file (the bit file)
  • pnr.json is the input .json file (output of PnR stage)

Once you entered the command you should not see any errors, and should have a file named pack.fs.
If you open that file, you will see only 1s and 0s, that is why it is called bit file. It should be something like this:




Step7: Download Bit file to FPGA


Now that you have generated the bit file the remaining step is to download the bit file to your board. To do this, plug in you FPGA board to you computer, then type the following command: openFPGALoader -b tangnano9k pack.fs


The breakdown of the command is as follows:
  •  openFPGALoader is the tool for downloading the bit file
  • -b tangnano9k specifies the type of FPGA board
  • pack.fs is the bit file to be downloaded

Once you executed the command, you should see something like this:



As you can see in the terminal, the bit file was loaded on SRAM which is a volatile memory. This means that whenever you remove power such such unplugging the board from the USB port, the configuration data will be lost and you need to reconfigure again. If you want your configuration to be persistent, you have to download it into the flash memory which is a non-volatile memory. You can do that by simply adding -f in the command similar to the snapshot below.




Step8: Validate your Design


Check that your design is properly implemented in the physical board. Sometimes tools do not show errors but it does not guarantee design will work perfectly in physical word. This is because even simulation is tying to model the physical world, it cannot be modelled 100%, so you should do your due diligence and validate the design.


Shown below is a simplified schematic diagram for the pushbutton and LED found in Tang nano 9k board. The diagram below is not 100% the same with official schematic but functionally equivalent (you can download the official schematic on Sipeed Tang Nano 9k official website). If we take a look on this, in order of the LED to turn on, PIN 10 should be driven by logic 0. Meanwhile, for the pushbutton, the normal state (not pressed) is logic 1 because PIN 3 is pulled up. 


Going back to our NOT gate design, this means that if we pressed the button, we are setting PIN 3 which is mapped to port a to logic 0. This will drive PIN 10 to logic 1 (because b = ~a), this means that LED will be OFF. If we do NOT press the button, we are setting port a to logic 1 and therefore should expect LED will be ON. 






Shown in the photos below are the the actual results.


Input is 1 (pushbutton is NOT pressed) Output is 0 (LED on)


Input is 0 (pushbutton is pressed) Output is 1 (LED off)

Things you can try


For me, I learn more if I try things out so if you reached up to this point of the tutorial, you probably read and understand the basic concepts or maybe you just scrolled down 😂. Kidding aside, you can try implementing the other logic gates. 

This are some useful information if you want to try that exercise.

Verilog Bit-wise Operators


  • & is AND
  • | is OR
  • ^ is XOR
  • ~^ is XNOR

Constraint File


Pushbuttons are connected at pins 3 and 4. LEDs are connected at pins 10, 11, 13, 14, 15, 16. I specified all of the 6 LED locations you can play around with it. You can turn everything on at the same time, use them to show the state on the input (whether push button is pressed or not) and many more. 

Credits


Thank you to all the contributors of the open source tools in the OSS-CAD-Suite. Your contributions towards democratizing fpga or chip design is very valuable.







Designing Binary to Seven Segment for FPGA

In this post, I will be teaching you how to build a binary to seven segment Verilog module and download it into the Tangnano 9k FPGA board. ...