tydi 0.0.7

An HDL code generation utility for components described using the Tydi open specification.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
Logical streams
===============

A Tydi logical stream consists of a bundle of physical streams and a group of
user-defined signals, parameterized by way of a logical stream type. The
logical stream type, in turn, represents some representation of an abstract
data type (in the same way that for instance `std::vector` and `std::list` both
represent a sequence in C++). Conceptually, the resulting logical stream can
then be used to stream instances of its corresponding abstract data type from
the source to the sink. The sink can only receive or operate on one instance at
a time and only in sequence (hence the name "stream"), but the logical stream
type can be designed such that the sink as random-access capability within the
current instance.

Because of the degree of flexibility in specifying Tydi logical streams, much
of this specification consists of the formal definitions of the structures
needed to describe a Tydi logical stream, and operations thereupon. The
resulting constraints on the signal lists and behavior of the signals are then
described based on these.

Logical stream type
-------------------

This structure is at the heart of the logical stream specification. It is used
both to specify the type of a logical stream and internally for the process of
lowering the recursive structure down to physical streams and signals.

### Structure

The logical stream type is defined recursively by means of a number of node
types. Two classes of nodes are defined: stream-manipulating nodes, and
element-manipulating nodes. Only one stream-manipulating node exists
(\\(\textrm{Stream}\\)), which is defined in the first subsection. The
remainder of the subsections describe the element-manipulating nodes.

#### Stream

The \\(\textrm{Stream}\\) node is used to define a new physical stream. It is
defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where:

 - \\(T_e\\) is any logical stream type, representing the type of element
   carried by the logical stream;

   > This may include additional \\(\textrm{Stream}\\) nodes to handle cases
   > such as structures containing sequences, handled in Tydi by transferring
   > the two different dimensions over two different physical streams, as well
   > as to encode request-response patterns.

 - \\(t\\) is a positive real number, representing the minimum number of
   elements that should be transferrable on the child stream per element in the
   parent stream, or if there is no parent stream, the minimum number of
   elements that should be transferrable per clock cycle;

   > \\(t\\) is short for throughput ratio. As we'll see later, the \\(N\\)
   > parameter for the resulting physical stream equals
   > \\(\left\lceil\prod t\right\rceil\\) for all ancestral
   > \\(\textrm{Stream}\\) nodes, including this node.
   >
   > This significance is as follows. Let's say that you have a structure like
   > \\(\textrm{Stream}(T_e = \textrm{Group}(\textrm{a}: \textrm{Bits}(16),
   > \textrm{b}: \textrm{Stream}(d = 1, T_e = \textrm{Bits}(8), ...)), ...)\\),
   > you're expecting that you'll be transferring about one of those groups
   > every three cycles, and you're expecting that the list size of `b` will
   > be about 8 on average. In this case, \\(t = 1/3\\) for the outer stream
   > node, and \\(t = 8\\) for the inner stream node. That'll give you
   > \\(\lceil 1/3\rceil = 1\\) element lane on the outer stream (used at
   > ~33% efficiency) and \\(\lceil 1/3 \cdot 8\rceil = 3\\) element lanes
   > for the inner `b` stream (used at ~88% efficiency), such that neither
   > stream will on average be slowing down the design.

 - \\(d\\) is a nonnegative integer, representing the dimensionality of the
   child stream with respect to the parent stream;

   > As we'll see later, the \\(D\\) parameter for the resulting physical
   > stream equals \\(\sum d\\) for all ancestral
   > \\(\textrm{Stream}\\) nodes, including this node, up to and including the
   > closest ancestor (or self) for which \\(s = \textrm{Flatten}\\).

 - \\(s\\) must be one of \\(\textrm{Sync}\\), \\(\textrm{Flatten}\\),
   \\(\textrm{Desync}\\), or \\(\textrm{FlatDesync}\\), representing the
   relation between the dimensionality information of the parent stream and the
   child stream;

   > The most common relation is \\(\textrm{Sync}\\), which enforces that for
   > each element in the parent stream, there must be one \\(d\\)-dimensional
   > sequence in the child stream (if \\(d\\) is 0, that just means one
   > element). Furthermore, it means that the `last` flags from the parent
   > stream are repeated in the child stream.  This repetition is technically
   > redundant; since the sink will have access to both streams, it only needs
   > this information once to decode the data.
   >
   > If this redundant repetition is not necessary for the sink or hard to
   > generate for a source, \\(\textrm{Flatten}\\) may be used instead. It is
   > defined in the same way, except the redundant information is not repeated
   > for the child stream.
   >
   > Consider for example the data `[(1, [2, 3]), (4, [5])], [(6, [7])]`. If
   > encoded as \\(\textrm{Stream}(d=1, T_e=\textrm{Group}(\textrm{Bits}(8), \textrm{Stream}(d=1, s=\textrm{Sync}, ...)), ...)\\),
   > the first physical stream will transfer `[1, 4], [6]`, and the second
   > physical stream will transfer `[[2, 3], [5]], [[7]]`. If
   > \\(\textrm{Flatten}\\) is used for the inner \\(\textrm{Stream}\\)
   > instead, the data transferred by the second physical stream reduces to
   > `[2, 3], [5], [7]`; the structure of the outer sequence is implied by the
   > structure transferred by the first physical stream.
   >
   > \\(\textrm{Desync}\\) may be used if the relation between the elements in
   > the child and parent stream is dependent on context rather than the `last`
   > flags in either stream. For example, for the type
   > \\(\textrm{Stream}(d=1, T_e=\textrm{Group}(\textrm{Bits}(8), \textrm{Stream}(s=\textrm{Desync}, ...)), ...)\\),
   > if the first stream carries `[1, 4], [6]` as before, the child stream may
   > carry anything of the form `[...], [...]`. That is, the dimensionality
   > information (if any) must still match, but the number of elements in the
   > innermost sequence is unrelated or only related by some user-defined
   > context.
   >
   > \\(\textrm{FlatDesync}\\), finally, does the same thing as
   > \\(\textrm{Desync}\\), but also strips the dimensionality information from
   > the parent. This means there the relation between the two streams, if any,
   > is fully user-defined.

 - \\(c\\) is a complexity number, as defined in the physical stream
   specification, representing the complexity for the corresponding physical
   stream interface;

 - \\(r\\) must be one of \\(\textrm{Forward}\\) or \\(\textrm{Reverse}\\),
   representing the direction of the child stream with respect to the parent
   stream, or if there is no parent stream, with respect to the direction of
   the logical stream (source to sink);

   > \\(\textrm{Forward}\\) indicates that the child stream flows in the same
   > direction as its parent, complementing the data of its parent in some way.
   > Conversely, \\(\textrm{Reverse}\\) indicates that the child stream acts as
   > a response channel for the parent stream. If there is no parent stream,
   > \\(\textrm{Forward}\\) indicates that the stream flows in the natural
   > source to sink direction of the logical stream, while
   > \\(\textrm{Reverse}\\) indicates a control channel in the opposite
   > direction. The latter may occur for instance when doing random read access
   > to a memory; the first stream carrying the read commands then flows in the
   > sink to source direction.

 - \\(T_u\\) is a logical stream type consisting of only element-manipulating
   nodes, representing the `user` signals of the physical stream; that is,
   transfer-oriented data (versus element-oriented data); and

   > Note that while \\(T_u\\) is called a "logical stream type" for
   > consistency, the \\(\textrm{Stream}\\) node being illegal implies that no
   > additional streams can be specified within this type.

 - \\(x\\) is a boolean, representing whether the stream carries "extra"
   information beyond the `data` and `user` signal payloads.

   > \\(x\\) is normally false, which
   > implies that the \\(\textrm{Stream}\\) node will not result in a physical
   > stream if both its `data` and `user` signals would be empty according to
   > the rest of this specification; it is effectively optimized away. Setting
   > \\(x\\) to true simply overrides this behavior.
   >
   > This optimization handles a fairly common case when the logical stream
   > type is recursively generated by tooling outside the scope of this layer
   > of the specification. It arises for instance for nested lists, which may
   > result in something of the form
   > \\(\textrm{Stream}(d=1, T_e=\textrm{Stream}(d=1, s=\textrm{Sync}, T_e=...))\\).
   > The outer stream is not necessary here; all the dimensionality information
   > is carried by the inner stream.
   >
   > It could be argued that the above example should be reduced to
   > \\(\textrm{Stream}(d=2, s=\textrm{Sync}, T_e=...)\\) by said external
   > tool. Indeed, this description is equivalent. This reduction is however
   > very difficult to define or specify in a way that it handles all cases
   > intuitively, hence this more pragmatic solution was chosen.

