From 12bd9133ac7a4e5c8bef5ffcfd15627a8e836b61 Mon Sep 17 00:00:00 2001 From: Jeroen van Straten Date: Tue, 10 Mar 2020 16:16:07 +0100 Subject: [PATCH 1/4] Remove constraints on user-friendly representation --- book/src/specification/logical.md | 152 +++++++++++------------------ book/src/specification/physical.md | 97 +++--------------- 2 files changed, 73 insertions(+), 176 deletions(-) diff --git a/book/src/specification/logical.md b/book/src/specification/logical.md index ff49b823..01c1a711 100644 --- a/book/src/specification/logical.md +++ b/book/src/specification/logical.md @@ -220,24 +220,9 @@ defined as \\(\textrm{Group}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\), where: > 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. +The names must be unique within the group. #### Union @@ -277,24 +262,9 @@ is defined as \\(\textrm{Union}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\), where: > 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. +The names must be unique within the union. ### Operations on logical stream @@ -332,44 +302,44 @@ 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)\\), +\\(\textrm{SplitStreams}(T_{signals}, P_1 : T_1, P_2 : T_2, ..., P_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 + - all \\(P\\) are unique name paths, each path consisting of zero or more + strings, where each string is nonempty and consists of letters, numbers, + and/or underscores; 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\\). +> with index \\(i\\) and name path \\(P_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. +> The name paths 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 paths are represented here with +> \\(\varnothing\\). \\(\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. + - Initialize \\(P\\) 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 \\(\varnothing\\) (an empty name path) to the end of \\(P\\). - 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). + - Extend \\(P\\) with \\(\textrm{split}(T_e)\_{paths}\\) (i.e., + all stream name paths 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')\\). @@ -383,50 +353,46 @@ where: - \\(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)\\). + - Return \\(\textrm{SplitStreams}(\textrm{Null}, P_1 : T_1, P_2 : T_2, ..., P_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})\\), + - If \\(T_{in} = \textrm{Group}(N_1 : T_{g,1}, N_2 : T_{g,2}, ..., N_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})\\) + - \\(T_{signals} := \textrm{Group}(N_1 : \textrm{split}(T_{g,1})\_{signals}, ..., N_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. + - Initialize \\(P\\) 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. + - For all \\(\textrm{path} \in \textrm{split}(T_{g,i})\_{paths}\\) (i.e., + for all stream name paths returned by the \\(\textrm{split}\\) function), + append the concatenation of \\(\textrm{path}\\) and \\((N_i)\\) to the end + of \\(P\\). - 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|\\). + - Return \\(\textrm{SplitStreams}(T_{signals}, P_1 : T_1, P_2 : T_2, ..., P_m : T_m)\\), + where \\(m = |P| = |T|\\). - - If \\(T_{in} = \textrm{Union}(N_{u,1} : T_{u,1}, N_{u,2} : T_{u,2}, ..., N_{u,n} : T_{u,n})\\), + - If \\(T_{in} = \textrm{Union}(N_1 : T_{u,1}, N_2 : T_{u,2}, ..., N_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})\\) + - \\(T_{signals} := \textrm{Union}(N_1 : \textrm{split}(T_{u,1})\_{signals}, ..., N_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. + - Initialize \\(P\\) 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. + - For all \\(\textrm{path} \in \textrm{split}(T_{u,i})\_{paths}\\) (i.e., + for all stream name paths returned by the \\(\textrm{split}\\) function), + append the concatenation of \\(\textrm{path}\\) and \\((N_i)\\) to the end + of \\(P\\). - 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|\\). + - Return \\(\textrm{SplitStreams}(T_{signals}, P_1 : T_1, P_2 : T_2, ..., P_m : T_m)\\), + where \\(m = |P| = |T|\\). > Note that the algorithm for \\(\textrm{Group}\\) and \\(\textrm{Union}\\) is > the same, aside from returning \\(\textrm{Group}\\) vs \\(\textrm{Union}\\) @@ -449,8 +415,7 @@ node is as defined in the physical stream specification. > > 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. +> the type. Empty strings are represented here with \\(\varnothing\\). \\(\textrm{fields}(T_{in})\\) is evaluated as follows. @@ -461,61 +426,58 @@ node is as defined in the physical stream specification. - 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})\\), + - If \\(T_{in} = \textrm{Group}(N_1 : T_{g,1}, N_2 : T_{g,2}, ..., N_n : T_{g,n})\\), apply the following algorithm. - - Initialize \\(N_o\\) and \\(b_o\\) to empty lists. + - Initialize \\(P_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})\\). + \\(\textrm{Fields}(P_{e,1} : b_{e,1}, P_{e,2} : b_{e,2}, ..., P_{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 the concatenation of \\((N_i)\\) and \\(P_{e,j}\\) to the end of + \\(P_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|\\). + - Return \\(\textrm{Fields}(P_{o,1} : b_{o,1}, P_{o,2} : b_{o,2}, ..., P_{o,p} : b_{o,p})\\), + where \\(p = |P_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})\\), + - If \\(T_{in} = \textrm{Union}(N_1 : T_{u,1}, N_2 : T_{u,2}, ..., N_n : T_{u,n})\\), apply the following algorithm. - - Initialize \\(N_o\\) and \\(b_o\\) to empty lists. + - Initialize \\(P_o\\) and \\(b_o\\) to empty lists. - If \\(n > 1\\): - - Append `"tag"` to the end of \\(N_o\\). + - Append `"tag"` to the end of \\(P_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})\\). + \\(\textrm{Fields}(P_{e,1} : b_{e,1}, P_{e,2} : b_{e,2}, ..., P_{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 `"union"` to the end of \\(P_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|\\). + - Return \\(\textrm{Fields}(P_{o,1} : b_{o,1}, P_{o,2} : b_{o,2}, ..., P_{o,p} : b_{o,p})\\), + where \\(p = |P_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)\\), +node is defined as \\(\textrm{LogicalStream}(F_{signals}, P_1 : S_1, P_2 : S_2, ..., P_n : S_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})\\), + \\(\textrm{Fields}(P_{s,1} : b_{s,1}, P_{s,2} : b_{s,2}, ..., P_{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 + - all \\(S\\) 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 + - all \\(P\\) are unique name paths, consisting of zero or more non-empty + strings, each consisting of letters, numbers, and/or underscores; and - \\(n\\) is a nonnegative integer. @@ -525,12 +487,12 @@ where: \\(\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)\\). + \\(\textrm{SplitStreams}(T_{signals}, P_1 : T_1, P_2 : T_2, ..., P_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)\\). + - \\(S_i := \textrm{PhysicalStream}(\textrm{fields}(T_e), \lceil t\rceil, d, c, \textrm{fields}(T_u))\\) + - Return \\(\textrm{LogicalStream}(F_{signals}, P_1 : S_1, P_2 : S_2, ..., P_n : S_n)\\). #### Type compatibility function diff --git a/book/src/specification/physical.md b/book/src/specification/physical.md index 4065c092..3d6154a1 100644 --- a/book/src/specification/physical.md +++ b/book/src/specification/physical.md @@ -23,9 +23,10 @@ The significance of these parameters is defined in the following subsections. ### Element content (E) and user/transfer content (U) \\(E\\) and \\(U\\) are of the form -\\(\textrm{Fields}(N_1 : b_1, N_2 : b_2, ..., N_n : b_n)\\), where \\(N\\) are -names, \\(b\\) are positive integers representing bit counts, and \\(n\\) is a -nonnegative integer, describing a list of \\(n\\) named bit vector signals. +\\(\textrm{Fields}(P_1 : b_1, P_2 : b_2, ..., P_n : b_n)\\), where \\(P\\) are +name paths, \\(b\\) are positive integers representing bit counts, and \\(n\\) +is a nonnegative integer, describing a list of \\(n\\) named bit vector +signals. > The element type can be given zero fields to make a "null" stream. Such > streams can still be useful, as physical streams also carry metadata. The @@ -42,26 +43,12 @@ encodes one instance of the user/transfer content. > network-on-chip-like structure by attaching routing information such as > source and destination addresses through this method. -The name of each field is a string consisting of letters, numbers, and/or -underscores. It may be empty, represented in this specification as -\\(\varnothing\\). +The name paths are tuples of strings, each string consisting of letters, +numbers, and/or underscores. The tuple may be empty. -The name cannot start or end with an underscore. +The name paths must be unique within the set of named fields. -The name cannot start with a digit. - -> It is illegal to start or end with an underscore or start with a number to -> prevent confusion when the name is prefixed to form the signal name, and -> for compatibility with VHDL. - -The name must be case-insensitively unique within the set of named fields. - -> The identifier is case-insensitive because compatibility with VHDL is -> desired. - -\\(|\textrm{Fields}(N_1 : b_1, N_2 : b_2, ..., N_n : b_n)|\\) is a shorthand -defined to equal \\(\sum_{i=1}^{n} b_i\\); that is, the sum of the field bit -count over all fields in the element. +The name path components cannot be empty. ### Number of element lanes (N) @@ -526,20 +513,15 @@ below must be driven for the omitted signals. Streams may be named, in order to prevent name conflicts due to multiple streams existing within the same namespace. Such a name is to be prefixed to -the signal names using a double underscore. - -> Double underscores are used as a form of unambiguous hierarchy separation to -> allow user-specified field names in the logical stream types (defined later) -> to contain (non-consecutive) underscores without risk of name conflicts. +the signal names using an underscore. The canonical representation for the `data` and `user` signals is the LSB-first concatenation of the contained fields. For \\(N > 1\\), the lanes are concatenated LSB first, such that the lane index is major and the field index is minor. -Where applicable, the signals must be listed in the following order: `dn`, -`up`, `valid`, `ready`, `data`, `last`, `stai`, `endi`, `strb`, `user` (`dn` -and `up` are part of the alternative representation defined below). +Where applicable, the signals must be listed in the following order: `valid`, +`ready`, `data`, `last`, `stai`, `endi`, `strb`, `user`. > This is mostly just a consistency thing, primarily because it helps to get > used to a single ordering when interpreting simulation waveforms. It may also @@ -550,55 +532,8 @@ The signal names must be lowercase. > This is for interoperability between languages that differ in case > sensitivity. -#### Alternative representation - -To improve code readability in hardware definition languages supporting array -and aggregate constructs (record, struct, ...), the following changes are -permissible. However, it is recommended to fall back to the conventions above -for interoperability with other streamlets on the "outer" interfaces of an IP -block. - - - `valid`, `data`, `last`, `stai`, `endi`, `strb`, and `user` may be bundled - in an aggregate type named `__dn__type` with signal name - `__dn` (`dn` is short for "downstream"). - - - `ready` may be "bundled" in an aggregate type named - `__up__type` with signal name `__up` for symmetry - (`up` is short for "upstream"). - - - If the language allows for signal direction reversal within a bundle, all - stream signals may also be bundled into a single type named - `__type`, with `ready` in the reverse direction. - - - The data and user fields may be bundled in aggregate types named - `__data__type` and `__user__type` respectively. - The `data` signal becomes an array of `__data__type`s from 0 to - \\(N - 1\\) if \\(N > 1\\) or when otherwise desirable. - - - Data and user fields consisting of a single bit may be interpreted as either - a bit vector of size one or a scalar bit depending on context. - - - Fields with a common double-underscore-delimited prefix may be aggregated - recursively using `__data____type`, in such a - way that the double underscores in the canonical signal name are essentially - replaced with the hierarchy separator of the hardware definition language. - -#### Arrays, vectors, and concatenations - -Where applicable, the bitrange for bit vector signals is always `n-1 downto 0`, -where `n` is the number of bits in the signal. - -Concatenations of bit vector signals are done LSB-first. - -> That is, lower indexed entries use lower bit indices and thus occur on the -> right-hand side when the bit vector is written as a binary number. Note that -> this results in the inverse order when using the concatenation operator in -> VHDL (and possibly other languages). LSB-first order is chosen because it is -> more intuitive when indexing the vector. Ultimately this is just a -> convention. - -Where applicable, the range for arrays is always `0 to n-1`, where `n` is the -number of entries in the array. - -> This is the more natural order for array-like structures, actually putting -> the first entry on the left-hand side. +Whenever interoperability is not a concern, users may choose to use a more +ergonomic representation, making use of whatever abstractions the target +hardware definition language supports, as long as this representation +synthesizes to the same raw bits as the canonical representation, and can thus +be converted at zero cost. From e9472a86015a1d6e4851152905f0cb8944fdec26 Mon Sep 17 00:00:00 2001 From: Jeroen van Straten Date: Tue, 10 Mar 2020 17:45:51 +0100 Subject: [PATCH 2/4] Allow specification of non-empty sequences --- book/src/specification/logical.md | 53 +++++++++++++++--------------- book/src/specification/physical.md | 44 ++++++++++++++----------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/book/src/specification/logical.md b/book/src/specification/logical.md index 01c1a711..a9cb9dad 100644 --- a/book/src/specification/logical.md +++ b/book/src/specification/logical.md @@ -58,7 +58,7 @@ defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where: > > 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), ...)), ...)\\), + > \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 @@ -68,11 +68,12 @@ defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where: > 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; + - \\(d\\) is tuple of booleans, representing the dimensionality of the child + stream with respect to the parent stream (\\(\|d\|\\)) and whether each + added dimension \\(i\\) can be empty (\\(d_i\\)); > As we'll see later, the \\(D\\) parameter for the resulting physical - > stream equals \\(\sum d\\) for all ancestral + > stream equals the concatenation of \\(d\\) for all ancestral > \\(\textrm{Stream}\\) nodes, including this node, up to and including the > closest ancestor (or self) for which \\(s = \textrm{Flatten}\\). @@ -82,8 +83,8 @@ defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where: 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 + > 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 @@ -95,7 +96,7 @@ defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where: > 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}, ...)), ...)\\), + > 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}\\) @@ -106,7 +107,7 @@ defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where: > \\(\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}, ...)), ...)\\), + > \\(\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 @@ -159,12 +160,12 @@ defined as \\(\textrm{Stream}(T_e, t, d, s, c, r, T_u, x)\\), where: > 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=...))\\). + > \\(\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 + > \\(\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. @@ -349,7 +350,7 @@ where: - 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\\). + assign \\(d'\\) to the concatenation of \\(d'\\) and \\(d\\). - \\(t' := t' \cdot t\\). - Append \\(\textrm{Stream}(T'\_d, t', d', s', c', r', T'\_u, x')\\) to \\(T\\). @@ -561,13 +562,13 @@ 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))\\\\ +> 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)\\] +> \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 +> - 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. > @@ -588,7 +589,7 @@ stream as defined by the \\(s\\) parameter of the child stream. > 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 +> When \\(x = \textrm{Sync}\\), stream `c` has \\(\|D\| = 2\\) and carries the > following transfers: > > ```text @@ -608,7 +609,7 @@ stream as defined by the \\(s\\) parameter of the child stream. > 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 +> Therefore, stream `c` has \\(\|D\| = 1\\) and carries only the following > transfers: > > ```text @@ -619,7 +620,7 @@ stream as defined by the \\(s\\) parameter of the child stream. > > 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, +> 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 @@ -664,21 +665,21 @@ as follows: 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 + - \\(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, + - \\(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, + - \\(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 + - \\(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 + - \\(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). + 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 @@ -695,7 +696,7 @@ differing from the natural order defined above. > > Consider the following simple example: > -> \\[C = \textrm{Stream}(s = \textrm{Sync}, d = 0, ...)\\\\ +> \\[C = \textrm{Stream}(s = \textrm{Sync}, \|d\| = 0, ...)\\\\ > \textrm{Group}(\textrm{a}: C, \textrm{b}: C)\\] > > `[(a: 1, b: 2), (a: 3, b: 4)]` diff --git a/book/src/specification/physical.md b/book/src/specification/physical.md index 3d6154a1..aedbb3e4 100644 --- a/book/src/specification/physical.md +++ b/book/src/specification/physical.md @@ -59,10 +59,14 @@ lane. ### Dimensionality (D) -\\(D\\) must be an integer greater than or equal to zero. It specifies the -number of `last` bits needed to represent the data. - -> Intuitively, each sequence nesting level adds one to this number. For +\\(D\\) must be an tuple of booleans. Each boolean represents a dimension of +the represented data; it can be empty if the data is scalar. The boolean +signifies whether that dimension consists of zero or more elements (true) or +one or more elements (false). \\(\|D\|\\) (the number of elements in \\(D\\)) +represents the number of `last` bits needed to represent the data. + +> Intuitively, each sequence nesting level adds a dimension, whith the boolean +> corresponding to the dimension set to whether the sequence is emptyable. For > instance, to stream two-dimensional sequences, two `last` bits are needed: > one to mark the boundaries of the inner sequence, and one to mark the > boundary of each two-dimensional sequence. @@ -126,7 +130,7 @@ A physical stream is comprised of the following signals. | `valid` | Source | Stalling the data stream due to the source not being ready. | | `ready` | Sink | Stalling the data stream due to the sink not being ready. | | `data` | Source | Data transfer of \\(N\\) \\(\|E\|\\)-bit elements. | -| `last` | Source | Indicating the last transfer for \\(D\\) levels of nested sequences. | +| `last` | Source | Indicating the last transfer for \\(\|D\|\\) levels of nested sequences. | | `stai` | Source | Start index; encodes the index of the first valid lane. | | `endi` | Source | End index; encodes the index of the last valid lane. | | `strb` | Source | Strobe; encodes individual lane validity for \\(C \ge 8\\), empty sequences otherwise. | @@ -140,7 +144,7 @@ bit vectors with the following widths. | `valid` | *scalar* | | `ready` | *scalar* | | `data` | \\(N \times \|E\|\\) | -| `last` | \\(N \times D\\) | +| `last` | \\(N \times \|D\|\\) | | `stai` | \\(\lceil \log_2{N} \rceil\\) | | `endi` | \\(\lceil \log_2{N} \rceil\\) | | `strb` | \\(N\\) | @@ -270,16 +274,16 @@ transfer. The `last` signal consists of `N` concatenated lanes, each carrying a termination bit for each nested sequence represented by the physical stream. The `last` bit for lane \\(i\\) and dimension \\(j\\) (i.e., bit -\\(i \cdot D + j\\)) being asserted indicates completion of a sequence with -nesting level \\(D - j - 1\\), containing all elements streamed between the +\\(i \cdot \|D\| + j\\)) being asserted indicates completion of a sequence with +nesting level \\(\|D\| - j - 1\\), containing all elements streamed between the previous lane/transfer this occured for dimension \\(j' \ge j\\) (or system reset, if never) exclusively and this point inclusively, where nesting level is defined to be 0 for the outermost sequence, 1 for its inner sequence, up to -\\(D - 1\\) for the innermost sequence. It is active-high. +\\(\|D\| - 1\\) for the innermost sequence. It is active-high. > For example, one way to represent the value > `["Hello", "World"], ["Tydi", "is", "nice"], [""], []` with -> \\(N = 6, C \ge 8\\) is as follows. Note that \\(D = 2\\) follows +> \\(N = 6, C \ge 8\\) is as follows. Note that \\(\|D\| = 2\\) follows > from the data type, \\(|E|\\) depends on the character encoding, and the > example does not depend on \\(U\\). > ```text @@ -488,16 +492,16 @@ on the interface. When two interfaces with differing but compatible complexities are connected together, the default value specified in the table below must be driven for the omitted signals. -| Name | Condition | Default | -|---------|---------------------------------------------|-----------| -| `valid` | *see below* | `'1'` | -| `ready` | *see below* | `'1'` | -| `data` | \\(\|E\| > 0\\) | all `'0'` | -| `last` | \\(D \ge 1\\) | all `'1'` | -| `stai` | \\(C \ge 6 \wedge N > 1\\) | 0 | -| `endi` | \\((C \ge 5 \vee D \ge 1) \wedge N > 1\\) | \\(N-1\\) | -| `strb` | \\(C \ge 7 \vee D \ge 1\\) | all `'1'` | -| `user` | \\(\|U\| > 0\\) | all `'0'` | +| Name | Condition | Default | +|---------|-------------------------------------------------|-----------| +| `valid` | *see below* | `'1'` | +| `ready` | *see below* | `'1'` | +| `data` | \\(\|E\| > 0\\) | all `'0'` | +| `last` | \\(\|D\| \ge 1\\) | all `'1'` | +| `stai` | \\(C \ge 6 \wedge N > 1\\) | 0 | +| `endi` | \\((C \ge 5 \vee \|D\| \ge 1) \wedge N > 1\\) | \\(N-1\\) | +| `strb` | \\(C \ge 7 \vee (\|D\| \ge 1 \wedge \sum{D})\\) | all `'1'` | +| `user` | \\(\|U\| > 0\\) | all `'0'` | `valid` may be omitted for sources that are always valid. From 6db60e0b241c21fa866183a105d9d3121c9d742e Mon Sep 17 00:00:00 2001 From: Jeroen van Straten Date: Tue, 10 Mar 2020 17:51:44 +0100 Subject: [PATCH 3/4] Add Tuple(n, T) abbreviation --- book/src/specification/logical.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/src/specification/logical.md b/book/src/specification/logical.md index a9cb9dad..b9e9bf6b 100644 --- a/book/src/specification/logical.md +++ b/book/src/specification/logical.md @@ -219,7 +219,9 @@ defined as \\(\textrm{Group}(N_1: T_1, N_2: T_2, ..., N_n: T_n)\\), where: > 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. +> `record` in most programming languages, or can be used to specify an array of +> a defined size if the types are all the same. The latter may be abbreviated +> as \\(\textrm{Tuple}(n, T)\ \rightarrow\ \textrm{Group}(0: T, 1: T, ..., n-1: T)\\). The names cannot be empty. From 8c51645f2368dd5f05286bbe62d9793de9612860 Mon Sep 17 00:00:00 2001 From: Jeroen van Straten Date: Tue, 10 Mar 2020 18:01:32 +0100 Subject: [PATCH 4/4] Clarify the intent of the user-defined signals --- book/src/specification/logical.md | 39 +++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/book/src/specification/logical.md b/book/src/specification/logical.md index b9e9bf6b..96d74465 100644 --- a/book/src/specification/logical.md +++ b/book/src/specification/logical.md @@ -2,15 +2,15 @@ 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. +user-defined signals for quasi-constant values, 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 and the constants 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 @@ -729,8 +729,15 @@ differing from the natural order defined above. > 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 -------------------------------- +User-defined signals +-------------------- + +The purpose of the user-defined signals is to communicate quasi-constant values +from the source to the sink, where the definition of "quasi-constant" is mostly +up to the user. + +> They may for instance be control register values that are initialized +> asynchronous to the streams between invocations of a larger algorithm. Tydi places the following constraints on the user-defined signals. @@ -739,7 +746,9 @@ 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. +> opposite direction as well, you should use a second logical stream, and if +> you need to use any form of handshaking outside of Tydi's streams, you should +> use another mechanism to describe those signals. > > The reasoning behind this design choice is mostly practical in nature. > Firstly, with the way the logical stream type is currently defined, @@ -748,12 +757,12 @@ The user-defined signals flow exclusively in the source to sink direction. > 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 +> the Tydi physical streams, but 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. +> to automatically generate interconnect IP that includes registers. 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