Search code examples
adaunionsbit-fields

How do I declare a bitfield format based an inherited type?


I'm having difficultly understanding Ada's type system. Coming from C/C++ it's I have a hard time grasping it type syntax and its polymorphism. I would like to create an abstract register class which can have 2 to 8 bytes of data storage:

package CPU is
   pragma Preelaborate;

    type Registers is array(0..4) of Register;

    type DeviceId is range 0..6;

    -- a register can be anywhere from 2 to 8 bytes
    type Byte_Array is array (1 .. 8) of Byte;
    type Register is record
        Data: Byte_Array;
    end record;

    type IORegiser is new Register(1..2) with record  
        Address : Byte;
        Busy    : Boolean;
        Error   : Boolean;
        Id      : DeviceId;
    end record;
    for IORegiser use record
        Address at 0 range 0..7;
        Busy    at 1 range 0..1;
        Error   at 1 range 2..2;
        Id      at 1 range 3..8;
    end record;
end CPU;

Based on this abstraction, I want to create a standard register class where its data has no format and another register class where its data is formatted into fields:

A : Register := 16;  -- put the number 16 in the register.
IOReg : IORegister (Address => #16#0011#, Busy => true, Error => false, Id => #16#0011#);
B : LongRegister; -- an 8 byte register

CPURegs : Registers := (A, IOReg, B); -- Array of registers

Also, standard Registers classes store data in several different fixed sizes. In C++, I would use a union, but it's unclear how to implement it in Ada.


Solution

  • Edits: Removing the use of tagged types.

    Unformatted records of various sizes can be implemented as modular types such as

    type U8  is mod 2**8;
    type U16 is mod 2**16;
    type U32 is mod 2**32;
    type U64 is mod 2**64;
    

    A register type for a 16 bit unformatted record is therefore

    type Unformatted_16 is record with
        Data : U16 := 0;
    end record;
    

    The beauty of using a modular type for the unformatted 16-bit register is the fact that the value assigned to the register can never overflow the register. The same modular arithmetic behavior is found in all Ada modular types.

    Similarly your IORegister type could be the following on a machine with a 16 bit word size:

    type IORegister is record with record  
        Address : U8;
        Busy    : Boolean;
        Error   : Boolean;
        Id      : DeviceId;
    end record;
    for IORegister use record
        Address at 0 range 0..7;
        Busy    at 0 range 8..9;
        Error   at 0 range 10..10;
        Id      at 0 range 11..15;
    end record;
    

    An array four bytes is not the same as a 32-bit integer. Ada representation clauses deal with the word number and the bit offset from the start of the word. In the example above with a 16 bit word all offsets are from word 0.