Inherit via Back Propagation

The purpose of the setting Inherit via Back Propagation is for a block have its output data type set external to the block.

This is a powerful capability that allows the creation of subsystems that react to incoming data types in ways desired by the author of the subsystem.

As a simple example, look under the mask of the Data Type Conversion Inherited block from the base Simulink library.

The Conversion block has its Output Data Type parameter set to Inherit via Back Propagation.

Using the Inherit via Back Propagation setting in combination with the Data Type Duplication block will automatically set the data type of the Conversion block output to be the same as the Inport block named DTS Reference.

The Data Type Conversion Inherited is equivalent to MATLAB's powerful cast-like capability.

y = cast(u,'like',DTS_reference);

Any Simulink block with the option Inherit via Back Propagation, not just data type conversion, can have a 'like' capability.

In addition, the data type can be set via many mechanisms not just Data Type Duplication. The Data Type Propagation block is just one of the many other mechanisms that can be used. Lots of flexibility is in the hands of the subsystem author to create their own custom data type propagation rules.

To understand more about the flow of data type information between blocks, please watch this

Inherit via Internal Rule

Let's try to get a sense of the Inherit via Internal Rule at a conceptual level. The goal here is not to describe every detail of the rule for every block. The goal is to just give a deeper sense of the high level intent of the rule.

Let's use an specific example.

"Simulink chooses a data type to

balance numerical accuracy, performance, and generated code size,

while taking into account the properties of the embedded target hardware."

To determine the "balance of numerical accuracy, performance, and generated code size", the block considers

- Attributes of the inputs, especially their data types
- Parameters specifying operation of the block
- Production embedded target as specified on model's Hardware Implementation pane, especially ASIC/FPGA vs micro

Double dominates, then Single, then everything else

Using this information, Product block will first give priority to floating-point.

- If any input is double, then the output is double
- Otherwise, if any input is single, then the output is single

Note: you can expect double then single to be the dominant rule on other blocks supporting an inherit via internal rule.

Fixed-Point and Integer

If floating-point does not "win", then fixed-point rules will be applied. Note, the rule treats integers as fixed-point types that just happen to have trivial scaling.

Numerical Accuracy Starting Point: First Determine Ideal Full Precision Type

As an example, let's consider the ideal product of int8 times int16.

------------------------------

Input 1 representable extremes.

Data type is numerictype(1,16,0)

Real World Notation: Binary Point

-32768 = 1000000000000000.

32767 = 0111111111111111.

------------------------------

Input 2 representable extremes.

Data type is numerictype(1,8,0)

Real World Notation: Binary Point

------------------------------

Product of Input 1 and 2 extremes.

Data type is numerictype(1,24,0)

Real World Notation: Binary Point

-4194176 = 110000000000000010000000.

-1 = 111111111111111111111111.

0 = 000000000000000000000000.

1 = 000000000000000000000001.

4194304 = 010000000000000000000000.

------------------------------

Example product hi times hi.

Type Real World Notation: Binary Point

numerictype(1,16,0) 32767 = 0111111111111111.

numerictype(1,8,0) 127 = 01111111.

numerictype(1,24,0) 4161409 = 001111110111111110000001.

------------------------------

Example product lo times lo.

Type Real World Notation: Binary Point

numerictype(1,16,0) -32768 = 1000000000000000.

numerictype(1,8,0) -128 = 10000000.

numerictype(1,24,0) 4194304 = 010000000000000000000000.

------------------------------

Example product hi times lo.

Type Real World Notation: Binary Point

numerictype(1,16,0) 32767 = 0111111111111111.

numerictype(1,8,0) -128 = 10000000.

numerictype(1,24,0) -4194176 = 110000000000000010000000.

The just big enough type to perfectly represent all possible products without overflow or loss of precisions is 24 bit signed, numerictype(1,24,0), equivalently fixdt(1,24,0).

If we did the same with non-trivial fixed-point scaling, the bit patterns and sizes would look exactly the same except for scaling. Say signed 8 bits with 4 bits to the right of the binary-point multiplied times signed 16 bits with 3 bits to the right of the binarypoint.

------------------------------

Input 1 representable extremes.

Data type is numerictype(1,16,3)

Real World Notation: Binary Point

-4096 = 1000000000000.000

-0.125 = 1111111111111.111

0.125 = 0000000000000.001

4095.875 = 0111111111111.111

------------------------------

Input 2 representable extremes.

Data type is numerictype(1,8,4)

Real World Notation: Binary Point

------------------------------

Product of Input 1 and 2 extremes.

Data type is numerictype(1,24,7)

Real World Notation: Binary Point

-32767 = 11000000000000001.0000000

-0.0078125 = 11111111111111111.1111111

0 = 00000000000000000.0000000

0.0078125 = 00000000000000000.0000001

32768 = 01000000000000000.0000000

------------------------------

Example product hi times hi.

Type Real World Notation: Binary Point

numerictype(1,16,3) 4095.875 = 0111111111111.111

numerictype(1,8,4) 7.9375 = 0111.1111

numerictype(1,24,7) 32511.0078125 = 00111111011111111.0000001

------------------------------

Example product lo times lo.

Type Real World Notation: Binary Point

numerictype(1,16,3) -4096 = 1000000000000.000

numerictype(1,8,4) -8 = 1000.0000

numerictype(1,24,7) 32768 = 01000000000000000.0000000

