Using protected entry argument in barrier condition - arguments

I have a protected Hashed_Map with Vectors of data. To get an element from a specific Vector, I need to pass its key to the entry and, if the Vector is empty, wait for new elements to appear in it. In the barrier condition, the key argument is not yet available and I had to make an entry nested in procedure that takes a key. In this case, a warning appears about a possible blocking operation.
Is there any other way to do this?
with Ada.Containers.Vectors;
with Ada.Containers.Hashed_Maps;
package Protected_Map is
use Ada.Containers;
type Element_Key is new Positive;
type Data_Type is null record;
package Data_Vectors is new Vectors
(Index_Type => Natural,
Element_Type => Data_Type);
function Data_Vector_Hash
(Key : Element_Key) return Ada.Containers.Hash_Type is
(Hash_Type (Key));
package Data_Vector_Maps is new Hashed_Maps
(Key_Type => Element_Key,
Element_Type => Data_Vectors.Vector,
Hash => Data_Vector_Hash,
Equivalent_Keys => "=",
"=" => Data_Vectors."=");
protected Map is
procedure Create (Key : out Element_Key);
procedure Put (Data : Data_Type);
procedure Get
(Key : Element_Key;
Data : out Data_Type);
procedure Delete (Key : Element_Key);
private
entry Get_Element
(Key : Element_Key;
Data : out Data_Type);
Data_Vector_Map : Data_Vector_Maps.Map;
end Map;
end Protected_Map;

