Engee documentation

Working with parameters of signals for custom templates

Page in progress.

This article describes an approach to obtaining detailed signal information using custom templates. It is recommended to read the article on custom templates code generation before studying the current one.

By generating code based on custom templates, it is possible to learn useful information about signals such as size, data type, sampling rate, and other data.

Next, let’s look at the key structures, fields, and their use in custom templates to learn information about signals.

Structure signal

The signal structure describes the signal in custom templates and provides access to all necessary parameters through the ty field. These signals are represented as elements of the ins (input ports) and outs (output ports) arrays, or as individual ports, such as input(1) or output(1). The following fields of the signal structure are available:

  • expr :: Cstring - the expression associated with the signal.

  • qual_name :: Cstring - the fully qualified name of the signal.

  • pretty_name :: Cstring - the readable (pretty) name of the signal.

  • ieas_name :: Cstring - the name of the atomic subsystem closest to the selected block.

  • indirect :: Cuchar - flag of indirect reference to the signal.

  • rows :: Csize_t - number of lines in the signal.

  • cols :: Csize_t - number of columns in the signal.

  • bits :: Cint - data bitness of the signal.

  • fractional_bits :: Cint - number of fractional bits.

  • is_unsigned :: Cuchar - flag determining whether the signal is unsigned.

  • is_floating :: Cuchar - flag determining whether the signal is a floating point number.

  • is_complex :: Cuchar - flag defining whether the signal is complex.

Field ty

The ty field operates in the signal structure and provides information about the signal data type. The ty field has the following key parameters to describe the data type:

  • bits :: Cint - data digitisation.

  • fractional_bits :: Cint - the number of fractional bits.

  • is_unsigned :: Cuchar - flag determining whether the type is unsigned.

  • is_floating :: Cuchar - flag determining whether the type is a floating point number.

  • is_complex :: Cuchar - flag determining whether the type is complex.

Then the example of usage via ports including signal and ty will look like this:

/* Получение информации о типе данных и размерах первого входного сигнала */
if $(ins[1].ty.is_floating) {
    // Логика для чисел с плавающей точкой
}

if $(ins[1].rows) > 1 {
    // Логика для многомерного массива
}

The example shows the use of the signal and ty structures to obtain information about an input signal represented as ins[1]. Let us consider the main points:

  1. Accessing data type information via ty:

    if $(ins[1].ty.is_floating) {
        // Логика для чисел с плавающей точкой
    }
    • The ty field contains the characteristics of the signal’s data type.

    • In this case, the is_floating property is used, which returns a flag (true or false) indicating whether the signal is a floating point number.

    • The condition checks whether the signal is of type float. If yes (the logic is executed), then this logic is specific to floating point number processing.

  2. Access to the dimensionality of the signal via rows:

    if $(ins[1].rows) > 1 {
        // Логика для многомерного массива
    }
    • The rows property returns the number of rows in the signal.

    • Here we check if the signal is a multidimensional array (the number of rows is greater than 1). If the condition is fulfilled, the logic for processing multidimensional signals is applied.

In the example, ins[1] represents the input signal, which is an instance of the signal structure. Information about the signal is accessed through its properties (rows, cols, ty and others). For example, rows and cols give the dimensions of the signal, and the ty field provides detailed information about the signal data type (e.g., digit, fractional part, signability).


It is important to remember that the ins and outs arrays represent input and output ports respectively. In case a block autonomously generates signals (e.g., Sin or Constant blocks), arrays may not be necessary. Instead, inbuilt functions or parameters are used:

  • Example for block Sine Wave Function:

    //! BlockType = :Sin
    //! TargetLang = :C
    
    //! @Step
    $(output_datatype_name(1)) $(output(1)) = sin($(get_baserate()) * $(model_substep(1)));
  • Example code for the block Switch:

    //! BlockType = :Switch
    //! TargetLang = :Chisel
    
    //! @Definitions
    val $(output(1)) = Wire(FixedPoint($(output(1).ty.bits).W, $(output(1).ty.fractional_bits).BP))
    
    /*! @Step
    function get_operator()
        param.Criteria == "u2 > Threshold" ? "$(input(2)) > $(param.Threshold).U" : "$(input(2)) =/= 0.U"
    end
    function get_number_type(s::String)
        out = ".asTypeOf(FixedPoint($(output(1).ty.bits).W, $(output(1).ty.fractional_bits).BP))"
    end
    */
    when($(get_operator())) {
        $(output(1)) := $(input(1))$(get_number_type(input(1).full_qual_name))
    }
    .otherwise {
        $(output(1)) := $(input(3))$(get_number_type(input(3).full_qual_name))
    }

