Tutorial¶
This tutorial goes through a simple example generating a C file from an
input block specification.  For this example the running sources are
found in the tests/inputs folder of the ZOTI-Gen project root.
The target file which we are trying to obtain in this tutorial is a C file representing the sequential implementation of a moving-average algorithm applied on a sequence of numbers. It consists in a composite pattern described functionally as
Specification Files¶
We define the code blocks structure for the implemented system in the
files main.yaml representing module main and genspec_leafs.yaml
representing module genspec_leafs.
 1module: main
 2version: '0.1.0'
 3description: Source for tutorial
 4top: main
 5---
 6block:
 7- name: main
 8  type: {module: Generic, name: Infinite}
 9  prototype: |
10      int {{ name }}(void) {
11        int input[10];
12        int output[10];
13        int COEF[4] = {1, 2, 2, 1};
14        {{placeholder['code']}}
15      } 
16  label:
17  - {name: input, usage: "{{ label[p].name }}"}
18  - {name: output, usage: "{{ label[p].name }}"}
19  param:
20    schedule: ["readio", "exec", "writeio"]
21    myformat: "\"%d\"" # TEST
22  instance:
23  - placeholder: readio
24    block: {module: Generic.IO, name: ReadArray}
25    directive: ["expand"]
26    bind:
27    - value_to_param: {value: "\"%d\"", child: format}
28    - value_to_param: {value: "10", child: size}
29    - label_to_label: {parent: input, child: arg, usage: "{{ label[p].name }}"}
30  - placeholder: writeio
31    block: {module: Generic.IO, name: PrintArray}
32    directive: ["expand"]
33    bind:
34    - value_to_param: {value: "10", child: size}
35    - param_to_param: {child: format, parent: myformat}
36    - label_to_label: {child: arg, parent: output, usage: "{{ label[p].name }}"}
37  - placeholder: exec
38    block: {module: main, name: mav_1}
39    directive: ["expand"]
40    bind: 
41    - label_to_label: {child: in1, parent: input, usage: "{{ label[p].name }}"}
42    - label_to_label: {child: out1, parent: output, usage: "{{ label[p].name }}"}
43    usage: "int _it;\nint _range;\n{{placeholder['code']}}"
44
45- name: mav_1
46  type: {module: Skeleton.Compute, name: ShiftFarm}
47  label:
48  - {name: in1,    usage: "{{ label[p].name }}"}
49  - {name: out1,   usage: "{{ label[p].name }}"}
50  - {name: COEF,   usage: "{{ label[p].name }}"}
51  - {name: _it,    usage: "{{ label[p].name }}"} 
52  - {name: _range, usage: "{{ label[p].name }}"}
53  param:
54    iterate_over: [in1]
55  instance:
56  - placeholder: f
57    block: {module: main, name: fred_1}
58    directive: ["expand"]
59    bind:
60    - label_to_label:
61        {parent: in1, child: in1, usage: "{{ label[p].name }}+{{label._it.name}}"}
62    - label_to_label: {child: in2, parent: COEF, usage: "{{ label[p].name }}"}
63    - label_to_label: {child: size1, parent: _range, usage: "{{ label[p].name }}"}
64    - label_to_label:
65        {parent: out1, child: out1, usage: "{{ label[p].name }}[{{label._it.name}}]"}
66    usage: "int _it0;\nint _acc;\n{{placeholder['code']}}"
67
68- name: fred_1
69  type: {module: Skeleton.Compute, name: FarmRed_Acc}
70  label:
71  - {name: in1,   usage: "{{ label[p].name }}"}
72  - {name: in2,   usage: "{{ label[p].name }}"}
73  - {name: size1, usage: "{{ label[p].name }}"}
74  - {name: out1,  usage: "{{ label[p].name }}"}
75  - {name: _acc,  usage: "{{ label[p].name }}"}
76  - {name: _it,   usage: "{{ label[p].name }}"}
77  param:
78    iterate_over:
79      size1: { range: "name"}
80      in2:
81  instance:
82  - placeholder: f
83    block: {module: genspec_leafs, name: mulacc}
84    # directive: ["expand"]
85    usage: "{{name}}({{label.in1.name}}, {{label.in2.name}}, {{label.acc.name}}, {{label.out.name}})"
86    bind:
87    - label_to_label:
88        parent: in1
89        child: in1
90        usage: "({{ label[p].name }})[{{label._it.name}}]"
91    - label_to_label:
92        parent: in2
93        child: in2
94        usage: "{{ label[p].name }}[{{label._it.name}}]"
95    - label_to_label: {child: acc, parent: _acc, usage: "{{ label[p].name }}"}
96    - label_to_label: {child: out, parent: _acc, usage: "{{ label[p].name }}"}
Notice the definition of mav_1 which is an implementation of
component Skeleton.Compute.ShiftFarm (i.e., the shift-map pattern)
and instantiates block fred_1 which is an implementation of
Skeleton.Compute.FarmRed_Acc (i.e., the map-reduce pattern with an
accumulator). The latter instantiates mulacc defined below as a
regular block, containing the “native” kernel code in its code
entry. Also note that the top block main implements a
Generic.Infinite template (i.e., an infinite loop) scheduling three
placeholders as of its param/schedule: readio (for reading a
stream of input numbers), exec (expanding the mav_1 block) and
writeio (for printing the results to the terminal).
 1module: genspec_leafs
 2top: mulacc
 3---
 4block:
 5- name: mulacc
 6  label:
 7  - {name: in1, usage: "{{ label[p].name }}"}
 8  - {name: in2, usage: "{{ label[p].name }}"}
 9  - {name: acc, usage: "{{ label[p].name }}"}
