2. Bits

2.1. Arrays and Pointers

This section is a short recap of some standard C functionality: data types, memory allocations, pointer arithmetic. If these tasks look difficult to you:

  • Try to get some additional practice outside of the class.

  • Ask questions if you have any. No matter how “basic” they might be.

Tasks

  1. In all your tests below, compile in debug-mode, i.e., use -g -O0 and run your program through Valgrind.

  2. Use the sizeof-operator to determine the size of the data types unsigned char, char, unsigned int, float, double. What’s the size of respective pointers, e.g., unsigned int * or float *?

  3. Use the functions malloc and free to allocate and free an array. The array should be large enough to hold 1,500 values of type int.

  4. Initialize the array: \(a[i] = 3 \cdot i\).

  5. Go through pointer arithmetic (do not use the []-operator) to print the value at position 500. Print the memory addresses at position 250 and position 750, what’s their distance in bytes?

2.2. Working with Bits

We discussed data encodings in class. These are key to understand how numerical, textual and program data is stored. The used encoding can have a significant impact on the obtained performance. For example, when microbenchmarking the sustainable peak performance on V1, we observed a two-times higher floating point throughput when switching from double- to single-precision arithmetic.

In practice, we might have to manipulate raw data at the bit level. C/C++ offers a series of operators and functions to do exactly this. In this section we’ll look at some low(er)-level C/C++ operators and functions.

Tasks

  1. Write C/C++ code to print the binary representations of the following variables. Make sure that you understand why you obtain the respective results.

    unsigned char l_data1  = 1;
    unsigned char l_data2  = 255;
    unsigned char l_data3  = l_data2 + 1;
    unsigned char l_data4  = 0xA1;
    unsigned char l_data5  = 0b1001011;
    unsigned char l_data6  = 'H';
             char l_data7  = -4;
    unsigned int  l_data8  = 1u << 11;
    unsigned int  l_data9 = l_data8 << 21;
    unsigned int  l_data10  = 0xFFFFFFFF >> 5;
    unsigned int  l_data11 = 0b1001 ^ 0b01111;
    unsigned int  l_data12 = ~0b1001;
    unsigned int  l_data13 = 0xF0 & 0b1010101;
    unsigned int  l_data14 = 0b001 | 0b101;
    unsigned int  l_data15 = 7743;
             int  l_data16 = -7743;
    
  2. Write the function instruction_asimd_compute which has the following signature:

    unsigned int instruction_asimd_compute( unsigned int  i_vec_instr,
                                            unsigned char i_vec_reg_dst,
                                            unsigned char i_vec_reg_src_0,
                                            unsigned char i_vec_reg_src_1 );
    

    It should manipulate i_vec_instr in the following way:

    • Bits 0-4 should be set to bits 0-4 of i_vec_reg_dst

    • Bits 5-9 should be set to bits 0-4 of i_vec_reg_src_0

    • Bits 16-20 should be set to bits 0-4 of i_vec_reg_src_1

    The return value is the result of this procedure. For example, assume that you get the following input:

    • i_vec_instr = 0b01001110001000001100110000000000

    • i_vec_reg_dst = 0b00000000

    • i_vec_reg_src_0 = 0b00000001

    • i_vec_reg_src_1 = 0b00000010

    then the returned value should be 0b01001110001000101100110000100000.

  3. Have a look at the documentation of FMLA (vector). What did we just accomplish through the example above?