hardware descriptions

Real-Time dynamic simulation with FPGA vol 2 : Differential equations on a chip

Sources are found under the Github link at ac_inout_psu/source/math_library/

In part 1, a general purpose state variable object was designed and it was used to build a LC filter model. Now the designed modelling tools are used for the entire power supply model seen in Figure 1. The designed model is then coded in VHDL and run on FPGA for a real-time simulation. The model is exited with different steps to load and duty ratios and the results are collected from the FPGA with UART. The presented system is developed with cyclone 10 lp evaluation kit and the console software used for data collection is also included in the repository and the link to build instructions is found on the navigation panel.

The architecture of the power supply under design is shown in Figure 1. The power supply has an inverter to interface the grid to DC voltage and another inverter to transform the DC voltage to arbitrary AC voltage. An isolated DC/DC converter is used to galvanically isolate the inverters DC links from each other. The electromagnetic interface (EMI) filters are additional LC filter sections that are added between the grid inverter and the grid voltage and the output inverter and load to limit interference from the power supply.

Figure 1. power supply architecture. The grid voltage is filtered with grid side emi filter and rectified with input inverter. The inverter DC link is isolated with an isolated DC/DC converter and the isolated DC is then transformed into AC with output inverter.

Inverter model

A basic switch mode converter is controlled with two electronic switches switched in alternating pattern. The basic switching operation is visualized in Figure 2. This arrangement of two switches is typically called a leg. Looking at Figure 1, when switch 1 is conducting the output voltage u_{sw} equals the dc voltage and the dc current i_{dc} equals the load current i_{L}. When switch 2 is conducting the dc current and u_{sw} voltage are zero.

By switching the electronic switches, typically mosfets or igbts, at some set frequency and varying the proportion of the switch period when a switch is conducting, a constant voltage can be transformed into variable voltage.

This is called pulse width modulation(PWM), see for example https://en.wikipedia.org/wiki/Pulse-width_modulation or references therein for more in-depth explanation. The ratio of conduction time to switching period is called the duty ratio.

The average value of a unit amplitude PWM signal is simply the duty ratio, thus the switched voltage u_{sw} is the dc voltage multiplied by the duty ratio and the dc current i_{dc} is the load current i_{L} multiplied by the duty ratio.

Figure 2. Simplified switching converter

If we wish to implement a switching converter where the polarity of the voltage can be changed, we can connect the load current between two legs as shown in Figure 3. When the switch 4 is conducting, the pwm voltage polarity is positive over the load current and when switch 3 is conducting, the pwm modulated voltage is negative over the load. There are also many other switching patterns which produce the same voltage over the load but methods are essentially the same when only average values are considered.

Figure 3 Basic inverter with ability change voltage polarity

We are going to define positive duty ratios to those where voltage is positive and negative duty ratios when switched voltage is negative. Replacing the load current with and LC filter an inverter obtained which can produce AC voltage over the load from a single DC source. The input voltage of the LC filtered inverter is simply duty * dc link voltage and current of the dc link is duty * inductor current and this works for all duty ratios between -1 and 1

Figure 4. Inverter model with LC filter and DC link capacitor

The model for type of inverter is thus a LC filter where the inductor input voltage is the DC link voltage multiplied by the duty ratio an additional state variable for the DC link voltage. The model for inverter with a DC link and output LC filter can be written as

With LC filter developed in previous blog post, we now have the modeling tools to model the entire power supply under development.

Power supply model design

The power supply with all of the inductors are capacitors is pictured on Figure 5. The power supply has grid inverter and output inverter with an isolated bidirectional converter to isolate the DC links from each other.

The grid and output side EMI filter components are numbered with green for grid side and blue used for output side EMI filter components. The large black cans are the grid and output side dc link capacitors. The transformer of the DC/DC converter has not been built as of writing, so it is not visible in the Figure 5.

Figure 5. The power supply with reactive components populated. The green numbered components are the primary side reactive components of the grid inverter and blue numbers correspond to output inverter filter components.

Grid inverter model

Looking at the power supply in the picture the grid inverter can be observed to have a two stage EMI filter which is formed by the capacitors 4 and 6 and by the EMI filter inductors 3 and 5 in addition to the main LC filter formed by inductor 1 and capacitor 2. The circuit model for the grid inverter is shown in Figure 6 with main and EMI filter stages pictured.