Application in templates

In custom templates, you can refer to signal properties via the ins and outs arrays. For example, define the output signal type data and process the input signal:

//! BlockType = :RelationalOperator
//! TargetLang = :Chisel

/*! @Definitions
function get_dec_type(port)
    if port.ty.fractional_bits == 0 && port.ty.bits == 1
        out = "Bool()"
    elseif port.ty.fractional_bits == 0
        out = "UInt($(port.ty.bits).W)"
    else
        out = "FixedPoint($(port.ty.bits).W,$(port.ty.fractional_bits).BP)"
    end
    out
end
*/
val $(output(1)) = Wire($(get_dec_type(output(1))))

/*! @Step
function get_type(port)
    if port.ty.fractional_bits == 0 && port.ty.is_unsigned == 1
        out = ".asTypeOf(UInt($(port.ty.bits).W))"
    else
        out = ".asTypeOf(FixedPoint($(port.ty.bits).W,$(port.ty.fractional_bits).BP))"
    end
    out
end
*/

$(output(1)):=$(input(1)) $(param.Operator) $(input(2))$(get_type(input(1)))

In this template for the Relational Operator block, the signal is used via the input(1), input(2), and output(1) ports. The ty field provides access to information about the type of these signals. Next, let’s analyse the main parts of the code:

  • Determining the output signal type:

    function get_dec_type(port)
        if port.ty.fractional_bits == 0 && port.ty.bits == 1
            out = "Bool()"
        elseif port.ty.fractional_bits == 0
            out = "UInt($(port.ty.bits).W)"
        else
            out = "FixedPoint($(port.ty.bits).W,$(port.ty.fractional_bits).BP)"
        end
        out
    end
    val $(output(1)) = Wire($(get_dec_type(output(1))))

    Here the get_dec_type function defines the data type for the output signal type output(1):

    • If the signal has 1 bit and no fractional part (fractional_bits == 0), then its type is set as Bool().

    • If the signal is integer (fractional_bits == 0), then its type is set as UInt with the specified number of bits (bits).

    • In other cases usage of fixed point (FixedPoint) with specified digit capacity and number of fractional bits is assumed.


  • Obtain the data type for the input signal:

    function get_type(port)
        if port.ty.fractional_bits == 0 && port.ty.is_unsigned == 1
            out = ".asTypeOf(UInt($(port.ty.bits).W))"
        else
            out = ".asTypeOf(FixedPoint($(port.ty.bits).W,$(port.ty.fractional_bits).BP))"
        end
        out
    end

    This function determines the conversion type for the input signal input(1):

    • If the signal is integer and unsigned (is_unsigned == 1), it is converted to UInt type.

    • In other cases the signal is converted to FixedPoint type.


  • Basic block logic:

    $(output(1)):=$(input(1)) $(param.Operator) $(input(2))$(get_type(input(1)))

    Signal comparison is performed here:

    • The comparison operator ($(param.Operator)) is passed through the template parameters.

    • The left operand is the input signal input(1) and the right operand is input(2).

    • The type of conversion for operands is set using the get_type function.

Total, in this code of the custom template:

  • ty is used to get information about the digit capacity (bits), the number of fractional bits (fractional_bits), the signability (is_unsigned), and other signal characteristics.

  • The input and output ports represent instances of the signal structure, the properties of which are accessed through the ty field.

  • The main task of the code is correct definition of data types for signals and their transformation depending on the parameters of the block.