10  - {name: out, usage: "{{ label[p].name }}"}
11  prototype: "void {{name}}(in1, in2, acc, &out) {\n {{ placeholder['code'] }} \n};"
12  code:
13    out = acc + in1 * in2;
Notice that the input specification is as verbose as it needs to be to
facilitate the parsing of data. This is because these files are not
meant to be written by humans, much rather generated by upstream
tools[1]. Also, the convenient information in the usage and
prototype entries is also meant to be provided by an upstream
pre-processor with the help of a type system, but this is outside the
scope of ZOTI-Gen.
Bindings and Names¶
After resolving the structure here is how the block components look like. Pay attention to the label bindings, as well as their resolved names, as compared to their initial description.
![digraph G {
	graph [bb="0,0,966.29,1040",
		fontname=Verdana,
		rankdir=LR
	];
	node [label="\N"];
	subgraph "cluster_cluster_main.main" {
		graph [bb="0.15274,8,958.29,1032",
			color=black,
			fillcolor=white,
			label="main : Generic.Generic.Infinite",
			lheight=0.21,
			lp="479.22,1020.5",
			lwidth=2.96,
			shape=record,
			style=filled
		];
		subgraph "cluster_cluster_main.main.ReadArray" {
			graph [bb="586.2,16,837.08,145",
				fillcolor=lightgrey,
				label="ReadArray : Generic.IO.ReadArray",
				lheight=0.21,
				lp="711.64,133.5",
				lwidth=3.26,
				shape=record,
				style=filled
			];
			"main.main.ReadArray-arg"	[height=0.5,
				label=input,
				pos="711.14,96",
				shape=oval,
				width=0.97491];
			"main.main.ReadArray-format"	[height=0.5,
				label=format,
				pos="711.14,42",
				shape=parallelogram,
				width=1.7206];
		}
		subgraph "cluster_cluster_main.main.PrintArray" {
			graph [bb="296.31,872,543.19,1001",
				fillcolor=lightgrey,
				label="PrintArray : Generic.IO.PrintArray",
				lheight=0.21,
				lp="419.75,989.5",
				lwidth=3.21,
				shape=record,
				style=filled
			];
			"main.main.PrintArray-arg"	[height=0.5,
				label=output,
				pos="419.25,898",
				shape=oval,
				width=1.1555];
			"main.main.PrintArray-format"	[height=0.5,
				label=format,
				pos="419.25,952",
				shape=parallelogram,
				width=1.7206];
		}
		subgraph "cluster_cluster_main.main.mav_1" {
			graph [bb="8.1527,153,757.49,864",
				fillcolor=lightgrey,
				label="mav_1 : Skeleton.Compute.ShiftFarm",
				lheight=0.21,
				lp="382.82,852.5",
				lwidth=3.56,
				shape=record,
				style=filled
			];
			subgraph "cluster_cluster_main.main.mav_1.fred_1" {
				graph [bb="16.153,261,481.19,735",
					color=black,
					fillcolor=white,
					label="fred_1 : Skeleton.Compute.FarmRed_Acc",
					lheight=0.21,
					lp="248.67,723.5",
					lwidth=3.89,
					shape=record,
					style=filled
				];
				subgraph "cluster_cluster_main.main.mav_1.fred_1.mulacc" {
					graph [bb="24.153,369,237.15,606",
						fillcolor=lightgrey,
						label="mulacc : zoti_gen.core.Block",
						lheight=0.21,
						lp="130.65,594.5",
						lwidth=2.74,
						shape=record,
						style=filled
					];
					"main.main.mav_1.fred_1.mulacc-in1"	[height=0.5,
						label=in1,
						pos="130.15,395",
						shape=oval,
						width=0.75];
					"main.main.mav_1.fred_1.mulacc-in2"	[height=0.5,
						label=in2,
						pos="130.15,449",
						shape=oval,
						width=0.75];
					"main.main.mav_1.fred_1.mulacc-acc"	[height=0.5,
						label=acc,
						pos="130.15,557",
						shape=oval,
						width=0.75];
					"main.main.mav_1.fred_1.mulacc-out"	[height=0.5,
						label=out,
						pos="130.15,503",
						shape=oval,
						width=0.75];
				}
				"main.main.mav_1.fred_1-in1"	[height=0.5,
					label="input+_it",
					pos="419.25,341",
					shape=oval,
					width=1.4985];
				"main.main.mav_1.fred_1-in2"	[height=0.5,
					label=COEF,
					pos="419.25,449",
					shape=oval,
					width=1.0652];
				"main.main.mav_1.fred_1-size1"	[height=0.5,
					label=_range,
					pos="130.15,632",
					shape=oval,
					width=1.1735];
				"main.main.mav_1.fred_1-out1"	[height=0.5,
					label="output[_it]",
					pos="130.15,686",
					shape=oval,
					width=1.661];
				"main.main.mav_1.fred_1-_acc"	[height=0.5,
					label=_acc,
					pos="419.25,530",
					shape=oval,
					width=0.84854];
				"main.main.mav_1.fred_1-_it"	[height=0.5,
					label=_it0,
					pos="130.15,341",
					shape=oval,
					width=0.77632];
				"main.main.mav_1.fred_1-iterate_over"	[height=0.5,
					label=iterate_over,
					pos="130.15,287",
					shape=parallelogram,
					width=2.7265];
				"main.main.mav_1.fred_1.mulacc-in1" -> "main.main.mav_1.fred_1-in1"	[pos="e,371.88,349.72 156.38,390.26 202.07,381.66 299.54,363.33 361.82,351.61"];
				"main.main.mav_1.fred_1.mulacc-in2" -> "main.main.mav_1.fred_1-in2"	[pos="e,380.52,449 157.39,449 205.68,449 309.25,449 370.36,449"];
				"main.main.mav_1.fred_1.mulacc-acc" -> "main.main.mav_1.fred_1-_acc"	[pos="e,388.71,532.78 157.14,554.56 207.53,549.82 318.81,539.35 378.53,533.73"];
				"main.main.mav_1.fred_1.mulacc-out" -> "main.main.mav_1.fred_1-_acc"	[pos="e,388.71,527.22 157.14,505.44 207.53,510.18 318.81,520.65 378.53,526.27"];
			}
			"main.main.mav_1-in1"	[height=0.5,
				label=input,
				pos="711.14,287",
				shape=oval,
				width=0.97491];
			"main.main.mav_1-out1"	[height=0.5,
				label=output,
				pos="419.25,815",
				shape=oval,
				width=1.1555];
			"main.main.mav_1-COEF"	[height=0.5,
				label=COEF,
				pos="711.14,449",
				shape=oval,
				width=1.0652];
			"main.main.mav_1-_it"	[height=0.5,
				label=_it,
				pos="130.15,233",
				shape=oval,
				width=0.75];
			"main.main.mav_1-_range"	[height=0.5,
				label=_range,
				pos="419.25,761",
				shape=oval,
				width=1.1735];
			"main.main.mav_1-iterate_over"	[height=0.5,
				label=iterate_over,
				pos="130.15,179",
				shape=parallelogram,
				width=2.7265];
			"main.main.mav_1.fred_1-in1" -> "main.main.mav_1-in1"	[pos="e,678.16,293.25 467.27,332.73 490.18,328.66 518.14,323.64 543.19,319 585.79,311.11 634.44,301.74 668.18,295.19"];
			"main.main.mav_1.fred_1-in2" -> "main.main.mav_1-COEF"	[pos="e,672.61,449 457.85,449 510.2,449 605.06,449 662.36,449"];
			"main.main.mav_1.fred_1-size1" -> "main.main.mav_1-_range"	[pos="e,376.82,761.05 171.56,635.74 192.54,639.23 217.77,645.97 237.15,659 273.85,683.67 258.75,715.65 296.31,739 317.14,751.95 343.61,\
757.73 366.56,760.16"];
			"main.main.mav_1.fred_1-out1" -> "main.main.mav_1-out1"	[pos="e,379.35,809.8 152.84,702.74 183.24,725.39 241.04,765.57 296.31,788 319.48,797.41 346.52,803.82 369.25,808.02"];
		}
		"main.main-input"	[height=0.5,
			label=input,
			pos="915.19,191",
			shape=oval,
			width=0.97491];
		"main.main-output"	[height=0.5,
			label=output,
			pos="711.14,894",
			shape=oval,
			width=1.1555];
		"main.main-schedule"	[height=0.5,
			label=schedule,
			pos="130.15,79",
			shape=parallelogram,
			width=2.1176];
		"main.main-myformat"	[height=0.5,
			label=myformat,
			pos="711.14,952",
			shape=parallelogram,
			width=2.2765];
		"main.main.ReadArray-arg" -> "main.main-input"	[pos="e,891.21,177.72 739.78,106.82 765.33,117.04 804.2,133.12 837.08,149 852.14,156.27 868.45,165.02 882.17,172.65"];
		"%3"	[height=0.20833,
			pos="419.25,42",
			shape=plain,
			width=0.31944];
		"%3" -> "main.main.ReadArray-format"	[pos="e,661.8,42 430.91,42 466.15,42 580.7,42 651.67,42"];
		"main.main.PrintArray-arg" -> "main.main-output"	[pos="e,669.33,894.56 460.86,897.44 512.79,896.72 602.73,895.48 659.2,894.7"];
		"main.main.PrintArray-format" -> "main.main-myformat"	[pos="e,645.93,952 468.77,952 514.33,952 583.15,952 635.66,952"];
		"main.main.mav_1-in1" -> "main.main-input"	[pos="e,888.81,203.06 737.73,274.84 773.71,257.75 839.35,226.56 879.64,207.42"];
		"main.main.mav_1-out1" -> "main.main-output"	[pos="e,672.48,887.14 452.08,826.24 485.28,837.71 538.97,855.54 586.2,868 611.2,874.6 639.38,880.62 662.55,885.2"];
	}
}](_images/graphviz-f8712fd096d0331bbe75ce15dca5a3389428b4cb.png)
Library Components¶
The library components are grouped into two modules (or packages):
Generic for harboring generic templates, and Skeleton for defining
templates for specialized collective operations, all targeting a
sequential C implementation.
Generic¶
The generic components are spread throughout several files showcasing some of the template design tips described in Template Libraries.
 1from dataclasses import dataclass, field
 2from zoti_gen import with_schema, Block
 3
 4
 5@with_schema(Block.Schema)
 6@dataclass
 7class Infinite(Block):
 8    """ The base C implementation for an infinite loop. """
 9
