avradaatmegagnatgnat-gps

Maintaining fixed memory addresses for record members in Ada


I installed the GNAT-GPS and the AVR-ELF 3 days ago to play with. I got a blinky going and thought I might play around some more. I have no non-VHDL Ada experience.

Here's the scenario I have working in C:

I have it set up so that using a GPIO typedef, I can refer to all the information necessary to set up an GPIO pin (i.e. pin number, pin reg address, dd reg address and port reg address). Then I do the same for, say LED0, so that logically I can connect LED0 to GPIO15, which is itself connected to PB1 of the AVR microcontroller.

I try to do the same in Ada. I feel like I might be writing C in Ada; feel free to let me know afterwards if there's a better way to do this in Ada.

I set up the AVR registers for a particular pin to connect to its short name reference:

       -- PB1
   PB1_Port_reg : Unsigned_8;
   PB1_Dd_reg   : Unsigned_8;
   PB1_Pin_reg  : Unsigned_8;
   for PB1_Port_reg'Address use AVR.Atmega328p.PORTB'Address;
   for PB1_Dd_reg'Address use AVR.Atmega328p.DDRB'Address;
   for PB1_Pin_reg'Address use AVR.Atmega328p.PINB'Address;
   PB1_Pin : constant := 1;

Then I setup its short name reference to connect to its package pin number:

   -- ATmega328p DIP28 Pin15 is PB1
   Pin15_Port_reg : Unsigned_8;
   Pin15_Dd_reg   : Unsigned_8;
   Pin15_Pin_reg  : Unsigned_8;
   for Pin15_Port_reg'Address use PB1_Port_reg'Address;
   for Pin15_Dd_reg'Address use PB1_Dd_reg'Address;
   for Pin15_Pin_reg'Address use PB1_Pin_reg'Address;
   Pin15_Pin : constant := PB1_Pin;

Next I define a record to hold all the parameters for the pin together:

   type gpio_t is record
      pin   : Unsigned_8;
      pin_reg   : Unsigned_8;
      dd_reg    : Unsigned_8;
      port_reg  : Unsigned_8;
   end record;

This is to allow me to write the following function:

 procedure gpio_map (gpio_t_dest : in out gpio_t; gpio_t_pin, gpio_t_pin_reg, gpio_t_dd_reg, gpio_t_port_reg : in Unsigned_8) is

   begin
      gpio_t_dest.pin       := gpio_t_pin;
      gpio_t_dest.pin_reg   := gpio_t_pin_reg;
      gpio_t_dest.dd_reg    := gpio_t_dd_reg;
      gpio_t_dest.port_reg  := gpio_t_port_reg;     
   end gpio_map;

In the future, I'll be looking to have it as:

procedure gpio_map_future (gpio_t_dest : in out gpio_t; gpio_t_src : in gpio_t) is

       begin
          gpio_t_dest.pin       := gpio_t_src.pin;
          gpio_t_dest.pin_reg   := gpio_t_src.pin_reg;
          gpio_t_dest.dd_reg    := gpio_t_src.dd_reg;
          gpio_t_dest.port_reg  := gpio_t_src.port_reg;     
       end gpio_map;

This gpio_map function is used to connect a package pin gpio_t to a package pin number:

gpio_map(gpio15, Pin15_pin, Pin15_pin_reg, Pin15_dd_reg, Pin15_port_reg);

I find that the LED is correctly initialized if I use this function:

core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(Pin15_pin))); -- works

But is not correctly initialized if I do:

