Getting started with FPGA control development
Table of Contents
This note explains how to get started with the implementation of power converter control algorithms in FPGA. The benefit of offloading all or parts of the computations from the CPU to the FPGA is that it often results in much faster closed-loop control systems.
First, the FPGA control starter template is presented and a tutorial on how to create this template is provided. Then, the reader will learn how to retrieve ADC results from the FPGA as well as to exchange data with the CPU. The page ends with a simple hello-world example illustrating all the keys steps of FPGA control implementation.
This page is the first of a 3-part tutorial explaining step-by-step how to implement the closed-loop control of a buck converter in FPGA without using VHDL or Verilog. The second note explains how to generate a PWM modulator in FPGA using the Simulink blockset Xilinx System Generator or MATLAB HDL Coder. The last note shows how to create the PI-based current control using high-level synthesis with Xilinx Model Composer (Simulink blockset) or Xilinx Vitis HLS (C++).
Presentation of the FPGA control starter template
The FPGA control started template allows for easy integration of custom FPGA-based control algorithms in the sandbox area of the B-Box RCP or B-Board PRO. As shown in the image below, it consists of:
- the obfuscated “imperix firmware IP” which contains the FPGA logic required to operate imperix controllers (documented in PN116), and
- the “ix axis interface” module which provides easy-to-use AXI4-Stream interfaces to exchange data with the user logic.

The provided AXI4-Stream interface module connects to the data interfaces and timing signals of the imperix firmware IP, interprets these signals, and converts them into much more user-friendly AXI4-Stream (AXIS) interfaces (ADC
, CPU2FPGA
and FPGA2CPU
). The AXI4-Stream protocol is a widely used standard to interconnect components that exchange data. This means that the provided template can directly be connected to a wide range of Xilinx-provided IPs or to user-made algorithms developed using High-Level Synthesis (HLS) design tools such as Vitis HLS (C++) or Model Composer (Simulink).
This AXI4-Stream interface module is written in VHDL. It is provided as a starting point and will meet the need of most applications. However, if required, it can be edited by the user to add extra input/outputs, rename them, change their data sizes, etc.
Creating the FPGA control starter template
Downloading the required sources
All the required sources are packed into the sandbox_sources_3.x.zip archive which can be downloaded by clicking on the button below.
For imperix SDK 3.6.x.x
(requires Vivado 2019.2)
For imperix SDK 3.7.x.x:
(requires Vivado 2021.2)
For imperix SDK 3.8.x.x:
(requires Vivado 2022.1)
- Download the archive sandbox_sources_3.x.zip.
- Unzip it and save the content somewhere on the PC. In this tutorial we’ll use the
C:\imperix\
folder.

sandbox_sources
content. Then from Vivado top bar menu: Reports -> Report IP Status. And finally click the Upgrade button from the IP Status bottom tab.Creating an imperix sandbox project
- Open Vivado.
- Click Create Project.
- Chose a name and a location.
- Select project type RTL Project and check the box Do not specify sources at this time.
- Select the part named xc7z030fbg676-3.
- Hit Finish. The project should open.
- Go to the IP Catalog, right-click on Vivado Repository, hit Add repository…
SelectC:/imperix/sandbox_sources/ix_repository/
The IMPERIX_FW IP, clock_gen, and user_regs interfaces should be found as shown on the image below.

- Click on Create block design, name it “top” and click OK.
- Open the freshly created block design, do a right-click in a blank area of the design, select Add IP… and search for “IMPERIX_FW” and hit ENTER.

- Keep the [Ctrl] key pressed and select the IP pins
flt
,gpi
,private_in
,DDR
,FIXED_IO
,gpo
,pwm
andprivate_out
(andUSR
for version 3.7 and up).
Hit [Ctrl+T] to create top-level ports. - By default Vivado adds “_0” after each port name. Remove the “_0” from every port. For instance “
flt_0[15:0]
” becomes “flt[15:0]
“. - The
user_fw_id
input may be used to identify the firmware version. We recommend instantiating a Constant IP (Right-click, Add IP…, search for Constant) to give an identification number to the design.

