Migrating from MCU world to FPGA - PART 1
Before wasting too much of your precious time I’ll reply - straight forward - to those 3 questions:
- What to expect from this document?
- What not to expect?
- To Whom is this article addressed?
What to expect from this document
What not to expect
This document is not an exhaustive guide for the full VHDL syntax, nor is it a document written by an FPGA expert. I have learned on the job, but after years of practice, thousands of hours of coding and debugging and putting many FPGA-based products on the market, I feel confident enough to help others get into that amazing world of FPGAs.
Also, this document does not handle Verilog programming language (which is another language used for FPGA). I highly encourage you to find a VHDL programming reference (out of many references available for free on the internet) to complete this article.
To Whom is this article addressed
This series of articles can be a good starting point for anyone who has been working (or playing around!) with Microcontrollers, Arduino, RaspberyPi, and other embedded systems and wishes to get access to a whole new level of processing power.
Before getting into serious stuff, let me present to you FPGA’s, the way I understand them. As their name implies - Field Programmable Gate Arrays - are chips with a huge amount of logic gates that can be customized to do various functions. Imagine a circuit you build with many logic gates like flip-flops, OR gates, AND gates, Multiplexers or Decoders and many, many, MANY wires: Well, that’s an FPGA, with the difference that it takes a few dozen square millimeters of surface on your circuit board. You feel that this gives endless possibilities, right? Well, don’t get too excited! FPGAs are indeed powerful beasts, but the more you try to push the limits of what they can do, the more you have to deal with very obvious limitations like the number of used gates to build your circuit or the propagation delay between the gates. There are many details that we’re not used to taking care of when working with micro-controllers but are paramount in the world of FPGAs.
The big picture
In software programming, you write code that is “executed”, instruction after instruction, by a microcontroller or microprocessor. So, you’re used to writing code, and if the syntax is good, it compiles into a binary file (executable) that is made of instructions, and that’s pretty much the whole story (in an oversimplified way). When you write code for FPGA (using the VHDL language), well, you’re not writing code that will execute in the chip, you’re not! You’re writing code that describes a circuit. Remember that big circuit with many many wires and logic gates we were talking about? Well, when you’re writing VHDL code, you’re writing code that ultimately describes a (usually) complex hardware circuit. As a matter of fact: the “HDL” part of VHDL stands for Hardware Description Language. Now although sometimes you’ll find that VHDL lets you describe the operation of your circuit rather than describe your circuit (and we’re thankful for that), it’s important to always keep in mind that at some point in the “life cycle” of your code, it will all be converted to a very real electrical circuit, with very real gates and (almost) real wiring between them. Those gates and wiring have physical limitations like any other digital circuit, that need to be taken into consideration. Never forget that.
That process of converting your VHDL code into a “circuit” is called “synthesis” (and the closest thing to that process in the C programming world would be called “compiling”, although it’s not the same thing). We’ll get into the specific of that process later.
FPGA’s are versatile
An important difference you need to grasp, is that FPGAs are much more versatile than MCUs or MPUs. For instance, in MCU you’ve got GPIO pins, multiplexed with some other peripheral features, like timers, communication ports, PWM generators, etc. FPGAs have IO pins, and those can be configured to do almost anything. Although some “special” pins are usually dedicated for getting a clock signal inside the FPGA, there is no absolute obligation to use them. Of course, FPGAs have some special functions pins used for JTAG programming.
It’s up to you to “build” the whole architecture of your system from the ground up. Decide the different modules that will be implemented (like timers, UART interface or I2C interfaces), and actually “code” those modules from scratch. This may seem like a tedious task, and frankly, it is. But it is also a opportunity to build the modules you need exactly like you need them. Do you need a 24 bit, ultra precise PWM signal generator? No problems! Do you need to have 20 of those modules? Well, you just need to add exactly 2 lines of code to instantiate (replicate) a block as many times as you wish.
Things can get much further: You can even build a fully functional MCU inside your FPGA. This lets you combine the flexibility of microcontrollers with the versatility of FPGAs, but it’s quite an advanced feature that I wouldn’t recommend this for a beginner.
One very important advantage of FPGAs: You can fully test your design in simulation. Simulation can go as far as you willing to take it. You can, for example, take into account propagation delays inside the FPGA fabric, allowing you to test and validate complex designs in a comfortable test environment. Other techniques exist, similar to JTAG debug for MCUs. For xilinx it’s called ChipScope if you wish to further explore this later. We won’t be addressing ChipScope or equivalent in system debugging solutions in this document.
There are some limits to this versatility though. As stated before, high speed clock signals may need to be routed to very specific pins to help the design tools you’re using (like Xilinx’s ISE or Vivado) to optimize the design and routing of the signals. Also, you’ll notice that GPIOs are grouped in what we call “banks”. A bank - or a group of pins - share the same power supply and hence operate at the same voltage. For example, you can’t have 1.8V and 3.3V logic on the same bank. However, there is nothing wrong in having a 1.8V bank, and 3.3V bank on the same chip. Finally, you need to understand that although you have the right to ask for any I/O signal to be routed to any I/O pins, there is no guarantee that it will be physically possible (you’ll discover why later). It’s not uncommon for designers to need to change I/O placement to overcome some physical limitations of the kind.
Anatomy of a simple FPGA design
In C programming, you have two main types of files: c files and header files. (there is also a make file, a project file, or some other files of the sort). In an FPGA design, there are really, really a lot of files that get generated in dozens (if not hundreds) of folders. Thankfully, you usually don’t need to do anything with those, so let’s concentrate on the 4 essential components.
The most important part are modules. It’s equivalent to the *.c file in your beloved C programming world; it’s a plain text file that has the extension “vhd”, “vhdl”, or “v”. Since we’ll be focusing on the VHDL language in this article, from now on, let’s stick to the *.vhd extension.
A module is like a piece of circuit (or the code describing this circuit to be more precise). It has a section that describes the ports of that circuit (inputs and outputs) and then, there is the actual description of that circuit, including all gates and wiring.
A design can contain many other modules, that we call “instances”, but only one module instance in your design can be raised to the “top” level: it’s called the “top module”, and the port of that module directly connects to the pins of the FPGA.
Let’s imagine a project that generates 3 pwm signals. There are endless ways to code that module, of course, but an efficient approach would be to build only a single “PWM generator” module, instantiate it 3 times in a top module that “glues” everything together. Something like the graph below:
We’ll get into the details of how to actually write the VHDL code for that architecture later.
Those are equivalent to headers in C files. It allows you to define data types, signals buses and pieces of code that can be reused many times in a project (or even in other projects). Although you may complete a whole project from end to end without needing packages, know that they exist. Actually there is no obligation to use them, but at some point of your evolution in the world of FPGAs, you’ll feel the need to better organize your code and make it easier to maintain. When that time comes, google about VHDL package.
This plain text file lets you define the placement of different I/O pins, i.e. the real physical position of the pins in the package of the FPGA chip. It also lets you define the logic family to be used for each pin. Finally, it lets you add what we call “timing constraints”: an indication of the speed at which some signals are clocked. Timing constraints are very important for a design, and they let the tools that route your FPGA design try to meet those constraints, and alert you in case it can’t. Unless your design is so simple that it doesn’t include a single clock going faster than 1M Hz, timing constraints are mandatory. Most modern tools have GUI and step-by-step wizards to help you generate that constrain file.
A test bench is another plain text file that, as the name implies, allows a user to test a module. A test bench is yet another VHDL file, that describes a circuit that would connect to your FPGA in your final application. Let’s take an example: you’re building a board that needs to store data to an SRAM memory. In order to test the module that communicates with the SRAM, you can build a test bench that includes an emulated SRAM. You would usually grab the datasheet and try to build a test bench that mimics the actual timing characteristics of the SRAM as closely as possible, the idea being to test and validate your module in the most realistic conditions.
It’s worth noting that a test bench will never be actually synthesized into real gates. That means that you can use a line code that says:
wait for 15 ns
Which is something you can’t do in a VHDL code that needs to describe actual logic gates. At least not that easily: to create a 15ns delays, you would need to have a clock that feeds a counter that counts elapsed time. Again, don’t worry about the code for now: we’ll get into actual implementation the parts of this series of articles!
Continue reading part 2.