Next: , Previous: Bit_Order Clauses, Up: Representation Clauses and Pragmas


9.9 Effect of Bit_Order on Byte Ordering

In this section we will review the effect of the Bit_Order attribute definition clause on byte ordering. Briefly, it has no effect at all, but a detailed example will be helpful. Before giving this example, let us review the precise definition of the effect of defining Bit_Order. The effect of a non-standard bit order is described in section 13.5.3 of the Ada Reference Manual:

"2 A bit ordering is a method of interpreting the meaning of the storage place attributes."

To understand the precise definition of storage place attributes in this context, we visit section 13.5.1 of the manual:

"13 A record_representation_clause (without the mod_clause) specifies the layout. The storage place attributes (see 13.5.2) are taken from the values of the position, first_bit, and last_bit expressions after normalizing those values so that first_bit is less than Storage_Unit."

The critical point here is that storage places are taken from the values after normalization, not before. So the Bit_Order interpretation applies to normalized values. The interpretation is described in the later part of the 13.5.3 paragraph:

"2 A bit ordering is a method of interpreting the meaning of the storage place attributes. High_Order_First (known in the vernacular as 'big endian') means that the first bit of a storage element (bit 0) is the most significant bit (interpreting the sequence of bits that represent a component as an unsigned integer value). Low_Order_First (known in the vernacular as 'little endian') means the opposite: the first bit is the least significant."

Note that the numbering is with respect to the bits of a storage unit. In other words, the specification affects only the numbering of bits within a single storage unit.

We can make the effect clearer by giving an example.

Suppose that we have an external device which presents two bytes, the first byte presented, which is the first (low addressed byte) of the two byte record is called Master, and the second byte is called Slave.