> As the \\(\textrm{Stream}\\) node carries many parameters and can therefore
> be hard to read, some abbreviations are in order:
>
>  - \\(\textrm{Dim}(T_e, t, c, T_u) \ \rightarrow\ \textrm{Stream}(T_e, t, 1, \textrm{Sync},    c, \textrm{Forward}, T_u, \textrm{false})\\)
>  - \\(\textrm{New}(T_e, t, c, T_u) \ \rightarrow\ \textrm{Stream}(T_e, t, 0, \textrm{Sync},    c, \textrm{Forward}, T_u, \textrm{false})\\)
>  - \\(\textrm{Des}(T_e, t, c, T_u) \ \rightarrow\ \textrm{Stream}(T_e, t, 0, \textrm{Desync},  c, \textrm{Forward}, T_u, \textrm{false})\\)
>  - \\(\textrm{Flat}(T_e, t, c, T_u)\ \rightarrow\ \textrm{Stream}(T_e, t, 0, \textrm{Flatten}, c, \textrm{Forward}, T_u, \textrm{false})\\)
>  - \\(\textrm{Rev}(T_e, t, c, T_u) \ \rightarrow\ \textrm{Stream}(T_e, t, 0, \textrm{Sync},    c, \textrm{Reverse}, T_u, \textrm{false})\\)
>
> For the above abbreviations, the following defaults apply in addition:
>
>  - \\(T_u = \textrm{Null}\\);
>  - \\(c =\\) complexity of parent stream (cannot be omitted if there is no
>    parent);
>  - \\(t = 1\\).

#### Null

The \\(\textrm{Null}\\) leaf node indicates the transferrence of one-valued
data: it is only valid value is \\(\varnothing\\) (null).