------------------------------

Example product hi times lo.

Type Real World Notation: Binary Point

numerictype(1,16,3) 4095.875 = 0111111111111.111

numerictype(1,8,4) -8 = 1000.0000

numerictype(1,24,7) -32767 = 11000000000000001.0000000

The just big enough type to perfectly represent all possible products without overflow or loss of precisions is again 24 bit signed, but with 3 + 4 = 7 bits to the right of the binary point. So the full-precision types is numerictype(1,24,7), equivalently fixdt(1,24,7).

If Production Target is ASIC/FPGA

Given the full-precison type, the next step is to consider the production target. If that target is ASIC or FPGA, then a 24 bit data type is perfectly natural and can be supported efficiently. So if the model's Hardware Implementation pane says the production target is ASIC/FPGA, then the output will be 24-bits, and exactly match the full precision type.

If Production Target is a microprocessor, low cost case

If the Hardware Implementation pane says the production target is a microcontrol such as ARM Cortex M, and support long long is turned on, the the target provides four integer types native to the C compiler [8, 16, 32, 64].

The full-precision type needs 24 bits which is less than the biggest integer available (64 bits), so handling 24 bits is low cost and the output will be full-precision. But the output will be put in the smallest of the four native integer sizes that can hold the ideal size. So 24 bits ideal will be "puffed up" to 32 bits native with the extra padding bits put on the most significant end.

numerictype(0,24,0) will puff to numerictype(0,32,0).

numerictype(0,24,7) will puff to numerictype(0,32,7).

For that latter example, this is what the "puffing up" looks like.

Product of Input 1 and 2 extremes.

Type Real World Notation: Binary Point

numerictype(1,24,7) -32767 = 11000000000000001.0000000

numerictype(1,32,7) -32767 = 1111111111000000000000001.0000000

numerictype(1,24,7) -0.0078125 = 11111111111111111.1111111

numerictype(1,32,7) -0.0078125 = 1111111111111111111111111.1111111

numerictype(1,24,7) 0 = 00000000000000000.0000000

numerictype(1,32,7) 0 = 0000000000000000000000000.0000000

numerictype(1,24,7) 0.0078125 = 00000000000000000.0000001

numerictype(1,32,7) 0.0078125 = 0000000000000000000000000.0000001

numerictype(1,24,7) 32768 = 01000000000000000.0000000

numerictype(1,32,7) 32768 = 0000000001000000000000000.0000000

If Production Target is a microprocessor, too expensive case

If the ideal product needs more bits than the biggest integer provided by the compiler of the production hardware, then supporting that large type is deemed to have too big of a negative impact on performance and code size. So the ideal product type will be trimmed down. Overflows would have the worst impact on numeric accuracy, so bits will NOT be trimmed from the most significant range end. Instead bits will be trimmed from the least significant precision end.

Let's consider an example of signed 32 bits times signed 16 bits. So the ideal product is signed 48 bits. Now let's assume the biggest integer size available on the production embedded target is 32 bits. So 8 precision bits from the least significant end of the ideal product type will be discard.

Example

nt_input1 = numerictype(1,32,30);

nt_input2 = numerictype(1,8,4);

nt_ideal_product = numerictype(1,40,34);

nt_out_internal_rule = numerictype(1,32,26);

example values, ideal vs. actual internal rule output

Product of Input 1 and 2 extremes.

Type Real World Notation: Binary Point

numerictype(1,40,34) -15.999999992549419 = 110000.0000000000000000000000000010000000

numerictype(1,32,26) -15.999999985098839 = 110000.00000000000000000000000001

numerictype(1,40,34) 5.8207660913467407e-11 = 000000.0000000000000000000000000000000001

numerictype(1,32,26) 0 = 000000.00000000000000000000000000

numerictype(1,40,34) 16 = 010000.0000000000000000000000000000000000

numerictype(1,32,26) 16 = 010000.00000000000000000000000000

As you can see, 8 precision bits have been dropped leading to modest rounding errors. But the good news is that no big overflow errors have occured due to using a smaller than ideal output data type.

When trimming bits, ideal integer may get non-trivial fixed-point scaling

When trimming of bits is necessary, it can occur that built-in integer inputs can produce a fixed-point output.

nt_input1 = numerictype(1,32, 0);

nt_input2 = numerictype(1, 8, 0);

nt_ideal_product = numerictype(1,40, 0);

nt_out_internal_rule = numerictype(1,32, -8);

Notice that the output type has non-trivial scaling (Slope = 256) because the 8 least significant bits of the ideal product were dropped. Note because the Slope is greater than 1, binary point notation breaks down. This is easy to handle by switching from "point" notation to "scientific notation." But instead for switching decimal-point notation to decimal power of 10 scientific notation, we are switching from binary-point notation to binary power of 2 scientific notation.

Type Real World Notation: Integer Mantissa

numerictype(1,40,0) 272730423169 = 0011111101111111111111111111111110000001 * 2^0

numerictype(1,32,-8) 272730423040 = 00111111011111111111111111111111 * 2^8

Summary on Internal Rule

Hopefully, these examples have given a better sense of what is meant by

Internal Rule Goal: balance numerical accuracy, performance, and generated code size

Similar principles would apply to other blocks. Double wins, then Single, then full-precision is left alone, puffed-up, or trimmed-down depending on the target. For integer and fixed-point, avoid overflows and give up precision only if too costly to keep.