- Go to the Sources tab, right-click on the block design file (top.bd) and select Create HDL Wrapper…
In the dialog box choose Let Vivado manage wrapper and auto-update and hit OK. - Right-click on the Design Sources folder
Choose Add Sources…
Check Add or create constraint
Click on Add Files
SelectC:/imperix/sandbox_sources/constraints/sandbox_pins.xdc
Uncheck Copy sources into the project
Hit Finish
From this point the project is synthesizable.
Adding the AXI4-Stream interface
Adding the AXI4-Stream interface is optional. It is useful if the FPGA control design uses AXI4-Stream interfaces.
- Right-click on Design Sources
Choose Add Sources….
Check Add or create design sources. - Go to your repository and select
C:/imperix/sandbox_sources/hdl/AXIS_interface.vhd
.
We recommend unchecking “Copy sources into project” and working directly from the files in the folderC:/imperix/sandbox_sources/hdl/
so the sources can be shared across multiple projects. - Right-click somewhere in the block design and choose Add module… and select the ix_axis_interface module.
Alternatively, the file listed in the Design Sources can be drag and dropped it on the block diagram. - Connect the pins as follows:

AXIS_interface.vhd
can easily be edited to improve the readability, by renaming the interfaces and removing the unused ones for instance. In this example, we’ll keep the interface as it is.How the AXI4-Stream interface operates
This section focuses on the AXI-Stream interface module. For further information on the imperix firmware IP please refer to the imperix firmware IP product guide.
Retrieving analog measurement with the M_AXIS_ADC interfaces
The Master AXI4-Stream interfaces M_AXIS_ADC_00 to M_AXIS_ADC_15 correspond to the 16 analog inputs of the B-Box RCP or B-Board PRO.
They return the raw 16-bit signed integer result from the ADC each time conversion results are available. Consequently, users should manually perform the data-type conversion and apply correct gains in their FPGA projects to compute the acquired value in its physical unit. To learn how to compute this gain, please refer to the last section of the ADC page.
The ADC sampling frequency can only be configured using the CONFIG block. ADC acquisition can not be triggered from within the FPGA.
Exchanging data using M_AXIS_CPU2FPGA and S_AXIS_FPGA2CPU
The Master AXI4-Stream interfaces M_AXIS_CPU2FPGA and the Slave AXI4-Stream interfaces S_AXIS_FPGA2CPU serve to exchange 32-bit data between the CPU code and the FPGA.
To read/write values on the FPGA2CPU/CPU2FPGA ports, the user can download the Simulink model from the step-by-step hello world section below and re-use the following blocks


The provided template uses the following mapping between the 16-bit SBI/SBO registers and the 32-bit AXI4-Stream interfaces:

If the user chooses to write a single-precision floating-point data on the AXI4-Stream interfaces M_AXIS_CPU2FPGA_00, then he can:
- use a MATLAB Function block to transform a single value into two uint16 values (see code below)
- and then use the SBO block to send these two uint16 values to
SBO_reg_00
andSBO_reg_01
(CPU2FPGA_00
).

function [y1,y2] = single2sbo(u)
temp = typecast(single(u),'uint16');
y1 = temp(1);
y2 = temp(2);
Code language: Matlab (matlab)
And if he wishes to read a result from the FPGA to the CPU (still in single-precision floating-point format) using S_AXIS_FPGA2_CPU_01, then he can:
- use the SBI block to retrieve the two uint16 values from
SBO_reg_02
andSBO_reg_03
(FPGA2CPU_01
) - and then use a MATLAB Function block to transform these two uint16 values into a single value (see code below).