> This is useful primarily when used within a \\(\textrm{Union}\\), where the
> data type itself carries information. Furthermore, \\(\textrm{Stream}\\)
> nodes carrying a \\(\textrm{Null}\\) will always result in a physical stream
> (whereas \\(\textrm{Stream}\\) nodes carrying only other streams will be
> "optimized out"), which may still carry dimensionality information, user
> data, or even just transfer timing information.

#### Bits

The \\(\textrm{Bits}\\) leaf node, defined as \\(\textrm{Bits}(b)\\), indicates
the transferrence of \\(2^b\\)-valued data carried by means of a group of
\\(b\\) bits, where \\(b\\) is a positive integer.

> Tydi does not specify how the bits should be interpreted: such interpretation
> is explicitly out of scope.

#### Group

The \\(\textrm{Group}\\) node acts as a product type (composition). It is
defined as \\(\textrm{Group}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\), where:

 - \\(N_i\\) is a string consisting of letters, numbers, and/or underscores,
   representing the name of the \\(i\\)th field;
 - \\(T_i\\) is the logical stream type for the \\(i\\)th field;
 - \\(n\\) is a nonnegative integer, representing the number of fields.

> Intuitively, each instance of type \\(\textrm{Group}(...)\\) consists of
> instances of *all* the contained types. This corresponds to a `struct` or
> `record` in most programming languages.

The names cannot contain two or more consecutive underscores.

> Double underscores are used by Tydi as a hierarchy separator when flattening
> structures for the canonical representation. If not for the above rule,
> something of the form
> \\(\textrm{Group}(\textrm{'a'}:\textrm{Group}(\textrm{'b__c'}:...),\textrm{'a__b'}:\textrm{Group}(\textrm{'c'}:...))\\)
> would result in a name conflict: both signals would be named `a__b__c`.

The names cannot start or end with an underscore.

The names cannot start with a digit.

The names cannot be empty.

The names must be case-insensitively unique within the group.

> The above requirements on the name mirror the requirements on the field names
> for the physical streams.

#### Union

The \\(\textrm{Union}\\) node acts as a sum type (exclusive disjunction). It
is defined as \\(\textrm{Union}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\), where:

 - \\(N_i\\) is a string consisting of letters, numbers, and/or underscores,
   representing the name of the \\(i\\)th field;
 - \\(T_i\\) is the logical stream type for the \\(i\\)th field;
 - \\(n\\) is a positive integer, representing the number of fields.

> Intuitively, each instance of type \\(\textrm{Union}\\) consists of
> an instance of *one* the contained types (also known as variants), as well
> as a tag indicating which field that instance belongs to. This corresponds
> to a `union` or `enum` in most programming languages. Mathematically, Tydi
> unions represent tagged unions.
>
> One might argue that unions could also be specified without the
> \\(\textrm{Union}\\) node, by simply grouping the variants together along
> with a manually specified \\(\textrm{Bits}\\) field for the union tag, and
> using don't-cares for the variants not selected through the tag field.
> However, there are a number of downsides to such a sparse union approach.
>
>  - For unions with many variants, the `data` signal of the corresponding
>    stream becomes very wide.
>  - Any streams nested inside the union variants would need to be stuffed
>    with don't-care *transfers* as well, which may result in additional
>    control logic, delay, area, etc..
>
> While such sparse unions have their place, dense unions can be more suitable
> for a given application. These could also be represented without a
> \\(\textrm{Union}\\) node, but not in a nicely recursive manner without loss
> of information about the variant types. Therefore, (dense) unions were made a
> first-class type through \\(\textrm{Union}\\).
>
> \\(\textrm{Union}\\)s can also be used to elegantly describe nullable types.
> The pattern for that is simply \\(\textrm{Union}(\textrm{Null}, T)\\). In
> this case, the union's tag field acts like a validity bit.

The names cannot contain two or more consecutive underscores.

> Double underscores are used by Tydi as a hierarchy separator when flattening
> structures for the canonical representation. If not for the above rule,
> something of the form
> \\(\textrm{Union}(\textrm{'a'}:\textrm{Union}(\textrm{'b__c'}:\textrm{Stream}...),\textrm{'a__b'}:\textrm{Union}(\textrm{'c'}:\textrm{Stream}...))\\)
> would result in a name conflict: both physical streams would be named `a__b__c`.

The names cannot start or end with an underscore.

The names cannot start with a digit.

The names cannot be empty.

The names must be case-insensitively unique within the union.

> The above requirements on the name mirror the requirements on the field names
> for the physical streams.

### Operations on logical stream

This section defines some operations that may be performed on logical stream
types, most importantly the \\(\textrm{synthesize}()\\) function, which returns
the signals and physical streams for a given type.

#### Null detection function

This section defines the function
\\(\textrm{isNull}(T_{in}) \rightarrow \textrm{Bool}\\), which returns
true if and only if \\(T_{in}\\) does not result in any signals.

The function returns true if and only if one or more of the following
rules match:

 - \\(T_{in} = \textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\),
   \\(\textrm{isNull}(T_e)\\) is true, \\(\textrm{isNull}(T_u)\\) is true,
   and \\(x\\) is false;

 - \\(T_{in} = \textrm{Null}\\);

 - \\(T_{source} = \textrm{Group}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\) and
   \\(\textrm{isNull}(T_i)\\) is true for all \\(i \in (1, 2, ..., n)\\); or

 - \\(T_{source} = \textrm{Union}(N_1: T_1)\\) and \\(\textrm{isNull}(T_1)\\)
   is true.

   > Note that any union with more than one field is not null regardless of
   > its types, because the union tag also carries information in that case.