10    code: str = field(
11        default="\
12while (1) { \n\
13  {% for inst in param.schedule %} \n\
14  {{ placeholder[inst] }} \n\
15  {% endfor %} \n\
16}"
17    )
18
19    def check(self):
20        assert "schedule" in self.param
21        assert isinstance(self.param["schedule"], list)
 1from dataclasses import dataclass, field
 2from zoti_gen import read_at, with_schema, Block, Requirement, Template, C_DELIMITERS
 3
 4file_args = {"module": __name__,
 5             "filename": "templates.c",
 6             "delimiters": C_DELIMITERS}
 7
 8
 9@with_schema(Block.Schema)
10@dataclass
11class ReadArray(Block):
12    """Generic implementation for monitoring a system. Gets outside
13    information from the console.
14    """
15
16    requirement: Requirement = field(
17        default=Requirement({"include": ["stdio.h"]}))
18
19    code: str = field(
20        default=Template(
21            read_at(**file_args, name="ReadArray.C"),
22            parent="code")
23    )
24
25    def check(self):
26        assert "format" in self.param
27        assert "size" in self.param
28        assert "arg" in self.label
29
30
31@with_schema(Block.Schema)
32@dataclass
33class PrintArray(Block):
34    """Generic implementation for monitoring a system. Prints an array to
35    the console.
36    """
37
38    requirement: Requirement = field(
39        default=Requirement({"include": ["stdio.h"]}))
40
41    code: str = field(
42        default=Template(
43            read_at(**file_args, name="PrintArray.C"),
44            parent="code")
45    )
46
47    def check(self):
48        assert "format" in self.param
49        assert "size" in self.param
50        assert "arg" in self.label
 1// Template: ReadArray.C
 2{int __io_it;
 3  for (__io_it = 0; __io_it < {{ param.size }}; __io_it++){
 4    scanf({{ param.format }}, &{{ label.arg.name }}[__io_it]);
 5  }
 6}
 7// End: ReadArray.C
 8
 9