There is a notable issue with connecting the model to grid. Since the capacitor is first, an input voltage cannot be directly added as the capacitor voltage equation is interfaced with current. Either a resistance or a inductance is needed to transform the grid voltage to capacitor current. We will use an inductance in this case to model the inductance of the grid.

Figure 6. Grid inverter electrical model

The state-space model for the inverter with a two stage EMI filter is built by connecting the EMI LC filters in series with the LC filter of the inverter. This can be done quite easily by just writing several LC filter equations and using the previous LC filters capacitor as the input voltage and succeeding filters inductor current as the load as was described in the previous blog post. The state-space model for the grid side inverter without DC link is

Output inverter model

The output inverter is filtered with a single section EMI filter, thus it has the same model as the grid inverter but without the grid inductance and one less EMI filter section.

Figure 7. Grid inverter electrical model

DC link model

The DC link capacitors are loaded by the grid and output inverter currents and a bidirectional DC/DC converter which is controlling the difference of the DC link voltages. The converter is assumed to have dynamics so fast that the PI controller controls the current directly. The PI controller discharges the grid side DC link and charges the output side DC link with the same current. The model for the DC links is thus two capacitors which are loaded by the inverters and a PI controller that controls the voltage difference by charging the output DC link from the input DC link.

Figure 8. PI controller connecting the two dc links together.

For the model, symbol \omega is used for the integrator state of the PI controller and the constants k_p and k_i are the PI controller gains. The controlled variable is the current i_{pi}. With the PI controller state added, the DC link model can be written as

The full system model is just the combination of all of these sub systems. The equations are interconnected as the grid inverters primary inductor current is the source for the grid side DC link capacitor current, multiplied by the grid inverter duty ratio. The PI controller controls the DC link voltages by loading the primary and secondary side DC links with currents of alternate signs but equal value, thus discharging the primary side with same amount as it is charging the secondary side. The secondary DC link is loaded by the output inverter main inductor current and the current is transferred to the load of the output inverter through the EMI filter. The overall system is quite complex, but it is made manageable by designing the different circuits separately.

Next the system simulation with synthesizable VHDL is developed.

For full source code of the individual parts of the system see math library sources and associated test benches in the github /ac_inout_psu/source/math_library/

Full system simulation with FPGA

The full system is instantiated by a signal with power_supply_model_record type. The power supply simulation is created from the object by a call to create_power_supply_simulation_model procedure.

The power supply model object has a grid inverter, a output inverter, a pi controller for controlling the DC links, a state variable for the grid inductor model and two multipliers for the pi controller and grid inductor model calculation. All of these objects have similar create_model procedures which are called in succession inside the create_power_supply_simulation_model.

The implementation of the create_power_supply_simulation_model simply creates the different building blocks and connects them together as described in the model creation.

				
					library math_library;
    use math_library.multiplier_pkg.all;
    use math_library.state_variable_pkg.all;
    use math_library.psu_inverter_simulation_models_pkg.all;
    use math_library.pi_controller_pkg.all;
 
package power_supply_simulation_model_pkg is
 
------------------------------------------------------------------------
    type power_supply_model_record is record
        grid_inverter_simulation       : grid_inverter_record;
        output_inverter_simulation     : output_inverter_record;
        dab_pi_controller              : pi_controller_record;
        multiplier                     : multiplier_record;
        grid_inductor_model_multiplier : multiplier_record;
        grid_inductor_model            : state_variable_record;
    end record;
 
    constant power_supply_model_init : power_supply_model_record := (grid_inverter_init, output_inverter_init, pi_controller_init, multiplier_init_values,  multiplier_init_values, init_state_variable_gain(5e2));
 
    --------------------------------------------------
    procedure create_power_supply_simulation_model (
        signal power_supply_simulation : inout power_supply_model_record;
        grid_voltage : int18;
        output_inverter_load_current : int18);
    --------------------------------------------------
    procedure request_power_supply_calculation (
        signal power_supply_simulation : inout power_supply_model_record;
        grid_inverter_duty_ratio       : int18;
        output_inverter_duty_ratio     : int18);
------------------------------------------------------------------------
 
end package power_supply_simulation_model_pkg;
 