function y = sbi2single(u1,u2)
y = single(0); % fix simulink bug: force compiled size of output
y = typecast([uint16(u1) uint16(u2)], 'single');
Code language: Matlab (matlab)
Getting the sample time Ts
The M_AXIS_Ts interface provides the sample period in nanoseconds in a 32-bit unsigned integer format. This signal may be used, for instance, by the integrators of PI controllers. This value is measured by counting the time difference between two adc_done_pulse.
Using reset signals
The AXI4-Stream module also provides two reset signals:
- nReset_sync: this reset signal is activated each time the user code is loaded through BB Control. It can be used as a standard reset signal.
- nReset_ctrl: this reset is triggered from the CPU through SBO_reg_63 using a core state block. Its intended use is, for instance, to reset the PI controller integrator when the converter is not operating (when the PWM outputs are disabled).
Both signals are active-low and activated for 4 periods of clk_250_mhz
.
Step-by-step “hello world” example
Description of the FPGA “hello world” example
To showcase the complete Vivado workflow, the following example is used. It does the following:
- From the CPU, a gain value (single-precision) is transferred to the FPGA using the CPU2FPGA_00 interface (SBO_00 and SBO_01).
- In the FPGA, the data coming from the ADC_00 interface is converted into a single value.
- The ADC value is then multiplied by the gain.
- Finally, the multiplication result is sent back to the CPU through FPGA2CPU_00.
- The raw value of the ADC is also sent to the CPU using FPGA2CPU_01.

The FPGA logic will be implemented using exclusively readily available Xilinx Vivado IP, namely:
- Floating-point IP configured for Fixed-to-float operation to convert an int16 to a single-precision floating-point value.
- Floating-point IP configured for the Multiply operation to multiply two single-precision floating-point values.
- AXI4-Stream Broadcast IP to duplicate the output of the int16 to single block.
Other useful Xilinx Vivado IPs are listed in the AXI4-Stream IPs from Xilinx page. The user can also implement his own IP blocks, either using directly VHDL or Verilog, or high-level design tools such as Vitis HLS (C++) or Model Composer (Simulink).
CPU-side implementation
The CPU-side code provided below has been implemented using Simulink and the imperix ACG SDK. To make these variables available during run-time, the gain is set using a tunable parameter and the adc_raw and result are read using probes. These 3 values are encoded as single (32-bit single-precision floating-point). The MATLAB Function blocks single2sbo and sbi2single allow to easily map single values to SBI and SBO blocks.

FPGA-side implementation
As mentioned earlier, only standard Xilinx Vivado IP blocks are used to implement the algorithm in this example. The PN159_vivado_design.pdf file below shows the full Vivado FPGA design. Here are the step-by-step instructions to reproduce it.

- Add a block to convert the int16 data of ADC_00 to a single-precision floating-point
- Right-click somewhere in the block design and choose Add IP…
- Search for the Floating-point IP, drag-and-drop it on the diagram.
- Rename it as int16_to_single.
- Double-click on the int16_to_single block. In the pop-up window, select the Fixed-to-float operation and set the precision as int16 input, Single result.



- Add the multiplier block
- Add another Floating-point IP and rename it as single_multiplier.
- Double-click on the block.
- Select the Multiply operation and Single input.

- Broadcast one stream to two streams
- Right-click somewhere in the block design and choose Add IP…
- Search for the AXI4-Stream Broadcaster, drag it and drop it on the diagram.
- Keep all options to auto.

- Connect all the blocks as follow:
- M_AXIS_ADC_00 to S_AXIS_A of int16_to_single
- M_AXIS_RESULTof int16_to_single to S_AXIS of axis_boradcaster_0
- M00_AXIS of axis_broadcaster_0 to S_AXIS_FPGA2CPU_00
- M01_AXIS of axis_broadcaster_0 to S_AXIS_B of single_multiplier
- M_AXIS_CPU2FPGA_00 to S_AXIS_A of single_multiplier
- M_AXIS_RESULTof single_multiplier to S_AXIS_FPGA2CPU_00
- clk_250_mhz to all aclk inputs
- nReset_sync to aresetn of axis_broadcaster_0
- Finally, the design can be synthesized and the bitstream generatedClick Generate bitstream. It will launch the synthesis, implementation and bitstream generation.
- Always make sure that your design meets the timing requirements!
This information is available from the Project Summary
To learn more please visit the Xilinx documentation on Timing Closure.

