# Weird behaviour in record mapping

**URL:** https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825
**Category:** General
**Tags:** embedded
**Created:** [February 17, 2025, 10:47am UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825 "2025-02-17T10:47:33Z")
**Posts on this page:** 16
**Page:** 1

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 17, 2025, 10:47am UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/1 "2025-02-17T10:47:33Z")

</div>

I am trying to map a register (`SYSCFG_EXTICR1`) on a STM32F42xxx board and have come across some odd behaviour with the actual problem being that the desired pin isn’t mapped to external board interrupt.

Consider the following mapping:

```ada
with Common_Types;
with System;

package SYSCFG is
   
   Base_Address : constant := 16#40013800#;
   EXTICR_Offset : constant := 16#08#;
   
   type EXTI_Port is (PA,
                      PB,
                      PC,
                      PD,
                      PE,
                      PF,
                      PG,
                      PH,
                      PI) with size => 4;
   for EXTI_Port use (
                      PA => 2#0000#,
                      PB => 2#0001#,
                      PC => 2#0010#,
                      PD => 2#0011#,
                      PE => 2#0100#,
                      PF => 2#0101#,
                      PG => 2#0110#,
                      PH => 2#0111#,
                      PI => 2#1000#);
   
   type SYSCFG_EXTICR_Register is
      record
         EXTIO : EXTI_Port;
         EXTI1 : EXTI_Port;
         EXTI2 : EXTI_Port;
         EXTI3 : EXTI_Port;
         Unmapped1 : Common_Types.Arbitrary_Unmapped_Space (1 .. 15);
         EXTI4 : EXTI_Port;
         EXTI5 : EXTI_Port;
         EXTI6 : EXTI_Port;
         EXTI7 : EXTI_Port;
         Unmapped2 : Common_Types.Arbitrary_Unmapped_Space (1 .. 15);
         EXTI8 : EXTI_Port;
         EXTI9 : EXTI_Port;
         EXTI10 : EXTI_Port;
         EXTI11 : EXTI_Port;
         Unmapped3 : Common_Types.Arbitrary_Unmapped_Space (1 .. 15);
         EXTI12 : EXTI_Port;
         EXTI13 : EXTI_Port;
         EXTI14 : EXTI_Port;
         EXTI15 : EXTI_Port;
         Unmapped4 : Common_Types.Arbitrary_Unmapped_Space (1 .. 15);
      end record with Volatile, Object_Size => 128;
   for SYSCFG_EXTICR_Register use
      record
         EXTIO at 0 range 0 .. 3;
         EXTI1 at 0 range 4 .. 7;
         EXTI2 at 0 range 8 .. 11;
         EXTI3 at 0 range 12 .. 15;
         Unmapped1 at 0 range 16 .. 31;
         EXTI4 at 4 range 0 .. 3;
         EXTI5 at 4 range 4 .. 7;
         EXTI6 at 4 range 8 .. 11;
         EXTI7 at 4 range 12 .. 15;
         Unmapped2 at 4 range 16 .. 31;
         EXTI8 at 8 range 0 .. 3;
         EXTI9 at 8 range 4 .. 7;
         EXTI10 at 8 range 8 .. 11;
         EXTI11 at 8 range 12 .. 15;
         Unmapped3 at 8 range 16 .. 31;
         EXTI12 at 12 range 0 .. 3;
         EXTI13 at 12 range 4 .. 7;
         EXTI14 at 12 range 8 .. 11;
         EXTI15 at 12 range 12 .. 15;
         Unmapped4 at 12 range 16 .. 31;
      end record;
         
   -- Access points
   SYSCFG_EXTICR_Reg : SYSCFG_EXTICR_Register with Address => System'To_Address (Base_Address + EXTICR_Offset);
   SYSCFG_EXTICR_Reg_Unmapped : SYSCFG_EXTICR_Register;
   
end SYSCFG;

```

where `Common_Types` is just some package with well, common types so `Common_Types.Arbitrary_Unmapped_Space` is:

`type Arbitrary_Unmapped_Space is array (Positive range <>) of Boolean with Component_Size => 1, Unreferenced;`

