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

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` (<n>) | A vector of <n> bits |

`unsigned` (<n>) | An <n>-bit vector, interpreted as an unsigned integer |

`signed` (<n>) | An <n>-bit vector, interpreted as a signed two's complement integer |

`unsigned` (<n>, <m>) | An <n>-bit vector, interpreted as an unsigned fixed-point number with <m> integer bits |

`signed` (<n>, <m>) | An <n>-bit vector, interpreted as a signed fixed-point number with <m> integer bits |

The index that addresses individual positions in a vector type, is always in the range 0 to <n>–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.

- bit_addressing.arx
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

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 `<o-mode>`

and `<q-mode>`

:

unsigned(<n>, <m>, <o-mode>, <q-mode>) signed(<n>, <m>, <o-mode>, <q-mode>)

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. |

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

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 <size> enclosed in square brackets, which gives the number of elements that the array will have. An array index runs from 0 to <size> – 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

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.

- convert-example.arx
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

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.

- reinterpret-example.arx
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