#### Split function

This section defines the function
\\(\textrm{split}(T_{in}) \rightarrow \textrm{SplitStreams}\\),
where \\(T_{in}\\) is any logical stream type. The \\(\textrm{SplitStreams}\\)
node is defined as
\\(\textrm{SplitStreams}(T_{signals}, N_1 : T_1, N_2 : T_2, ..., N_n : T_n)\\),
where:

 - \\(T_{signals}\\) is a logical stream type consisting of only
   element-manipulating nodes;
 - all \\(T_{1..n}\\) are \\(\textrm{Stream}\\) nodes with the data and user
   subtypes consisting of only element-manipulating nodes;
 - all \\(N\\) are case-insensitively unique, emptyable strings consisting of
   letters, numbers, and/or underscores, not starting or ending in an
   underscore, and not starting with a digit; and
 - \\(n\\) is a nonnegative integer.

> Intuitively, it splits a logical stream type up into simplified stream types,
> where \\(T_{signals}\\) contains only the information for the user-defined
> signals, and \\(T_i\\) contains only the information for the physical stream
> with index \\(i\\) and name \\(N_i\\).
>
> The names can be empty, because if there are no \\(Group\\)s or \\(Union\\)s,
> the one existing stream or signal will not have an intrinsic name given by
> the type. Empty strings are represented here with \\(\varnothing\\). Note
> that the names here can include double underscores.