package body power_supply_simulation_model_pkg is
 
------------------------------------------------------------------------
    procedure create_power_supply_simulation_model
    (
        signal power_supply_simulation : inout power_supply_model_record;
        grid_voltage : int18;
        output_inverter_load_current : int18
    ) is
        alias grid_inverter_simulation is power_supply_simulation.grid_inverter_simulation;
        alias output_inverter_simulation is power_supply_simulation.output_inverter_simulation;
        alias dab_pi_controller is power_supply_simulation.dab_pi_controller;
        alias multiplier is power_supply_simulation.multiplier;
        alias grid_inductor_model_multiplier is power_supply_simulation.grid_inductor_model_multiplier;
        alias grid_inductor_model is power_supply_simulation.grid_inductor_model;
    begin
        create_multiplier(multiplier);
        create_pi_controller(multiplier, dab_pi_controller, 18e2, 4e2); 
 
        create_multiplier(grid_inductor_model_multiplier);
        create_state_variable(grid_inductor_model, grid_inductor_model_multiplier, power_supply_simulation.grid_inverter_simulation.grid_emi_filter_2.capacitor_voltage.state + grid_voltage);
 
        create_grid_inverter(grid_inverter_simulation, -dab_pi_controller.pi_out, grid_inductor_model.state);
        create_output_inverter(output_inverter_simulation, dab_pi_controller.pi_out, output_inverter_load_current);
         
    end create_power_supply_simulation_model;
				
			

With the layered design, the full power supply simulation model is now available with only three lines of application code, one for instantiating signal for power supply simulator object, one that creates a power supply using the power_supply_simulation object and then a trigger for starting the simulation.

Hierarchical design of the simulation model

If we peek under the hood to see on how the simulation is actually created, we can see that even the individual parts of the complete simulator are further instantiating objects that form them. For example the system model calls create_grid_inverter, which further creates an inverter, two LCR filters and a state variable for the DC link voltage that form the grid inverter. The LCR filter and state variables were developed in VOL 1 of this simulation saga.

				
					package psu_inverter_simulation_models_pkg is
 
------------------------------------------------------------------------
    type grid_inverter_record is record
        grid_inverter : inverter_model_record;
        multiplier1 : multiplier_record;
        multiplier2 : multiplier_record;
        grid_emi_filter_1 : lcr_model_record;
        grid_emi_filter_2 : lcr_model_record;
    end record;
 
    constant grid_inverter_init : grid_inverter_record := (init_inverter_model, multiplier_init_values, multiplier_init_values,init_lcr_model_integrator_gains(25e3, 2e3), init_lcr_model_integrator_gains(25e3, 2e3));
------------------------------------------------------------------------
.
.
.
 
package body psu_inverter_simulation_models_pkg is
------------------------------------------------------------------------
    procedure create_grid_inverter
    (
        signal grid_inverter : inout grid_inverter_record;
        dc_link_load_current : in int18;
        ac_load_current : in int18
    ) is
        alias emi_filter1 is grid_inverter.grid_emi_filter_1;
        alias emi_filter2 is grid_inverter.grid_emi_filter_2;
        alias inverter_lc is grid_inverter.grid_inverter.inverter_lc_filter;
    begin
        create_multiplier(grid_inverter.multiplier1);
        create_multiplier(grid_inverter.multiplier2);
        create_inverter_model(grid_inverter.grid_inverter , dc_link_load_current      , -emi_filter1.inductor_current);
        create_lcr_filter(grid_inverter.grid_emi_filter_1 , grid_inverter.multiplier1 , inverter_lc.capacitor_voltage - emi_filter1.capacitor_voltage , emi_filter1.inductor_current - emi_filter2.inductor_current);
        create_lcr_filter(grid_inverter.grid_emi_filter_2 , grid_inverter.multiplier2 , emi_filter1.capacitor_voltage - emi_filter2.capacitor_voltage , emi_filter2.inductor_current - ac_load_current);
 
    end create_grid_inverter;
				
			

Going even further down the type hierarchy to the inverter creation we start seeing some implementation for how the inverter model is actually calculated and even it is is using state variables which is further built using an interface for a multiplier object.

				
					package body inverter_model_pkg is
 
