Addresses, Overlays, Volatile & Atomic

Work with System.Address and System.Storage_Elements (address arithmetic, To_Integer), place objects at specific addresses (memory-mapped I/O) and overlay objects with the Address aspect plus Import, use Volatile, Atomic and Volatile_Full_Access for shared and device memory, and query machine properties (Storage_Unit, Word_Size, Default_Bit_Order).

Embedded software must read and write specific memory locations: device registers, DMA buffers, shared memory. Ada supports this directly and explicitly. The Address aspect places an object at a given address, Volatile and Atomic tell the compiler that memory may change outside the program's control, and System.Storage_Elements provides typed address arithmetic.

Addresses and Storage Elements

System.Address is a private type representing machine addresses; X'Address gives an object's address. System.Storage_Elements adds Storage_Offset, Address + Offset, Address - Address, and To_Integer / To_Address for conversions to Integer_Address (for alignment arithmetic or printing). System also defines Storage_Unit (bits per addressable unit), Word_Size, Default_Bit_Order and Null_Address. Address values should never be hard-coded outside board-support packages.

• X'Address; System.Storage_Elements for offsets and arithmetic. • To_Integer / To_Address; Storage_Unit, Word_Size, Default_Bit_Order.

Placing and Overlaying Objects

'UART_Status : Status_Register with Address => System'To_Address (16#4000_1000#), Volatile_Full_Access, Import;' maps a device register (Import suppresses default initialization, which would otherwise write to the device). The same mechanism overlays two objects on the same memory: 'Bytes : Byte_Array with Address => Value'Address, Import;' — a view of an Unsigned_32 as four bytes, which reveals the machine's byte order (little-endian on x86: 16#44#, 16#33#, 16#22#, 16#11#). Overlays bypass type safety like Unchecked_Conversion; keep them in low-level packages.

• with Address => A, Import — place without initializing. • Overlays view the same bytes as another type. • Keep address-based code isolated.

Volatile, Atomic, Volatile_Full_Access

Volatile forces every read and write in the source to access memory, in order — required for memory-mapped I/O and data modified by interrupt handlers or other processors. Atomic additionally guarantees indivisible reads/writes (the object must fit a machine word); it is the basis of simple lock-free flags. Volatile_Full_Access (Ada 2022) ensures that even component updates read and write the whole object, as many device registers require. None of these provide mutual exclusion for read-modify-write sequences such as Counter := Counter + 1 — use protected objects or System.Atomic_Operations (Ada 2022) for that.

• Volatile: no caching/reordering of accesses. • Atomic: indivisible access too. • Volatile_Full_Access: whole-object access for registers. • Read-modify-write still needs protection (protected objects, System.Atomic_Operations).

Code Examples

On little-endian x86-64 the bytes of 16#11223344# appear as 68, 51, 34, 17 (16#44#, 16#33#, 16#22#, 16#11#). Writing 16#FF# into byte 0 changes Value to 16#112233FF# = 287 454 207. Actual addresses are never printed because they differ between runs. Output: System.Default_Bit_Order = LOW_ORDER_FIRST Bytes of 16#11223344# in memory: 68 51 34 17 After Bytes (0) := 16#FF#, Value = 287454207 Next - Base = 2 storage elements Value'Address mod Unsigned_32'Alignment = 0 Storage_Unit = 8 bits, Word_Size = 64 bits Atomic Flag = TRUE, Volatile Counter = 1

Reg : Register with Address => To_Address (16#4000_1000#);

Reg : Register with Address => To_Address (16#4000_1000#), Volatile, Import;

Without Volatile the compiler may cache accesses; without Import it may initialize the device.

Count: Integer with Atomic;... Count:= Count + 1; -- from several tasks -- use a protected counter or System.Atomic_Operations.Integer_Arithmetic

Atomic makes individual reads/writes indivisible, not the increment.

-- assuming byte order when overlaying -- check System.Default_Bit_Order or use Scalar_Storage_Order

Overlays expose endianness; portable code must not assume it.

Why add Import when placing an object at a device address? A. To link with C B. To suppress default initialization that would write to the device C. For speed D. It is required for all objects

Does Atomic make 'X := X + 1' safe across tasks? A. Yes B. No, only individual reads/writes are indivisible C. Only on x86 D. Only with Volatile

What is 16#11223344# as bytes in memory on a little-endian machine? A. 11 22 33 44 B. 44 33 22 11 C. 33 44 11 22 D. Unspecified B. (verified as 68 51 34 17 decimal)

Which package provides Address + Storage_Offset? A. System B. System.Storage_Elements C. Interfaces D. Ada.Addresses Trace the address example.

Real output: System.Default_Bit_Order = LOW_ORDER_FIRST Bytes of 16#11223344# in memory: 68 51 34 17 After Bytes (0) := 16#FF#, Value = 287454207 Next - Base = 2 storage elements Value'Address mod Unsigned_32'Alignment = 0 Storage_Unit = 8 bits, Word_Size = 64 bits Atomic Flag = TRUE, Volatile Counter = 1

with Ada.Text_IO;             use Ada.Text_IO;
with System;                  use System;
with System.Storage_Elements; use System.Storage_Elements;
with Interfaces;              use Interfaces;

procedure L59_Address is
   Value : aliased Unsigned_32 := 16#1122_3344#;

   --  Overlay: view the same four bytes as an array (no copy)
   type Byte_Array is array (0 .. 3) of Unsigned_8;
   Bytes : Byte_Array
     with Address => Value'Address, Import;          --  Import: no default initialization

   --  Volatile: every read/write really goes to memory (e.g. device registers,
   --  variables changed by interrupts). Atomic: indivisible access as well.
   Flag    : Boolean := False with Atomic;
   Counter : Unsigned_32 := 0 with Volatile;

   Base : constant Address := Value'Address;
   Next : constant Address := Base + Storage_Offset (2);    --  address arithmetic
begin
   Put_Line ("System.Default_Bit_Order = " & Bit_Order'Image (Default_Bit_Order));
   Put ("Bytes of 16#11223344# in memory:");
   for B of Bytes loop
      Put (" " & Unsigned_8'Image (B));
   end loop;
   New_Line;
   Bytes (0) := 16#FF#;                                     --  write through the overlay
   Put_Line ("
…