HDL Design Process
The HDL design process is imperative to have when designing a complex system. When you are first starting out a HDL design process may seem to be too cumbersome when trying to get a counter to work. But when you are moving data from an ADC clock domain to a faster signal processing clock domain, performing signal processing techniques, and switching clock domains again to send the data off to its destination a HDL design process is imperative to ensure proper operation of the system. It’s good practice to follow the HDL design process laid out here to ensure your ability to handle large and complex systems.
The HDL design process illustrated below can and should be applied to all levels in a design hierarchy. When a large complex design is broken down into small manageable sub-components. Each sub-component should be test-benched to ensure proper operation when wired together in the larger system.
First in a high level language of your choice develop the algorithms you would like to move to the FPGA. Popular choices are MATLAB and Python. Once the system model is in place you can start refining the system model to be more representative of what will happen in hardware. Then you can start writing RTL, and a test-bench for the RTL to do exactly what your hardware refined system model does. And finally you’ll use the vendor tools to ensure your design meetings timing.
Verifying output from a VHDL simulation is tedious and has the potential for error. We will be writing VHDL test benches that have three parts, stimulus, DUT, and verification. The system model provides the stimulus and one of the operands for comparison in verification. The system model defines successful operation of your DUT.
Your system model can be as simple as an adder or as complex as a full system implementing machine learning or image processing algorithms. I would hope there would be a test bench for all the components in a complex design.
There are some features in test-benches that it is not necessary to have a MATLAB or Python model for such as a simple adder. A simple adder can have stimulus, the two operands, generated in VHDL and the addition can be done in VHDL and compared to the DUT output. There isn’t a need to have text files written out or read into VHDL. Stimulus and verification still occur, it’s just all contained in the VHDL test bench.
In Python’s numpy and MATLAB good optimized code is organized in vectors and matrices. In the system model matrix and vector calculations are used with no restrictions. These calculations can be used to determine system performance and a lot of times Monte Carlo simulations take a while to complete when optimized. Once the system model is complete we look to make the model more like what will happen in hardware.
Hardware Refined System Model
The second step of the HDL design process is the hardware refined system model. Here we will calculate the same results as the system model just calculated in the same fashion as how the FPGA would. For example, in MATLAB if you want to take the FFT of a sine wave you could do this in one line of code (but for readability you should break it up into more lines). However in VHDL this task will take hundreds if not thousands of lines of code. The hardware refined system model will have each step broken out so that as we are writing VHDL we can compare to ensure no bugs are present.
RTL and Test-bench with System Model Stimulus
Decomposing the algorithms into VHDL entities, is the third step of the HDL design process. Examples of VHDL entities would include an FFT block or filter implementation. Maybe a not so obvious block could be a multiplexer which would separate data based on a classifier that provides selection lines as input to the data multiplexer. Once a design is decomposed into entities, development of each block can start.
For each component in a design we need to write a test bench. The test bench will read from a file and generate inputs to the component or stimulate the component. Writing a test bench is similar to wiring a chip to test on a bread board. We are checking if we send in all (or close to all) valid input we get the correct behavior at the output.
Once all the entities are test benched we move on to another test bench that wires all the components together. Depending on the hierarchy and the complexity of the interconnection of the blocks, incremental test-benching maybe in order to isolate any potential bugs. In the end you should have a top level test-bench that instantiates a DUT that represents the FPGA and the test bench that represents all peripherals to the FPGA.
Verify Design Timing
If a design has been simulated with adequate coverage of the input space the design is ready for the final step of the HDL design process, timing analysis. Timing analysis is done by the vendor tools for the FPGA you are targeting, which checks to see if any combinatorial path has a latency longer than the clock period. If the latency is longer then the design does not meet timing and non deterministic behavior will occur.
The timing analysis tools will help identify the paths that fail timing. There are of course many ways to solve various timing violations but the solution will come down to either slowing down the clock or insert more registers in the critical path.
Project Directory Structure
We have seen there are many stages of FPGA development. With each stage there are many files that are generated by you and by simulation and vendor tools. Keeping all the files organized will help you feel in control of your design.
The purpose of having a directory structure is to be able to find what you need when you need it. At first it may seem like too big of a hassle to do this for a small design but as you develop larger design it will be helpful. Eventually you will want to go back and reuse code you’ve already written; why write a UART twice. Just go to the UART project directory and copy the RTL sub-directory into your new project directory.
The RTL sub-directory is the folder that you develop all of you synthesizable code in. Synthesizeable code is code that can be put on the FPGA. Unsynthesizble code would be a test-bench that uses wait statements. If a design is large enough to have multiple levels of hierarchy sub-directories in RTL help keep code organized.
TB and Sim Sub-Directory
For every RTL file there should be a test bench that covers that entity in the folder TB. The RTL and TB folders should have the same sub-directory names. The same names add consistency so that it’s clear where the test bench is for the RTL files. You’ll want to simulate the design as these files are being developed. For this we have a SIM folder. Inside the SIM folder the simulator (QuestaSim, Rivera-Pro, or GHDL) will generate compiled models of you design as well as vendor IP. The SIM folder keep the simulator tool files separate from you code.
Once the design is completely developed and passes functional simulation you will want to program the FPGA. To compile the design you will have a specific FPGA to target. FPGA specific files go into the folder PAR. PAR means place and route, which is specific to an FPGA. If you routed a design for a Xilinx FPGA it wouldn’t work on an Altera or Microsemi FPGA.
Lastly a folder called DOCS keeps any data sheets for peripherals that interface to the FPGA. Keeping the data sheets and any other documentation organized for future reference will be helpful. If the project is only internal to the FPGA the DOCS folder isn’t really needed.