------------------------------------------------------------------------
    procedure create_inverter_model
    (
        signal inverter_model : inout inverter_model_record;
        dc_link_load_current : in int18;
        load_current : in int18
    ) is
        alias inverter_multiplier is inverter_model.inverter_multiplier;
        alias dc_link_voltage is inverter_model.dc_link_voltage;
        alias dc_link_current is inverter_model.dc_link_current;
        alias inverter_lc_filter is inverter_model.inverter_lc_filter;
        alias input_voltage is inverter_model.input_voltage;
        alias grid_inverter_state_counter is inverter_model.grid_inverter_state_counter;
        alias duty_ratio is inverter_model.duty_ratio;
 
    --------------------------------------------------
 
    begin
        create_multiplier(inverter_multiplier);
        create_state_variable(dc_link_voltage, inverter_multiplier, -dc_link_current - dc_link_load_current); 
        create_lcr_filter(inverter_lc_filter, inverter_multiplier, input_voltage - inverter_lc_filter.capacitor_voltage, inverter_lc_filter.inductor_current.state + load_current);
 
        CASE grid_inverter_state_counter is
            WHEN 0 =>
                multiply(inverter_multiplier, dc_link_voltage.state, duty_ratio);
                grid_inverter_state_counter <= grid_inverter_state_counter + 1;
            WHEN 1 =>
                multiply(inverter_multiplier, inverter_lc_filter.inductor_current.state, duty_ratio);
                grid_inverter_state_counter <= grid_inverter_state_counter + 1;
            WHEN 2 =>
                if multiplier_is_ready(inverter_multiplier) then
                    input_voltage <= get_multiplier_result(inverter_multiplier, 15);
                    grid_inverter_state_counter <= grid_inverter_state_counter + 1;
                end if;
            WHEN 3 =>
                dc_link_current <= get_multiplier_result(inverter_multiplier, 15);
                grid_inverter_state_counter <= grid_inverter_state_counter + 1;
            WHEN 4 =>
                calculate(dc_link_voltage);
                increment_counter_when_ready(inverter_multiplier, grid_inverter_state_counter);
            WHEN 5 =>
                calculate_lcr_filter(inverter_lc_filter);
                grid_inverter_state_counter <= grid_inverter_state_counter + 1;
            WHEN others => -- wait for restart
        end CASE;
         
    end create_inverter_model;
				
			

As was discussed with the bandpass filter design, the use of a layered design like this allows complex designs to be created by designing smaller parts incrementally with the incremental parts being tested and designed individually. There are a bunch of hard coded constants in the design at the moment, but since VHDL allows overloading and nesting procedure and function calls, the constants can be made changeable later by adding the required features when needed.

FPGA simulation of the power supply

For the FPGA simulation, the power supplies are operated with constant voltage at the input, duty ratios of 0.5 are used for input and output inverters and output is fed with a resistive load. In the simulation, the duty ratios have a valid range of -32768 to 32768. The simulation model is instantiated in a test code in system_components, which also controls the uart and ethernet communications. The highlighted parts of the code correspond to the power supply simulation model creation, datalogging and response generation.

				
					 signal hw_multiplier                : multiplier_record := multiplier_init_values;
    signal output_resistance            : int18             := 12e3;
    signal output_inverter_load_current : int18             := 0;
 
    signal power_supply_simulation : power_supply_model_record := power_supply_model_init;
 
    signal grid_duty_ratio : int18 := 15e3;
    signal output_duty_ratio : int18 := 15e3;
    signal output_load_current : int18 := 2e3;
--------------------------------------------------
begin
 
