AdaCore / learn

Sources for learn.adacore.com
https://learn.adacore.com
Creative Commons Attribution 4.0 International
92 stars 37 forks source link

Ada For The Embedded C Developer - enhance "Interfacing with Devices" chapter #1062

Closed m-kru closed 1 week ago

m-kru commented 2 months ago

I am currently studying the "Ada For The Embedded C Developer Book". I feel like the book misses the information on how to write multiple elements of a single record in single operation while preserving the value of other record elements. It would probably be the best to describe this aspect in the "Interfacing with Devices" section.

The whole book presents how to solve programming problems encountered in the embedded world. However, it misses an example of how to write multiple register fields in a single operation while preserving the value of remaining fields. This is a very common scenario easily solved in C by applying bitwise and, or and shift operations.

The "Register overlays" section provides an example of a power management register. However, this example is too simple, it contains only one valid record element USBCLK.

       --  System Clock Enable Register
       type PMC_SCER_Register is record
          --  Reserved bits
          Reserved_0_4   : UInt5            := 16#0#;
          --  Write-only. Enable USB FS Clock
          USBCLK         : USB_Clock_Enable := 16#0#;
          --  Reserved bits
          Reserved_6_15  : UInt10           := 16#0#;
       end record
         with
           Volatile,
           Size      => 16,
           Bit_Order => System.Low_Order_First;

       for PMC_SCER_Register use record
          Reserved_0_4   at 0 range 0 .. 4;
          USBCLK         at 0 range 5 .. 5;
          Reserved_6_15  at 0 range 6 .. 15;
       end record;

Now, imagine the register is a little bit more complex:

       --  System Clock Enable Register
       type PMC_SCER_Register is record
          --  Reserved bits
          Reserved_0_4   : UInt5            := 16#0#;
          --  Write-only. Enable USB FS Clock
          USBCLK0         : USB_Clock_Enable := 16#0#;
          USBCLK1         : USB_Clock_Enable := 16#0#;
          USBCLK2         : USB_Clock_Enable := 16#0#;
          --  Reserved bits
          Reserved_8_15  : UInt8           := 16#0#;
       end record
         with
           Volatile,
           Size      => 16,
           Bit_Order => System.Low_Order_First;

       for PMC_SCER_Register use record
          Reserved_0_4   at 0 range 0 .. 4;
          USBCLK0        at 0 range 5 .. 5;
          USBCLK1        at 0 range 6 .. 6;
          USBCLK2        at 0 range 7 .. 7;
          Reserved_8_15  at 0 range 8 .. 15;
       end record;

How do I write elements USBCLK0 and USBCLK2 (in a single opration), while preserving the value of USBCLK1 element?

m-kru commented 1 month ago

@gusthoff ping

gusthoff commented 1 month ago

Hello @m-kru,

To modify multiple components in a single assignment, you can use delta aggregates, see here:

https://learn.adacore.com/courses/advanced-ada/parts/data_types/aggregates.html#delta-aggregates

For example:

pragma Ada_2022;

with Registers; use Registers;

procedure Main is

   R : PMC_SCER_Register;

begin
   --  Initializing the registers
   R := (USBCLK0 => 1,
         USBCLK1 => 1,
         USBCLK2 => 1,
         others => <>);

   --  Modifying only USBCLK0 and USBCLK2 registers
   R := (R with delta USBCLK0 => 0, USBCLK2 => 0);
end Main;

Note that delta aggregates are an Ada 2022 feature.

Also, to ensure that you write all bits of the record at once, you can add GNAT's Volatile_Full_Access aspect to the record declaration:

[...] any reference to the object is guaranteed to be done only with instructions that read or write all the bits of the object. Furthermore, if the object is of a composite type, then any reference to a subcomponent of the object is guaranteed to read and/or write all the bits of the object.

https://gcc.gnu.org/onlinedocs/gnat_rm/Pragma-Volatile_005fFull_005fAccess.html

Alternatively, you can use the Full_Access_Only aspect introduced in Ada 2022:

All reads of or writes to any nonatomic subcomponent of a full access object are performed by reading and/or writing all of the nearest enclosing full access object.

Annotated Ada Reference Manual, C.6: http://www.ada-auth.org/standards/22aarm/html/AA-C-6.html

gusthoff commented 1 month ago

You can find a code example using the Volatile_Full_Access aspect in the Ada Drivers Library. For example: stm32_svd-gpio.ads.