\\(\textrm{split}(T_{in})\\) is evaluated as follows.

 - If \\(T_{in} = \textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), apply the
   following algorithm.

    - Initialize \\(N\\) and \\(T\\) to empty lists.
    - \\(T_{element} := \textrm{split}(T_e)\_{signals}\\) (i.e., the
      \\(T_{signals}\\) item of the tuple returned by the \\(\textrm{split}\\)
      function)
    - If \\(\textrm{isNull}(T_{element})\\) is false, \\(\textrm{isNull}(T_u)\\)
      is false, or \\(x\\) is true:
       - Append \\(\varnothing\\) (an empty name) to the end of \\(N\\).
       - Append \\(\textrm{Stream}(T_{element}, t, d, s, c, r, T_u, x)\\) to the end
         of \\(T\\).
    - Extend \\(N\\) with \\(\textrm{split}(T_e)\_{names}\\) (i.e.,
      all stream names returned by the \\(\textrm{split}\\) function).
    - For all \\(T' \in \textrm{split}(T_e)\_{streams}\\) (i.e.,
      for all named streams returned by the \\(\textrm{split}\\) function):
       - Unpack \\(T'\\) into \\(\textrm{Stream}(T'\_d, t', d', s', c', r', T'\_u, x')\\).
         This is always possible due to the postconditions of
         \\(\textrm{split}\\).
       - If \\(r = \textrm{Reverse}\\), reverse the direction of \\(r'\\).
       - If \\(s = \textrm{Flatten}\\) or \\(s = \textrm{FlatDesync}\\),
         assign \\(s' := \textrm{FlatDesync}\\).
       - If \\(s' \ne \textrm{Flatten}\\) and \\(s \ne \textrm{FlatDesync}\\),
         assign \\(d' := d' + d\\).
       - \\(t' := t' \cdot t\\).
       - Append \\(\textrm{Stream}(T'\_d, t', d', s', c', r', T'\_u, x')\\) to
         \\(T\\).
    - Return \\(\textrm{SplitStreams}(\textrm{Null}, N_1 : T_1, N_2 : T_2, ..., N_m : T_m)\\).

 - If \\(T_{in} = \textrm{Null}\\) or \\(T_{in} = \textrm{Bits}(b)\\),
   return \\(\textrm{SplitStreams}(T_{in})\\).

 - If \\(T_{in} = \textrm{Group}(N_{g,1} : T_{g,1}, N_{g,2} : T_{g,2}, ..., N_{g,n} : T_{g,n})\\),
   apply the following algorithm.

    - \\(T_{signals} := \textrm{Group}(N_{g,1} : \textrm{split}(T_{g,1})\_{signals}, ..., N_{g,n} : \textrm{split}(T_{g,n})\_{signals})\\)
      (i.e., replace the types contained by the group with the
      \\(T_{signals}\\) item of the tuple returned by the \\(\textrm{split}\\)
      function applied to the original types).
    - Initialize \\(N\\) and \\(T\\) to empty lists.
    - For all \\(i \in 1..n\\):
       - For all \\(\textrm{name} \in \textrm{split}(T_{g,i})\_{names}\\) (i.e.,
         for all stream names returned by the \\(\textrm{split}\\) function),
          - If \\(\textrm{name} = \varnothing\\), append \\(N_{g,i}\\) to the end
            of \\(N\\).
          - Otherwise, append the concatenation \\(N_{g,i}\\) and \\(\textrm{name}\\)
            to the end of \\(N\\), separated by a double underscore.
       - Extend \\(T\\) with \\(\textrm{split}(T_{g,i})\_{streams}\\) (i.e.,
         all named streams returned by the \\(\textrm{split}\\) function).
    - Return \\(\textrm{SplitStreams}(T_{signals}, N_1 : T_1, N_2 : T_2, ..., N_m : T_m)\\),
      where \\(m = |N| = |T|\\).

 - If \\(T_{in} = \textrm{Union}(N_{u,1} : T_{u,1}, N_{u,2} : T_{u,2}, ..., N_{u,n} : T_{u,n})\\),
   apply the following algorithm.

    - \\(T_{signals} := \textrm{Union}(N_{u,1} : \textrm{split}(T_{u,1})\_{signals}, ..., N_{u,n} : \textrm{split}(T_{u,n})\_{signals})\\)
      (i.e., replace the types contained by the group with the
      \\(T_{signals}\\) item of the tuple returned by the \\(\textrm{split}\\)
      function applied to the original types).
    - Initialize \\(N\\) and \\(T\\) to empty lists.
    - For all \\(i \in 1..n\\):
       - For all \\(\textrm{name} \in \textrm{split}(T_{u,i})\_{names}\\) (i.e.,
         for all stream names returned by the \\(\textrm{split}\\) function),
          - If \\(\textrm{name} = \varnothing\\), append \\(N_{u,i}\\) to the end
            of \\(N\\).
          - Otherwise, append the concatenation \\(N_{u,i}\\) and \\(\textrm{name}\\)
            to the end of \\(N\\), separated by a double underscore.
       - Extend \\(T\\) with \\(\textrm{split}(T_{u,i})\_{streams}\\) (i.e.,
         all named streams returned by the \\(\textrm{split}\\) function).
    - Return \\(\textrm{SplitStreams}(T_{signals}, N_1 : T_1, N_2 : T_2, ..., N_m : T_m)\\),
      where \\(m = |N| = |T|\\).

> Note that the algorithm for \\(\textrm{Group}\\) and \\(\textrm{Union}\\) is
> the same, aside from returning \\(\textrm{Group}\\) vs \\(\textrm{Union}\\)
> nodes for \\(T_{signals}\\).

#### Field conversion function

This section defines the function
\\(\textrm{fields}(T_{in}) \rightarrow \textrm{Fields}\\),
where \\(T_{in}\\) is any logical stream type, and the \\(\textrm{Fields}\\)
node is as defined in the physical stream specification.

> Intuitively, this function flattens a logical stream type consisting of
> \\(\textrm{Null}\\), \\(\textrm{Bits}\\), \\(\textrm{Group}\\) and
> \\(\textrm{Union}\\) nodes into a list of named bitfields. It is used for
> constructing the data and user field lists of the physical streams and the
> user-defined signal list from the logical stream types preprocessed by
> \\(\textrm{split}()\\). This function is normally only applied to logical
> stream types that only carry element-manipulating nodes.
>
> The names can be empty, because if there are no \\(Group\\)s or \\(Union\\)s,
> the one existing stream or signal will not have an intrinsic name given by
> the type. Empty strings are represented here with \\(\varnothing\\). Note
> that the names here can include double underscores.

\\(\textrm{fields}(T_{in})\\) is evaluated as follows.

 - If \\(T_{in} = \textrm{Null}\\) or
   \\(T_{in} = \textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), return
   \\(\textrm{Fields}()\\).

 - If \\(T_{in} = \textrm{Bits}(b)\\), return
   \\(\textrm{Fields}(\varnothing : b)\\).

 - If \\(T_{in} = \textrm{Group}(N_{g,1} : T_{g,1}, N_{g,2} : T_{g,2}, ..., N_{g,n} : T_{g,n})\\),
   apply the following algorithm.

    - Initialize \\(N_o\\) and \\(b_o\\) to empty lists.
    - For all \\(i \in 1..n\\):
       - Unpack \\(\textrm{fields}(T_{g,i})\\) into
         \\(\textrm{Fields}(N_{e,1} : b_{e,1}, N_{e,2} : b_{e,2}, ..., N_{e,m} : b_{e,m})\\).
       - For all \\(j \in 1..m\\):
          - If \\(N_{e,j} = \varnothing\\), append \\(N_{g,i}\\) to the end
            of \\(N_o\\). Otherwise, append the concatenation of \\(N_{g,i}\\)
            and \\(N_{e,j}\\), separated by a double underscore, to the end of
            \\(N_o\\).
          - Append \\(b_{e,j}\\) to the end of \\(b_o\\).
    - Return \\(\textrm{Fields}(N_{o,1} : b_{o,1}, N_{o,2} : b_{o,2}, ..., N_{o,p} : b_{o,p})\\),
      where \\(p = |N_o| = |b_o|\\).

 - If \\(T_{in} = \textrm{Union}(N_{u,1} : T_{u,1}, N_{u,2} : T_{u,2}, ..., N_{u,n} : T_{u,n})\\),
   apply the following algorithm.

    - Initialize \\(N_o\\) and \\(b_o\\) to empty lists.
    - If \\(n > 1\\):
       - Append `"tag"` to the end of \\(N_o\\).
       - Append \\(\left\lceil\log_2 n\right\rceil\\) to the end of \\(b_o\\).
    - \\(b_d := 0\\)
    - For all \\(i \in 1..n\\):
       - Unpack \\(\textrm{fields}(T_{g,i})\\) into
         \\(\textrm{Fields}(N_{e,1} : b_{e,1}, N_{e,2} : b_{e,2}, ..., N_{e,m} : b_{e,m})\\).
       - \\(b_d := \max\left(b_d, \sum_{j=1}^m b_{e,j}\right)\\)
    - If \\(b_d > 0\\):
       - Append `"union"` to the end of \\(N_o\\).
       - Append \\(b_d\\) to the end of \\(b_o\\).
    - Return \\(\textrm{Fields}(N_{o,1} : b_{o,1}, N_{o,2} : b_{o,2}, ..., N_{o,p} : b_{o,p})\\),
      where \\(p = |N_o| = |b_o|\\).

#### Synthesis function

This section defines the function
\\(\textrm{synthesize}(T_{in}) \rightarrow \textrm{LogicalStream}\\),
where \\(T_{in}\\) is any logical stream type. The \\(\textrm{LogicalStream}\\)
node is defined as \\(\textrm{LogicalStream}(F_{signals}, N_1 : P_1, N_2 : P_2, ..., N_n : P_n)\\),
where:

 - \\(F_{signals}\\) is of the form
   \\(\textrm{Fields}(N_{s,1} : b_{s,1}, N_{s,2} : b_{s,2}, ..., N_{s,m} : b_{s,m})\\),
   as defined in the physical stream specification;

   > This represents the list of user-defined signals flowing in parallel to
   > the physical streams.

 - all \\(P\\) are of the form \\(\textrm{PhysicalStream}(E, N, D, C, U)\\), as
   defined in the physical stream specification;

 - all \\(N\\) are case-insensitively unique, emptyable strings consisting of
   letters, numbers, and/or underscores, not starting or ending in an
   underscore, and not starting with a digit; and

 - \\(n\\) is a nonnegative integer.

> Intuitively, this function synthesizes a logical stream type to its physical
> representation.

\\(\textrm{synthesize}(T_{in})\\) is evaluated as follows.

 - Unpack \\(\textrm{split}(T_{in})\\) into
   \\(\textrm{SplitStreams}(T_{signals}, N_1 : T_1, N_2 : T_2, ..., N_n : T_n)\\).
 - \\(F_{signals} := \textrm{fields}(T_{signals})\\)
 - For all \\(i \in (1, 2, ..., n)\\):
    - Unpack \\(T_i\\) into \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\).
    - \\(P_i := \textrm{PhysicalStream}(\textrm{fields}(T_e), \lceil t\rceil, d, c, \textrm{fields}(T_u))\\)
 - Return \\(\textrm{LogicalStream}(F_{signals}, N_1 : P_1, N_2 : P_2, ..., N_n : P_n)\\).

#### Type compatibility function

This section defines the function
\\(\textrm{compatible}(T_{source}, T_{sink}) \rightarrow \textrm{Bool}\\),
where \\(T_{source}\\) and \\(T_{sink}\\) are both logical stream types. The
function returns true if and only if a source of type \\(T_{source}\\) can be
connected to a sink of type \\(T_{sink}\\) without insertion of conversion
logic. The function returns true if and only if one or more of the following
rules match:

 - \\(T_{source} = T_{sink}\\);

 - \\(T_{source} = \textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\),
   \\(T_{sink} = \textrm{Stream}(T'_d, t, d, s, c', r, T_u, x)\\),
   \\(\textrm{compatible}(T_e, T'_d) = \textrm{true}\\), and \\(c < c'\\);

 - \\(T_{source} = \textrm{Group}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\),
   \\(T_{sink} = \textrm{Group}(N_1: T'_1, N_2: T'_2, ..., N_n: T'_n)\\),
   and \\(\textrm{compatible}(T_i, T'_i)\\) is true for all
   \\(i \in (1, 2, ..., n)\\); or

 - \\(T_{source} = \textrm{Union}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\),
   \\(T_{sink} = \textrm{Union}(N_1: T'_1, N_2: T'_2, ..., N_n: T'_n)\\),
   and \\(\textrm{compatible}(T_i, T'_i)\\) is true for all
   \\(i \in (1, 2, ..., n)\\).

The name matching is to be done case sensitively.

> While two streams may be compatible in a case-insensitive language when only
> the case of one or more of the names differ, this would still not hold for
> case-sensitive languages. Therefore, for two streams to be compatible in all
> cases, the matching must be case sensitive.

Union semantics
---------------

When flattened into fields, a \\(\textrm{Union}\\) node consists of:

 - if there are two or more variants, a `"tag"` field of size
   \\(\left\lceil\log_2{n}\right\rceil\\), where \\(n\\) is the number of
   variants; and
 - if any variant carries data in the same stream as the union itself, a
   `"union"` field of size\
   \\(\max\limits_{\textrm{variants}} \sum\limits_{\textrm{subfields}} b_{\textrm{subfield}}\\).

The `"tag"` field is used to represent which variant is being represented, by
means of a zero-based index encoded as an unsigned binary number.

It is illegal for a source to drive the `"tag"` field of a \\(\textrm{Union}\\)
with \\(n\\) variants to \\(n\\) or higher.

> The above would otherwise be possible if \\(n\\) is not a power of two.

The `"union"` field of a \\(\textrm{Union}\\) is used to represent variant
data existing in the same stream as the union itself. It consists of the
LSB-first, LSB-aligned concatenation of the fields of the variant.

> No constraint is placed on the value that the source drives on the pad bits.

The physical streams represented by \\(\textrm{Stream}\\) nodes nested inside
\\(\textrm{Union}\\) variant types behave as if any other variants carried by
the parent stream do not exist, but are otherwise synchronized to the parent
stream as defined by the \\(s\\) parameter of the child stream.

> Consider the following example, keeping \\(x\\) generic for now:
>
> \\[B = \textrm{Group}(x: \textrm{Bits}(2), y: \textrm{Bits}(2))\\\\
> C = \textrm{Stream}(d=1, s=x, T_e=\textrm{Bits}(4))\\\\
> U = \textrm{Union}(\textrm{a} : \textrm{Bits}(3), \textrm{b} : \textrm{B}, \textrm{c} : C)\\\\
> \textrm{Stream}(d=1, T_e=U)\\]
>
> This results in two physical streams:
>
>  - the unnamed stream carrying the union with \\(D = 1\\), consisting of a
>    two-bit field named `tag` and a four-bit field named `union`; and
>  - a stream named `c`, consisting of a single unnamed four-bit field.
>
> Let's say that we need to represent the data `[a:0, b:(x:1, y:2)], [c:[3, 4, 5], a:6]`.
> The unnamed stream then carries the following four transfers, regardless of
> the value for \\(x\\):
>
> ```text
> [ (tag: "00", union: "-000"),     (1)
>   (tag: "01", union: "1001") ],   (2)
> [ (tag: "10", union: "----"),     (3)
>   (tag: "00", union: "-110") ]    (4)
> ```
>
> In transfer 1 and 4, the MSB of the `union` is don't-care, because variant
> `a` uses only three bits. In transfer 2, `x` is represented by the two LSB
> of the `union` field, while `y` is represented by the MSBs. Finally, in
> transfer 3, all bits of `union` are don't-care, as stream `c` is used to
> carry the data. Note that a `tag` value of `"11"` is illegal in this example.
>
> When \\(x = \textrm{Sync}\\), stream `c` has \\(D = 2\\) and carries the
> following transfers:
>
> ```text
> [              ],   (1)
> [ [ ("0011"),       (2)
>     ("0100"),       (3)
>     ("0101") ] ]    (4)
> ```
>
> Transfer 1 carries an empty outer sequence, corresponding to
> `[a:0, b:(x:1, y:2)]` of the represented data. There are no inner sequences
> because variant `c` never occurs in the first sequence, but the outer
> sequence must still be represented to correctly copy the dimensionality
> information of the parent stream, as mandated by the \\(\textrm{Sync}\\) tag.
> The second outer sequence represents `[c:[3, 4, 5], a:6]`; it carries one
> inner sequence, because variant `c` occurs once. The inner sequence directly
> corresponds to `[3, 4, 5]`.
>
> When \\(x = \textrm{Flatten}\\), the outer sequence is flattened away.
> Therefore, stream `c` has \\(D = 1\\) and carries only the following
> transfers:
>
> ```text
> [ ("0011"),     (1)
>   ("0100"),     (2)
>   ("0101") ]    (3)
> ```
>
> It is then up to the sink to recover the outer dimension if it needs it.
>
> When \\(x = \textrm{Desync}\\), \\(D = 2\\) as before. For the example data,
> the transfers would be exactly the same as for \\(x = \textrm{Sync}\\), but
> the zero-or-more relationship defined by the \\(\textrm{Desync}\\) means that
> a `c` variant in the parent stream may correspond with any number of inner
> sequences on the `c` stream. That does however still mean that the first
> outer sequence must be empty, as follows:
>
> ```text
> [         ],
> [ [ ... ]
>     ...
>   [ ... ] ]
> ```
>
> After all, there are no `c` variants in the first sequence to apply the
> zero-or-more relationship to.
>
> Finally, \\(x = \textrm{FlatDesync}\\) flattens the outer sequence away
> compared to \\(x = \textrm{Desync}\\), allowing for any number of transfers
> to occur on the `c` stream, all corresponding to the `c` variant in transfer
> 3 of the parent stream.
>
> If you're confused as to how a sink is to determine which transfers on the
> `c` stream correspond to which `c` variant in the parent stream, recall that
> the purpose of \\(\textrm{Desync}\\) and \\(\textrm{FlatDesync}\\) is to
> allow the user of this layer of the specification to define this relation as
> they see fit. Usually, a pattern of the form
> \\(\textrm{Group}(\textrm{len}: \textrm{Bits}(...), \textrm{data}: \textrm{Stream}(s=\textrm{Desync}, ...))\\)
> would be used, in which case the length of the encoded inner sequence would
> be transferred using the `union` field of the parent stream.

Natural ordering and inter-stream dependencies
----------------------------------------------

The natural ordering of a datum transferred over a logical stream is defined
as follows:

 - for \\(\textrm{Group}\\) nodes, the elements are ordered by left-to-right
   group order; and

 - for \\(\textrm{Stream}\\) nodes with a parent stream, for every element
   transferred on the parent stream, transfer the parent element first, then
   transfer the corresponding data on the child stream. This data depends on
   the \\(s\\) and \\(d\\) parameters of the \\(\textrm{Stream}\\) node:

    - \\(s \in (\textrm{Sync}, \textrm{Flatten})\\), \\(d = 0\\): one element
      on the parent stream corresponds to one element on the child stream;
    - \\(s = \textrm{Sync}\\), \\(d > 0\\): one element on the parent stream
      corresponds to one \\(d\\)-dimensional sequence on the child stream,
      delimited by the most significant `last` bit added on top of the `last`
      bits replicated from the parent stream;
    - \\(s = \textrm{Flatten}\\), \\(d > 0\\): one element on the parent stream
      corresponds to one \\(d\\)-dimensional sequence on the child stream,
      delimited by the most significant `last` bit;
    - \\(s \in (\textrm{Desync}, \textrm{FlatDesync})\\), \\(d = 0\\): one
      element on the parent stream corresponds to a user/context-defined number
      of elements on the child stream (zero or more); and
    - \\(s \in (\textrm{Desync}, \textrm{FlatDesync})\\), \\(d > 0\\): one
      element on the parent stream corresponds to a user/context-defined number
      of \\(d\\)-dimensional sequences on the child stream (zero or more).

> This corresponds to depth-first preorder traversal of the logical stream type
> tree. It also corresponds to the order in which you would naturally write the
> data down.

Sources may not assume that sinks will be able to handshake data in an order
differing from the natural order defined above.

Sinks may not assume that sources will be able to handshake data in an order
differing from the natural order defined above.

> The above two rules, if adhered to by both source and sink, prevent
> inter-physical-stream deadlocks.
>
> Consider the following simple example:
>
> \\[C = \textrm{Stream}(s = \textrm{Sync}, d = 0, ...)\\\\
> \textrm{Group}(\textrm{a}: C, \textrm{b}: C)\\]
>
> `[(a: 1, b: 2), (a: 3, b: 4)]`
>
> Let's say the source follows the natural ordering rule and can't buffer
> anything. That is, it drives `1` on physical stream `a`, waits for the
> transfer to be acknowledged before driving `2` on physical stream `b`, then
> waits for that to be acknowledged before driving `3`, and so on. If the
> natural-ordering rule for sinks did not exist, the sink may try to wait for
> stream `b` to be validated before it will acknowledge `a`, which would in
> this case cause a deadlock.
>
> The reasoning also works the other way around. That is, if a sink will only
> acknowledge in the natural order, but the source is allowed to drive `2` on
> `b` and wait for its acknowledgement before driving `1` on `a`, a similar
> deadlock will occur.
>
> The above does *not* mean that a source isn't allowed to drive data on the
> two streams independently as fast as it can. The same thing goes for
> acknowledgement by a sink. If both source and sink support full out-of-order
> transferral of both streams, the elements can be transferred in any order.
> It just means that sources and sinks must be compatible with an opposite end
> that does not support out-of-order transferral.
>
> The above reasoning is particularly important for streams flowing in opposing
> directions. In this case, for a physical stream `x` naturally-ordered before
> another stream `y` flowing in the opposing direction, `x` acts as a request
> stream and `y` acts as its response. That is, the source of `x` may not
> assume that the response will come before it sends the request.

User-defined signal constraints
-------------------------------

Tydi places the following constraints on the user-defined signals.

The user-defined signals flow exclusively in the source to sink direction.

> While physical streams can be reversed in order to transfer metadata in the
> sink to source direction, logical streams fundamentally describe dataflow in
> the source to sink direction. Therefore, if data needs to flow in the
> opposite direction as well, you should use a second logical stream.
>
> The reasoning behind this design choice is mostly practical in nature.
> Firstly, with the way the logical stream type is currently defined,
> unidirectional signals emerge naturally before the first stream node is
> opened, but reverse-direction signals do not. Therefore, additional nodes
> would be needed to describe this. Secondly, allowing signals to flow in both
> directions might be interpreted by users as a way for them to implement their
> own handshake method to use instead of or in addition to the handshakes of
> the Tydi physical streams. Most handshake methods break however when
> registers or clock domain crossings not specifically tailored for that
> particular handshake are inserted in between. With a purely unidirectional
> approach, latency bounds are not necessary for correctness, allowing tooling
> to automatically generate interconnect IP. Tydi does pose some constraints
> on such IP, however.

Interconnect components such as register slices and clock domain crossings may
interpret the signals as being asynchronous. Thus, a sink may not rely upon bit
flips driven in the same cycle by the source from occurring in the same cycle
at the sink.

> This allows clock domain crossings for the user-defined signals to reduce to
> simple synchronization registers.

The latency of any signal travelling from source to sink must be less than or
equal to the latency of the streams.

> This allows the user-defined signals to be used as a way to transfer
> quasi-constant values, such as the control register inputs for some kernel.
> As long as the constants are only mutated before the source sends the first
> stream transfer, the signals received by the sink are guaranteed to be stable
> by the time it receives the first transfer.
>
> Handshaking in the reverse direction may be done using a reversed stream.
> When the sink is done processing, it sends a transfer on this stream. This
> will necessarily be received by the source at a later point in time, which
> is then free to modify the user signals again.
>
> Note that slow-changing counter values (those that only increment or
> decrement slowly with respect to the involved clock domains) can be
> safely transferred using Gray code. Pulse/strobe signals can be safely
> transferred by the source toggling a user-defined signal whenever the
> strobe occurs, and the sink detecting the transitions in this signal.