Since your Element_Key is a discrete type, you could use an entry family (an array of entries). There's also no need to use an actual map here, an array will suffice.
In order to use an entry family, you would need to constrain the range of Element_Key to suit your actual problem (at least one popular compiler implements entry families as actual arrays, so you'll quickly run out of memory if the range is large).
Thus:
package Protected_Map is
use Ada.Containers;
type Element_Key is new Positive range 1..10; -- constrained range
type Data_Type is null record;
package Data_Vectors is new Vectors
(Index_Type => Natural,
Element_Type => Data_Type);
type Data_Vector_Array is array(Element_Key) of Data_Vectors.Vector;
protected Map is
procedure Put (Key : Element_Key; Data : Data_Type);
entry Get
(Element_Key) -- entry family
(Data : out Data_Type);
procedure Delete (Key : Element_Key);
private
Data_Vector_Map : Data_Vector_Array;
end Map;
end Protected_Map;
and the entry body:
entry Get
(for Key in Element_Key) -- entry family
(Data : out Data_Type)
when not Data_Vector_Map(Key).Is_Empty
is
begin
...
end Get;
and then (for example)
for Key in Element_Key'Range loop
Map.Get(Key)(The_Data);
end loop;

If the map key in your example is really some discrete value within a finite range, then the answer of #egilhh is indeed to consider. If this is not the case, then you might solve the problem by using a Get entry and some additional private Get_Retry entry as shown in the example below.
This "pattern" is used when you want to check for the availability of some item (the Get entry) and if not, requeue the request to another entry (Get_Retry) where it'll wait until new items arrive. The pattern is often used for programming thread-safe resource managers.
In this pattern, the Get entry is always enabled (i.e. the guard never blocks) so requests are always allowed to enter and see if an item of interest is already available:
entry Get (Key : Map_Key; Data : out Data_Type)
when True -- Never blocking guard.
is
begin
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get;
If no item is available, then the request is requeued to the Get_Retry entry. This (private) entry has a guard that is unblocked by the Put subprogram. If an item arrives via Put, then Put will record the number of requests waiting for a retry, unblock the guard, and allow pending requests to see if the new item is of interest to them.
procedure Put (Key : Map_Key; Data : Data_Type) is
begin
Data_Vector_Map (Key).Append (Data);
-- If there are requests for data, then record the number
-- of requests that are waiting and open the guard of Get_Retry.
if Get_Retry'Count /= 0 then
Get_Retry_Requests_Left := Get_Retry'Count;
Get_Retry_Enabled := True;
end if;
end Put;
Once all pending requests are served once, Get_Retry will disable itself to prevent any request that were requeued again to itself to be served for a second time.
entry Get_Retry (Key : Map_Key; Data : out Data_Type)
when Get_Retry_Enabled -- Guard unblocked by Put.
is
begin
-- Set guard once all pending requests have been served once.
Get_Retry_Requests_Left := Get_Retry_Requests_Left - 1;
if Get_Retry_Requests_Left = 0 then
Get_Retry_Enabled := False;
end if;
-- Check if data is available, same logic as in Get.
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get_Retry;
Note: both entry families (as discussed in the answer of #egilhh), as well as this pattern were discussed in a recent AdaCore blogpost.
protected_map.ads
with Ada.Containers.Vectors;
with Ada.Containers.Hashed_Maps;
package Protected_Map is
use Ada.Containers;
type Map_Key is new Positive;
type Data_Type is new Integer;
function Data_Vector_Hash (Key : Map_Key) return Hash_Type is
(Hash_Type (Key));
package Data_Vectors is new Vectors
(Index_Type => Natural,
Element_Type => Data_Type);
package Data_Vector_Maps is new Hashed_Maps
(Key_Type => Map_Key,
Element_Type => Data_Vectors.Vector,
Hash => Data_Vector_Hash,
Equivalent_Keys => "=",
"=" => Data_Vectors."=");
protected Map is
procedure Create (Key : Map_Key);
procedure Delete (Key : Map_Key);
procedure Put (Key : Map_Key; Data : Data_Type);
entry Get (Key : Map_Key; Data : out Data_Type);
private
entry Get_Retry (Key : Map_Key; Data : out Data_Type);
Get_Retry_Requests_Left : Natural := 0;
Get_Retry_Enabled : Boolean := False;
Data_Vector_Map : Data_Vector_Maps.Map;
end Map;
end Protected_Map;
protected_map.adb
package body Protected_Map is
protected body Map is
------------
-- Create --
------------
procedure Create (Key : Map_Key) is
begin
Data_Vector_Map.Insert (Key, Data_Vectors.Empty_Vector);
end Create;
------------
-- Delete --
------------
procedure Delete (Key : Map_Key) is
begin
Data_Vector_Map.Delete (Key);
end Delete;
---------
-- Put --
---------
procedure Put (Key : Map_Key; Data : Data_Type) is
begin
Data_Vector_Map (Key).Append (Data);
-- If there are requests for data, then record the number
-- of requests that are waiting and unblock the guard of Get_Retry.
if Get_Retry'Count /= 0 then
Get_Retry_Requests_Left := Get_Retry'Count;
Get_Retry_Enabled := True;
end if;
end Put;
--------------------
-- Data_Available --
--------------------
function Data_Available (Key : Map_Key) return Boolean is
begin
return Data_Vector_Map.Contains (Key) and then
not Data_Vector_Map (Key).Is_Empty;
end Data_Available;
---------
-- Get --
---------
entry Get (Key : Map_Key; Data : out Data_Type)
when True -- No condition.
is
begin
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get;
---------------
-- Get_Retry --
---------------
entry Get_Retry (Key : Map_Key; Data : out Data_Type)
when Get_Retry_Enabled -- Guard unblocked by Put.
is
begin
-- Set guard once all pending requests have been served once.
Get_Retry_Requests_Left := Get_Retry_Requests_Left - 1;
if Get_Retry_Requests_Left = 0 then
Get_Retry_Enabled := False;
end if;
-- Check if data is available, same logic as in Get.
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get_Retry;
end Map;
end Protected_Map;
main.adb
with Ada.Text_IO; use Ada.Text_IO;
with Protected_Map;
procedure Main is
task Getter;
task body Getter is
Data : Protected_Map.Data_Type;
begin
Protected_Map.Map.Get (2, Data);
Put_Line (Data'Image);
Protected_Map.Map.Get (1, Data);
Put_Line (Data'Image);
Protected_Map.Map.Get (3, Data);
Put_Line (Data'Image);
Protected_Map.Map.Get (1, Data);
Put_Line (Data'Image);
end;
begin
Protected_Map.Map.Create (1);
Protected_Map.Map.Create (2);
Protected_Map.Map.Create (3);
Protected_Map.Map.Put (1, 10);
delay 0.5;
Protected_Map.Map.Put (1, 15);
delay 0.5;
Protected_Map.Map.Put (2, 20);
delay 0.5;
Protected_Map.Map.Put (3, 30);
end Main;
output
$ ./obj/main
20
15
30
10

Related

How to extend a record type while remaining backwards compatible with an aggregate?

Many of our modules use an array of configuration descriptor records to configure a Configurable component. The array type, record type and configurable component are declared in a global package that everybody uses.
type config_descriptor_type is record
config0 : integer;
config1 : boolean;
end record;
type config_type is array(natural range <>) of config_descriptor_type;
component Configurable is
generic (
g_CONFIG : config_type
);
end component;
Every module that uses this Configurable has a package containing the configuration information for that module (all existing such configurations must remain unchanged).
constant c_config : config_type(0 to 3) := (
(1, true),
(2, false),
(8, false),
(4, true)
);
This per-module constant is passed to the Configurable instance (the instantiation must also remain unchanged).
config_inst : Configurable
generic map (
g_CONFIG => c_config
);
I am adding some features to Configurable that require an additional field in the config_descriptor_type record.
type new_config_descriptor_type is record
config0 : integer;
config1 : boolean;
config2 : integer; -- this field was added
end record;
I am free to make any changes I like to the global configurable package and entity, as well as to any new modules that use the new feature, but I should not touch any of the existing modules that use the configurable. The new field should obtain some default value if instantiated by an old module.
Is there some way to add such a field without having to modify all of the existing modules that use a configurable?
A type cannot contain a default value, that only comes from an object (signal, variable, constant). And when assigning a value to an object (like the initial value for a constant) all fields will need to be defined.
There is a workaround for this, but will require a code change for all previously defined constants as a one off, which should not need changing again. If you define a basic "init" function where all paramters have a default value, then if you add any more items to the base type then existing code will always return a legal object with all fields assigned.
type config_descriptor_type is record
config0 : integer;
config1 : boolean;
config2 : integer;
end record;
type config_type is array(natural range <>) of config_descriptor_type;
function init_config_descriptor_type( config0 : integer := 0;
config1 : boolean := true;
config2 : integer := 0 ) return config_descriptor_type is
variable r : config_descriptor_type;
begin
r.config0 := config0;
r.config1 := config1;
r.config2 := config2;
return r;
end function;
-- Now you can create configs from the function. Config2 will be default value (0)
constant c_config : config_type(0 to 3) := (
init_config_descriptor_type(1, true),
init_config_descriptor_type(2, false),
init_config_descriptor_type(8, false),
init_config_descriptor_type(4, true)
);

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.
You can't really get what you want via assignment of the two types. All that does is copy the current values, not the register addresses. Here is an option:
Create a type similar to your gpio_t type but make it exactly match the register map for your micro. That means you won't be storing the pin number in it and you need to include all the surrounding registers. Here is an example I found from another file for a different micro, but hopefully serves as an example
type Register_Layout is limited record
DIR : Unsigned_32;
DIRCLR : Unsigned_32;
DIRSET : Unsigned_32;
DIRTGL : Unsigned_32;
OUTVAL : Unsigned_32;
OUTCLR : Unsigned_32;
OUTSET : Unsigned_32;
OUTTGL : Unsigned_32;
INPUT : Unsigned_32;
CTRL : Unsigned_32;
WRCONFIG : Unsigned_32;
EVCTRL : Unsigned_32;
end record
with
Pack,
Volatile,
Size => 12*32;
The record type should be limited so that you ensure it is passed by reference and not by copy.
Note: You can also use a representation clause to provide the byte and bit layout of the structure instead. It will depend on the compiler that you use.
Once you have your micro's registers laid out to match the datasheet, you then create a variable and map that to the address you want, just like you did with the individual variables
Register_B : Register_Layout with
Address => System'To_Address(Some_Address),
Volatile => True,
Import => True;
This will map the entire record variable to that address.
After that, you need to modify your function calls to take the whole record as a parameter instead of the just the register. As an example:
Core_Reg_Write_DIR(Register_B, Shift_Left(1,Integer(PB1_Pin)));
If you need to have things be more fancy and have the right registers and mask value selected via pin, then you either need to use
CASE statements
Arrays of access types/addresses (using the pin type as the index).
A way to calculate the register address and mask from the pin and use that on a locally declared variable's address attribute inside a function call using a pin as a parameter.
You can't really have individual record components addressed differently (this is true in C and C++ as well).
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;
After a bit of thought, I decided to follow on what I already do in C. There, I have the following typedef defined
typedef struct {
IO_REG_TypeDef_t portr;
IO_REG_TypeDef_t ddr;
IO_REG_TypeDef_t pinr;
volatile uint8_t pin;
} GPIO_TypeDef_t;
And IO_REG_t is itself defined as
typedef struct {
volatile uint8_t* io_reg;
} IO_REG_TypeDef_t;
So clearly the key parameters for the gpio are lugged around in a typedef. I thought to do the same in Ada. Again, forgive me if I am speaking C in Ada; feel free to suggest more Ada-standard approaches.
I define the gpio pin components:
-- GPIO15 is PB1 on ATmega328p 28 DIP
gpio15_pin_reg : Unsigned_8;
for gpio15_pin_reg'Address use Atmega328p.PINB'Address;
gpio15_dd_reg : Unsigned_8;
for gpio15_dd_reg'Address use Atmega328p.DDRB'Address;
gpio15_port_reg : Unsigned_8;
for gpio15_port_reg'Address use Atmega328p.PORTB'Address;
gpio15_pin : constant Unsigned_8 := 1;
Register read & write functions are defined:
procedure core_reg_write (reg: in out Unsigned_8; value: in Unsigned_8) is
begin
reg := value;
end core_reg_write;
function core_reg_read (reg: in Unsigned_8) return Unsigned_8 is
value : Unsigned_8;
begin
value := reg;
return value;
end core_reg_read;
Then a record is defined, this time, to lug around the pin variable and, instead of variables for the pin, dd and port registers, their addresses instead:
type gpio_t is record
pin : Unsigned_8;
pin_reg_addr : System.Address;
dd_reg_addr : System.Address;
port_reg_addr : System.Address;
end record;
The record for a given gpio pin is assembled:
gpio15 : gpio_t := (gpio15_pin, gpio15_pin_reg'Address, gpio15_dd_reg'Address, gpio15_port_reg'Address);
Procedures that take this record and set parameters of the pin are defined:
procedure gpio_output (gpio : in gpio_t) is
dd_reg : Unsigned_8;
for dd_reg'Address use gpio.dd_reg_addr;
begin
core_reg_write(dd_reg, core_reg_read(dd_reg) or shift_left(1,integer(gpio.pin)));
end gpio_output;
procedure gpio_hi (gpio : in gpio_t) is
port_reg : Unsigned_8;
for port_reg'Address use gpio.port_reg_addr;
begin
core_reg_write(port_reg, core_reg_read(port_reg) or shift_left(1,integer(gpio.pin)));
end gpio_hi;
procedure gpio_lo (gpio : in gpio_t) is
port_reg : Unsigned_8;
for port_reg'Address use gpio.port_reg_addr;
begin
core_reg_write(port_reg, core_reg_read(port_reg) and not shift_left(1,integer(gpio.pin)));
end gpio_lo;
In each of these procedures, the required registers are, for lack of a better description, manually dereferenced.
The following sequence follows the begin keyword:
-- Initialize
gpio_output(gpio15);
For_loop_0:
loop
-- turn on
gpio_hi(gpio15);
-- loop
Lazy_delay_1:
for I in Unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_1;
-- turn off
gpio_lo(gpio15);
-- loop
Lazy_delay_2:
for I in Unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_2;
end loop For_loop_0;
And the led blinks.
This achieves what I want but I’m open to other approaches that take a composite gpio_t-like type and don’t require manual dereferencing of the address/pointer.
After playing around a bit, in this online compiler (https://www.tutorialspoint.com/compile_ada_online.php) I got this working:
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces; use Interfaces;
with System; use System;
procedure Hello is
-- pseudo hardware registers, unknown addresses, known contents
temp0 : interfaces.unsigned_8 := 2#00000101#; -- pinr
temp1 : interfaces.unsigned_8 := 2#10000000#; -- ddr
temp2 : interfaces.unsigned_8 := 2#10000000#; -- portr
-- core
type io_reg_t is limited record
io_reg : interfaces.unsigned_8;
end record;
pragma volatile(io_reg_t); -- Verify relevance.
-- processor
gpio15_pinr : aliased io_reg_t;
for gpio15_pinr'address use temp0'address;
gpio15_ddr : aliased io_reg_t;
for gpio15_ddr'address use temp1'address;
gpio15_portr : aliased io_reg_t;
for gpio15_portr'address use temp2'address;
gpio15_pin : constant interfaces.unsigned_8 := 1;
procedure core_reg_write_old (reg: in out unsigned_8; value: in unsigned_8) is
begin
reg := value;
end core_reg_write_old;
procedure core_reg_write (reg: access io_reg_t; value: in unsigned_8) is
begin
reg.io_reg := value;
end core_reg_write;
function core_reg_read (reg: access io_reg_t) return Unsigned_8 is
begin
return reg.io_reg;
end core_reg_read;
-- gpio
type gpio_t is record
pinr : access io_reg_t;
ddr : access io_reg_t;
portr : access io_reg_t;
pin : interfaces.unsigned_8;
end record;
pragma volatile(gpio_t); -- Verify relevance.
procedure gpio_output (gpio : in gpio_t) is
begin
core_reg_write(gpio.ddr,core_reg_read(gpio.ddr) or shift_left(1,integer(gpio.pin)));
end gpio_output;
procedure gpio_hi (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) or shift_left(1,integer(gpio.pin)));
end gpio_hi;
procedure gpio_lo (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) and not shift_left(1,integer(gpio.pin)));
end gpio_lo;
gpio15 : gpio_t := (
pinr => gpio15_pinr'access,
ddr => gpio15_ddr'access,
portr => gpio15_portr'access,
pin => gpio15_pin
);
-- led
type led_t is record
gpio : gpio_t;
end record;
led0 : led_t := (gpio => gpio15);
procedure led_init (led : in led_t) is
begin
gpio_output(led.gpio);
end led_init;
procedure led_on (led : in led_t) is
begin
gpio_hi(led.gpio);
end led_on;
procedure led_off (led : in led_t) is
begin
gpio_lo(led.gpio);
end led_off;
begin
put_line("Hello, world!");
-- Does it match the original value of 5?
put_line(gpio15.pinr.io_reg'Image);
-- Does modification via variable alter the value returned?
temp0 := 203;
put_line(gpio15.pinr.io_reg'Image);
-- Does modification via record alter the value returned?
gpio15.pinr.io_reg := 89;
put_line(gpio15.pinr.io_reg'Image);
-- Writes value in temp2 (128) to temp0.
core_reg_write_old(temp0,temp2);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
-- Writes value of pin (1) to pinr via record.
--core_reg_write(gpio15.ddr,gpio15.pin);
-- Writes 1 shifted value of pin times and or's that with ddr reg
--gpio_output(gpio15);
led_init(led0);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
--gpio_hi(led0.gpio);
led_on(led0);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
--gpio_lo(led0.gpio);
led_off(led0);
put_line(gpio15.pinr.io_reg'Image);
put_line(gpio15.ddr.io_reg'Image);
put_line(gpio15.portr.io_reg'Image);
put_line(gpio15.pin'Image);
end Hello;
I modified this for my embedded environment but it failed to compile, with the complaint:
undefined reference to `__gnat_last_chance_handler’
for the lines “reg.io_reg := value” and “return reg.io_reg”.
I found out that I actually didn’t need the last_chance_handler if my access types were explicitly declared to be “not null”.
So the updated program became:
with Interfaces; use Interfaces;
with System;
with Atmega328p;
procedure Main is
-- core
type io_reg_t is limited record
io_reg : interfaces.unsigned_8;
end record;
pragma volatile(io_reg_t); -- Verify relevance.
type dd_io_reg_t is new io_reg_t;
-- Location?
gpio15_pinr : aliased io_reg_t;
for gpio15_pinr'address use Atmega328p.PINB'Address;
gpio15_ddr : aliased io_reg_t;
for gpio15_ddr'address use Atmega328p.DDRB'Address;
gpio15_portr : aliased io_reg_t;
for gpio15_portr'address use Atmega328p.PORTB'Address;
gpio15_pin : constant interfaces.unsigned_8 := 1;
procedure core_reg_write (reg: not null access io_reg_t; value: in interfaces.unsigned_8) is
begin
reg.io_reg := value;
end core_reg_write;
function core_reg_read (reg: not null access io_reg_t) return interfaces.unsigned_8 is
begin
return reg.io_reg;
end core_reg_read;
-- gpio
type gpio_t is record
pinr : not null access io_reg_t;
ddr : not null access io_reg_t;
portr : not null access io_reg_t;
pin : interfaces.unsigned_8;
end record;
pragma volatile(gpio_t); -- Verify relevance.
-- gpio_output
procedure gpio_output (gpio : in gpio_t) is
begin
core_reg_write(gpio.ddr,core_reg_read(gpio.ddr) or shift_left(1,integer(gpio.pin)));
end gpio_output;
procedure gpio_hi (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) or shift_left(1,integer(gpio.pin)));
end gpio_hi;
procedure gpio_lo (gpio : in gpio_t) is
begin
core_reg_write(gpio.portr,core_reg_read(gpio.portr) and not shift_left(1,integer(gpio.pin)));
end gpio_lo;
gpio15 : gpio_t := (
pinr => gpio15_pinr'access,
ddr => gpio15_ddr'access,
portr => gpio15_portr'access,
pin => gpio15_pin
);
-- led
type led_t is record
gpio : gpio_t;
end record;
led0 : led_t := (gpio => gpio15);
procedure led_init (led : in led_t) is
begin
gpio_output(led.gpio);
end led_init;
procedure led_on (led : in led_t) is
begin
gpio_hi(led.gpio);
end led_on;
procedure led_off (led : in led_t) is
begin
gpio_lo(led.gpio);
end led_off;
begin
-- Initialize
-- Writes value of pin (1) to pinr via record.
--core_reg_write(gpio15.ddr,gpio15.pin);
-- Writes 1 shifted value of pin times and or's that with ddr reg
--gpio_output(gpio15);
led_init(led0);
For_loop_0:
loop
-- turn on
--gpio_hi(led0.gpio);
led_on(led0);
-- loop
Lazy_delay_1:
for i in interfaces.unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_1;
-- turn off
--gpio_lo(led0.gpio);
led_off(led0);
-- loop
Lazy_delay_2:
for i in interfaces.unsigned_32 range 0 .. 100_000 loop
null;
end loop Lazy_delay_2;
end loop For_loop_0;
end Main;
After this modification I compiled it burned it into the microcontroller.
And the led blinks.
I'll use this moving forward.

Read / write different Records data from / to Untyped files in Pascal?

I've a programming project in my college.
Using a File type for storing data is allowed, and I did exactly like this one: pascal-programming
And, here's what I achieved so far:
I tried to write the Records data into Untyped files instead and it worked
I want to override a function with dynamic parameter (e.g: I can switch which Record I want to process, in this case there's 2 different "Records").
Open(var f: File; var data)
data = represent can receive "anything". cmiiw
The reason why I did this, I don't think it's a good idea to recreate the same function over and over, e.g: when using 3 or more different "Records"
I also encountered a problem that the files can't store or backup the actual binary files to the temporary "Records" variable, it always give the 0 values.
go to my github source code
my solution here doesn't provide any generic related procedures (check the last sentences):
program test_untyped;
{ A crude database recording }
uses crt;
type
Temployee = record
name : string[20];
address : string[40];
phone : string[15];
age : byte;
salary : longint;
end;
arr_employee = array[1..100] of Temployee;
var
F : File;
c : char;
// r : Temployee;
r, realR : arr_employee;
s : string;
i, j, n : integer;
procedure fRead;
begin
seek(F, 0);
i := 0;
repeat
clrscr;
inc(i);
writeln('increment: ', i); readln;
writeln('File position : ',filepos(F));
blockRead(F, r[i], sizeOf(Temployee));
writeln('Name = ', r[i].name); { Input data }
writeln('Address = ', r[i].address);
writeln('Phone = ', r[i].phone);
writeln('Age = ', r[i].age);
writeln('Salary = ', r[i].salary);
write('Show data again (Y/N) ?');
repeat
c:=upcase(readkey); { Ask user : Input again or not }
until c in ['Y','N'];
writeln(c);
// realR[i] := r[i]; // backup, to show later
until c='N';
end; // end fRead
procedure fWrite;
begin
seek(F, filesize(F));
repeat
clrscr;
inc(i);
writeln('berapa nilai i: ', i);
writeln('File position : ',filepos(F));
write('Name = '); readln(r[i].name); { Input data }
write('Address = '); readln(r[i].address);
write('Phone = '); readln(r[i].phone);
write('Age = '); readln(r[i].age);
write('Salary = '); readln(r[i].salary);
blockWrite(F, r[i], sizeOf(Temployee)); { Write data to file }
write('Input data again (Y/N) ?');
repeat
c:=upcase(readkey); { Ask user : Input again or not }
until c in ['Y','N'];
writeln(c);
until c='N';
end;
// procedure fDelete;
// var
// nama: string;
// delElement: integer;
// tempR: Temployee;
// begin
// seek(F, 0);
// write('search your data by name: '); readln(nama);
// while not eof(F) do
// begin
// writeln('file position: ', filePos(F));
// blockRead(F, tempR, sizeOf(Temployee));
// if (nama = tempR.name) then
// begin
// delElement := filePos(F);
// end else
// begin
// // seek(F, )
// blockWrite(F, tempR, sizeOf(Temployee));
// end;
// end;
// end; // end fDelete
procedure fDisplay;
begin
writeln('nilai i saat ini: ', i); readln;
for j := 1 to i do
begin
clrscr;
writeln('Name = ', r[j].name); { Input data }
writeln('Address = ', r[j].address);
writeln('Phone = ', r[j].phone);
writeln('Age = ', r[j].age);
writeln('Salary = ', r[j].salary);
readln;
end;
end;
begin
clrscr;
// write('Input file name to record databases : '); readln(s);
s := 'coba1.dat';
assign(F,s); { Associate it }
{$I-}
reset(F, sizeOf(Temployee)); { First, open it }
{$I+}
n:=IOResult;
if n<>0 then { If it's doesn't exist then }
begin
{$I-}
rewrite(F, sizeOf(Temployee)); { Create it }
{$I+}
n:=IOResult;
if n<>0 then
begin
writeln('Error creating file !'); halt;
end;
end
else
begin { If it exists then }
n:=filesize(F); { Calculate total record }
// seek(F,n); { Move file pointer PAST the last record }
end;
fileMode := 2;
reset(F, sizeOf(Temployee));
fRead;
fWrite;
// fDelete;
fDisplay;
close(F);
end.
I'm wondering is the Pascal can be any good to use a generic programming? at least for this semester using Pascal in my college XD
Thank you and Best Regards,
EDIT:
Pascal still doesn't support Generic Programming 'till the day I posted this question. So sad, really.
You might wanna consider read this references instead.
I don't understand the main issue here, but would suggest using a typed file instead of an untyped one.
An untyped file is much harder to maintain, and provides (in my eyes) no benefits.
Consider the code:
type
Temployee = record
name : string[20];
address : string[40];
phone : string[15];
age : byte;
salary : longint;
end;
VAR
fEmployee : File Of Temployee;
Employees : ARRAY[0..100] Of Temployee;
Employee : Temployee;
PROCEDURE OpenEmployeeFile(CONST TheFileName:AnsiString);
BEGIN
AssignFile(fEmployee,TheFileName);
IF FileExistsUTF8(TheFileName) { *Converted from FileExists* }
THEN Reset(fEmployee)
ELSE Rewrite(fEmployee);
END;
PROCEDURE CloseEmployeeFile;
BEGIN
Close(fEmployee);
END;
FUNCTION ReadEmployee(Position:WORD): Temployee;
BEGIN
Seek(fEmployee,Position);
Read(fEmployee,Result);
END;
PROCEDURE WriteEmployee(CONST Employee:Temployee; Position:WORD);
BEGIN
Seek(fEmployee,Position);
Write(fEmployee,Employee);
END;
Error handling not implemented.
Code samples as a guideline, not complete.
It provides a basic skeleton for opening and closing the employee-file, as well as reading and writing at specific positions (specific records) in the file.
Open file.
Write all the records you want.
Close file.
Or.
Open file.
Read all the records you want.
Close file.

arrays of VHDL protected types

I am trying to make better use of VHDL protected types, so I threw together the following test (just for illustration, of course - my actual use case is considerably more complex):
type prot_type1 is protected
procedure set (new_data : integer);
impure function get return integer;
end protected prot_type1;
type prot_type1 is protected body
variable data : integer := 0;
procedure set (new_data : integer) is
begin
data := new_data;
end procedure set;
impure function get return integer is
begin
return data;
end function get;
end protected body prot_type1;
This compiles. However, the following line does not:
type prot_type1_array is array (natural range <>) of prot_type1;
Ashenden says (3rd Ed., p. 589) "Protected types cannot be used as elements of ... composite types". This is unfortunate. I was hoping to be able to create another protected type with the body:
type prot_type2 is protected body
variable data : prot_type1_array(0 to 3);
procedure set (idx : natural; new_data : integer) is
begin
data(idx).set(new_data);
end procedure set;
...
end protected body prot_type2;
and avoid duplicating the code in prot_type1.set() (which is admittedly trivial in this case, but would be much more complex in my actual use case). It seems my only choice, though, is (1) to basically rewrite the entirety of prot_type1 except with an array type for my private variable. Or (2), flatten the array internally, like:
type prot_type2 is protected body
variable data0 : prot_type1;
variable data1 : prot_type1;
procedure set (idx : natural; new_data : integer) is
begin
case idx is
when 0 =>
data0.set(new_data);
when 1 =>
data1.set(new_data);
when others =>
-- handle exceptions here
end case;
end procedure set;
...
end protected body prot_type2;
This works, but is mildly undesirable for small arrays, and is extremely undesirable for large arrays. Is there another way?
here is a suggestion based on Morten Zilmer comment. The prot1_type get an access on integer instead of a unique integer. I have used function append, remove and get to manage the integer values.
Here is the code :
type array_int is array (natural range <>) of integer;
type a_integer is access array_int;
type prot_type1 is protected
-- add a new value at the end of the vector
procedure append (new_data : integer);
-- remove a value from the vector, return 0 ik OK, -1 is the item doesn't exist
impure function remove (index : integer) return integer;
-- return the integer value of the item
impure function get(index : integer) return integer;
end protected prot_type1;
type prot_type1 is protected body
variable data : a_integer;
procedure append(new_data : integer) is
variable temp : a_integer;
begin
-- create a temporary vector with the new values
temp := new array_int'(data.all & new_data);
-- free memory of the real vector
Deallocate(data);
-- reallocate the real vector with the good values
data := new array_int'(temp.all);
-- free memory of the temporary vector
Deallocate(temp);
end procedure append;
impure function remove(index : integer) return integer is
variable temp : a_integer;
begin
if (index > data'length-1 or index < 0) then -- not sure if the vector is (0 to length -1) or (1 to length). to be tested !!!
return -1;
else
-- create a temporary vector with the new values
temp := new array_int'(data(0 to index-1) & data(index+1 to data'length-1));
-- free memory of the real vector
Deallocate(data);
-- reallocate the real vector with the good values
data := new array_int'(temp.all);
-- free memory of the temporary vector
Deallocate(temp);
return 0;
end if;
end function remove;
impure function get(index : integer) return integer is
begin
return data(index);
end function get;
end protected body prot_type1;

In Ada, how do I get the current time in seconds as a value I can print out and perform operations with?

I am doing a project for an operating systems class. I need to write a program that prints out the current time every ten seconds but also accounts for the delay of the overhead so that it does not drift when it has been running for a long time. I need it to be up to at least 1 decimal place as well.
I am stuck on step 1 as I can't figure out how to get the current time in seconds as a value. I have searched but could only find out how to get the current time in the HH:MM:SS format.
Thanks
Here's what I came up with:
writing_test.ads
package Writing_Test is
protected Writer is
entry write( Text : String; New_Line : Boolean:= True );
end Writer;
task Timer is
entry Start;
entry Pause;
entry Stop;
end Timer;
private
Timer_Frequency : constant Duration:= 10.0;
end Writing_Test;
writing_test.adb
with
Ada.Calendar,
Ada.Text_IO;
package body Writing_Test is
protected body Writer is
entry write( Text : String; New_Line : Boolean:= True ) when True is
begin
Ada.Text_IO.Put( Text );
if New_Line then
Ada.Text_IO.New_Line;
end if;
end;
end Writer;
task body Timer is
Active,
Stop_Task : Boolean:= False;
Next_Time : Ada.Calendar.Time;
use type Ada.Calendar.Time;
begin
MAIN:
loop
if not Active then
select
accept Start do
Active:= True;
Next_Time:= Ada.Calendar.Clock + Timer_Frequency;
end Start;
or
terminate;
end select;
else
select
accept Pause do
Active:= False;
end Pause;
or
accept Stop do
Stop_Task:= True;
end Stop;
or
delay until Next_Time;
end select;
exit MAIN when Stop_Task;
if Active then
declare
Use Ada.Calendar;
Now : Time renames Clock;
Str : String renames
Day_Duration'Image( Ada.Calendar.Seconds(Now) );
--' Formatter-correction trick
begin
Writer.write(Text => Str);
Next_Time:= Next_Time + Timer_Frequency;
end;
end if;
end if;
end loop MAIN;
end Timer;
end Writing_Test;
Here is a simple program that does the "print every 10s" part of the job. You could easily use this code in a separate package.
with Ada.Text_IO;
with Ada.Calendar;
procedure Periodic_Printer is
task type My_Printer_Task is
end My_Printer_Task;
task body My_Printer_Task is
use Ada.Calendar; -- for the "-" and "+" operations on Time
Start_Time : Ada.Calendar.Time;
Next_Cycle : Ada.Calendar.Time;
Period : constant Duration := 10.0;
begin
Start_Time := Ada.Calendar.Clock;
Next_Cycle := Start_Time;
loop
Ada.Text_IO.Put_Line(Duration'Image(Ada.Calendar.Clock - Start_Time)); --'
-- You could use Next_Cycle instead of Ada.Calendar.Clock - Start_Time
-- so the printing does not depend of the time needed to do the elapsed
-- time calculation
delay 3.0; -- like a long operation, takes time......
-- This pattern assumes the each cycle last less than Period
-- If you cannot ensure that, you should consider improving
-- the pattern or reduce the computation load of each cycle
Next_Cycle := Next_Cycle + Period;
delay until Next_Cycle;
end loop;
end My_Printer_Task;
Printer : My_Printer_Task;
begin
delay 90.0; -- You can do your 'real work' here.
-- Unclean way to terminate a task, you should consider improve it for a
-- real world scenario
abort Printer;
Ada.Text_IO.Put_Line("End of program");
end Periodic_Printer;

Resources