--------------------------------------------------
    test_with_uart : process(clock)
    --------------------------------------------------
        --------------------------------------------------
         
    begin
        if rising_edge(clock) then
 
            -------------------------------------------------- 
            create_bandpass_filter(bandpass_filter);
 
            -------------------------------------------------- 
            init_mdio_driver(mdio_driver_data_in);
 
            -------------------------------------------------- 
            idle_adc(spi_sar_adc_data_in);
 
            -------------------------------------------------- 
            init_uart(uart_data_in);
            receive_data_from_uart(uart_data_out, uart_rx_data);
            system_components_FPGA_out.test_ad_mux <= integer_to_std(number_to_be_converted => uart_rx_data, bits_in_word => 3);
            -------------------------------------------------- 
            create_power_supply_simulation_model(power_supply_simulation, 8e3, output_inverter_load_current + output_load_current);
 
            -------------------------------------------------- 
            create_multiplier(hw_multiplier); 
            sequential_multiply(hw_multiplier, power_supply_simulation.output_inverter_simulation.output_emi_filter.capacitor_voltage.state, output_resistance);
            if multiplier_is_ready(hw_multiplier) then
                output_inverter_load_current <= get_multiplier_result(hw_multiplier, 15);
            end if;
            -------------------------------------------------- 
 
            uart_transmit_counter <= uart_transmit_counter - 1; 
            if uart_transmit_counter = 0 then
                uart_transmit_counter <= counter_at_100khz;
                start_ad_conversion(spi_sar_adc_data_in); 
            end if; 
 
            if ad_conversion_is_ready(spi_sar_adc_data_out) then
                request_power_supply_calculation(power_supply_simulation, -grid_duty_ratio, output_duty_ratio);
 
                CASE uart_rx_data is
                    WHEN 10 => transmit_16_bit_word_with_uart(uart_data_in, get_filter_output(bandpass_filter.low_pass_filter) );
                    WHEN 11 => transmit_16_bit_word_with_uart(uart_data_in, (bandpass_filter.low_pass_filter.filter_input - get_filter_output(bandpass_filter.low_pass_filter))/2+32768);
                    WHEN 12 => transmit_16_bit_word_with_uart(uart_data_in, get_filter_output(bandpass_filter)/2+32768);
                    WHEN 13 => transmit_16_bit_word_with_uart(uart_data_in, bandpass_filter.low_pass_filter.filter_input - get_filter_output(bandpass_filter));
                    WHEN 14 => transmit_16_bit_word_with_uart(uart_data_in, get_adc_data(spi_sar_adc_data_out));
                    WHEN 15 => transmit_16_bit_word_with_uart(uart_data_in, uart_rx_data);
                    WHEN 16 => transmit_16_bit_word_with_uart(uart_data_in, power_supply_simulation.output_inverter_simulation.output_emi_filter.capacitor_voltage.state/2 + 32768);
                    WHEN 17 => transmit_16_bit_word_with_uart(uart_data_in, power_supply_simulation.output_inverter_simulation.output_emi_filter.inductor_current.state/2+ 32768);
                    WHEN 18 => transmit_16_bit_word_with_uart(uart_data_in, power_supply_simulation.output_inverter_simulation.output_inverter.dc_link_voltage.state/2);
                    WHEN 19 => transmit_16_bit_word_with_uart(uart_data_in, power_supply_simulation.grid_inverter_simulation.grid_emi_filter_2.capacitor_voltage.state/4 + 32768);
                    WHEN 20 => transmit_16_bit_word_with_uart(uart_data_in, power_supply_simulation.grid_inverter_simulation.grid_emi_filter_2.inductor_current.state/4+ 32768);
                    WHEN 21 => transmit_16_bit_word_with_uart(uart_data_in, power_supply_simulation.grid_inverter_simulation.grid_inverter.dc_link_voltage.state/2);
                    WHEN others => -- get data from MDIO
				
			

The measured signals from the simulation model as chosen to be the current and voltage of the EMI filter which is connected to the grid. The DC link voltages and output inverter EMI filter voltage and current. These are chosen by transmitting numbers 16 – 21 to the FPGA via the debug console software. See the build instructions in the navigation panel for the PC console software.

The model is exited by applying steps to grid side inverter duty ratio, output inverter duty ratio, output resistance and output load current. The calculation step is 10us or 100kHz which was chosen as it can be transmitted with the UART without buffering the data.

				
					test_counter <= test_counter + 1; 
if test_counter = 65535 then
    test_counter <= 0;
end if;
 
CASE test_counter is
    WHEN 0     => output_duty_ratio   <= 20e3;
    WHEN 8192  => grid_duty_ratio     <= 15e3;
    WHEN 16384 => output_resistance   <= 40e3;
    WHEN 24576 => output_load_current <= -output_load_current;
    WHEN 32768 => output_duty_ratio   <= 15e3;
    WHEN 40960 => grid_duty_ratio     <= 20e3;
    WHEN 49152 => output_resistance   <= 30e3;
    WHEN 57344 => output_load_current <= -output_load_current;
     
    WHEN others => -- do nothing
