DSP Builder for Intel® FPGAs (Advanced Blockset): Handbook

ID 683337
Date 9/30/2024
Public
Document Table of Contents

3.4.6.2. MEX Model Code

To clarify the code structure, this code example has extra blanks lines compared to the code that DSP Builder generates.

#include "support/csl_io.h"
#include "demo_fft_FFT_2K.h"
#include "mex.h"`

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    demo_fft_FFT_2K_t inst0;
    inst0.reset();

    demo_fft_FFT_2K_t::io_xc_s_t io_xc_s_t0;
    inst0.write(io_xc_s_t0);

    demo_fft_FFT_2K_t::io_xr_im_t io_xr_im_t0;
    inst0.write(io_xr_im_t0);

    demo_fft_FFT_2K_t::io_xr_re_t io_xr_re_t0;
    inst0.write(io_xr_re_t0);

    demo_fft_FFT_2K_t::io_xv_s_t io_xv_s_t0;
    inst0.write(io_xv_s_t0);

    demo_fft_FFT_2K_t::io_zc_s_t io_zc_s_t0;
    inst0.read(io_zc_s_t0);

    demo_fft_FFT_2K_t::io_zr_im_t io_zr_im_t0;
    inst0.read(io_zr_im_t0);

    demo_fft_FFT_2K_t::io_zr_re_t io_zr_re_t0;
    inst0.read(io_zr_re_t0);

    demo_fft_FFT_2K_t::io_zv_s_t io_zv_s_t0;
    inst0.read(io_zv_s_t0);
}

This code:

  • Creates an instance of the device level model class, demo_fft_FFT_2K_t, and resets its internal state by calling reset on it.
  • Creates instances of the input structs and writes them to the model using the write function. DSP Builder does not populate the data on these input structs because you removed the stimulus read calls in Driving the Model. The code has four input signals compared to three in the design as the complex data input signal xr is unrolled to xr_re and xr_im. Writing these input structs to the model allows the model to progress its internal state. This process occurs automatically per-input to the greatest extent possible.
  • Reads the outputs by creating instances of the output structs and calling read on them. The code has four signals rather than three because of unrolling. For some designs you need not write all input values before reading an output. Only the inputs that the output depends on are required according to the topology of the model.

The code must populate the input structs via the MEX function inputs. The software model for the demo_fft design (and most designs) has internal state, so you must ensure that state is not lost between invocations of the MEX function. You can either:

  • Make the model class instance exist at a higher scope so that it persists between calls
  • Design the MEX function to take an array of inputs and provide an array of outputs.

This design uses the second option and has an array per input and per output signal.

The design has two input and output data signals, whose widths are less than 32 bits. You can use MATLAB int32 arrays to drive the software model and return data from it. For this example, set the valid and channel signals to a fixed value on the inputs and only use the valid signal from the output. The design also uses an int32 array for the valid output. You must allocate these arrays for the outputs, for example:

...
size_t sizeIm = mxGetN(prhs[0]);
size_t sizeRe = mxGetN(prhs[1]);
// check the input arrays are all the same size
assert(sizeIm == sizeRe);

// get pointers to the integer data
int* pIm = (int*)mxGetData(prhs[0]);
int* pRe = (int*)mxGetData(prhs[1]);

// allocate and initialize memory for output arrays
for (int i = 0; i < 3; ++i)
{
    plhs[i] = mxCreateNumericMatrix(1, sizeIm, mxINT32_CLASS, mxREAL);
}

// get int pointers to the output arrays
int* pOutValid = (int*)mxGetData(plhs[0]);
int* pOutRe = (int*)mxGetData(plhs[1]);
int* pOutRe = (int*)mxGetData(plhs[2]);
...

A production-quality implementation of this code should additionally have checks on the input data and the number of outputs.

To see how to assign values to the data structs, inspect the original stimulus read functions (readFromStm(...)). Or you can look directly at the model IO structs in their respective files, for example the xr_re input struct:

struct io_xr_re_t
{
    int16_t port_xr_re;

    io_xr_re_t()
        : port_xr_re(0)
    {
    }
};

In this code a single 16-bit integer represents the signal's value. DSP Builder uses a fundamental C++ type or a MPIR or MPFR type greater than or equal to the signal width. For designs where the device-level model is not a primitive subsystem, as in the demo_fft example, the input and output data structs are always one per-signal. Where the device level model is also a primitive subsystem, the input and output data structs correspond to the Channel In/Out and GPIn/Out blocks. For example, the input struct for the inner bit reverse subsystem in demo_fft:

struct io_ChannelIn_t
{
    int8_t port_in_1_v_tpl;
    int8_t port_in_2_c_tpl;
    int16_t port_in_3_real_x_tpl;
    int16_t port_in_3_imag_x_tpl;

    io_ChannelIn_t()
        : port_in_1_v_tpl(0)
        , port_in_2_c_tpl(0)
        , port_in_3_real_x_tpl(0)
        , port_in_3_imag_x_tpl(0)
    {
    }
};

This subsystem contains a ChannelIn block that takes as input all four of the signals from the device level. The device and channel block are 1-bit and 8-bit wide, so have int8_t representations in the software model. The data signals at this stage are 16-bit wide, and so use int16_t types. Although the model uses signed types, it processes them as plain data and interprets them according to the signal attributes.