Use Template FUNCTION_BLOCKs¶
This guide covers writing a template FUNCTION_BLOCK<N: UINT>, instantiating
it at different sizes, and wiring it into a parent block. It assumes you already
know how to write plain FBs and how the WIRING block works.
Write a template FUNCTION_BLOCK¶
Add a <N: UINT> header after the FB name. Inside the body, use N wherever
a concrete integer would appear:
FUNCTION_BLOCK Vec<N: UINT>
VAR_INPUT
a : ARRAY[0..N-1] OF LREAL;
b : ARRAY[0..N-1] OF LREAL;
END_VAR
VAR_OUTPUT
dot : LREAL;
END_VAR
IMPLEMENTATION
dot := SUM(k IN 0..N-1 : a[k] * b[k]);
END_IMPLEMENTATION
END_FUNCTION_BLOCK
Save as Vec.lola. The file still has exactly one top-level FUNCTION_BLOCK;
the <N: UINT> makes it generic, not a special kind of file.
Declare multiple type parameters¶
Separate parameters with a comma. The nested-array syntax ARRAY[..] OF
ARRAY[..] OF T works with template bounds, and the double-bracket index
a[i][j] is fully supported:
FUNCTION_BLOCK Mat<M: UINT, N: UINT>
VAR_INPUT
a : ARRAY[0..M-1] OF ARRAY[0..N-1] OF LREAL;
END_VAR
VAR_OUTPUT
s : LREAL;
END_VAR
IMPLEMENTATION
s := SUM(i IN 0..M-1 : SUM(j IN 0..N-1 : a[i][j]));
END_IMPLEMENTATION
END_FUNCTION_BLOCK
For flat row-major access the single-bracket form also works:
FUNCTION_BLOCK MatVec<M: UINT, K: UINT>
VAR_INPUT
a : ARRAY[0..M*K-1] OF LREAL; (* row-major: a[i*K+k] = row i, col k *)
x : ARRAY[0..K-1] OF LREAL;
END_VAR
VAR_OUTPUT
y : ARRAY[0..M-1] OF LREAL;
END_VAR
IMPLEMENTATION
y := ARRAY(i IN 0..M-1 : SUM(k IN 0..K-1 : a[i*K+k] * x[k]));
END_IMPLEMENTATION
END_FUNCTION_BLOCK
Bounds may use addition, subtraction, and multiplication over type parameters
(N-1, M*K-1). Other arithmetic operators (/, MOD) also work when both
operands are known at compile time.
Instantiate a template block¶
In the VAR block of any parent FB or PROGRAM, append the concrete UINT values
in angle brackets:
FUNCTION_BLOCK Dot3
VAR_INPUT
p : ARRAY[0..2] OF LREAL;
q : ARRAY[0..2] OF LREAL;
END_VAR
VAR_OUTPUT
result : LREAL;
END_VAR
VAR
v : Vec<3>; (* N is substituted with 3 *)
END_VAR
WIRING
v(a := p, b := q);
result := v.dot;
END_WIRING
END_FUNCTION_BLOCK
The compiler resolves Vec.lola, substitutes N = 3, and compiles the
resulting Vec<3> FB as an ordinary sub-instance.
Wire template instances¶
Template instances use exactly the same WIRING syntax as plain instances (§10.2a in the Language Reference):
WIRING
inst(InputPort := expr, …); (* bind child inputs *)
parent_output := inst.OutputPort; (* forward child output *)
END_WIRING
Dot-assignment inst.Port := expr is not accepted. Use the parenthesis form
for inputs.
Use two instantiations of the same template at different sizes¶
Each argument tuple produces an independent concrete type:
VAR
v3 : Vec<3>;
v4 : Vec<4>;
END_VAR
WIRING
v3(a := p3, b := q3);
v4(a := p4, b := q4);
d3 := v3.dot;
d4 := v4.dot;
END_WIRING
Vec<3> and Vec<4> are distinct types; each has its own proof context,
its own generated array sizes, and its own hidden locals after flattening.
Verify a parent that uses templates¶
No special flags are needed:
lola Dot3.lola --target check
The resolver finds Vec.lola on the search path, monomorphizes Vec<3>, and
verifies the full composite program. If Vec.lola is absent, the compiler
reports a structure error on the instance declaration line.
Common mistakes¶
v.a := p in WIRING is rejected. Use v(a := p, b := q); instead.
Wrong number of type arguments. Vec<3, 4> when the template declares
<N: UINT> (one parameter) is a compile error: the compiler reports how many
arguments were expected.
Template bound references a non-parameter name. ARRAY[0..X-1] when X
is a VAR_INPUT (not a type parameter) is rejected. Only type parameters and
literals are allowed in template-dep bounds.
Type parameter in wrong position. The <N: UINT> header must appear
directly after the FB name; FUNCTION_BLOCK Vec <N: UINT> (with a space) is
accepted but <N: UINT> elsewhere in the block is a syntax error.