Main Content

Generate C Code from Spectral Analysis Algorithm and Verify Using Parameterized Equivalence Tests

Since R2023a

This example shows how to write a class-based unit test to verify that the functionality of the generated C code is equivalent to that of your source MATLAB® code. The MATLAB code used in this example performs basic spectral analysis of an input signal using the Fast Fourier Transform.

This example consists of the following steps:

  • Create and run a MATLAB entry-point function that uses the fft and islocalmax functions to perform basic spectral analysis on an input time-domain signal.

  • Generate and run a MEX function for this MATLAB function to check for potential run-time issues in the generated code.

  • Author a test class that inherits from matlabtest.coder.TestCase to perform parameterized equivalence testing of the generated code against MATLAB.

  • Instantiate this test class to create a test case for the MEX target. Run this test case to establish functional equivalence between the generated MEX code and MATLAB execution.

  • Generate a standalone C static library for the MATLAB entry-point function. Also, generate a Software-In-the-Loop (SIL) interface that enables you to run the static library code from inside MATLAB.

  • Instantiate the equivalence test class that you already authored to create a test case for the SIL target. Run this test case to establish functional equivalence between the generated standalone code and MATLAB execution.

Define MATLAB Entry-Point Function

Define an entry-point function powerSpectrumPeaks that calculates the Fourier transform of a time-domain signal sampled at frequency fs. The function then squares the absolute value of the Fourier transform to obtain the power spectrum of the signal in the frequency domain. Finally, it returns the frequencies at which the peaks of the power spectrum, which have a pre-specified minimum prominence, are located.

type powerSpectrumPeaks.m
function peaks = powerSpectrumPeaks(signal,fs,prominence) %#codegen
arguments
    signal (1,:) double {coder.mustBeComplex}
    fs (1,1) double
    prominence (1,1) double
end

n = length(signal);
signalIndices = 1:n;

y = fft(signal);
power = abs(y).^2/n;

peakIndices = signalIndices(islocalmax(power,MinProminence=prominence));
peaks = (peakIndices - 1)*fs/n;
end

Define a complex time-domain signal x that has two frequency components at 50 Hz and 300 Hz. Here fs is the sampling frequency and L is the length of the signal.

fs = 1000;                                
T = 1/fs;                    
L = 1500;             
t = (0:L-1)*T;        
x = 0.7*exp(2*1i*pi*50*t) + 1.3*exp(2*1i*pi*300*t);  

To filter out the noise from the power spectrum, define a minimum peak prominence that the islocalmax function uses.

prominence = 0.001;

Call the entry-point function powerSpectrumPeaks with these inputs. The function returns a 2-by-1 array that contains the two component frequencies of the input signal.

powerSpectrumPeaks(x,fs,prominence)
ans = 1×2

    50   300

Check For Run-Time Issues by Generating and Running MEX

Use the codegen command to generate a MEX for the powerSpectrumPeaks function. The arguments block in the function body specifies to the code generator that the input signal is an unbounded variable-size row vector of complex double values. Similarly, the sampling frequency and prominence are scalar doubles. Specify the name of the code generation folder as mexcode using the -d option.

codegen powerSpectrumPeaks -d mexcode
Code generation successful.

Run the generated MEX with the same inputs. The output of the MEX matches that of MATLAB execution.

powerSpectrumPeaks_mex(x,fs,prominence)
ans = 1×2

    50   300

Author Parameterized Equivalence Test

Author a class tEquivalence that contains:

  • A property Target that specifies the code generation target. This property can have three possible values denoting MEX, SIL, and PIL targets. The constructor allows you to specify the target when instantiating a test case.

  • Test parameters that specify the functional form of the signal, signal length, sampling frequency, and prominence. By default, the testing framework uses an exhaustive combination of the parameter values when running the tests. Because you generated code that can accept a variable-size signal, you can run your test for signals with different lengths.

  • A test method that performs equivalence testing of the generated code artifact (for example, MEX or SIL executable) against MATLAB execution.

This class provides a flexible interface that you can use to test both the generated MEX and the generated standalone code across a range of inputs.

type tEquivalence.m
classdef tEquivalence < matlabtest.coder.TestCase
    properties
        Target {mustBeMember(Target,["mex","sil","pil"])} = "mex"
    end

    properties (TestParameter)
        SignalFunction = {@(t) 0.7*exp(2*1i*pi*50*t) + 1.3*exp(2*1i*pi*300*t), ...
            @(t) 0.5*exp(2*1i*pi*100*t) + 2*exp(2*1i*pi*500*t)}
        LengthOfSignal = {1500,2000}
        SamplingFrequency = {1000,3000}
        Prominence = {0.001,0.01}
    end

    methods
        function testCase = tEquivalence(target)
            testCase.Target = target;
        end
    end

    methods (Test)
        function tMexForPowerSpectrumPeaks(testCase,SignalFunction,LengthOfSignal,SamplingFrequency,Prominence)
            T = 1/SamplingFrequency;
            t = (0:LengthOfSignal-1)*T;
            signal = SignalFunction(t);

            mexPath = fullfile(pwd,strcat("powerSpectrumPeaks_", ...
                testCase.Target,".",mexext));
            executionResults = execute(testCase,mexPath,Inputs= ...
                {signal,SamplingFrequency,Prominence});
            verifyExecutionMatchesMATLAB(testCase,executionResults)
        end
    end
end

Validate Generated MEX by Using Equivalence Testing

Create a test case for the MEX target.

testcase = tEquivalence("mex");

Run this test. The test passes, indicating that the generated MEX is functionally equivalent to your source MATLAB code for an exhaustive combinations of the inputs you provided in the class definition.

run(testcase)
Running tEquivalence
.......... ......
Done tEquivalence
__________
ans = 
  1×16 TestResult array with properties:

    Name
    Passed
    Failed
    Incomplete
    Duration
    Details

Totals:
   16 Passed, 0 Failed, 0 Incomplete.
   0.38024 seconds testing time.

Generate Static Library and SIL Interface

Use the codegen command to generate a static library and a SIL interface for the powerSpectrumPeaks function. Specify the name of the code generation folder as silcode using the -d option.

cfg = coder.config('lib', 'ecoder', true);
cfg.VerificationMode = 'SIL';
codegen -config cfg powerSpectrumPeaks -d silcode -report
Code generation successful: View report

Run the generated SIL MEX with sample inputs. The output of the SIL MEX matches that of MATLAB execution.

powerSpectrumPeaks_sil(x,fs,prominence)
### Starting SIL execution for 'powerSpectrumPeaks'
    To terminate execution: clear powerSpectrumPeaks_sil
ans = 1×2

    50   300

Validate Generated Static Library By Using Equivalence Testing

Create a test case for the SIL target using the input parameters you used when running the SIL MEX.

testcaseSil = tEquivalence("sil");

Run this test. The test passes, indicating that the generated static library is functionally equivalent to your source MATLAB code for an exhaustive combinations of the inputs you provided in the class definition.

run(testcaseSil)
Running tEquivalence
.......... ......
Done tEquivalence
__________
ans = 
  1×16 TestResult array with properties:

    Name
    Passed
    Failed
    Incomplete
    Duration
    Details

Totals:
   16 Passed, 0 Failed, 0 Incomplete.
   15.9465 seconds testing time.

Finally, clear the SIL MEX function from memory.

clear powerSpectrumPeaks_sil

See Also

Classes

Functions

Related Examples

More About