10// Template: PrintArray.C
11{int __io_it;
12  for (__io_it = 0; __io_it < {{ param.size }}; __io_it++){
13    printf({{ param.format }}, {{ label.arg.name }}[__io_it]);
14  }
15}
16// End: PrintArray.C
17
Specialized Collective Operations¶
The Skeleton module showcases more advanced template design using
the ZOTI-Gen Jinja extensions.
 1from dataclasses import dataclass, field
 2from zoti_gen import read_at, with_schema, Block, Requirement, Template, C_DELIMITERS
 3
 4
 5file_args = {"module": __name__,
 6             "filename": "templates.c",
 7             "delimiters": C_DELIMITERS}
 8
 9
10@with_schema(Block.Schema)
11@dataclass
12class ShiftFarm(Block):
13    """
14    Shift-farm specialized skeleton
15    """
16
17    requirement: Requirement = field(
18        default=Requirement({"include": ["cutils.h"]}))
19
20    code: str = field(
21        default=Template(
22            read_at(**file_args, name="ShiftFarm.C"),
23            parent="code")
24    )
25
26    def check(self):
27        # internal iterator (resolved name)
28        assert "_it" in self.label
29
30        # auto-bound to 'size1' from placeholder? TODO
31        assert "_range" in self.label
32
33        # called template
34        assert "f" in [i.placeholder for i in self.instance]
35
36
37@with_schema(Block.Schema)
38@dataclass
39class FarmRed_Acc(Block):
40    """
41    Farm-reduce skeleton with initial element, fused map-reduce kernel function
42    and programmable (exposed) size param.
43    """
44
45    requirement: Requirement = field(
46        default=Requirement({"include": ["cutils.h"]}))
47
48    code: str = field(
49        default=Template(
50            read_at(**file_args, name="FarmRed_Acc.C"),
51            parent="code")
52    )
53
54    def check(self):
55        # internal iterator (resolved name)
56        assert "_it" in self.label
57
58        # auto-bound to 'size1' from placeholder? TODO
59        assert "_acc" in self.label
60
61        # label IDs over which '_it' iterates
62        assert "iterate_over" in self.param
63
64        # called fused farm-reduce kernel
65        assert "f" in [i.placeholder for i in self.instance]
Notice that both ShiftFarm and FarmRed_Acc specify a requirement
upon a certain target library header cutils.h. This is where
functions used in their template such as min are supposed to be
defined.
 1// Template: ShiftFarm.C
 2{% set iterate_over = param.iterate_over|setdefaults({"offset": "0", "range": "name" }) %}
 3
 4{% macro itrange() -%}
 5{% set ranges = [] %}
 6{% for k,p in iterate_over.items() -%}
 7{% do ranges.append(label[k]|find(p.range)) %}
 8{%- endfor %}min({{ ranges|join(", ") }})
 9{%- endmacro %}
