Arrays¶
Important
A VHDL access that is used for a constrained/bounded array is tied to a type that specifies its length. In Constrained/bounded integer arrays and Matrices below, one and two dimensional lengths are specified, respectively. This means that a separate type needs to be declared for an array with different dimensions and/or different sizes in any dimension. In order to create a single type for many differently shaped arrays, it would need to be unconstrained. See ghdl-cosim#3 for work in progress regarding unconstrained types.
Constrained/bounded integer arrays¶
As explained in Restrictions on type declarations, unconstrained arrays are represented by a fat pointer in C, and it is not suggested to use fat pointers unless it is unavoidable. Hence, it is desirable for data buffers which are to be represented as arrays to be constrained. However, constraining arrays in VHDL and C separately is error prone.
This example includes multiple solutions to set the bounding size once only (either in C or in VHDL), and have the parameter passed to the other language so that matching types are defined. Moreover, independently of where the size is defined, it is possible to allocate and free the buffers in any of the languages. The following examples show how to define them in C or VHDL and have them (de)allocated in either of them.
Attention
Pointers/accesses MUST be freed/deallocated in the same language that was used to allocate them. Hence, it is not possible to allocate in VHDL and free in C, nor vice versa.
Sized in C¶
Integer arrays with the content fully defined in C can be passed to VHDL by first passing their size, so that an appropriate array type can be created in VHDL. After that, another VHPIDIRECT subprogram can be defined either to pass the array to be filled (allocated in VHDL), or to return the array access (pointer) allocated in C.
This example shows how to hardcode both the length and the content of an array in C, with matching types are created in VHDL:
proc: an array is allocated in VHDL and a procedure is used. After filling the array in C, it is read and modified from VHDL.
fcn: an array is allocated in C and the pointer is passed to VHDL, where the content is read and modified.
If the integer array must be created or filled at runtime by some more advanced process, it is possible to execute the GHDL simulation within a custom
main()
entrypoint (see basic). By usingmain.c
instead ofcaux.c
, the content of the array is written programatically in C, before callingghdl_main
. Note that the content of the array is read from C both before and after executing the simulation.
Note
The length (or any other argument) can also be set through top level generics and/or as a custom CLI argument. See Command-Line Arguments.
Sized in VHDL¶
Complementing the examples above, when the size of a bounded/constrained array is defined in VHDL, it is possible to have
the (de)allocation performed in either VHDL or C. However, while accesses to constrained VHDL types do contain metada about
the bounds, pointers in C do not. Hence, in these examples, the length is explicitly passed along with the pointer/access.
Note that other possible implementations would save the length in a variable in C, so that it does not need to be passed
each time. This is done in fcn
above.
In this example three equivalent architectures are provided.
calloc: allocation and deallocation is done in C, invoked from VHDL through
[c_]allocIntArr
and[c_]freePointer
, respectively.vhdlallocarr: allocation of an array is done in VHDL.
vhdlallocacc: allocation of an access is done in VHDL.
Apart from that, all three implementations are functionally equivalent:
A constrained array is allocated.
The content is initialized from C.
The content is read and modified from VHDL.
A function in C is used to assert the modifications and to print the results.
The array is deallocated.
Note that VHPIDIRECT resources are defined in a package (as shown in package).
The same package and the corresponding C source file (caux.c
) are used in all three examples; however:
[c_]allocIntArr
and[c_]freePointer
are used bycalloc
only.The C implementation of the functions used in
vhdlallocarr
andvhdlallocacc
is the same, even though the definitions in VHDL are different. This is because both constrained arrays and accesses to constrained arrays are mapped to the same types in C. See Restrictions on type declarations.
Vector of std_logic¶
Commonly signals in VHDL are of a logic type or a vector thereof (std_logic
and std_logic_vector
), coming from IEEE’s
std_logic_1164
package. These types can hold values other than high and low (1
and 0
) and are enumerated as:
0 = 'U'
, 1 = 'X'
, 2 = '0'
, 3 = '1'
, 4 = 'Z'
, 5 = 'W'
, 6 = 'L'
, 7 = 'H'
and 8 = '-'
.
As mentioned in Restrictions on type declarations:
Because the number of enumeration values is less than 256, logic values are transported in 8 bit words (a
char
type in C).In this example two declarations make handling logic values in C a bit easier:
Providing logic values in C as their enumeration numbers is simplified with the use of a matching enumeration,
HDL_LOGIC_STATES
.Printing out a logic value’s associated character is also simplified with the
const char HDL_LOGIC_CHAR[]
declaration.
Logic vectors, of a bounded size, can be easily used in C as a
char[]
and passed to VHDL. These can be read as either anaccess
of a subtype ofstd_logic_vector
, or as the subtype itself.
This example builds on the integer vector example (Constrained/bounded integer arrays), by instead passing an array of logic values. Foreign subprograms are declared that enable receiving the size of two different logic vectors from C. There is only one subprogram to get the size of both C arrays, and it takes in an argument to determine which array’s size gets returned.
Hint
The getLogicVecSize
in VHDL is declared as receiving a boolean
argument. In C the function is declared to receive a
char
argument. The VHDL booleans false
and true
are enumerations, and have integer values, 0
and 1
respectively. As with the logic values, the boolean enumerations use fewer than 8 bits, so the single byte in C’s char
variable receives the VHDL enumeration correctly.
For illustrative purposes, the two vectors are allocated and populated with logic values in different ways:
logicVecA is allocated in C, it is returned as an access type through a function, and indices are manually filled with enumeration values from
HDL_LOGIC_STATES
:vec[0] = HDL_U;
.logicVecB is allocated in VHDL, it is passed as an
std_logic_vector
subtype (by reference) through a procedure, and indices are filled with integer values:for(int i = 0; i < SIZE_LOGIC_VEC_B; i++){ vec[i] = 8-i; }
.
Attention
The integer values that are given to char
variables in C which are intended to be read as VHDL logic values, must be
limited to [0, 8]. This ensures that they represent one of the 9 enumerated logic values.
Matrices¶
Constrained multidimensional arrays of doubles/reals¶
In many signal and image processing applications, large amounts of data need to be transferred between software and hardware. In software, it is common to use floating-point data types, since most general-purpose processors include hard floating-point units. Conversely, fixed-point formats are used in hardware, in order to optimise area and power. Converting data formats and using intermediate files to transfer test data to/from a simulation model can be tedious and error-prone.
This example builds on intvector. Precisely, it’s an extension of case Sized in C. A general procedure to share constrained multidimensional arrays of any size is shown. Dimensions of a 2D matrix of doubles are defined in C and a helper function is used for VHDL to read those values into the declaration of an array of reals type. Then, the pointer to the matrix (in C) is retrieved as an access (in VHDL), through another helper function.
For completeness, IEEE’s fixed_generic_pkg
package is used to multiply each value with a constant using fixed-point
formats. This is to illustrate that VHDL 2008 can be used as fixed-point toolbox in numerical processing environments.
Array and AXI4 Stream Verification Components¶
Hint
This example is based on VUnit, an open source unit testing framework for VHDL/SystemVerilog.
Instead of a shell script, the main entrypoint to this example is a run.py
Python script. Users who are not familiar
with VUnit are encouraged to first read User Guide and get familiar with VUnit example array_axis_vcs.
VUnit provides an integer_array package with load_csv
and save_csv
functions. Those are used in Array and AXI4 Stream Verification Components,
along with AXI4 Stream components from the vunit:vc_library, to load data from CSV files to a UUT. While CSVs as
intermediate files are useful for integration with Matlab, Octave, NumPy, etc., not having an equivalent real_array
package posses an additional complexity in applications such as DSP or machine learning. This is because values to be
handled in fixed-point need to be first converted from doubles to integers.
Subdir vunit_axis_vcs of this example contains a modified
version of a VUnit example (array_axis_vcs), where integer_array
and CSV files are replaced with VHPIDIRECT functions, so that data is read from C directly. In fact, no additional
co-simulation sources are included in the subdir because the main.c
and pkg.vhd
from the parent dir are used.
These will share the matrix as in the parent example, which is then passed to/from the verification components to test
the AXI Stream master/slave setup. The top-level processes stimuli
and receive
are the master sending and the
slave receiving, respectively, data from/to the matrix variable. For completeness only, stimuli
verifies the contents
of the matrix before sending it, row by row.
This example illustrates how to separate sources for synthesis from testbench/simulation resources, enhanced with GHDL’s
co-simulation features and with VUnit’s verification components. At the same time, this is a showcase of how to combine
a VUnit run.py
script (for building and test management) along with custom VHPIDIRECT resources.
Hint
Combining VUnit’s verification components with VHPIDIRECT allows to build simulation models for VHDL designs with complex top-level interfaces, while providing a C API to interact with them. Find work in progress in this regard at VUnit/cosim.
VGA (RGB image buffer)¶
Attention
These examples require ImageMagick and/or Xlib.
In image generation and processing applications, it can be tedious to debug the designs by looking at waveforms or logs. Saving data from RAMs and/or typical graphics standards to images or video allows to spot visual artifacts easily.
This example is based on Sized in VHDL from intvector. A 2D array of integers is allocated in a (generic) VHDL package and it is used as a frame buffer. Each integer represents a pixel: the 24 least significant bits are used for R, G and B (8 bits each); while the 8 most significant are not used. See Wikipedia: Color_depth#True_color_(24-bit).
In the same package, foreign function save_screenshot
is defined. It accepts a pointer to an array of integers, along
with integers defining the width and the height, and an integer used as an identifier of the frame number. Hence, unlike
previous examples with matrices, in this example calls to the VHPIDIRECT function are atomic and do not depend on passing
any parameter beforehand.
In practice, this example provides the foundation to build virtual screens for simulation purposes. Two different implementations are shown:
With caux.c, the content of the frame buffer is saved to a binary file in RGB24 format. Then,
convert
from ImageMagick is used to convert it to PNG. When the simulation ends,convert
is used again, to merge all the PNGs into an animated GIF.With caux_x11.c, X11 libraries are used to generate a window on the desktop. Then, when
save_screenshot
is called, the canvas is updated with the content of the frame buffer.
Hint
The resolution of the screen (frame buffer) is defined in pkg.vhd,
which is a generic package. Corresponding generics are defined in the top-level entity (testbench). Hence, it is possible to
modify the size of the screen through CLI args.
At the same time, the size of the canvas (X11 window) is defined in the testbench and passed to C as arguments of sim_init
(see caux.c). By default, the size of the window matches the
size of the screen (frame buffer). However, this is not a requirement.
Moreover, two different architectures are provided for the testbench (tb.vhd):
In architecture
test
, 16 frames/images are generated. The content of the buffer is set through literal assignments such asscreen(j,i) := 16#FFFF00#;
. The generated pattern is a yellow background and an animated cyan box (it is moved to the right and to the bottom as frames advance).In architecture
bars
, a single frame/image is generated. The content of the buffer is set through anstd_logic_vector(2 downto 0)
(RGB) signal. A helper function (RGB_to_integer
, provided in the package) is used to convert the 3 bit signal into RGB24. The generated static pattern is eight equally spaced vertical bars, each correponding to a value of the 3 bit signal; from left to right: black, red, green, yellow, blue, magenta, cyan and white.
Tip
In dbhi/vboard: VGA test pattern a virtual VGA screen is implemented based
on this shared frame buffer example. A test core is provided, which captures the VSYNC, HSYNC and RGB signals of a UUT, and writes
RGB24 integers to the buffer. Then, apart from Imagemagick, Tkinter
is supported. Tkinter is the Tcl/Tk interface built in Python. Hence, dbhi/vboard: vga/test/tkinter
shows how to combine this framebuffer example with pycb. The result is similar to
using X11/Xlib.h
, but NumPy and Pillow are used, instead of coding in C.