This two-part series describes a workflow for implementing a complex waveform in an FPGA when starting from a MATLAB m-code file. This workflow uses the MATLAB interpreted language, Simulink with System Generator blockset, and Nutaq’s Model-Based Design Kit (MBDK).
The MATLAB interpreted language is an excellent way of making quick proof-of-concepts. The language is easy to learn and very popular in the scientific community. Unfortunately, a waveform developed with MATLAB m-code isn’t ready for hardware implementation. The simulation results are expressed in a floating-point manner with a very high binary resolution. In a fixed-point hardware architecture like a field-programmable gate array (FPGA), these two constraints are extremely important because each bit represents a signal and thus requires hardware resources. Other important factors when making a successful FPGA design include resource consumption, processing delays, timing constraints, and algorithm complexity. To help us tackle these issues before compilation time, we use Xilinx System Generator.
System Generator is a MATLAB/Simulink blockset that automatically generates HDL code. Being supported by Xilinx itself, System Generator blocks can provide bit-true/cycle-true simulation. This feature is very useful as it enables us to evaluate the dynamic range and processing delay of each processing block before HDL code generation. In other words, you can simulate the behaviour of the HDL code before generating it. This accelerates your development as generating a bitstream takes a considerable amount of time.
We add Nutaq blocks to generate the code for the hardware interfacing portion of the design. In the end, you have one Simulink model, using both System Generator and Nutaq blocks, that automatically generates the bitstream file of your design.
For example, let’s implement a simple QAM64 orthogonal frequency-division multiplexing (OFDM) system, represented by the following block diagram:
An m-code implementation of such an application takes few lines of code:
In the MATLAB environment, it takes no time at all to implement the application and use the included tools to analyze the performance of our algorithms. So when we are satisfied with the results, it is time to start our FPGA design, block per block. As an example, let’s move the inverse fast Fourier transform (IFFT) processing portion to a System Generator model. The goal is to stay connected to our m-code file but to use the IFFT from System Generator instead of the IFFT function from MATLAB. The following block diagram illustrates our goal:
This can be achieved in three steps:
- Create a test vector by exporting the QAM modulation output to MATLAB’s workspace.
- Implement the IFFT using the System Generator block and then simulate using the generated test vector.
- Output the simulation results of the IFFT to the MATLAB workspace and then use these results as an input for the FFT block.
Here’s the necessary Simulink/System Generator model:
The inputs of this model are the real and imaginary part of the QAM modulator, followed by a data valid signal. The yellow blocks are used to switch from a floating to fixed-point data type. The scopes are then used to observe the simulation results and validate our implementation. The IFFT is implemented using a System Generator block and the first-in/first-out (FIFO) blocks at the input are used to control the flow of the data (since now our IFFT presents a processing delay of a few clock cycles). The IFFT results are then sent to the MATLAB workspace, back into the floating-point environment to be used as a source for the rest of the process (m-code FFT). To validate that our IFFT, implemented using System Generator blocks, works as expected, we use a small MATLAB script that simply performs the FFT and plots the obtained constellation:
Since we are able to see a good constellation, this means that our IFFT is working correctly and that the time samples at the output of the IFFT are represented by enough bits. It also tests our flow control logic.
This process isolates each processing block and saves debugging time. In next part of the blog series, we repeat this process to obtain the complete Simulink/System Generator model and then we generate the bitstream. Stay tuned!