10
11for ({{ label._it.name }} = 0; {{ label._it.name }} < {{ itrange() }} ; {{ label._it.name }}++){
12  {{ label._range.name }} = {{ itrange() }} - {{ label._it.name }};
13  {{ placeholder["f"] }}
14}
15// End: ShiftFarm.C
16
17
18// Template: FarmRed_Acc.C
19{% set iterate_over = param.iterate_over|setdefaults({"offset": "0", "range": "name" }) %}
20
21{% macro itrange() -%}
22{% set ranges = [] %}
23{% for k,p in iterate_over.items() -%}
24{% do ranges.append(label[k]|find(p.range)) %}
25{%- endfor %}min({{ ranges|join(", ") }})
26{%- endmacro %}
27
28for ({{ label._it.name }} = 0; {{ label._it.name }} < {{ itrange() }} ; {{ label._it.name }}++){
29  {{ placeholder["f"] }}
30}
31{{ label.out1.name }} = {{ label._acc.name }};
32// End: FarmRed_Acc.C
The template definition shows an example of using local Jinja
macro,
as well as the in-house
zoti_gen.render.JinjaExtensions.setdefaults() filter.
Output Artifacts¶
Finally, the generated artifacts can be seen in this section. In this case the artifacts are presented as two separate files:
- a JSON file representing the include dependencies, useful for a post-processor, e.g. to generate the preamble of the .c file. 
- a .c file containing the bulk of the code (without the preamble). 
1{"include": ["stdio.h", "cutils.h"]}
Notice that the generated code reflects the directive flags passed
during block instantiation: all blocks are expanded as inline text
except mulacc which is not using the "expand" directive. This is
because it contains plain non-template native code which needs to be
isolated as a separate function.
 1void mulacc(in1, in2, acc, &out) {
 2  // vvv mulacc
 3  out = acc + in1 * in2;
 4  // ^^^ mulacc
 5};
 6
 7int main(void) {
 8  int input[10];
 9  int output[10];
10  int COEF[4] = {1, 2, 2, 1};
11  // vvv main
12  while (1) {
13
14    // vvv ReadArray
15
16    {
17      int __io_it;
18      for (__io_it = 0; __io_it < 10; __io_it++) {
19        scanf("%d", &input[__io_it]);
20      }
21    }
22    //
23    // ^^^ ReadArray
24
25    int _it;
26    int _range;
27    // vvv mav_1
28
29    for (_it = 0; _it < min(input); _it++) {
30      _range = min(input) - _it;
31      int _it0;
32      int _acc;
33      // vvv fred_1
34
35      for (_it0 = 0; _it0 < min(_range, COEF); _it0++) {
36        mulacc((input + _it)[_it0], COEF[_it0], _acc, _acc)
37      }
38      output[_it] = _acc;
39      //
40      // ^^^ fred_1
41    }
42    //
43    // ^^^ mav_1
44
45    // vvv PrintArray
46
47    {
48      int __io_it;
49      for (__io_it = 0; __io_it < 10; __io_it++) {
50        printf("%d", output[__io_it]);
51      }
52    }
53    //
54    // ^^^ PrintArray
55  }
56  // ^^^ main
57}
To obtain these files we ran the ZOTI-Gen CLI tool using the following configuration, assuming that all source files, libraries as well as the CLI tool are accessible from the current folder.
[zoti.gen]
lib = ["."]
deps = "deps.json"
input = ["main.yaml", "genspec_leafs.yaml"]
output = "out.c"
begin_block = "// vvv {comp.name}\n" 
end_block = "\n// ^^^ {comp.name}"