Engee documentation
Notebook

Fixed-point data types

In this example, let's look at the possibilities of interacting with fixed-point numbers in both scripts and models.

Fixed–point numbers are numbers whose binary representation is limited by the size of their integer and fractional parts. These numbers do not have the ability to change the position of a point in the bit representation of a number.

Let's look at the implementations presented in Engee. To determine a fixed-point number, the function is used fi(Value, Sign, Total_bits, Fractional_bits). It allows you to set any numbers within a single command, and it is also not bound to a fixed number of bits per word, which, accordingly, allows you to obtain an algorithm with optimal memory costs.

Now let's look at what the parameters of this function mean.

  1. Value – the values of the original number.
  2. Sign – a sign (1-signed, 0-unsigned types).
  3. Total_bits – full word size.
  4. Fractional_bits – the size of the fractional part.
In [ ]:
Value = 128.9
Sign = 1;
Total_bits = 16;
Fractional_bits = 7;

println("fi: $(fi(Value, Sign, Total_bits, Fractional_bits))")
fi: 128.8984375

Let's do some tests for fi() .

Let's set an unsigned 7-bit number with a zero integer part.

In [ ]:
x = fi(0.8,0,7,7)
Out[0]:
fi(0.796875, 0, 7, 7)

Now add a number with an integer part to it. As we can see, the number type was automatically reassigned.

In [ ]:
x + fi(2.8,0,8,6)
Out[0]:
fi(3.59375, 0, 10, 7)

Now let's declare a negative 8-bit number with 5 bits per fractional part.

In [ ]:
x = fi(-1.8,1,8,5)
Out[0]:
fi(-1.8125, 1, 8, 5)

Multiply it by 3. As we can see, the final number has 64 bits more allocated memory for the whole part. This is due to the fact that the triple has an Int64 data type.

In [ ]:
3x
Out[0]:
fi(-5.4375, 1, 72, 5)

Accordingly, if we perform a subtraction operation, the data type will also be redefined.

In [ ]:
fi(2.8,1,16,11)-x
Out[0]:
fi(4.6123046875, 1, 17, 11)

In addition, we can explicitly reassign the number type ourselves.

In [ ]:
x = fi(x,1,7,5)
Out[0]:
fi(-1.8125, 1, 7, 5)

As we can see, the representation implemented in Engee is a truly versatile and overflow–resistant tool, as it automatically adds bits to a word depending on the data types used for a particular number.

Application of fixed-point calculations in models

Now let's move on to using fixed-point numbers in models. To do this, Engee provides the option to select the output data type in the block settings. The figure below shows the interface of one of these blocks.

In this example, a PID controller model is implemented using fixed-point logic.
A proportional-integral-differentiating regulator is a device in a feedback control circuit. The PID controller in the automatic control system gives a control signal of good accuracy and high quality.

The figure below shows the model we implemented.

Let's move on to the launch of this model.

In [ ]:
function run_model( name_model, path_to_folder )
    
    Path = path_to_folder * "/" * name_model * ".engee"
    
    if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
        model = engee.open( name_model ) # Открыть модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
    else
        model = engee.load( Path, force=true ) # Загрузить модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
        engee.close( name_model, force=true ); # Закрыть модель
    end

    return model_output
end
Out[0]:
run_model (generic function with 1 method)
In [ ]:
# Запуск модели
run_model( "simple_model_pid_fixed", @__DIR__ )
Building...
Progress 0%
Progress 78%
Progress 100%
Out[0]:
SimulationResult(
    "SubSystem.command" => WorkspaceArray{Fixed{1, 16, 13, Int16}}("simple_model_pid_fixed/SubSystem.command")

)

Next, to process the simulation data recorded in CSV, we will need to connect two libraries – DataFrames and CSV. To display this data, use the Plots library.

In [ ]:
# Подключение библиотек
using CSV
using DataFrames
using Plots

Now let's read the data from the CSV, plot the resulting graph, and analyze this data.

In [ ]:
command_fixed = Matrix(CSV.read("$(@__DIR__)/command_fixed.csv", DataFrame)); #загрузка данных
command_fixed = (command_fixed[:,2]);

Let's look at the structure of the recorded data. As we can see below, the recorded data is presented in a standard format and does not require additional processing.

In [ ]:
dump(command_fixed[101])
Float64 3.0198974609375

We will plot the recorded data, as well as analyze it based on the floating-point data recorded during the modeling process and compare their accuracy.

In [ ]:
command = Matrix(CSV.read("$(@__DIR__)/command.csv", DataFrame)); #загрузка данных
# Построение графиков 
plot(command[:,2]) 
plot!(command_fixed)
Out[0]:

As we can see, the graphs are approximately the same. In order to accurately determine the error, we will find their difference.

In [ ]:
sum(command[:,2]-command_fixed)
Out[0]:
0.0

It turned out that the difference was zero. It can also be seen that as a result, the logic of fixed-point numbers we used did not affect the principle of operation of the PID controller. Despite the loss of accuracy, the system tends to balance.

Conclusion

In this example, we have analyzed the possibilities of using fixed-point logic in scripts and models. We also found out how they can be saved from the model and analyzed or further processed in scripts.

Blocks used in example