====== Arx Data Types ======
===== Scalar Data Types =====
The basic data types of Arx can be subdivided in //scalar// and
//vector// data types.
The scalar types are given in the table below:
^ Data type ^ Explanation ^
| ''bit'' | The data type for binary signals. Possible values are ''0'' and ''1'' |
| ''boolean'' | A data type with possible values ''true'' and ''false'' |
| ''integer'' | The //integer// as found in most common programming languages. |
| ''real'' | Floating-point number; becomes ''float'' in C |
**Remark**: //Arx code that uses the// ''real'' //data type, cannot be
converted into VHDL!//
Integer constants can be specified in decimal, hexadecimal or binary
notation. A hexadecimal constant starts with ''0h'' and may use the
characters 'a' to 'f' in either upper or lower case. A binary constant
starts with ''0b''. Hexadecimal and binary constants always designate a
positive integer, unless preceded by a minus sign.
Real constants can be used to specify fixed-point constants. This may
lead to loss of precision as a consequence of quantization and
overflow processing.
The code fragement below shows some examples of the use of constants.
register
# three registers initialized with the same value
bval1: bitvector(8) = 0b10101010
bval2: bitvector(8) = 0haa
bval3: bitvector(8) = 170
# more examples of constants
bval4: unsigned(8) = 0haa
bval5: unsigned(8,2) = 1.75 # no loss of precision
bval6: signed(8,2) = -1.5 # no loss of precision
bval7: signed(8,4) = 3.14 # will be converted to 3.125 = 50/16
===== Vector Data Types =====
The vector types all have in common that they consist of a sequence of bits.
They differ in the interpretation of these bits. The vector types are
listed below:
^ Data type ^ Explanation ^
| ''bitvector''() | A vector of bits |
| ''unsigned''() | An -bit vector, interpreted as an unsigned integer |
| ''signed''() | An -bit vector, interpreted as a signed two's complement integer |
| ''unsigned''(, ) | An -bit vector, interpreted as an unsigned fixed-point number with integer bits |
| ''signed''(, ) | An -bit vector, interpreted as a signed fixed-point number with integer bits |
The index that addresses individual positions in a vector type, is
always in the range 0 to --1. Square brackets are used in the
syntax for indexing.
Two indices separated by a colon and enclosed between square brackets
select a subrange or slice of a bitvector. As opposed to other HDLs
the left index should always be smaller or equal to the right one
(when both indices are equal a single bit is selected). A subrange can
be used both at the left as well as the right-hand side of an
assignment. The example below illustrates the addressing of bits in a
vector.
component top
word_length : generic integer = 8
T_IO : generic type = bitvector(word_length)
data_in : in T_IO
data_out : out T_IO
register
storage: T_IO = 0
begin
storage[0] = storage[word_length-1]
storage[1:word_length-1] = data_in[0:word_length-2]
data_out = storage
end
===== Quantization and Overflow in Fixed-Point Data Types =====
Consider the case that a signal carrying a fixed-point value is
wired to another signal with fewer bits. In Arx, this amounts to a
fixed-point signal being assigned to another fixed-point signal
(variable or register). In such an assignment the binary points of the
fixed-point patterns are always aligned. If
the signal at the left-hand side of the assignment has fewer bits at the
least-significant side of the bit pattern, //quantization// is
necessary. If there are fewer bits available at the most-significant
side, //overflow// occurs.
One can deal in many ways with overflow and quantization. The desired
behavior should be indicacted as part of the fixed-point type
declaration with two optional parameters, respectively '''' and '''':
unsigned(, , , )
signed(, , , )
The following quantization modes exist:
^ Quantization mode ^ Explanation ^
| ''trunc'' | Truncate (default). |
| ''round'' | Round to the closest value; break ties by going up. |
| ''round_zero'' | As ''round'', but break ties by going towards zero. |
| ''round_inf'' | As ''round'', but break ties by going away from zero. |
The following overflow modes exist:
^ Overflow mode ^ Explanation ^
| ''wrap'' | Wrap around (default). |
| ''sat'' | Saturate to the extreme values. |
| ''sat_sym'' | Saturate symmetrical, the minimal extreme value is the negative of the positive extreme value. |
===== Enumeration Data Type =====
As in many other languages for hardware description or programming,
Arx allows the definition of new data types with a finite number of
values. The possible values are identifiers that are enumerated at the
time of the declaration of the so-called //enumeration// data type.
Example:
type
input_state = enum(start, processing, ready)
The value of an enumerated type is indicated by preceding the declared
value by the type name and a dot. Example:
# a registered signal of type input_state with its reset value
register
current_state: input_state = input_state.start
# later on in the code
begin
if current_state == input_state.start
current_state = input_state.processing
end
===== Arrays =====
Arx supports 1-dimensional arrays. The array elements can be any of
the basic types mentioned above as well as enumeration data types.
Arrays declarations contain a enclosed in square brackets,
which gives the number of elements
that the array will have. An array index runs from 0 to -- 1.
Individual array elements are selected by an index enclosed in square
brackets.
Initial values of registers of an array type should be provided in a list
delimited by curly braces and separated by commas in order of
increasing index.
When a single value is provided, this value is used as the initial
value of all vector elements.
When the base type of an array is a vector, an individual bits or a
bit slice can be selected in a similar way as a single vector. In the
syntax, there are two pairs of indices enclosed in square brackets.
The first one selects the array element, the second the bits within
the vector. The code below, shows an example:
The following code fragment illustrates the use of arrays:
component top
T_IO : generic type = signed(10, 5, sat, round)
data_in : in T_IO
data_out : out T_IO
type
T_enum: enum(one, two, three)
T_ar1: array[3] of T_IO
T_ar2: array[3] of T_enum
register
v1 : T_ar1 = 0
v2 : T_ar2 = {T_enum.three, T_enum.two, T_enum.one}
v3 : array[5] of T_IO = {5, 4, 3, 2, 1}
begin
v1[1] = data_in
for i in 0:1
v2[i] = v2[i+1]
end
# example of accessing individual bits in an array of vectors
v3[0][0:4] = v1[2][5:9]
v3[0][5:9] = v1[2][0:4]
# rest of description left out
===== Explicit Type Conversion =====
In a situations where a signal of some type is wired to one of another
type, Arx is able to check whether the type conversion is possible and
insert overflow and quantization logic if necessary (see also above).
There exists, however, situations where one would like to impose a
type to an intermediate result in an expression. In such a case the
Arx function ''convert'' can be used.
The example below illustrates the use of ''convert''; without it, the
addition would be performed at the resolution of the widest operand
instead of the narrowest one.
component top
T_IO : generic type = signed(10, 5)
data_in : in T_IO
data_out : out T_IO
type
T_narrow: signed(6, 4, sat, round)
register
storage: T_narrow = 0
begin
storage = storage + convert(T_narrow, data_in)
data_out = storage
end
===== Reinterpretation =====
A sequence of bits can be interpreted in many ways. There are
situations in which the interpretation of the same pattern changes
across the hardware. The hardware itself does not change but the type
change needs to be made explicit in the hardware description. For
these situations, Arx uses the function ''reinterpret''.
The example below illustrates the use of reinterpretation. The
input to the hardware consists of a bit vector composed of two
signed numbers. In the description, the two numbers are first
extracted, added together, and the numerical result is then
interpreted again as a bit vector.
component top
word_length : generic integer = 8
T_in : generic type = bitvector(2*word_length)
T_out : generic type = bitvector(word_length+1)
data_in : in T_in
data_out : out T_out
type
T_num : signed(word_length)
T_num_p1: signed(word_length+1)
register
storage: T_out = 0
variable
left, right: T_num
sum: T_num_p1
begin
left = reinterpret(T_num, data_in[0:word_length-1])
right = reinterpret(T_num, data_in[word_length:2*word_length-1])
sum = left + right
storage = reinterpret(T_out, sum)
data_out = storage
end