- If the timing requirement are met, click on File → Export → Export Bitstream File… to save the bitstream somewhere on the computer.
Loading the bitstream into the imperix controller
Using Cockpit
Using imperix Cockpit, the bitstream can be loaded into the imperix controller device from the target configuration window.

Using BB Control
Using BB Control, the bitstream can be loaded into the imperix controller device using the following procedure:
- Go to the Configuration tab of BB Control.
- Click on the “import bitstream” button and select the generated bitstream. It will upload the bitstream into the B-Board SD card.



Experimental validation
Finally, the CPU code is generated from the Simulink model and loaded into the device, as explained in Getting started with BB Control.
Using BB Control, the gain variable (CPU variable) can be changed using the Debugging tool:

To test the design, a sinusoidal signal is fed to the analog input of the B-Box. Then, using BB Control’s datalogger, the result = 0.5*adc_raw can be observed:

Going further
Testing M_AXIS_Ts
This section goes a bit further in the demonstration of the provided AXI4-Stream M_AXIS_Ts to help understand the M_AXIS_Ts interface, and show how to transfer an uint32 value from the FPGA to the CPU.
The modification of the FPGA bitstream is quite simple: simply connect M_AXIS_Ts to S_AXIS_FPGA2CPU_02 as shown in orange on the image below. This allows reading the sample time Ts value from the CPU.

On the CPU, to retrieve the Ts value, the value is read from S_AXIS_FPGA2_CPU_02 (SBI_04 and SBI_05). Because the value is a 32-bit unsigned integer, the transformation is different from before as shown below.
Option 1

Option 2

function y = sbi2uint32(u1,u2)
y = uint32(0); % fix compiled size of output
y = typecast([uint16(u1) uint16(u2)], 'uint32');
Code language: Matlab (matlab)
Finally, using BB Control, the result can be observed. If the control task frequency (CLOCK_0) is set to 50 kHz, then M_AXIS_Ts will return 20’000 ns.


If CLOCK_0 is kept at 50 kHz, but an oversampling ratio of 20 is set, then M_AXIS_Ts will return 1000 ns, which corresponds to the actual sampling period.


Using the USR pins
The imperix controllers feature 36 user-configurable 3.3V I/Os (the USR pins) that are directly accessible from the FPGA. Their physical locations are available in the B-Board PRO datasheet and B-Box RCP datasheet.
By default, the USR pins are connect to the imperix IP. Currently they are only used to communicate with the with the motor interface. If the motor interface is not used, then the USR port can safely be deleted.

These pins are then available for other use. The screenshot show an example where the USR pins 0, 1, and 2 are used.

The constraints\sandbox_pins.xdc
must be edited accordingly. As shown below, we recommend commenting (#) the unused pins to avoid generated unnecessary warning the Vivado.For more information on constraints in Xilinx FPGA please refer to the using constraints in Vivado Design Suite user guide.

The FPGA-based SPI communication IP for ADC page shows an example where USR pins are used to drive an external ADC using the SPI protocol.
Additional tutorials
The page custom FPGA PWM modulator explains how to drive PWM outputs or to use the CLOCK interfaces through a simple example. The PWM modulator sources are provided as VHDL, as a Xilinx System Generator model, and as a MATLAB HDL Coder model.
The page high-level synthesis for FPGA developments shows how to integrate HLS-generated IPs in an FPGA control implementation using a PI-based current control of a buck power converter as an example.