The left most (most significant bit is called Control for each byte, and the remaining 7 bits are called V1, V2, ... V7, where V7 is the rightmost (least significant) bit.

On a big-endian machine, we can write the following representation clause

    type Data is record
       Master_Control : Bit;
       Master_V1      : Bit;
       Master_V2      : Bit;
       Master_V3      : Bit;
       Master_V4      : Bit;
       Master_V5      : Bit;
       Master_V6      : Bit;
       Master_V7      : Bit;
       Slave_Control  : Bit;
       Slave_V1       : Bit;
       Slave_V2       : Bit;
       Slave_V3       : Bit;
       Slave_V4       : Bit;
       Slave_V5       : Bit;
       Slave_V6       : Bit;
       Slave_V7       : Bit;
    end record;
    
    for Data use record
       Master_Control at 0 range 0 .. 0;
       Master_V1      at 0 range 1 .. 1;
       Master_V2      at 0 range 2 .. 2;
       Master_V3      at 0 range 3 .. 3;
       Master_V4      at 0 range 4 .. 4;
       Master_V5      at 0 range 5 .. 5;
       Master_V6      at 0 range 6 .. 6;
       Master_V7      at 0 range 7 .. 7;
       Slave_Control  at 1 range 0 .. 0;
       Slave_V1       at 1 range 1 .. 1;
       Slave_V2       at 1 range 2 .. 2;
       Slave_V3       at 1 range 3 .. 3;
       Slave_V4       at 1 range 4 .. 4;
       Slave_V5       at 1 range 5 .. 5;
       Slave_V6       at 1 range 6 .. 6;
       Slave_V7       at 1 range 7 .. 7;
    end record;

Now if we move this to a little endian machine, then the bit ordering within the byte is backwards, so we have to rewrite the record rep clause as:

    for Data use record
       Master_Control at 0 range 7 .. 7;
       Master_V1      at 0 range 6 .. 6;
       Master_V2      at 0 range 5 .. 5;
       Master_V3      at 0 range 4 .. 4;
       Master_V4      at 0 range 3 .. 3;
       Master_V5      at 0 range 2 .. 2;
       Master_V6      at 0 range 1 .. 1;
       Master_V7      at 0 range 0 .. 0;
       Slave_Control  at 1 range 7 .. 7;
       Slave_V1       at 1 range 6 .. 6;
       Slave_V2       at 1 range 5 .. 5;
       Slave_V3       at 1 range 4 .. 4;
       Slave_V4       at 1 range 3 .. 3;
       Slave_V5       at 1 range 2 .. 2;
       Slave_V6       at 1 range 1 .. 1;
       Slave_V7       at 1 range 0 .. 0;
    end record;

It is a nuisance to have to rewrite the clause, especially if the code has to be maintained on both machines. However, this is a case that we can handle with the Bit_Order attribute if it is implemented. Note that the implementation is not required on byte addressed machines, but it is indeed implemented in GNAT. This means that we can simply use the first record clause, together with the declaration

    for Data'Bit_Order use High_Order_First;

and the effect is what is desired, namely the layout is exactly the same, independent of whether the code is compiled on a big-endian or little-endian machine.

The important point to understand is that byte ordering is not affected. A Bit_Order attribute definition never affects which byte a field ends up in, only where it ends up in that byte. To make this clear, let us rewrite the record rep clause of the previous example as:

    for Data'Bit_Order use High_Order_First;
    for Data use record
       Master_Control at 0 range  0 .. 0;
       Master_V1      at 0 range  1 .. 1;
       Master_V2      at 0 range  2 .. 2;
       Master_V3      at 0 range  3 .. 3;
       Master_V4      at 0 range  4 .. 4;
       Master_V5      at 0 range  5 .. 5;
       Master_V6      at 0 range  6 .. 6;
       Master_V7      at 0 range  7 .. 7;
       Slave_Control  at 0 range  8 .. 8;
       Slave_V1       at 0 range  9 .. 9;
       Slave_V2       at 0 range 10 .. 10;
       Slave_V3       at 0 range 11 .. 11;
       Slave_V4       at 0 range 12 .. 12;
       Slave_V5       at 0 range 13 .. 13;
       Slave_V6       at 0 range 14 .. 14;
       Slave_V7       at 0 range 15 .. 15;
    end record;

This is exactly equivalent to saying (a repeat of the first example):

    for Data'Bit_Order use High_Order_First;
    for Data use record
       Master_Control at 0 range 0 .. 0;
       Master_V1      at 0 range 1 .. 1;
       Master_V2      at 0 range 2 .. 2;
       Master_V3      at 0 range 3 .. 3;
       Master_V4      at 0 range 4 .. 4;
       Master_V5      at 0 range 5 .. 5;
       Master_V6      at 0 range 6 .. 6;
       Master_V7      at 0 range 7 .. 7;
       Slave_Control  at 1 range 0 .. 0;
       Slave_V1       at 1 range 1 .. 1;
       Slave_V2       at 1 range 2 .. 2;
       Slave_V3       at 1 range 3 .. 3;
       Slave_V4       at 1 range 4 .. 4;
       Slave_V5       at 1 range 5 .. 5;
       Slave_V6       at 1 range 6 .. 6;
       Slave_V7       at 1 range 7 .. 7;
    end record;

Why are they equivalent? Well take a specific field, the Slave_V2 field. The storage place attributes are obtained by normalizing the values given so that the First_Bit value is less than 8. After normalizing the values (0,10,10) we get (1,2,2) which is exactly what we specified in the other case.

Now one might expect that the Bit_Order attribute might affect bit numbering within the entire record component (two bytes in this case, thus affecting which byte fields end up in), but that is not the way this feature is defined, it only affects numbering of bits, not which byte they end up in.

Consequently it never makes sense to specify a starting bit number greater than 7 (for a byte addressable field) if an attribute definition for Bit_Order has been given, and indeed it may be actively confusing to specify such a value, so the compiler generates a warning for such usage.

If you do need to control byte ordering then appropriate conditional values must be used. If in our example, the slave byte came first on some machines we might write:

    Master_Byte_First constant Boolean := ...;
    
    Master_Byte : constant Natural :=
                    1 - Boolean'Pos (Master_Byte_First);
    Slave_Byte  : constant Natural :=
                    Boolean'Pos (Master_Byte_First);
    
    for Data'Bit_Order use High_Order_First;
    for Data use record
       Master_Control at Master_Byte range 0 .. 0;
       Master_V1      at Master_Byte range 1 .. 1;
       Master_V2      at Master_Byte range 2 .. 2;
       Master_V3      at Master_Byte range 3 .. 3;
       Master_V4      at Master_Byte range 4 .. 4;
       Master_V5      at Master_Byte range 5 .. 5;
       Master_V6      at Master_Byte range 6 .. 6;
       Master_V7      at Master_Byte range 7 .. 7;
       Slave_Control  at Slave_Byte  range 0 .. 0;
       Slave_V1       at Slave_Byte  range 1 .. 1;
       Slave_V2       at Slave_Byte  range 2 .. 2;
       Slave_V3       at Slave_Byte  range 3 .. 3;
       Slave_V4       at Slave_Byte  range 4 .. 4;
       Slave_V5       at Slave_Byte  range 5 .. 5;
       Slave_V6       at Slave_Byte  range 6 .. 6;
       Slave_V7       at Slave_Byte  range 7 .. 7;
    end record;

Now to switch between machines, all that is necessary is to set the boolean constant Master_Byte_First in an appropriate manner.