end CASE;
				
			

Since the whole system is interconnected, all step responses are visible in all signals. Figure 9 shows the grid side EMI filter capacitor voltage and inductor current. As can be seen, all step changes are visible in both traces even though the steps are injected to different parts of the simulation model showing the interconnected nature of the whole system. Since the grid inductance does not have a series resistor, the input voltage always stabilizes to same value after all steps, but the presence of the grid inductor makes all steps visible in the voltage.

Figure 9. Grid side EMI filter current and voltage

The DC link voltage shown in Figure 10 has slight only slight variation in the voltage to which it settles to, since all other inductors have a small series resistance. However, the when the grid side inverter duty ratio is changed a significant change in the steady state can be observed.

The zoomed in figure of the duty ratio induced step in the voltage shows the interesting feature that the voltage initially goes in the opposite direction to which it then settles to after the transient. It is a bit hard to see due to the additional ringing coming from the rest of the circuit, but the first ringing part goes in the opposite direction to which the final voltage settles to.

The way this works is that initially the DC link current is lowered due to lowered duty ratio and the voltage starts to drop as discharging current is not changed immediately. This continues until the current in the inverter inductor increases to the point at which the product of the duty and inductor voltage is again higher than initially. The DC link voltage settles to a point where inductor current is again balanced, ie duty*dc link voltage equals the voltage in the grid side of the main inductor. This opposite direction dynamic during a duty ratio change is a well known feature of a boost converter quite intuitively named, nonminimum phase dynamic.

Figure 10 grid side DC link voltage

Figure 11 Zoomed in grid side dc link voltage during duty step change

The output side currents and voltages are shown in Figure 12. Since the system is currently operated without feedback control, the output current is influenced by the output voltage, which again is influenced by all of the voltages between the input and output of the power supply. When the output voltage and the DC link voltages are compared, the output voltage can be seen to have an additional voltage level. This is because the output duty ratio is changed.

Figure 12 output EMI inductor and capacitor voltage and current

FPGA Synthesis results

The resource utilization is shown in Figure 13. Although the full system simulation is built with several separate simulation models all with their own multipliers, the overall system is relatively efficient in its use of resources. This is mainly due to the use of 18 bit word length for the calculation, but also many of the multiplications inside the model utilize common multipliers.

For testing purposes, the full system model is instantiated in the system components layer which takes a total of 1287 registers and slightly under 2600 logic cells, and the whole design currently can meet timing up to 130MHz for the core clock. The test code still has the all of the parts of the design developed in previous blog posts in it including bandpass filter, uart, ethernet with its small communication stack so the simulation model takes up rougly half of the entire design or approximately 1900 logic cells. This is still quite low amount of logic, as even the smallest FPGA of the cyclone 10 lp family has 6000 logic cells available and the entire design currently consuming only 14% of the total logic available in the 25k logic FPGA.

Figure 13 the resource utilization of the design with simulation model for the power electronics included

One last thing that needs to emphasized is that even though the code is developed with well partitioned hierarchical design built from separate callable objects, the resulting synthesized circuit is not in any way slowed down by the used abstraction. If this real-time simulator was developed using for example C++ and run on a DSP, the function calls and overloaded operators used for calling the individual state variable objects and their overloaded arithmetic operations would likely add heavy amount of overhead to the execution in the form additional function calls when compared to writing the same simulation code in C++ without the abstractions. When real-time performance is concerned, VHDL is among the only languages with abstraction actually having no impact on performance. By making the code more understandable, abstraction actually makes it easier to write resource efficient code.

The full model takes 24 clock cycles for calculating a single simulation cycle and this could be easily lowered by using more multipliers or pipelining the multiplications inside LCR filter or inverter simulations. VHDL is considered a very verbose language, but here we are instantiating a 14th order dynamic system simulation of a power supply with just a few lines of application code which are run in real time on one of the lowest cost FPGAs at clock speed of 120MHz resulting in maximum achievable calculation speed of 5MHz!

Next in line

The original intent was to use the real inductor and capacitor values for the model, but in order to have the full system model operational, the different converters need to have feedback controls and their associated modules in place. Also sine calculation is needed for the grid voltage simulator as well as the inverter voltage reference and these will be developed in future blog posts.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top