Maintaining fixed memory addresses for record members in Ada - avr

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.

Related

vhdl ERRROR: Attribute "range" requires a constrained array prefix

Two questions:
how to get rid of the warning: "Shared variables must be of a protected type." while keeping it as a "shared variable"?
How to fix Attribute "range" requires a constrained array prefix?
First of all, what is a constrained array prefix?
$ vcom.exe -2002 -l test3.vhd
** Warning: test3.vhd(14): (vcom-1236) Shared variables
must be of a protected type.
** Error: test3.vhd(20): (vcom-14402) Attribute "range"
requires a constrained array prefix.
library ieee;
use ieee.std_logic_1164.all;
entity test3 is
end entity;
architecture beh of test3 is
constant dw :integer := 8;
constant depth :integer := 128;
type mem_t is array (integer range<>) of std_logic_vector(dw-1 downto 0);
shared variable ram_block :mem_t(0 to depth-1);
begin
process
variable i:integer;
begin
for i in mem_t'range loop
report integer'image(i);
end loop;
end process;
end architecture;
A protected type in VHDL, is similar to a class in OO programming, where it can have member methods and it can retain state. Since 2002, it is required that shared variables must be of a protected type. By default, most tools only throw a warning to maintain backwards compatibility unless you turn on strict rule checking
So you have two options to remove the warning
revert to VHDL 1993 standard.
Create a protected type.
Your example shows no need for a shared variable. It could be made into a normal (non shared) variable inside the process.
Question 2, I found... But Question 1, I'm still not sure about..
architecture beh of test3 is
constant dw :integer := 8;
constant depth :integer := 128;
type mem_t is array (integer range<>) of std_logic_vector(dw-1 downto 0);
shared variable ram_block :mem_t(0 to depth-1);
begin
process
variable i:integer;
begin
report "left:" & integer'image( ram_block'left);
report "right:" & integer'image( ram_block'right);
for i in ram_block'range loop
report integer'image(i);
end loop;
wait;
end process;
end architecture;

VHDL : Array of records with variable field length size

