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 using main.c instead of caux.c, the content of the array is written programatically in C, before calling ghdl_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 by calloc only.

  • The C implementation of the functions used in vhdlallocarr and vhdlallocacc 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 an access of a subtype of std_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 example `array_axis_vcs <http://vunit.github.io/examples.html#id9>`_

Block diagram of 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.

Modified version of the example, renamed to :cosimtree:`vunit_axis_vcs <vhpidirect/arrays/matrices/vunit_axis_vcs>`

Block diagram of the modified version of the VUnit example, renamed to vunit_axis_vcs.

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 as screen(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 an std_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.