Based on the Reference Manual the mapping must be something like:

 ![register_details](https://forum.ada-lang.io/uploads/default/original/1X/ee88ed45ce821dd0cc2c4fbc1eb89808addbe2ed.png)

and the base address:

 ![address_map](https://forum.ada-lang.io/uploads/default/original/1X/4a300c45d7d9e4710e0f4c758aeec0854b8877c7.png)

(This configuration is to do with mapping external pins to board interrupts and there are four registers available which all only use the lower 16 bits to configure a specific pin against an external interrupt. I’ve mapped all of these registers in one go using a single record)

Now consider the following code:

```ada
1. SYSCFG.SYSCFG_EXTICR_Reg.EXTI4 := SYSCFG.PB; -- After running this EXTI4 should be set to PB! (i.e. the pin we're trying to map is PB4)
   
2. SYSCFG.SYSCFG_EXTICR_Reg_Unmapped.EXTI4 := SYSCFG.PB;
   
3. SYSCFG.SYSCFG_EXTICR_Reg.Unmapped4 (1) := True;
   
4. SYSCFG.SYSCFG_EXTICR_Reg_Unmapped.EXTI4 := SYSCFG.PB; -- Not relevant just needed a hook to put a breakpoint to.

```

Putting a breakpoint in line 1 and printing the value of EXTI4 and dumping the raw memory in gdb, I’m getting:

```ada
(gdb) set lang ada
(gdb) p SYSCFG.SYSCFG_EXTICR_Reg.EXTI4
$1 = pa

```

which is correct as the line hasn’t been executed yet.  
Now, executing line 1 and priting the value still gives `PA`! :

```ada
(gdb) n
(gdb) p SYSCFG.SYSCFG_EXTICR_Reg.EXTI4
$2 = pa

```

Dumping the raw bytes also shows the same (using byte-size chunks as I _think_ gdb will print scalars with a size greater than a byte in big-endian, but might be wrong):

```ada
(gdb) x/16tb 0x40013808
0x40013808: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x40013810: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

```

My initial thought was that there’s something up with the way the compiler has laid out the record (i.e. maybe in big-endian rather than little-endian and setting the variable hits the register area which should be left untouched) so I set out to prove this theory but initially I wanted to prove that an unmapped record behaves as expected hence the `SYSCFG_EXTICR_Reg_Unmapped` variable in the `SYSCFG` package above. So, prior to executing line 2 above I printed the EXTI4 value:

```ada
(gdb) p SYSCFG.SYSCFG_EXTICR_Reg_Unmapped.EXTI4
$3 = pa

```

and then executing line 2 and printing the value and dumping the raw values yields the correct result:

```ada
(gdb) n
(gdb) p SYSCFG.SYSCFG_EXTICR_Reg_Unmapped.EXTI4
$4 = pb
(gdb) x/16tb SYSCFG.SYSCFG_EXTICR_Reg_Unmapped'Address
0x20001548 <syscfg__syscfg_exticr_reg_unmapped>: 00000000 00000000 00000000 00000000 00000001 00000000 00000000 00000000
0x20001550 <syscfg__syscfg_exticr_reg_unmapped+8>: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

```

where the first bit of the second word has been set correctly (this is little-endian, mind you).

So, clearly, something is wrong with the memory-mapping…As I said, I tried proving that by attempting to set a bit in the unmapped area shown above and executed line 3 but still got the same result (i.e. all bits zero) though come to think of it, if the record had been laid out in big-endian then setting any bit in the unmapped area should have no effect at all.

I am building this in Alire and some bits of the configuration are  
(_aaa.gpr_):

```ada
for Target use "arm-eabi";
for Runtime ("ada") use "ravenscar-full-stm32f429disco";

package Compiler is
   for Default_Switches ("ada") use Microcontrollers_Config.Ada_Compiler_Switches;
end Compiler;
............

```

(aaa\_config.gpr):

```ada
Ada_Compiler_Switches := External_As_List ("ADAFLAGS", " ");
   Ada_Compiler_Switches := Ada_Compiler_Switches &
          (
            "-Og" -- Optimize for debug
           ,"-ffunction-sections" -- Separate ELF section for each function
           ,"-fdata-sections" -- Separate ELF section for each variable
           ,"-g" -- Generate debug info
           .... rest of the formatting switches

```

the configured toolchain is:

```ada
gnat_native=14.2.1
gprbuild=22.0.1

```

Interestingly enough, the _exact_ same mapping when passed through an older gprbuild i.e.:

```ada
gprbuild --version
GPRBUILD Community 2021 (20210519) (x86_64-pc-linux-gnu)

```

works fine and the interrupt handler is triggered indeed when the voltage on the pin drops below a certain level using a potentiometer (i.e. falling-edge).

Any ideas what might be going on or any further troubleshooting tips?

Apologies for the long post.

Thanks.

---

<div class="post-metadata">

### Author: ![Irvise](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/i/7ba0ec/32.png) [@Irvise](https://forum.ada-lang.io/u/Irvise)
#### Post date: [February 17, 2025, 6:04pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/2 "2025-02-17T18:04:26Z")

</div>

Hi @sidisyom,

Here are my two cents (which may fully help you or may get you closer to a solution).

I would mark the variable `SYSCFG_EXTICR_Reg` as `aliased` just in case. Aliased just tells the compiler that the variable needs to live somewhere in memory but not the heap. It may not be needed, but still. Secondly, if you think there is some endianess issues… Tell the compiler to follow the convention you want! See [Gem #140: Bridging the Endianness Gap | AdaCore](https://www.adacore.com/gems/gem-140-bridging-the-endianness-gap)

`type Arbitrary_Unmapped_Space is array (Positive range <>) of Boolean with Component_Size => 1, Unreferenced;` I would add the `Pack` attribute or even the `Size` too, just for good measure.

> [@sidisyom](#):
>
> Interestingly enough, the _exact_ same mapping when passed through an older gprbuild i.e.:

If you sourced the Community package from 2021, you are not just using an older GPRbuild, but an entirely different toolchain too! I believe Alire already has the newer GPRbuild v24, so you should be able to update to it using the `toolchain` system of Alire. Additionally, what happens if you build your code in release mode in Alire? What happens if you do an assembly comparison between the different generated binaries from the different toolchains?

Best regards,  
Fer

---

<div class="post-metadata">

### Author: ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)
#### Post date: [February 17, 2025, 6:43pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/3 "2025-02-17T18:43:00Z")

</div>

I think this is generating accesses to APB memory that are not 32-bits wide, which is a bug that has bitten me more than once.

> When a 16- or an 8-bit access is performed on an APB register, the access is transformed into a 32-bit access: the bridge duplicates the 16- or 8-bit data to feed the 32-bit vector  
> – [RM0090](https://www.st.com/resource/en/reference_manual/rm0090-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) 2.11.1 (pg.64)

You need to create 32-bit wide types and apply the [Volatile\_Full\_Access](https://gcc.gnu.org/onlinedocs/gnat_rm/Pragma-Volatile_005fFull_005fAccess.html) aspect/pragma. This tells the compiler that reads/write must **only** be 32-bits wide.

```ada
type EXTICR_Register is array (0 .. 3) of EXTI_Port
    with Component_Size => 4, Object_Size => 32, Volatile_Full_Access;
type EXTICR_Array is array (1 .. 4) of EXTICR_Register
   with Component_Size => 32, Volatile;

EXTICR : EXTICR_Array
   with Import, Address => System'To_Address (SYSCFG_BASE + 16#08#);

```

This does make the code harder to read and requires calculating indices in the calling code, but there’s really no other way to tell gcc that this memory region requires word-sized accesses.

Regarding `Pack`: This is an optimization hint only. It does not guarantee anything about the memory layout of the type. However, `Component_Size` applied to an array does eliminate padding between elements.

---

<div class="post-metadata">

### Author: ![godunko](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/godunko/32/22_2.png) [@godunko](https://forum.ada-lang.io/u/godunko)
#### Post date: [February 17, 2025, 8:17pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/4 "2025-02-17T20:17:12Z")

</div>

Your code is very naive 🙂 The best way to understand what is happens is to debug it at assembler level.

You need to provide much more information to compiler, like to disable some optimizations, use access of appropriate width (byte/half/word).

You can take a look what `svd2ada` generates, for example for STM32F401

> <https://github.com/godunko/a0b-stm32f401/blob/6f58bd847f4c97c1702d07dd7f52c228ca9f7a26/source/svd/a0b-stm32f401-svd-syscfg.ads#L78>

> <https://github.com/godunko/a0b-stm32f401/blob/6f58bd847f4c97c1702d07dd7f52c228ca9f7a26/source/svd/a0b-stm32f401-svd-syscfg.ads#L251>

PS. Magic thing is `pragma Volatile_Full_Access`, it requires to use read-modify-store sequence as 32bit value without any optimizations.

---

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 17, 2025, 11:04pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/5 "2025-02-17T23:04:26Z")

</div>

Thanks very much @Irvise for your suggestions!

Interesting about `aliased` and how it can be used as a hint to the compiler for stack-allocated objects.

> [@Irvise](#):
>
> Secondly, if you think there is some endianess issues… Tell the compiler to follow the convention you want!

Thanks, sounds useful, will go through that. (did try to specify the bit order but that didn’t make any difference)

> [@Irvise](#):
>
> I would add the `Pack` attribute or even the `Size` too, just for good measure.

As @JeremyGrosser mentioned, I was too under the impression that this is just a hint to the compiler i.e. a confirmation that the compiler can lay the record out in at least that many bits (_maybe_ fewer and certainly more) but the deterministic aspects are the `*_Size` ones.

> [@Irvise](#):
>
> If you sourced the Community package from 2021, you are not just using an older GPRbuild, but an entirely different toolchain too! I believe Alire already has the newer GPRbuild v24, so you should be able to update to it using the `toolchain` system of Alire. Additionally, what happens if you build your code in release mode in Alire? What happens if you do an assembly comparison between the different generated binaries from the different toolchains?

Hmm, I see…with my current Alire setup, I am using the latest versions of `gnat_native` and `gprbuild` that are shown when I run an `alr toolchain --select` but what about the cross-compiler? Where’s that coming from? 🤔

For some assembly dumps, please see below in my main answer.

---

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 17, 2025, 11:15pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/6 "2025-02-17T23:15:17Z")

</div>

Oh, right, I see! So, if I get this right, with byte accesses say, the APB will effectively load the first byte and if that’s zero will wipe-out the entire word…Interesting, thanks for sharing!

Did give your suggestion a try but the compiler complains with:  
`atomic access to component of EXTICR_Array cannot be guaranteed`. I wonder if that means the compiler is trying to view access to the _entire_ array as atomic? I did try a variation of that (to be posted below) but that didn’t work either.

Out of curiosity @JeremyGrosser, what is the purpose of the `Import` on the mapped variable? (i.e. I know the semantics of the aspect in general, I’m just wondering what effect that has on the compiler regarding memory accesses. I.e. isn’t `Volatile` sufficient?)

---

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 17, 2025, 11:29pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/7 "2025-02-17T23:29:38Z")

</div>

So, I tried the following changes:

```ada
type SYSCFG_EXTICR_Register is
   record
      EXTIO : EXTI_Port;
      EXTI1 : EXTI_Port;
      EXTI2 : EXTI_Port;
      EXTI3 : EXTI_Port;
      Unmapped : Common_Types.Arbitrary_Unmapped_Space (1 .. 15);
   end record with 
     Volatile, 
     Object_Size => 32, 
     Bit_Order => System.Low_Order_First,        
     Volatile_Full_Access;
   
for SYSCFG_EXTICR_Register use
   record
      EXTIO at 0 range 0 .. 3;
      EXTI1 at 0 range 4 .. 7;
      EXTI2 at 0 range 8 .. 11;
      EXTI3 at 0 range 12 .. 15;
      Unmapped at 0 range 16 .. 31;
   end record;
   
type SYSCFG_EXTICR_Bank is array (1 .. 4) of SYSCFG_EXTICR_Register with Volatile, Component_Size => 32;
   
SYSCFG_EXTICR1_Reg : aliased SYSCFG_EXTICR_Register with Address => System'To_Address (Base_Address + EXTICR_Offset);
   
SYSCFG_EXTICR_Regs : aliased SYSCFG_EXTICR_Bank with Address => System'To_Address (Base_Address + EXTICR_Offset);

```

but that didn’t have any effect i.e. executing this line:  
`SYSCFG.SYSCFG_EXTICR1_Reg.EXTI1 := SYSCFG.PB;`  
or this:  
`SYSCFG.SYSCFG_EXTICR_Regs (2).EXTI1 := SYSCFG.PB;`  
refuses to change the `EXTI1` to `PB`.

However, dumping some disassembled code confirms the suggestions regarding the size accesses.

When no `Volatile_Full_Access` is specified then the compiler produces:

```ada
(gdb) disas 0x8000026, 0x8000032
Dump of assembler code from 0x8000026 to 0x8000032:
=> 0x08000026 <_ada_main+18>: ldr r2, [pc, #40] ; (0x8000050 <_ada_main+60>)
   0x08000028 <_ada_main+20>: ldrb r3, [r2, #8]
   0x0800002a <_ada_main+22>: movs r1, #1
   0x0800002c <_ada_main+24>: bfi r3, r1, #4, #4
   0x08000030 <_ada_main+28>: strb r3, [r2, #8]
End of assembler dump.

```

which shows that the loads (`0x08000028`) and stores (`0x08000030`) are done in bytes indeed. (not sure what the `bfi` instruction does).

When `Volatile_Full_Access` _is_ specified, then accesses are in words:

```ada
(gdb) disas 0x8000026, 0x8000036
Dump of assembler code from 0x8000026 to 0x8000036:
   0x08000026 <_ada_main+18>: ldr r2, [pc, #44] ; (0x8000054 <_ada_main+64>)
   0x08000028 <_ada_main+20>: ldr.w r3, [r2, #2056] ; 0x808
   0x0800002c <_ada_main+24>: movs r1, #1
   0x0800002e <_ada_main+26>: bfi r3, r1, #4, #4
   0x08000032 <_ada_main+30>: str.w r3, [r2, #2056] ; 0x808
End of assembler dump.

```

This seems to be the case when all registers are bundled together in one array too (i.e. when using the variable `SYSCFG_EXTICR_Regs` above)

```ada
(gdb) disas 0x8000026, 0x8000032
Dump of assembler code from 0x8000026 to 0x8000032:
   0x08000026 <_ada_main+18>: ldr r2, [pc, #52] ; (0x800005c <_ada_main+72>)
   0x08000028 <_ada_main+20>: ldr r3, [r2, #0]
   0x0800002a <_ada_main+22>: movs r1, #1
   0x0800002c <_ada_main+24>: bfi r3, r1, #4, #4
   0x08000030 <_ada_main+28>: str r3, [r2, #0]
End of assembler dump.

```

(I believe the unqualified `ldr` translates to word?)

But again, this doesn’t seem to work.

---

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 17, 2025, 11:41pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/8 "2025-02-17T23:41:29Z")

</div>

Thanks, will take a look at the svd-generated code. Interesting how a variant record is generated even though `As_Array => False` is used for all register declarations (didn’t fully go through that, tbh). I’ll try to mimic this approach and see if that makes any difference.

> [@godunko](#):
>
> You need to provide much more information to compiler, like to disable some optimizations, use access of appropriate width (byte/half/word).

Would you be able to share any specific examples?

Thanks!

---

<div class="post-metadata">

### Author: ![JeremyGrosser](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/jeremygrosser/32/3_2.png) [@JeremyGrosser](https://forum.ada-lang.io/u/JeremyGrosser)
#### Post date: [February 17, 2025, 11:57pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/9 "2025-02-17T23:57:08Z")

</div>

> The declaration of an imported object shall not include an explicit initialization expression. Default initializations are not performed.  
> – [ARM B.1.24](https://ada-lang.io/docs/arm/AA-B/AA-B.1#p24)

`Import` ensures that the compiler doesn’t try to zero the memory during elaboration.

---

<div class="post-metadata">

### Author: ![godunko](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/godunko/32/22_2.png) [@godunko](https://forum.ada-lang.io/u/godunko)
#### Post date: [February 18, 2025, 3:18pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/10 "2025-02-18T15:18:25Z")

</div>

I suppose you don’t need more examples, you have find why it doesn’t work.

Pragma/aspect `Volatile_Full_Access` is actually combination of `Volatile` and `Full_Access_Only`.

`Volatile` means that value can be changed at any time, thus disable some optimizations.

`Full_Access_Only` means that all components of the object should be read/written by single operation, object doesn’t allow byte/half word access.

Array of the elements is a bit orthogonal. You can try to declare register type to be `Volatile_Full_Access`, or use `Volatile_Component`. You can find more information in RM

[https://docs.adacore.com/live/wave/arm22/html/arm22/arm22-C-6.html](https://docs.adacore.com/live/wave/arm22/html/arm22/arm22-C-6.html)

---

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 19, 2025, 9:17am UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/11 "2025-02-19T09:17:19Z")

</div>

Just a note to add some further findings.

I read the behaviour of the APB bus a bit more carefully and I started to think that zeroing-out of all bits shouldn’t actually be happening. If anything, more bits should be set!  
 ![Screenshot from 2025-02-19 08-26-15](https://forum.ada-lang.io/uploads/default/optimized/1X/c7bb54db177d120055c0414dbd150e7128cad373_2_690x47.png)

Indeed, that is what is happening (well, _nearly_ that) when I build the project using plain gprbuild which is using the configured cross-compiler for the given `Target`.

Given this:

```ada
type SYSCFG_EXTICR_Register is
      record
         EXTIO: EXTI;
         EXTI1: EXTI;
         EXTI2: EXTI;
         EXTI3: EXTI;
         Unmapped: Arbitrary_Unmapped_Space(1 .. 15);
      end record with Volatile, Object_Size => 32;

for SYSCFG_EXTICR_Register use
   record
      EXTIO at 0 range 0 .. 3;
      EXTI1 at 0 range 4 .. 7;
      EXTI2 at 0 range 8 .. 11;
      EXTI3 at 0 range 12 .. 15;
      Unmapped at 0 range 16 .. 31;
   end record;

type SYSCFG_EXTICR_Arr is array (1 .. 4) of SYSCFG_EXTICR_Register with Component_Size => 32;

SYSCFG_EXTICR_Reg : aliased SYSCFG_EXTICR_Arr with Import, Address => System'To_Address (Base_Address); -- Base address includes offset here

```

and executing that:

`SYSCFG_EXTICR_Reg(2).EXTIO := PB;`

the assembly is:

```ada
Dump of assembler code from 0x8000256 to 0x8000262:
=> 0x08000256 <pin_interrupt__setup_interrupts+10>: ldr r2, [pc, #56] ; (0x8000290 <pin_interrupt__setup_interrupts+68>)
   0x08000258 <pin_interrupt__setup_interrupts+12>: ldrb r3, [r2, #4]
   0x0800025a <pin_interrupt__setup_interrupts+14>: movs r1, #1
   0x0800025c <pin_interrupt__setup_interrupts+16>: bfi r3, r1, #0, #4
   0x08000260 <pin_interrupt__setup_interrupts+20>: strb r3, [r2, #4]
End of assembler dump.

```

(so, byte accesses are issued) and dumping the memory gives:

```ada
(gdb) x/8tb SYSCFG_EXTICR_Reg'Address
0x40013808: 00000000 00000000 00000000 00000000 00000001 00000001 00000000 00000000

```

so the “1” from the actual changing byte has been duplicated onto the second byte only, which I think is kinda half-consistent with the RM i.e. I’d expect that duplication to take place across all bytes of the word and result in:  
`00000001 00000001 00000001 00000001`  
but perhaps a separate issue.

Now, setting `Volatile_Full_Access` on the record results in the following assembly:

```ada
Dump of assembler code from 0x8000256 to 0x8000268:
   0x08000256 <pin_interrupt__setup_interrupts+10>: ldr r3, [pc, #64] ; (0x8000298 <pin_interrupt__setup_interrupts+76>)
   0x08000258 <pin_interrupt__setup_interrupts+12>: adds r3, #4
   0x0800025a <pin_interrupt__setup_interrupts+14>: ldr r3, [r3, #0]
   0x0800025c <pin_interrupt__setup_interrupts+16>: movs r2, #1
   0x0800025e <pin_interrupt__setup_interrupts+18>: bfi r3, r2, #0, #4
   0x08000262 <pin_interrupt__setup_interrupts+22>: ldr r2, [pc, #52] ; (0x8000298 <pin_interrupt__setup_interrupts+76>)
   0x08000264 <pin_interrupt__setup_interrupts+24>: adds r2, #4
   0x08000266 <pin_interrupt__setup_interrupts+26>: str r3, [r2, #0]

```

so, word accesses are issued (aka unqualified instructions) and dumping the memory gives:

```ada
(gdb) x/8tb SYSCFG_EXTICR_Reg'Address
0x40013808: 00000000 00000000 00000000 00000000 00000001 00000000 00000000 00000000

```

and all is good here.

None of that is happening in the Alire project. I tried using the exact same compiler flags as the gprbuild project and although the assembly is now identical there’s no change in the behaviour.

What’s more interesting is that the same happens if I use a single scalar for the entire register, i.e. given this:

```ada
SYSCFG_EXTICR1_Reg : aliased Interfaces.Unsigned_32 with
     Import,
     Volatile_Full_Access,
     Address => System'To_Address (Base_Address + EXTICR_Offset);

```

and executing:  
`SYSCFG.SYSCFG_EXTICR1_Reg := 1;`

the assembly is:

```ada
(gdb) disas 0x8000022, 0x8000028
Dump of assembler code from 0x8000022 to 0x8000028:
   0x08000022 <_ada_main+14>: ldr r3, [pc, #36] ; (0x8000048 <_ada_main+52>)
   0x08000024 <_ada_main+16>: str.w r2, [r3, #2056] ; 0x808
End of assembler dump.

```

so, just plain word loads and stores (didn’t check but I assume `r2` is set to “1” a bit further up) but yet `SYSCFG.SYSCFG_EXTICR1_Reg` remains stubbornly stuck to “0” as if that instruction were a nop.

So, it looks like something is wrong with my Alire setup.

Perhaps someone with more in-depth knowledge of all the components involved might be able to shed some light. I’m going to revert back to gprbuild for now and continue to investigate.

Thanks everyone for all the helpful comments.

---

<div class="post-metadata">

### Author: ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)
#### Post date: [February 21, 2025, 3:42am UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/12 "2025-02-21T03:42:10Z")

</div>

> [@sidisyom](#):
>
> ```ada
> type SYSCFG_EXTICR_Register is record
> EXTIO, EXTI1, EXTI2, EXTI3: EXTI;
> Unmapped : Arbitrary_Unmapped_Space(1 .. 15);
> end record
> with Volatile, Object_Size => 32;
> 
> for SYSCFG_EXTICR_Register use record
> EXTIO at 0 range 0 .. 3;
> EXTI1 at 0 range 4 .. 7;
> EXTI2 at 0 range 8 .. 11;
> EXTI3 at 0 range 12 .. 15;
> Unmapped at 0 range 16 .. 31;
> end record;
> 
> ```

Let me fix that for you.

---

<div class="post-metadata">

### Author: ![sidisyom](https://forum.ada-lang.io/letter_avatar_proxy/v4/letter/s/5fc32e/32.png) [@sidisyom](https://forum.ada-lang.io/u/sidisyom)
#### Post date: [February 25, 2025, 10:38am UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/13 "2025-02-25T10:38:47Z")

</div>

A quick update on this, fwiw.

As it is so often the case, this was down to user error. After banging my self hard in the head with the STM32F42 Reference Manual over the past couple of days, it finally hit me (apologies for the terrible pun).

If the system configuration controller clock isn’t enabled then interacting with any register under that memory range is effectively a nop. The reason it works in my other gprbuild project is naturally because the controller clock _is_ turned-on in some other part of the flow. So, no issues with my Alire setup.

What I’m keeping from this is that `Volatile_Full_Access` is crucial when interacting with this board as the behaviour will most likely be non-deterministic (i.e. setting random bits _may_ or may not have any program side-effects).

The downside, as previously mentioned, is that it some implementation details from the register mapping, do leak into the client (i.e. offset calculation etc). Maybe, a small mapping function will add an extra layer of encapsulation (albeit the register array still exposes it’s index structure):

```ada
subtype Exti_Range is Natural range 0 .. 15;
function Exti_To_Offset (Exti : Exti_Range) return Natural is (Exti / 4);

```

I’m sure other solutions can be applied (i.e. private types etc) but that might be taking it a bit too far.

Thanks all for looking into it and apologies for the confusion.

---

<div class="post-metadata">

### Author: ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)
#### Post date: [February 25, 2025, 8:31pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/14 "2025-02-25T20:31:04Z")

</div>

> [@sidisyom](#):
>
> I’m sure other solutions can be applied (i.e. private types etc) but that might be taking it a bit too far.

Private types are not “too far”.  
Private types are perfectly appropriate for many things, but particularly for separating implementation from interface. One feature that, in recent years, has been neglected, is the `Separate` body.

You can use `Separate` to take a large subprogram and isolate it, like say the parser for reading a file (esp if you’re doing multi-format, auto-detect reads), but you can _also_ use them for isolating/implementing system dependency. (Now, to be fair, this latter usage is often tied to your build-system and is not exclusively a language issue.)

```ada
Package Paths is
  Type Path(<>) is limited private;
   Function To_Path( Object : String ) return Path;
   Function To_String( Object : Path ) return String;
   -- Other functions.
Private
   Package Path_Vector is Ada.Containers.Indefinite_Vector
    (Index => Positive, Element => String, "=" => Ada.Strings.Case_Insensitive_Equals);
   Type Path is new Path_Vector.Vector with null record;

   -- This is used to set the character used for path separation.
   Function Separator return Character is Separate;
End Paths;

```

| Windows | Unix |
| --- | --- |
| src\win\paths.separator.adb | src/unix/paths.separator.adb |
| `Separate(Paths) Function Separator return Character renames '\';` | `Separate(Paths) Function Separator return Character renames '/';` |

Thus, whether you include `src\win` or `src/unix` in your build process, determines how your to-path parses a given string, while keeping that completely from anyone using the package, as the implementation is completely hidden. (Granted, it’s a trivial and stupid little example, but shows how you can leverage the feature to isolate system/architecture dependencies from leaking through your interface.) – Ada is robust enough that very often it is beneficial to sit down, describe your problem in the type-system, write out how you want the interface to be, and _then_ implement.

Don’t be afraid to completely isolate _ **all** _ implementation details into the `BODY` or `PRIVATE` parts. I’ve had times where I’ve completely changed the implementation of a type and only had to recompile that package (and its children).

---

<div class="post-metadata">

### Author: ![Nordic\_Dogsledding](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/nordic_dogsledding/32/547_2.png) [@Nordic\_Dogsledding](https://forum.ada-lang.io/u/Nordic_Dogsledding)
#### Post date: [February 26, 2025, 12:54pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/15 "2025-02-26T12:54:01Z")

</div>

> [@OneWingedShark](#):
>
> ```ada
> Package Paths is
> Private
> -- This is used to set the character used for path separation.
> Function Separator return Character is Separate; <== illegal
> End Paths;
> 
> ```

This body stub is illegal in the spec, put it into the body.

---

<div class="post-metadata">

### Author: ![OneWingedShark](https://forum.ada-lang.io/user_avatar/forum.ada-lang.io/onewingedshark/32/305_2.png) [@OneWingedShark](https://forum.ada-lang.io/u/OneWingedShark)
#### Post date: [February 26, 2025, 9:47pm UTC](https://forum.ada-lang.io/t/weird-behaviour-in-record-mapping/1825/16 "2025-02-26T21:47:18Z")

</div>

Ah, I had forgotten that, typing it out on-the-fly.  
Thank you.