I am trying to implement a generic std_logic_vector data logger for a testbench. This component would generate an output signal for each signal logged.
Each signal should be of the type t_probe
type t_probe is record:
min_delay : time;
max_delay : time;
num_exp_words : integer;
trig_lvl : std_logic;
data: std_logic_vector;
done : std_logic;
end record;
Would like to have a probe array record type:
type t_probe_array is array(0 to c_n_probes - 1) of t_probe;
Now , I have the problem of declaring a st_probe signal type which has the "data" field constrained:
signal coarse_probe : t_probe
Will obviously crash when launching simulation. Can anyone indicate how to define and initialize such an array?
thanks!
It is assumed that this is required for simulation only?
Then you could work with variables and access types (probably the workaround #Tricky was mentioning above?).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tst014 is
end entity tst014;
architecture sim of tst014 is
type pstring is access string;
type rec is record
ps : pstring;
end record;
type rec_array is array(natural range <>) of rec;
begin
p : process
variable recs : rec_array(1 to 3);
begin
recs(1) := (ps => new string'("Hello"));
recs(2) := (ps => new string'("World!"));
report "recs(1).ps.all = " & recs(1).ps.all severity note;
report "recs(2).ps.all = " & recs(2).ps.all severity note;
end process p;
end architecture sim;

VHDL null file handle

I have a procedure (testbench only, non-synthesisable) which receives data via an AXIS interface and writes it to a byte array. I also want the option to write the received data to file. To do this i've added a file handle such that any test bench using this procedure can declare a file handle and pass it to the procedure and the received data will be written to the given file via the file handle.
Here's the procedure declaration:
procedure AXI_STREAM_RECEIVER
(
-- AXI-Stream Parameters
variable PAYLOAD : inout p_byte_array;
constant SLAVE_READY_BEHAVE : in t_slave_ready_behave := always_ready;
constant READY_GAP_RANGE : in natural := 20;
constant VERIFY_TKEEP : in boolean := false;
file file_handle : text;
-- Interface Clock
signal CLK : in std_logic;
-- Master/Slave I/O
signal axis_tdata : in std_logic_vector;
signal axis_tkeep : in std_logic_vector;
signal axis_tvalid : in std_logic;
signal axis_tlast : in std_logic;
signal axis_tready : out std_logic;
-- Misc.
constant VERIFY_TLAST : in boolean := true;
constant VERBOSE : in boolean := c_verbosity_default;
constant DEBUG_SEVERITY_LEVEL : in severity_level := c_axil_debug_severity_level;
constant DEBUG_PAYLOAD_CONTENT : in boolean := false
);
As I want the write to file to be optional, I was hoping to be able to provide a 'null' file handle as a default when writing to file is not required. I've tried assigning a default but I get:
FILE interface declaration must not contain a default expression
Then i've tried assigning it to 'null' when instanced:
Illegal use of NULL literal.
But then if I leave it with no default and not assigned I get:
No feasible entries for subprogram "AXI_STREAM_RECEIVER"
Anybody know if it's possible to pass in some sort of null file descriptor?
You can achieve this using a package with generics using VHDL-2008. The file handle and procedure are declared separately within the package header. Here's an example:
library ieee;
use ieee.std_logic_1164.all;
library std;
use std.textio.all;
package gen_pkg is
generic (
PRINT_TO_FILE : boolean;
FILE_NAME : string;
FILE_MODE : file_open_kind := write_mode
);
file outfile : text;
procedure TEST_PROCEDURE;
end gen_pkg;
package body gen_pkg is
procedure TEST_PROCEDURE is
variable outline : line;
begin
write(outline,string'("TEST STRING"));
if PRINT_TO_FILE then
file_open(outfile, FILE_NAME, FILE_MODE);
writeline(outfile, outline);
file_close(outfile);
end if;
end TEST_PROCEDURE;
end gen_pkg;
I've only shown a string being written, but use any of the overloaded variants of write in the textio package depending on your required datatype or you can use the VHDL-2008 function to_string which supports conversions of all types.
Then in your testbench, create a new instantiation of the package and access procedures/functions, etc. using the instantiation name:
library ieee;
use ieee.std_logic_1164.all;
library std;
use std.textio.all;
entity tb is
end tb;
architecture arch of tb is
package gp is new work.gen_pkg
generic map (
PRINT_TO_FILE => TRUE,
FILE_NAME => "./test_file.txt",
FILE_MODE => append_mode
);
begin
process begin
for i in 0 to 4 loop
gp.TEST_PROCEDURE;
end loop;
wait;
end process;
end arch;
Please note, if you write to the file more than once, like shown in this example, file mode must be append_mode. In write_mode, the file will be overwritten everytime file_open is called. If you only write to the file once per simulation, write_mode is fine. You can also have multiple new instantiations of your generic package in multiple locations, all writing to the same file, as along as they all use append_mode for the file mode.
Here's the working example on EDA playground setup to use Aldec Riviera Pro 2017.02. A login is required to run it. You must have pop-up blocker disabled in your browser in order to download the output file to inspect. The string "TEST STRING" should be written to the file 5 times.
In VHDL 2008 and earlier, you must always connect a file object in an interface list to another file object. Accessing the object when it is not open will cause an error, but there is no way to detect if the file is open or not. VHDL2019 does add a FILE_STATE function to do this, but I assume you still need to connect it to an existing file object with no defaults allowed.
Would it be easier to pass in a string of the filepath, which can have a default of ""? If it is a null string then dont open the file.

Global Package to merge several packages vhdl

I have a system described in vhdl that shall run with different configurations. For that I have put the last in different packages files. I have then a global package where I uncomment the config I want to be synthesized. I use this global package every where then to declare my entities.
But the problem is that in fact while synthesizing, types and constants declared in the config packages are not visible.
I tried to declare the "use my_package.all" in the global package file above the declaration of the global package, and I tried also inside the global package.
I mean I tried:
use work.config1.all;
package global_package is
...
end global_package;
and I tried:
package global_package is
use work.config1.all;
...
end global_package;
that is actually accepting by the synthesizer.
Does someone have a solution ? I really don't want to comment and uncomment in all my files the config I want.
Thank you!
EDIT :
For example, if I have:
file 1 :
package config1 is
constant my_variable : integer := 1;
end config1;
file 2 :
package config2 is
constant my_variable : integer := 2;
end config2;
file 3 :
use work.config1.all
-- use work.config2.all
package global is
constant another_variable : integer := 5;
end global;
file 4 :
use work.global.all
entity my_entity is
port(clk : in std_logic);
end my_entity ;
architecture Behavioral of my_entity is
signal sig1, sig2 : integer;
begin
sig1 <= another_variable; -- I can see and use here the constant "another_variable".
sig2 <= my_variable; -- Error ! I would like to have access here to the constant "my_variable" that the synthesizer can't see.
end Behavioral;
Having then these 4 files, I can't have access to "my_variable" through only "global" package. Furthermore, I would like this constant has the value given in package config1 or config2 in function of the one that is not commented.
Have you tried a context clause declaration and a reference to it? Here is one I have for reference:
context OsvvmContext is
library OSVVM ;
use OSVVM.AlertLogPkg.all ;
use OSVVM.RandomPkg.all ;
use OSVVM.CoveragePkg.all ;
use OSVVM.MemoryPkg.all ;
. . .
end context OsvvmContext ;
Then in your design, you reference it:
library osvvm ;
context osvvm.OsvvmContext ;
This way, you can edit your context clause and change the set of packages you include for your entire project. It is VHDL-2008, so for synthesis YMMV, but for simulation, it is well supported.
I don't know of a easy way to make something in one package visible by using another package. (This is possible in system-verilog using export, but that doesn't help you.)
Instead, there are various other ways that you could solve your problem. I don't exactly know what you problem is, but here is one suggestion:
package global is
constant another_variable : integer := 5;
function set_my_variable (i : integer) return integer;
constant my_variable : integer := set_my_variable(another_variable);
end global;
package body global is
function set_my_variable (i : integer) return integer is
begin
if i = 5 then
return 1;
else
return 2;
end if;
end function;
end package body global;
library IEEE;
use IEEE.std_logic_1164.all;
use work.global.all;
entity my_entity is
port(clk : in std_logic);
end my_entity ;
architecture Behavioral of my_entity is
signal sig1, sig2 : integer;
begin
sig1 <= another_variable; -- I can see and use here the constant "another_variable".
sig2 <= my_variable; -- Error ! I would like to have access here to the constant "my_variable" that the synthesizer can't see.
end Behavioral;
https://www.edaplayground.com/x/5xNd
Here I am initialising a constant using a function whose input is another constant. Then by simply editing that constant, I can change the value of lots of other constants. You could write various functions to initialise the various constants you wish to initialise.
Interestingly, such a function executes during elaboration. This can make debugging tricky. If you need to debug it, you can perhaps call the function to initialise a signal or variable, so that the function executes after time 0 rather than before it.

Initialize dynamic VHDL array

--in the package
type t_array is array (natural range <>) of std_logic_vector (7 downto 0);
type p_array is access t_array;
--in my testbench
variable my_array : p_array := null;
begin
my_array := new t_array(0 to 19);
my_array := ( X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
Error: Target type util_lib.tb_pkg.p_array in variable assignment is different from expression type util_lib.tb_pkg.t_array.
How can I compactly assign all the elements of the array?
(1). Dereference your poincough access type.
my_array.all := (...);
(2) Initialise it from a function
begin
my_array := new_array(20);
The gory details of initialising it can be buried in the function, which could calculate the values algorithmically, copy them from a constant array, or even read the contents from file.
constant initialiser : t_array := (...);
function new_array(length : natural range initialiser'range) return t_array is
variable temp : p_array := new t_array(0 to length - 1);
begin
-- either this
for i in temp'range loop
temp(i) := initialiser(i);
end loop;
-- or simply this
temp.all := initialiser(temp'range);
return temp;
end new_array;
(note the constraint on arguments to new_array : that ensures it won't create an array larger than the initialiser.)
If you like one step, you can also do:
--in my testbench
variable my_array : p_array := null;
begin
my_array := new t_array'( X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
You can also to do this in an initialization:
--in my testbench
variable my_array : p_array := new t_array'(
X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
begin
Your error message:
Error: Target type util_lib.tb_pkg.p_array in variable assignment is different from expression type util_lib.tb_pkg.t_array.
tells us the subtype of the target doesn't match the right hand expression.
That can be cured several ways:
library ieee;
use ieee.std_logic_1164.all;
package initialize is
--in the package
type t_array is array (natural range <>) of std_logic_vector (7 downto 0);
type p_array is access t_array;
end package;
library ieee;
use ieee.std_logic_1164.all;
use work.initialize.all;
entity testbench is
end entity;
architecture fum of testbench is
begin
process
--in my testbench
variable my_array : p_array := null;
begin
my_array := new t_array(0 to 19);
my_array (my_array'range) := (
X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
wait;
end process;
end architecture;
here using a slice name with the range provided by my_array'range.
Without the range the target name is interpreted as an access type name. As a slice name with a discrete range the target name denotes the value of the object type denotes:
IEEE Std 1076-2008 8. Names 8.1 General Paragraph 3 - 4:
Certain forms of name (indexed and selected names, slice names, and attribute names) include a prefix that is a name or a function call. If the prefix of a name is a function call, then the name denotes an element, a slice, or an attribute, either of the result of the function call, or (if the result is an access value) of the object designated by the result. Function calls are defined in 9.3.4.
A prefix is said to be appropriate for a type in either of the following cases:
— The type of the prefix is the type considered.
— The type of the prefix is an access type whose designated type is the type considered.
Here it helps to understand designate is a synonym for denote used to describe the relationship between a value of an access type and the object it references.
paragraph 5:
The evaluation of a name determines the named entity denoted by the name. The evaluation of a name that has a prefix includes the evaluation of the prefix, that is, of the corresponding name or function call. If the type of the prefix is an access type, the evaluation of the prefix includes the determination of the object designated by the corresponding access value. In such a case, it is an error if the value of the prefix is a null access value. It is an error if, after all type analysis (including overload resolution), the name is ambiguous.
In this case you can use a slice name that encompasses the entire array.
You can use a selected name for the access type object designates:
architecture fie of testbench is
begin
process
variable my_array : p_array := null;
begin
my_array := new t_array(0 to 19);
my_array.all := ( X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
wait;
end process;
end architecture;
8.3 Selected names paragraph 5:
For a selected name that is used to denote the object designated by an access value, the suffix shall be the reserved word all. The prefix shall belong to an access type.
Using these methods distinguishes between assignment to an object of an access type (which isn't the type of the composite in the right hand expression) and the allocated object designated by the object of the access type.

Resources