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

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

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

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.

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

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.

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
 
arx/datatypes.txt · Last modified: 2023/07/17 22:40 by 127.0.0.1