Working with parameters of signals for custom templates
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.
By embedding $(sprint(dump, input(1))) in a template, you can find out, for example, the entire contents of all fields of the first input signal.
|
Structure of signal
The signal
structure describes the signal in user-defined templates and provides access to all necessary parameters via 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::String
- the expression of a constant or configurable parameter, if the signal is one; -
full_qual_name::String
- the fully qualified name of the signal; -
name::String
- readable (beautiful) name of the signal; -
ieas::String
- name of the atomic subsystem closest to the selected block; -
indirect::Bool
- flag of indirect reference to the signal; -
rows::UInt64
- number of lines in the signal; -
cols::UInt64
- number of columns in the signal; -
ty::typ
- signal type (see below); -
sample_time::Float64
- sampling frequency of the signal.
Field ty
The ty
field is used 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::Int
- data bit (width); -
fractional_bits::Int
- number of fractional bits; -
is_unsigned::Bool
- flag determining whether the type is unsigned; -
is_floating::Bool
- flag determining whether the type is a floating-point number; -
is_complex::Bool
- 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:
-
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
orfalse
) 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.
-
-
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
|
Application in templates
The template below implements the Relational Operator block for Verilog generation. Since Verilog code generation is done using Chisel, the templates are also written in Chisel (with usage of Julia metacode).
_ Example template for Relational Operator block _
//! BlockType = :RelationalOperator
//! TargetLang = :Chisel
/*! #----------------------------------------------------# */
//! @Definitions
val $(output(1)) = Wire($(show_chisel_type(output(1))))
/*! #----------------------------------------------------# */
/*! @Step
function patch_op(op)
op == "==" ? "===" : op == "~=" ? "=/=" : op
end
function get_idx(len, blen)
if len == 1
return ""
elseif len == blen
return "(i)"
else
return "(i % $(len))"
end
end
function maybe_zext(sig, sig2)
if sig.ty.is_unsigned && !sig2.ty.is_unsigned
return ".zext"
end
""
end
function impl(sig1, sig2, op, out)
dim0 = dim(out)
dim1 = dim(sig1)
dim2 = dim(sig2)
blen = lcm(dim1, dim2) # broadcasted length
if blen == 1
loop_decl = ""
else
loop_decl = "for (i <- 0 until $(blen)) "
end
"$(loop_decl)$(output(1))$(get_idx(dim0, blen)) := \
$(sig1)$(get_idx(dim1, blen))$(maybe_zext(sig1, sig2)) \
$(patch_op(op)) \
$(sig2)$(get_idx(dim2, blen))$(maybe_zext(sig2, sig1))""
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 show_chisel_type(x::typ) :: String if x.bits == 1 && x.fractional_bits == 0 out = "Bool()" elseif x.fractional_bits == 0 out = (x.is_unsigned ? "U" : "S") * "Int($(x.bits).W)" else out = "FixedPoint($(x.bits).W,$(x.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 asBool()
. -
If the signal is integer
(fractional_bits == 0)
, then its type is set asUInt
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 isinput(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
andoutput
ports represent instances of thesignal
structure, the properties of which are accessed through thety
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.