core_reg_write(gpio15.dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- does not work

This, however, works:

core_reg_write(Pin15_dd_reg, Shift_Left(1,Integer(gpio15.pin))); -- works

It is clear to me that I have

Pin15_pin = 1 @ address (don't care - a variable)
Pin15_pin_reg = (don't care) @ address 0x23
Pin15_dd_reg = (0b00000000) @ address 0x24
Pin15_port_reg = (don't care) @ address 0x25

And that

gpio15.pin = 1 @ address (don't care, but not same as Pin15_pin address)
gpio15.pin_reg = (don't care) @ address IS NOT 0x23
gpio15.dd_reg = (don't care) @ address IS NOT 0x24
gpio15.port_reg = (don't care) @ address IS NOT 0x25

How do I maintain fixed memory addresses for record members, i.e., get

gpio15.pin_reg = (don't care) @ address 0x23
gpio15.dd_reg = (don't care) @ address 0x24
gpio15.port_reg = (don't care) @ address 0x25

And even better if I can also get

gpio15.pin = 1 @ address (same as Pin15_pin address)

Sorry for the long question; hoping it helped make it clear.


Solution

  • Ok, after looking at your example, I came up with a similar solution in Ada. That said, I don't really care for how exposed access types are here. I'll leave my previous answer since I feel using records directly is a better method overall, but to specifically answer your question, here is an example I tested out in GNAT GPL 2017 using a handmade runtime (for another chip, but it was enough to verify compilation). Trying to compile it in a non embedded version of GNAT met with compiler crashes (I am assuming because the addresses were bad for windows). Hopefully this gives an example that better fits your personal requirements

    registers.ads

    with Interfaces;
    
    -- Basic Register type and functionality
    package Registers with Pure is
    
       type Register is limited private;
       type Register_Access is access all Register with Storage_Size => 0;
    
       procedure Core_Reg_Write
          (Target : not null Register_Access;
           Value  : Interfaces.Unsigned_8)
          with Inline;
    
       function  Core_Reg_Read
          (Source : not null Register_Access) 
           return Interfaces.Unsigned_8
          with Inline;
    
    private
    
       type Register is limited record
          Value : Interfaces.Unsigned_8;
       end record
          with Volatile, Size => 8;
    
    end Registers;
    

    registers.adb

    package body Registers is
    
       procedure Core_Reg_Write
          (Target : not null Register_Access;
           Value  : Interfaces.Unsigned_8)
       is begin
    
          Target.Value := Value;
    
       end Core_Reg_Write;
    
       function  Core_Reg_Read
          (Source : not null Register_Access) 
           return Interfaces.Unsigned_8
       is begin
    
          return Source.Value;
    
       end Core_Reg_Read;
    
    end Registers;
    

    io_registers.ads

    with Registers;
    
    -- Specific Register types and functionality
    package IO_Registers with Pure is
    
       -- Use different ones for each register to avoid accidental copy/paste
       -- errors.
       type Port_Register is new Registers.Register_Access;
       type DD_Register   is new Registers.Register_Access;
       type Pin_Register  is new Registers.Register_Access; 
    
       type Pin_Number is new Positive range 1 .. 8;      
    
       type GPIO_Register is record
          Port_Reg : Port_Register;
          DD_Reg   : DD_Register;
          Pin_Reg  : Pin_Register;
          Pin      : Pin_Number;
       end record;
    
    end IO_Registers;
    

    predefined_registers.ads

    with Registers;
    with System;
    
    package Predefined_Registers is
    
       -- Fake addresses here, since I don't have your atmega package
       GPIO_15_Pin_Reg : aliased Registers.Register
          with 
             Address => System'To_Address(16#80000400#),
          Volatile,
          Convention => C,
          Import;
    
       GPIO_15_DD_Reg : aliased Registers.Register 
          with 
             Address => System'To_Address(16#80000401#),
          Volatile,
          Convention => C,
          Import;       
    
       GPIO_15_Port_Reg : aliased Registers.Register
          with 
             Address => System'To_Address(16#80000402#),
          Volatile,
          Convention => C,
          Import;
    
       GPIO_15_Pin : constant := 1;
    
    end Predefined_Registers;
    

    program.adb

    with IO_Registers;
    with Predefined_Registers;
    
    procedure Program is
       GPIO_15 : IO_Registers.GPIO_Register :=
                   (Port_Reg => Predefined_Registers.GPIO_15_Port_Reg'Access,
                    Pin_Reg  => Predefined_Registers.GPIO_15_Pin_Reg'Access,
                    DD_Reg   => Predefined_Registers.GPIO_15_DD_Reg'Access,
                    Pin      => Predefined_Registers.GPIO_15_Pin);
    begin
       -- Notice the use of IO_Registers for this call.  The new types were
       -- created there, so the corresponding ops were too
       IO_Registers.Core_Reg_Write(GPIO_15.Port_Reg,16#01#);
    end Program;