.. program:: ghdl .. _COSIM:VHPIDIRECT:Examples:arrays: Arrays ####### .. IMPORTANT:: A VHDL access that is used for a constrained/bounded array is tied to a type that specifies its length. In :ref:`COSIM:VHPIDIRECT:Examples:arrays:intvector` and :ref:`COSIM:VHPIDIRECT:Examples:arrays: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 :cosimsharp:`3` for work in progress regarding unconstrained types. .. _COSIM:VHPIDIRECT:Examples:arrays:intvector: Constrained/bounded integer arrays ********************************** As explained in :ref:`Restrictions_on_foreign_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*. .. _COSIM:VHPIDIRECT:Examples:arrays:intvector:csized: :cosimtree:`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: * :cosimtree:`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. * :cosimtree:`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 :ref:`COSIM:VHPIDIRECT:Examples:quickstart:wrapping: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 :ref:`COSIM:VHPIDIRECT:Examples:quickstart:cli`. .. _COSIM:VHPIDIRECT:Examples:arrays:intvector:vhdlsized: :cosimtree:`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`` :ref:`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 :ref:`COSIM:VHPIDIRECT:Examples:quickstart: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 :ref:`Restrictions_on_foreign_declarations`. .. _COSIM:VHPIDIRECT:Examples:arrays:logicvectors: :cosimtree:`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 :ref:`Restrictions_on_foreign_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 (:ref:`COSIM:VHPIDIRECT:Examples:arrays:intvector`), 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. .. _COSIM:VHPIDIRECT:Examples:arrays:matrices: :cosimtree:`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 :ref:`intvector `. Precisely, it's an extension of case :ref:`COSIM:VHPIDIRECT:Examples:arrays:intvector:csized`. 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. .. _COSIM:VHPIDIRECT:Examples:arrays:matrices:axis: :cosimtree:`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 :ref:`vunit:user_guide` and get familiar with VUnit example `array_axis_vcs `_. .. figure:: img/matrices_array_axis_vcs.png :alt: VUnit example `array_axis_vcs `_ :align: center :width: 500px Block diagram of VUnit example ``array_axis_vcs``. `VUnit `_ provides an :ref:`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 :ref:`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. .. figure:: img/matrices_vunit_axis_vcs.png :alt: Modified version of the example, renamed to :cosimtree:`vunit_axis_vcs ` :align: center :width: 500px Block diagram of the modified version of the VUnit example, renamed to :cosimtree:`vunit_axis_vcs `. Subdir :cosimtree:`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 `_. .. _COSIM:VHPIDIRECT:Examples:arrays:matrices:vga: :cosimtree:`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 :ref:`COSIM:VHPIDIRECT:Examples:arrays:intvector:vhdlsized` from :ref:`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 :cosimtree:`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 :cosimtree:`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 :cosimtree:`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 :cosimtree:`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 (:cosimtree:`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 :ref:`COSIM:VHPIDIRECT:Examples:shared:pycb`. The result is similar to using ``X11/Xlib.h``, but `NumPy `_ and `Pillow `_ are used, instead of coding in C.