I'm a C programmer and started learning Ada a few weeks ago. I have been puzzled by how Ada handles foreign binary data, such as when decoding a communications packet stored in a serial input buffer.
In C, I would define a packed structure to reflect the layout of the packet, and then cast the pointer-to-buffer to pointer-to-structure to access the individual elements in the communications data. What is the typical way to do such decoding in Ada?
I have tried to replicate the same method for C in Ada with the following
-- Fundamental types for fields in the packet
type Station_Addr_Type is mod 2**8;
type Func_Code_Type is (FUNC1, FUNC2);
for Func_Code_Type use
(
FUNC1 => 1,
FUNC2 => 2
);
type Packet is
record
Station_Addr : Station_Addr_Type;
Func_Code : Func_Code_Type;
end record;
-- attempts to reflect packet binary layout
for Packet use
record at mod 1;
Station_Addr at 0 range 0 .. 7;
Func_Code at 1 range 0 .. 7;
end record;
I then defined the array for the communications receive buffer that accepts foreign binary data (possibly from a different architecture):
type Communication_Data is mod 2**8;
for Communication_Data'Size use 8;
type Communication_Buffer is array (Natural range <>) of Communication_Data;
Buffer : Communication_Buffer (0 .. 20);
Then a procedure that decodes such communications
procedure Decode_Packet (Packet_Provided : in Packet);
-- non-working sample
declare
begin
-- Attempts to sanity-check packet by object casting
Decode_Packet (Packet (Buffer));
---------------^----
Error: invalid conversion, not compatible with type "Communication_Buffer"
exception
when others =>
raise Decode_Failure;
end;
However, the compiler forbids such casting with error as shown. Thanks for reading this far. My question is,
Regarding the correct way of decoding foreign binary data, am I "in the ball park", or is there a better way of doing this?
If you sure that the data has a right alignment, you can map a Packet object to space allocated by Communication_Data:
Buffer : Communication_Buffer (0 .. 20);
Pkg : Packet;
pragma Import (Ada, Pkg);
for Pkg'Address use Buffer (0)'Address;
The same with aspect syntax:
Buffer : Communication_Buffer (0 .. 20);
Pkg : Packet
with Import, Address => Buffer (0)'Address;
Another way is to use Ada.Unchecked_Conversion, but you should be sure that Buffer and Packet have the same size:
subtype Packet_Buffer is Communication_Buffer (1 .. 2);
function To_Packet is new Ada.Unchecked_Conversion
(Packet_Buffer, Packet);
Pkg : Packet := To_Packet (Buffer (0 .. 1));
PS. If you want an endianness independent code you may also need a Scalar_Storage_Order (GNAT implementation defined) aspect.
PS. I recommend also take a look at "Safe Communication" chapter of Safe and Secure Software booklet.
Related
While writing bindings for interfacing with C code, I am finding issues translating the numerous instances of structs with flexible array members to Ada, as such
struct example {
size_t length;
int body[];
};
I've been told for Ada that behaviors like that can be replicated with discriminated types, but I cannot find a way to use the length field as a discriminant, while maintaining the layout of the structure so that the records can be used to interface with the C code, something like
type Example (Count : Integer := Length) is record
Length : Unsigned_64;
Body : Integer (1 .. Count);
end record;
Is there any way to create a type like that with that array? I've been defaulting for now to grabbing the address of that location and declaring the array myself on place for use, is there any better way? Thanks in advance.
Here is an example in which Ada code calls C code, passing objects of this kind from Ada to C and from C to Ada. The C header is c_example.h:
typedef struct {
size_t length;
int body[];
} example_t;
extern void example_print (example_t *e /* in */);
// Print the contents of e.
extern example_t * example_get (void);
// Return a pointer to an example_t that remains owned
// by the C part (that is, the caller can use it, but should
// not attempt to deallocate it).
The C code is c_example.c:
#include <stdio.h>
#include "c_example.h"
void example_print (example_t *e /* in */)
{
printf ("C example: length = %zd\n", e->length);
for (int i = 0; i < e->length; i++)
{
printf ("body[ %d ] = %d\n", i, e->body[i]);
}
} // example_print
static example_t datum = {4,{6,7,8,9}};
example_t * example_get (void)
{
return &datum;
} // example_get
The C-to-Ada binding is defined in c_binding.ads:
with Interfaces.C;
package C_Binding
is
pragma Linker_Options ("c_example.o");
use Interfaces.C;
type Int_Array is array (size_t range <>) of int
with Convention => C;
type Example_t (Length : size_t) is record
Bod : Int_Array(1 .. Length);
end record
with Convention => C;
type Example_ptr is access all Example_t
with Convention => C;
procedure Print (e : in Example_t)
with Import, Convention => C, External_Name => "example_print";
function Get return Example_Ptr
with Import, Convention => C, External_Name => "example_get";
end C_Binding;
The test main program is flexarr.adb:
with Ada.Text_IO;
with C_Binding;
procedure FlexArr
is
for_c : constant C_Binding.Example_t :=
(Length => 5, Bod => (55, 66, 77, 88, 99));
from_c : C_Binding.Example_ptr;
begin
C_Binding.Print (for_c);
from_c := C_Binding.Get;
Ada.Text_IO.Put_Line (
"Ada example: length =" & from_c.Length'Image);
for I in 1 .. from_c.Length loop
Ada.Text_IO.Put_Line (
"body[" & I'Image & " ] =" & from_c.Bod(I)'Image);
end loop;
end FlexArr;
I build the program thusly:
gcc -c -Wall c_example.c
gnatmake -Wall flexarr.adb
And this is the output from ./flexarr:
C example: length = 5
body[ 0 ] = 55
body[ 1 ] = 66
body[ 2 ] = 77
body[ 3 ] = 88
body[ 4 ] = 99
Ada example: length = 4
body[ 1 ] = 6
body[ 2 ] = 7
body[ 3 ] = 8
body[ 4 ] = 9
So it seems to work. However, the Ada compiler (gnat) gives me some warnings from the C_Binding package:
c_binding.ads:14:12: warning: discriminated record has no direct equivalent in C
c_binding.ads:14:12: warning: use of convention for type "Example_t" is dubious
This means that while this interfacing method works with gnat, it might not work with other compilers, for example Ada compilers that allocate the Bod component array separately from the fixed-size part of the record (whether such a compiler would accept Convention => C for this record type is questionable).
To make the interface more portable, make the following changes. In C_Binding, change Length from a discriminant to an ordinary component and make Bod a fixed-size array, using some maximum size, here 1000 elements as an example:
type Int_Array is array (size_t range 1 .. 1_000) of int
with Convention => C;
type Example_t is record
Length : size_t;
Bod : Int_Array;
end record
with Convention => C;
In the test main program, change the declaration of for_c to pad the array with zeros:
for_c : constant C_Binding.Example_t :=
(Length => 5, Bod => (55, 66, 77, 88, 99, others => 0));
For speed, you could instead let the unused part of the array be uninitialized: "others => <>".
If you cannot find a reasonably small maximum size, it should be possible to define the C binding to be generic in the actual size. But that is getting rather messy.
Note that if all the record/struct objects are created on the C side, and the Ada side only reads and writes them, then the maximum size defined in the binding is used only for index bounds checking and can be very large without impact on the memory usage.
In this example I made the Ada side start indexing from 1, but you can change it to start from zero if you want to make it more similar to the C code.
Finally, in the non-discriminated case, I recommend making Example_t a "limited" type ("type Example_t is limited record ...") so that you cannot assign whole values of that type, nor compare them. The reason is that when the C side provides an Example_t object to the Ada side, the actual size of the object may be smaller than the maximum size defined on the Ada side, but an Ada assignment or comparison would try to use the maximum size, which could make the program read or write memory that should not be read or written.
The discriminant is itself a component of the record, and has to be stored somewhere.
This code,
type Integer_Array is array (Natural range <>) of Integer;
type Example (Count : Natural) is record
Bdy : Integer_Array (1 .. Count);
end record;
compiled with -gnatR to show representtion information, says
for Integer_Array'Alignment use 4;
for Integer_Array'Component_Size use 32;
for Example'Object_Size use 68719476736;
for Example'Value_Size use ??;
for Example'Alignment use 4;
for Example use record
Count at 0 range 0 .. 31;
Bdy at 4 range 0 .. ??;
end record;
so we can see that GNAT has decided to put Count in the first 4 bytes, just like the C (well, this is a common C idiom, so I suppose it’s defined behaviour for C struct components to be allocated in source order).
Since this is to be used for interfacing with C, we could say so,
type Example (Count : Natural) is record
Bdy : Integer_Array (1 .. Count);
end record
with Convention => C;
but as Niklas points out the compiler is doubtful about this (it’s warning you that the Standard doesn’t specify the meaning of the construct).
We could confirm at least that we want Count to be in the first 4 bytes, adding
for Example use record
Count at 0 range 0 .. 31;
end record;
but I don’t suppose that would stop a different compiler using a different scheme (e.g. two structures, the first containing Count and the address of the second, Bdy).
C array indices always start at 0.
If you want to duplicate the C structure remember that the discriminant is a member of the record.
type Example (Length : Integer) is record
body : array(0 .. Length - 1) of Integer;
end record;
I have an odd problem with a simple Ada program that uses the "GNAT.Serial_Communications" package.
The program sends seven bytes (126, 1, 0, 0, 0, 0, 254) over the serial port device (8,N,1 # 115200 Baud), and I have verified that this is performed correctly using a logic analyser connected directly to the serial port pins.
The strange thing is that there are two large gaps (varies between 0.6 milliseconds and 2.4 ms) between the first three bytes sent. The rest of the bytes are sent at maximum speed as one would expect. Each individual byte is perfectly formed and sent at the correct rate. The idle time between bytes is the part that varies. In other words the timing looks like this...
[126]..........(big gap)..........[1]..........(big gap)..........[0][0][0][0][254]
I have a C version of this program that does not exhibit this behaviour, all bytes are sent in a single burst with no gaps.
Equally, catting the same data directly into the serial port from Bash also does not have these big gaps between the first three bytes.
So it appears that there is some aspect of the Ada libraries / runtimes that introduces this variable delay in the middle of my byte stream.
For my project, this is a tolerable problem but I would like to try to find out why this is happening.
There is apparently no problem with reading bytes from the serial port, it receives everything correctly.
with Interfaces; use Interfaces;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Streams; use Ada.Streams;
with GNAT.Serial_Communications;
procedure Main is
SP : GNAT.Serial_Communications.Serial_Port;
In_Data : String (1 .. 6) := "~ddddS"; -- this string literal is illustrative only
In_Buffer : Stream_Element_Array (1 .. 6);
Length : Stream_Element_Offset;
type PropIO_Octet is mod (2 ** 8);
type PropIO_Packet_Data is array (1 .. 4) of PropIO_Octet;
Packet_Start : constant PropIO_Octet := 16#7E#;
Packet_Escape_Marker : constant PropIO_Octet := 16#7D#;
Packet_Escape_XOR : constant PropIO_Octet := 16#20#;
Command_Hard_Reset : constant PropIO_Octet := 16#00#;
Command_Watchdog : constant PropIO_Octet := 16#01#;
procedure TX_Octet (Octet : in PropIO_Octet; Allow_Escape : in Boolean) is
Out_Buffer : Stream_Element_Array (1 .. 1);
Temp_Oct : PropIO_Octet := Octet;
begin
if Allow_Escape then
if (Temp_Oct = Packet_Start) or (Temp_Oct = Packet_Escape_Marker) then
Out_Buffer (Stream_Element_Offset (1)) := Character'Pos (Character'Val (Packet_Escape_Marker));
GNAT.Serial_Communications.Write (SP, Out_Buffer);
Temp_Oct := Temp_Oct xor Packet_Escape_XOR;
end if;
end if;
Out_Buffer (Stream_Element_Offset (1))
:= Character'Pos (Character'Val (Temp_Oct));
GNAT.Serial_Communications.Write (SP, Out_Buffer);
end TX_Octet;
procedure TX_Packet (
Command : in PropIO_Octet;
Data : in PropIO_Packet_Data
) is
CS : PropIO_Octet := 16#00#;
begin
TX_Octet (Packet_Start, False);
TX_Octet (Command, True);
CS := CS xor Command;
for D of Data loop
TX_Octet (D, True);
CS := CS xor D;
end loop;
CS := 16#FF# - CS;
TX_Octet (CS, True);
end TX_Packet;
begin
GNAT.Serial_Communications.Open(SP, "/dev/ttyUSB0");
GNAT.Serial_Communications.Set (
Port => SP,
Rate => GNAT.Serial_Communications.B115200,
Bits => GNAT.Serial_Communications.CS8,
Stop_Bits => GNAT.Serial_Communications.One,
Parity => GNAT.Serial_Communications.None
);
for i in 1 .. 10 loop
TX_Packet (Command_Watchdog, (0,0,0,0));
delay 1.0;
GNAT.Serial_Communications.Read (SP, In_Buffer, Length);
for i in 1 .. Length loop
In_Data (Integer (i)) := Character'Val (In_Buffer (i));
end loop;
Put_Line ("Response: " & In_Data (1 .. (Integer (Length))));
end loop;
GNAT.Serial_Communications.Close (SP);
end Main;
I'm running this code under Ubuntu 19.10 (latest updates) and Gnat 8.3.0. The serial device connected via USB is a legitimate FTDI device.
Any ideas what might cause this?
This is not the actual answer to the question (the answer is already in the comments I guess), but just a hint regarding the example code. As the type GNAT.Serial_Communications.Serial_Port derives from Ada.Streams.Root_Stream_Type, you can also use the stream-oriented attributes 'Read and 'Write to read and write to the serial port. As an example:
main.adb
with Ada.Text_IO;
with GNAT.Serial_Communications;
procedure Main is
package SC renames GNAT.Serial_Communications;
type Byte is mod 2**8;
type Byte_Array is array (Natural range <>) of Byte;
package Byte_IO is
new Ada.Text_IO.Modular_IO (Byte);
Port_1 : aliased SC.Serial_Port;
Port_2 : aliased SC.Serial_Port;
End_Token : constant := 16#FF#;
Buffer_Out : Byte_Array (0 .. 5) := (0, 1, 2, 3, 4, End_Token);
Buffer_In : Byte;
begin
-- Open the ports.
SC.Open (Port_1, "/dev/ttyS98");
SC.Open (Port_2, "/dev/ttyS99");
-- Write the byte array (packet) to port 1 in one go.
Byte_Array'Write (Port_1'Access, Buffer_Out);
-- Read the byte array (packet) back from port 2, byte-by-byte.
loop
Byte'Read (Port_2'Access, Buffer_In);
exit when Buffer_In = End_Token;
Byte_IO.Put (Buffer_In);
end loop;
Ada.Text_IO.New_Line;
-- Close the ports.
SC.Close (Port_1);
SC.Close (Port_2);
end Main;
The program can be tested by emulating two serial ports using socat (and pseudo-terminals, pty).
console 1 (create the emulated serial ports)
$ sudo socat -d -d pty,raw,echo=0,link=/dev/ttyS98 pty,raw,echo=0,link=/dev/ttyS99
2020/01/14 20:23:04 socat[2540] N PTY is /dev/pts/1
2020/01/14 20:23:04 socat[2540] N PTY is /dev/pts/2
2020/01/14 20:23:04 socat[2540] N starting data transfer loop with FDs [5,5] and [7,7]
console 2 (run the example program)
$ sudo ./main
0 1 2 3 4
Note: sudo is required to create and access the emulated devices.
I have a problem subtracting a STD_LOGIC_VECTOR from a integer.
This is the code I have right now:
entity ROM is
Port ( hcount: in STD_LOGIC_VECTOR(9 downto 0);
vcount: in STD_LOGIC_VECTOR(9 downto 0);
hpos: in integer;
vpos: in integer;
clk25: in STD_LOGIC;
Pixeldata: out std_logic);
end ROM;
architecture Behavioral of ROM is
signal romtemp : std_logic_vector(9 downto 0);
shared variable yas : integer range 0 to 9 := 0;
shared variable xas : integer range 0 to 9 := 0;
Type RomType is array (9 downto 0) of std_logic_vector(9 downto 0);
Constant Rom: RomType :=
( "0001111000", "0111111110", "0111111110", "1111111111", "1111111111"
, "1111111111", "1111111111", "0111111110", "0111111110", "0001111000");
begin
process(clk25)
begin
if(hpos > hcount - 10) and (hpos <= hcount) and (vpos > vcount - 10) and (vpos <= vcount) then
xas := hpos - to_integer(unsigned(hcount));
end if;
end process;
end Behavioral;
The problem is the following line of code:
xas := hpos - to_integer(unsigned(hcount));
I am trying to put the subtraction in the integer named xas.
The following errors occur on that line:
Error: Multiple declarations of unsigned included via multiple use clauses; none are made directly visible
Error: Expecting type unsigned for < unsigned(hcount) >.
Error: Formal < arg > has no actual or default value.
Error: Type integer is not an array type and cannot be indexed
Error: found '0' definitions of operator "=", cannot determine exact overload matching definition for "-"
Someone that can help me with this error? (I am a beginner in VHDL)
You haven't included your use clauses at the top of the file, but what this error is saying is that from the use clauses, it found two different definitions of unsigned. Because of this, the tool has ignored both definitions, generating an error and forcing you to deal with the problem.
The most likely explanation is that you have:
use ieee.numeric_std.all;
use ieee.std_logic_arith.all;
std_logic_arith is nonstandard, and you should implement your design using the types and functions available in numeric_std only. Remove the std_logic_arith line.
In general, if something is a number, use a numeric type to represent it. For example, your hcount and vcount inputs are clearly counters, and could use type unsigned. If you use more appropriate types in the first place, you avoid the need for awkward looking type conversions, for example:
xas := hpos - to_integer(unsigned(hcount));
would become
xas := hpos - hcount;
Additional problems in your code:
Your process sensitivity list contains only clk25, but the process is not actually a synchronous process, and so all the input signals used should be in the list (or you can use the reserved all keyword to generate an automatic list, i.e. process(all)).
Unless this is some special case, you are better off getting into the habit of writing synchronous processes. These look like this:
process(clk)
begin
if (rising_edge(clk)) then
-- Do things
end if;
end process;
xas is a shared variable, which implies that you might be assigning it in other processes as well. This will probably not work how you expect it to. You should avoid shared variables altogether until you have a good understanding of exactly how they work, and when it might be appropriate to use them.
In which section memory is allocated if I write something like
1. int *ptr;
*ptr = 22;
2. int *ptr = new int(22);
What I Understand is when we use keyword new then memory is get reserved into Heap and that reserved memory address is get returned .
But what happened in case we didn't use keyword new ?? Where memory is get allocated ??
is Both Syntax is Same ?? If No, what is Exact difference between these two statement ??
You code examples can be rephrased as follows:
1st:
int * ptr;
*ptr = 22;
2nd:
int * ptr;
ptr = new int; //the only difference
*ptr = 22;
What happens in the second one:
int * ptr; means create variable capable of storing address of int variable. For now variable isn't initialized, so it stores garbage. If you interpret garbage as pointer, it can points anywhere (it can be 0, or 0xabcdef11, or 0x31323334, or literally ANYTHING which is left on non-cleared memory form previous usage)
ptr = new int; means "allocate memory area capable of holding int and store its address in ptr variable". Since this line, ptr points to specific memory
*ptr = 22; means put value 22 to memory pointed by ptr.
In the first example you create variable, but don't initialize it. ptr contains garbage, but you ask to interpret it as address and store 22 to this address. What can happen:
address is invalid (e.g. 0, or out of address range, or points to protected memory) => program crashes
address is valid and writable, but memory area is used by another part of the program: you'll write 22, but it will corrupt someone's data, result totally unpredictable.
address is valid and writable, memory area isn't in use. You'll write 22, but you aren't guaranteed to read it back. Memory can become used for different purpose and 22 will be overwritten.
anything else. All this is actually an undefined behavior, everything is possible.
That's why it's always recommended to initialize pointer immediately:
int * ptr = NULL; //or better "nullptr" starting from C++11
Attempt to store value *ptr = 22; will at least explicitly crash the program.
I am new to VHDL and I wanted to ask that what generic term could I use If i wanted to write any size of input vector which could be developed?
GENERIC (n1 : integer);
x:IN BIT_VECTOR(n1-1 downto 0);
Is that a correct example?
Your generic has no default value visible.
Your declaration for x is incomplete. It appears to be an entity declarative item with a mode while you don't have a port declaration.
This VHDL code is syntactically and semantically valid:
entity foo is
generic ( n1: integer);
port (
x: in bit_vector(n1-1 downto 0)
);
end entity;
architecture fum of foo is
begin
end architecture;
It will analyze. It can't be elaborated without the value of n1 being known:
entity foo_tb is
constant N_1: integer := 4;
end entity;
architecture fum of foo_tb is
signal x: bit_vector (N_1-1 downto 0);
begin
DUT:
entity work.foo
generic map (n1 => N_1)
port map ( x => x);
end architecture;
Entity foo by itself can't be the top level of an elaborated model because n1 isn't defined for elaboration.
Entity foo_tb can be elaborated, it uses the constant N_1 to supply a value to n1.
foo_tb can even be simulated, but it will exit immediately because there are no pending signal assignments after initialization.
Neither foo nor foo_tb can be synthesize. foo_tb because it has no ports and any logic in it's design hierarchy would be optimized away as unused. foo because it only has an output and is at best a constant.
If foo had multiple ports, with outputs depending on inputs it would be eligible for synthesis or simulation as long as the generic was defined for elaboration.
(And the moral here is to use a Minimal, Complete, and Verifiable example so someone doesn't have to wave their hands around it's shortcomings).
You can use every term, as far as it's result does not exceed the BIT_VECTORS's array range.
BIT_VECTOR definition: type BIT_VECTOR is array (NATURAL range <>) of BIT;
So your term can have results from 0 to 2**32 - 1
Term examples:
4*n1 - 1 downto 0
n1/4 + 8 downto 0
log2ceilnz(n1) - 1 downto 0
2**n1 - 1 downto 0
According to the " Paebbels" comment I edit this answer :
Every time you want to synthesize your code, synthesis tool should know about the size of parameters you used, Otherwise what exactly you want to synthesize ?!!! (what hardware ?!)
If you want to synthesize your top module code which contains a generic parameter in it's own entity, you can assign it with a default value such as the following code :
ENTITY ... IS
GENERIC(n1 : INTEGER := 8);
PORT(
-- use generic parameter
);
END ENTITY;
Also you can use the generic parameter inside architecture ( size of signals, index of loops, ... ).