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

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)
);

Related

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.

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.

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.

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;

How do you associate an object to a TGridColumns object

I am running Lazarus 0.9.30.
I have a standard TStringGrid on a form and have a function that dynamically adds TGridColumns objects to it at run time. I have a collection of objects that contain all the attributes of each column (that I read out of a file at run time), and I want to associate each object with its corresponding column header.
I have tried the code below but at run time when I try to access the object behind the column header object, I get a 'nil object returned. I suspect the reason this is occurring is that the grid cell (that holds the column title) is blank, and you can't associate objects with grid cells that are empty.
type
TTmColumnTitles = class(TTmCollection)
public
constructor Create;
destructor Destroy; override;
function stGetHint(anIndex : integer) : string;
end;
type
TTmColumnTitle = class(TTmObject)
private
FCaption : string;
FCellWidth : integer;
FCellHeight : integer;
FFontOrientation : integer;
FLayout : TTextLayout;
FAlignment : TAlignment;
FHint : string;
procedure vInitialise;
public
property stCaption : string read FCaption write FCaption;
property iCellWidth : integer read FCellWidth write FCellWidth;
property iCellHeight : integer read FCellHeight write FCellHeight;
property iFontOrientation : integer read FFontOrientation write FFontOrientation;
property Layout : TTextLayout read FLayout write FLayout;
property Alignment : TAlignment read FAlignment write FAlignment;
property stHint : string read FHint write FHint;
constructor Create;
destructor Destroy; override;
end;
procedure TTmMainForm.vLoadGridColumnTitles
(
aGrid : TStringGrid;
aCollection : TTmColumnTitles
);
var
GridColumn : TGridColumn;
aColumnTitle : TTmColumnTitle; //Just a pointer!
anIndex1 : integer;
anIndex2 : integer;
begin
for anIndex1 := 0 to aCollection.Count - 1 do
begin
aColumnTitle := TTmColumnTitle(aCollection.Items[anIndex1]);
GridColumn := aGrid.Columns.Add;
GridColumn.Width := aColumnTitle.iCellWidth;
GridColumn.Title.Font.Orientation := aColumnTitle.iFontOrientation;
GridColumn.Title.Layout := aColumnTitle.Layout;
GridColumn.Title.Alignment := aColumnTitle.Alignment;
GridColumn.Title.Caption := aColumnTitle.stCaption;
aGrid.RowHeights[0] := aColumnTitle.iCellHeight;
aGrid.Objects[anIndex1, 0] := aColumnTitle;
end; {for}
end;
Just assigning an object to the Objects property isn't enough. You have to draw the title caption from that object yourself in an OnDrawCell event handler, or assign the Cells property as well.
and you can't associate objects with grid cells that are empty
Yes you can. The string and the object of one cell 'work' independent of each other.
So it should be:
for anIndex2 := 0 to aGrid.ColCount - 1 do
begin
aColumnTitle := aCollection.Items[anIndex2]; // Is aCollection.Count in sync
// with aGrid.ColCount??
aGrid.Cells[anIndex2, 0] := aColumnTitle.Caption;
aGrid.Objects[anIndex2, 0] := aColumnTitle;
end;

Resources