Custom MATLAB Function Code Generation with CRL
This example demonstrates the workflow for replacing a user's custom MATLAB functions with a custom Code Replacement Library (CRL) intended for Qualcomm Hexagon™ hardware during code generation. It uses coder.replace
to facilitate the replacement of the custom MATLAB function.
In this example, the custom function is an IIRInterpolator, which combines up-sampling with Biquad filtering. The entire MATLAB implementation of this function is replaced with mw_qhdsp_iirinterp_af
, serving as a wrapper for the Qualcomm Hexagon™ SDK function qhdsp_iirinterp_af
. The CRL is specifically tailored for Hexagon DSP v73 architecture version.
This example is ideal for users who have developed a custom MATLAB function and want to completely replace it with a manually implemented C function using a CRL. Alternatively, coder.ceval
can be used to generate C code for a specific function call within MATLAB code.
Using
coder.ceval
without creating any CRLs is suitable when you need to directly call external C functions from MATLAB code without setting up a full CRL. This approach allows for straightforward integration of external code but lacks the automation and broader replacement capabilities offered by a CRL. For more information, seecoder.ceval
.
Using
coder.ceval
with CRLs provides a balance between direct external integration and leveraging the advanced features of CRLs. For more information, see Replace coder.ceval Calls to External Functions.
Prerequisites
Setup the Qualcomm Hexagon SDK. For more information, see Launch Hardware Setup.
Custom MATLAB Function Definition
Here, upsample_iir.m
is the custom MATLAB function, and its signature should be defined to match the custom C function it is intended to replace during code generation.
% // Requires the states buffers to be 8 byte aligned % // length shouldn't be more than 512/r % // coefs - IIR filter coefficients - arranged as b0, b1, a1, b2, a2 % and cascade for all biquad filters % % int32_t mw_qhdsp_iirinterp_af(float* in_samples, % float* coefs, % float* states, % uint32_t num_biquads, % uint32_t length, % uint32_t r, % float* out_samples);
Therefore, define the upsample_fir
function to accept six input arguments and return two output arguments: one for the processed output and another for the updated states. Explicitly specify any buffer requiring specific alignment using coder.dataAlignment
. Set coder.replace
in the function definition for replacement with the appropriate implementation.
type upsample_iir.m
function [y,zi] = upsample_iir(x,h,zi,biquadSections,~,r,y) %#codegen coder.replace(); coder.dataAlignment('zi',8); a = ones(3,1,'like',h); b = zeros(3,1,'like',h); biquadLength = 5; sectionStatesLength = 2; % copy input to output as per the 'r' y(1:r:end) = x; for i = 1:biquadSections idx = (i-1)*biquadLength; idx2 = (i-1)*sectionStatesLength; % split h to b,a = [(b0,b1,a1,b2,a2),(b0,b1,a1,b2,a2)] b(1) = h(idx+1); b(2) = h(idx+2); a(2) = h(idx+3); b(3) = h(idx+4); a(3) = h(idx+5); [y, zi(idx2+1:idx2+2)] = filter(b,a,y,zi(idx2+1:idx2+2)'); end end
Currently, because the output length varies based on the upscaling factor (r)
, the coder treats the output as dynamically sized. The Code Replacement Library (CRL) does not support replacements for dynamically sized matrices. To work around this limitation, create a MATLAB wrapper function (like iirinterpolator.m
) that generates an output buffer and uses it as both an input and output argument for the upsample_iir
function.
type iirinterpolator.m
function [y,zi] = iirinterpolator(x,h,zi,r) %#codegen coder.dataAlignment('zi',8); inFrameSize = size(x,1); outFrameSize = inFrameSize*r; channels = size(x,2); sections = length(h)/5; % Output buffer should be twice the size of input buffer y = zeros(outFrameSize,channels,'like',x); [y,zi] = upsample_iir(x,h,zi,uint32(sections),uint32(inFrameSize),uint32(r),y); end
Creating CRL
Create a file named crl_table_iirinterpolator.m
to define the function entries for a code replacement table. This table entry should specify the source file (mw_iirinterpolator.c
), header file (mw_iirinterpolator.h
), and any additional dependent header files and libraries required. Ensure that the dependent files and libraries are accessible within the Qualcomm Hexagon SDK, whose folder path can be retrieved using the command matlabshared.toolchain.hexagonllvm.getSDKFolder()
after setting up and installing the Embedded Coder Support Package for Qualcomm Hexagon Processors.
For more information on creating the CRL entry, refer to Custom Function Code Replacement. Alternatively, this CRL can also be generated using Code Replacement Tool, a GUI way.
function hLib = crl_table_iirinterpolator() hLib = RTW.TflTable; % Get the Hexagon SDK root path hexagonSDKFolder = matlabshared.toolchain.hexagonllvm.getSDKFolder(); % Get the source files path srcPath = fullfile(fileparts(mfilename('fullpath')),'source'); % Get the additional dependency header file's path additionalIncPaths = {... fullfile(hexagonSDKFolder,'libs','qhl','inc','qhdsp'),... fullfile(hexagonSDKFolder,'libs','qhl','inc','qhblas'),... fullfile(hexagonSDKFolder,'libs','qhl','inc','qhcomplex'),... fullfile(hexagonSDKFolder,'libs','qhl','inc','qhmath'),... fullfile(hexagonSDKFolder,'libs','qhl','src','internal')}; % Get the v73 compiled library paths from the SDK's prebuilt location libPath = fullfile(hexagonSDKFolder, ... 'libs','qhl','prebuilt','hexagon_toolv87_v73'); %---------- entry: upsample_iir ----------- hEnt = RTW.TflCFunctionEntry; setTflCFunctionEntryParameters(hEnt, ... 'Key', 'upsample_iir', ... % MATLAB function to be replace 'Priority', 100, ... % Priority for replacement 'ArrayLayout', 'COLUMN_MAJOR', ... % Layout of the array 'ImplementationName', 'mw_qhdsp_iirinterp_af', ... % C equivalent function implementation of MATLAB function 'ImplementationHeaderFile', 'mw_iirinterpolator.h', ... % Implementation header file 'ImplementationHeaderPath', srcPath,... % Path to implementation source 'ImplementationSourceFile', 'mw_iirinterpolator.c', ... % Implementation source file 'ImplementationSourcePath', srcPath,... % Path to implementation header 'AdditionalIncludePaths', additionalIncPaths,... % Additional header files path 'AdditionalLinkObjs', ... {'libqhdsp.a','libqhdsp.a','libqhmath.a','libqhcomplex.a'},...% Libraries and its dependencies required by the above source file 'AdditionalLinkObjsPaths', ... {libPath,libPath,libPath,libPath}); % Path to the libraries mentioned above %-------- Create the conceptual representation ----- % Conceptual match checks if this entry matches to the arguments % and types of the upsample_iir.m % Maximum allowed output is 512, due to "mw_qhdsp_iirinterp_af" hEnt.createAndAddConceptualArg('RTW.TflArgMatrix',... 'Name','y1',... 'IOType','RTW_IO_OUTPUT',... 'BaseType','single',... 'DimRange',[1 1; 512 1]); hEnt.createAndAddConceptualArg('RTW.TflArgMatrix',... 'Name','y2',... 'IOType','RTW_IO_OUTPUT',... 'BaseType','single',... 'DimRange',[1 1; Inf 1]); % Maximum allowed input is 512 hEnt.createAndAddConceptualArg('RTW.TflArgMatrix',... 'Name','u1',... % for input data 'IOType','RTW_IO_INPUT',... 'BaseType','single',... 'DimRange',[1 1; 512 1]); hEnt.createAndAddConceptualArg('RTW.TflArgMatrix',... 'Name','u2',... % for coefficients 'IOType','RTW_IO_INPUT',... 'BaseType','single',... 'DimRange',[1 1; 1 Inf]); hEnt.createAndAddConceptualArg('RTW.TflArgMatrix',... 'Name','u3',... % for states 'IOType','RTW_IO_INPUT',... 'BaseType','single',... 'DimRange',[1 1; Inf 1]); arg = getTflArgFromString(hEnt, 'u4','uint32'); arg.IOType = 'RTW_IO_INPUT'; addConceptualArg(hEnt, arg); arg = getTflArgFromString(hEnt, 'u5','uint32'); arg.IOType = 'RTW_IO_INPUT'; addConceptualArg(hEnt, arg); arg = getTflArgFromString(hEnt, 'u6','uint32'); arg.IOType = 'RTW_IO_INPUT'; addConceptualArg(hEnt, arg); hEnt.createAndAddConceptualArg('RTW.TflArgMatrix',... 'Name','u7',... % for output buffer 'IOType','RTW_IO_INPUT',... 'BaseType','single',... 'DimRange',[1 1; Inf 1]); %-------- Create the implementation representation ----- arg = hLib.getTflArgFromString('unused', 'int32'); arg.IOType = 'RTW_IO_OUTPUT'; hEnt.Implementation.setReturn(arg); arg = hLib.getTflArgFromString('u1',['single' '*']); arg.IOType = 'RTW_IO_INPUT'; arg.Type.ReadOnly = true; hEnt.Implementation.addArgument(arg); arg = hLib.getTflArgFromString('u2',['single' '*']); arg.IOType = 'RTW_IO_INPUT'; arg.Type.ReadOnly = true; hEnt.Implementation.addArgument(arg); % Add states to implementation along with alignment requirements arg = hLib.getTflArgFromString('u3',['single' '*']); arg.IOType = 'RTW_IO_INPUT'; alignmentDesc = RTW.ArgumentDescriptor; alignmentDesc.AlignmentBoundary = 8; arg.Descriptor = alignmentDesc; hEnt.Implementation.addArgument(arg); % Update number of elements in do_match arg = hLib.getTflArgFromString('u4','uint32'); hEnt.Implementation.addArgument(arg); arg = hLib.getTflArgFromString('u5','uint32'); hEnt.Implementation.addArgument(arg); arg = hLib.getTflArgFromString('u6','uint32'); hEnt.Implementation.addArgument(arg); arg = hLib.getTflArgFromString('y1',['single' '*']); arg.IOType = 'RTW_IO_OUTPUT'; hEnt.Implementation.addArgument(arg); hLib.addEntry(hEnt); end
Create an rtwTargetInfo
file to register the code replacement library. Registration creates a code replacement library by defining the library name, code replacement tables, and other information. Create a registration file by using these specifications:
Hardware device type, which should be enabled for Qualcomm Hexagon hardware only.
CRL library name, which is QHL v73 Functions.
function rtwTargetInfo(tr) % rtwTargetInfo function to register a code % replacement library (CRL) for using with codegen % Register the CRL defined in local function locCrlRegFcn tr.registerTargetInfo(@locCrlRegFcn); end % End of RTWTARGETINFO function thisCrl = locCrlRegFcn % Instantiate a CRL registry entry thisCrl = RTW.TflRegistry; % create an alignment specification object as = RTW.AlignmentSpecification; as.AlignmentType = {'DATA_ALIGNMENT_LOCAL_VAR', ... 'DATA_ALIGNMENT_GLOBAL_VAR', ... 'DATA_ALIGNMENT_STRUCT_FIELD'}; as.AlignmentSyntaxTemplate = '__attribute__((aligned(%n)))'; as.SupportedLanguages={'c', 'c++'}; % add the alignment specification object da = RTW.DataAlignment; da.addAlignmentSpecification(as); da.RequireAlignmentOfMatrixElements = false; da.PreferDataCopyToFailAlignment = true; % add the data alignment object to target characteristics tc = RTW.TargetCharacteristics; tc.DataAlignment = da; % Define the CRL properties thisCrl.Name = 'QHL v73 Functions'; thisCrl.Description = 'Demonstration of custom function replacement'; thisCrl.TableList = {'crl_table_iirinterpolator.m'}; thisCrl.TargetHWDeviceType = {'Qualcomm->Hexagon'}; thisCrl.TargetCharacteristics = tc; end % End of LOCCRLREGFCN
Add the CRL to the MATLAB path.
addpath("crl_iir"); % Needs to be updated as per the CRL location
Refresh registration information. At the MATLAB command line, enter:
RTW.TargetRegistry.getInstance('reset');
Generating Code and PIL Verification
Before generating the code, get the reference output by run in MATLAB for the following inputs.
%% Test parameters % Input random data inputData = rand(48000,1,'single'); % IIR coefficients h = single([ 0.238129, 0.476258, -0.267690, 0.238129, 0.281005, ... 0.238129, 0.476258, -0.267690, 0.238129, 0.281005, ... 0.238129, 0.476258, -0.267690, 0.238129, 0.281005, ... ]); % Interpolation Factor interpolationFactor = 2; inputBlockLength = 240; inputLength = length(inputData); numBiquads = length(h)/5; % Each biquad section has 5 coefficients outputBlockLength = inputBlockLength*interpolationFactor; inputBlocks = floor(inputLength/inputBlockLength);
Fragment the entire input into frames of length 240
and loop across the function to get the reference.
% Test MATLAB implementation % Create the states buffer ziRef = zeros(numBiquads*2, 1,'like',inputData); % Each biquad has 2 states % Create the reference output buffer refData = zeros(length(inputData)*2,1,'like',inputData); for i = 1:inputBlocks in = inputData((i-1)*inputBlockLength+1:(i-1)*inputBlockLength+inputBlockLength); % Reference [ref,ziRef] = iirinterpolator(in,h,ziRef,interpolationFactor); % Accumulate the reference for comparison refData((i-1)*outputBlockLength+1:(i-1)*outputBlockLength+outputBlockLength) = ref; end
Configure the following properties in the coder.config
to generate the code.
% code-generation configurations cfg = coder.config('lib','ecoder',true); cfg.Hardware = coder.Hardware('Qualcomm Hexagon Simulator'); cfg.Hardware.ProcessorVersion = 'V73'; cfg.GenerateCodeReplacementReport = true; cfg.CodeReplacementLibrary = 'QHL v73 Functions'; cfg.VerificationMode = "PIL"; % Generate the code codegen -config cfg iirinterpolator -args {rand(inputBlockLength,1,'single'), h, ziRef, coder.Constant(interpolationFactor)} -launchreport
### Connectivity configuration for function 'iirinterpolator': 'Hexagon Simulator' Code generation successful: View report
Code replacements can be verified by viewing the Code Replacements Report.
Verify the generated code against the MATLAB implementation output.
% Test PIL % Create the states buffer zi = zeros(numBiquads*2, 1,'like',inputData); % Each biquad has 2 states % Create the output buffer outputData = zeros(length(inputData)*2,1,'like',inputData); for i = 1:inputBlocks in = inputData((i-1)*inputBlockLength+1:(i-1)*inputBlockLength+inputBlockLength); % PIL simulation [out,zi] = iirinterpolator_pil(in,h,zi,interpolationFactor); % Accumulate the PIL output for comparison outputData((i-1)*outputBlockLength+1:(i-1)*outputBlockLength+outputBlockLength) = out; end
### Starting application: 'codegen\lib\iirinterpolator\pil\iirinterpolator.elf' To terminate execution: clear iirinterpolator_pil
Inf-norm can be used to verify the numerical accuracy of PIL output with the MATLAB reference output.
fprintf("The inf-norm difference between the PIL output and reference output is %2.8f\n",norm(outputData-refData,inf));
The inf-norm difference between the PIL output and reference output is 0.00000012
Code Replacement of MATLAB Function in Simulink
The replacements can also be used in Simulink using MATLAB Function block. Where a mask can be created to define the tuning and non-tuning parameters of this function. For more information, see Use Data in Multiple MATLAB Function Blocks by Defining Parameter Variables.
The function block creates states using persistent variables and processes input data and coefficients through ports to compute the output. You can refer to the model implementation by opening the model using this command:
open_system('IIRInterpolationUsingCRL')