Inline level 2 C s-function with variable number of inputs

18 views (last 30 days)
I have created level 2 s-function that has one dialog parameter, which defines number of inputs of this s-function. Now I need to inline this function to be able to generate code. I have tried to create TLC file using legacy_code function but it errors, because it need to have number of inputs defined in TLC file.
First problem I have is with defining OutputFcnSpec as variable length?
I have looked at ssWriteRTWParamSettings to save dialog parameter and then load it with TLC and assign number of input in this parameter in TLC, but it wont match the OutputFcnSpec definition.
Is there a way to even accomplish this?
bftest.c
/*
* File : timestwo.c
* Abstract:
* An example C-file S-function for multiplying an input by 2,
* y = 2*u
*
* Real-Time Workshop note:
* This file can be used as is (noninlined) with the Real-Time Workshop
* C rapid prototyping targets, or it can be inlined using the Target
* Language Compiler technology and used with any target. See
* matlabroot/toolbox/simulink/blocks/tlc_c/timestwo.tlc
* matlabroot/toolbox/simulink/blocks/tlc_ada/timestwo.tlc
* the C and Ada TLC code to inline the S-function.
*
* See simulink/src/sfuntmpl_doc.c
*
* Copyright 1990-2004 The MathWorks, Inc.
* $Revision: 1.12.4.2 $
*/
#define S_FUNCTION_NAME bftest
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
#define NPARAMS 1
typedef unsigned int UINT16;
/*================*
* Build checking *
*================*/
/* Function: mdlInitializeSizes ===============================================
* Abstract:
* Setup sizes of the various vectors.
*/
static void mdlInitializeSizes(SimStruct *S)
{
static boolean_T *inPorts[16];
// check that input parameter i numerical vector
int_T i;
real_T *pr;
size_t el;
size_t nEls;
if (mxIsEmpty(ssGetSFcnParam(S, 0)) ||
mxIsSparse(ssGetSFcnParam(S, 0)) ||
mxIsComplex(ssGetSFcnParam(S, 0)) ||
mxIsLogical(ssGetSFcnParam(S, 0)) ||
!mxIsNumeric(ssGetSFcnParam(S, 0)) ||
!mxIsDouble(ssGetSFcnParam(S, 0)))
{
ssSetErrorStatus(S, "Parameters must be real finite vectors");
return;
}
pr = mxGetPr(ssGetSFcnParam(S, 0));
nEls = mxGetNumberOfElements(ssGetSFcnParam(S, 0));
for (el = 0; el < nEls; el++)
{
if (!mxIsFinite(pr[el]))
{
ssSetErrorStatus(S, "Parameters must be real finite vectors");
return;
}
}
ssSetNumSFcnParams(S, NPARAMS);
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S))
{
return; /* Parameter mismatch will be reported by Simulink */
}
ssSetSFcnParamNotTunable(S, 0);
if (!ssWriteRTWParamSettings(S, 1,
SSWRITE_VALUE_NUM, "inportVector", pr))
{
return; /* An error occurred. */
}
/*
* Configure the input ports
*/
// set specified number of input ports
if (!ssSetNumInputPorts(S, (int_T)nEls))
return;
for (i = 0; i < nEls; i++)
{
ssSetInputPortDataType(S, i, SS_BOOLEAN);
ssSetInputPortWidth(S, i, 1);
ssSetInputPortComplexSignal(S, i, COMPLEX_NO);
ssSetInputPortDirectFeedThrough(S, i, 1);
ssSetInputPortAcceptExprInRTW(S, i, 1);
ssSetInputPortOverWritable(S, i, 1);
ssSetInputPortOptimOpts(S, i, SS_REUSABLE_AND_LOCAL);
ssSetInputPortRequiredContiguous(S, i, 1);
}
/*
* Set the number of output ports.
*/
if (!ssSetNumOutputPorts(S, 1))
return;
/*
* Configure the output port 1
*/
ssSetOutputPortDataType(S, 0, SS_UINT16);
ssSetOutputPortWidth(S, 0, 1);
ssSetOutputPortComplexSignal(S, 0, COMPLEX_NO);
ssSetOutputPortOptimOpts(S, 0, SS_REUSABLE_AND_LOCAL);
ssSetOutputPortOutputExprInRTW(S, 0, 1);
// /*
// * Register reserved identifiers to avoid name conflict
// */
// if (ssRTWGenIsCodeGen(S) || ssGetSimMode(S)==SS_SIMMODE_EXTERNAL) {
// /*
// * Register reserved identifier for OutputFcnSpec
// */
// ssRegMdlInfo(S, "bftest", MDL_INFO_ID_RESERVED, 0, 0, ssGetPath(S));
// }
/*
* This S-function can be used in referenced model simulating in normal mode.
*/
ssSetModelReferenceNormalModeSupport(S, MDL_START_AND_MDL_PROCESS_PARAMS_OK);
/*
* Set the number of sample time.
*/
ssSetNumSampleTimes(S, 1);
/*
* All options have the form SS_OPTION_<name> and are documented in
* matlabroot/simulink/include/simstruc.h. The options should be
* bitwise or'd together as in
* ssSetOptions(S, (SS_OPTION_name1 | SS_OPTION_name2))
*/
ssSetOptions(S,
SS_OPTION_USE_TLC_WITH_ACCELERATOR |
SS_OPTION_CAN_BE_CALLED_CONDITIONALLY |
SS_OPTION_EXCEPTION_FREE_CODE |
SS_OPTION_WORKS_WITH_CODE_REUSE |
SS_OPTION_DISALLOW_CONSTANT_SAMPLE_TIME);
ssSupportsMultipleExecInstances(S, true);
}
/* Function: mdlInitializeSampleTimes =========================================
* Abstract:
* Specifiy that we inherit our sample time from the driving block.
*/
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
ssSetOffsetTime(S, 0, FIXED_IN_MINOR_STEP_OFFSET);
#if defined(ssSetModelReferenceSampleTimeDefaultInheritance)
ssSetModelReferenceSampleTimeDefaultInheritance(S);
#endif
}
/* Function: mdlOutputs ===================================================
* Abstract:
* In this function, you compute the outputs of your S-function
* block. Generally outputs are placed in the output vector(s),
* ssGetOutputPortSignal.
*/
static void mdlOutputs(SimStruct *S, int_T tid)
{
real_T *pr;
size_t nEls;
// get number and index of input bits
pr = mxGetPr(ssGetSFcnParam(S, 0));
nEls = mxGetNumberOfElements(ssGetSFcnParam(S, 0));
// define bitfield array
boolean_T BF[16] = {false};
size_t i;
for (i = 0; i < nEls; i++)
{
boolean_T *temp = (boolean_T *)ssGetInputPortSignal(S, i);
BF[(int_T)pr[i]] = *temp;
}
uint16_T *y1 = (uint16_T *)ssGetOutputPortSignal(S, 0);
*y1 = ((UINT16)BF[0]) | ((UINT16)BF[1] << 1U) | ((UINT16)BF[2] << 2U) | ((UINT16)BF[3] << 3U) | ((UINT16)BF[4] << 4U) | ((UINT16)BF[5] << 5U) | ((UINT16)BF[6] << 6U) | ((UINT16)BF[7] << 7U) | ((UINT16)BF[8] << 8U) | ((UINT16)BF[9] << 9U) | ((UINT16)BF[10] << 10U) | ((UINT16)BF[11] << 11U) | ((UINT16)BF[12] << 12U) | ((UINT16)BF[13] << 13U) | ((UINT16)BF[14] << 14U) | ((UINT16)BF[15] << 15U);
}
/* Function: mdlTerminate =====================================================
* Abstract:
* No termination needed, but we are required to have this routine.
*/
static void mdlTerminate(SimStruct *S)
{
}
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
bftest.tlc
%% file : bftest.tlc
%%
%% Description:
%% Simulink Coder TLC Code Generation file for bftest
%%
%% Simulink version : 10.3 (R2021a) 14-Nov-2020
%% TLC file generated on : 15-Nov-2023 09:17:15
/%
%%%-MATLAB_Construction_Commands_Start
def = legacy_code('initialize');
def.SFunctionName = 'bftest';
def.OutputFcnSpec = 'uint16 y1 = bftest(boolean u1, boolean u2, boolean u3, boolean u4, boolean u5, boolean u6, boolean u7, boolean u8, boolean u9, boolean u10, boolean u11, boolean u12, boolean u13, boolean u14, boolean u15, boolean u16)';
legacy_code('sfcn_tlc_generate', def);
%%%-MATLAB_Construction_Commands_End
%/
%implements bftest "C"
%% Function: FcnGenerateUniqueFileName ====================================
%function FcnGenerateUniqueFileName (filename, type) void
%assign isReserved = TLC_FALSE
%foreach idxFile = CompiledModel.DataObjectUsage.NumFiles[0]
%assign thisFile = CompiledModel.DataObjectUsage.File[idxFile]
%if (thisFile.Name==filename) && (thisFile.Type==type)
%assign isReserved = TLC_TRUE
%break
%endif
%endforeach
%if (isReserved==TLC_TRUE)
%assign filename = FcnGenerateUniqueFileName(filename + "_", type)
%endif
%return filename
%endfunction
%% Function: BlockTypeSetup ===============================================
%function BlockTypeSetup (block, system) void
%% The Target Language must be C
%if ::GenCPP==1 && !IsModelReferenceSimTarget()
%<LibReportFatalError("This S-Function generated by the Legacy Code Tool must be only used with the C Target Language")>
%endif
%if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
%assign hFileName = FcnGenerateUniqueFileName("bftest_wrapper", "header")
%assign hFileNameMacro = FEVAL("upper", hFileName)
%openfile hFile = "%<hFileName>.h"
%selectfile hFile
#ifndef _%<hFileNameMacro>_H_
#define _%<hFileNameMacro>_H_
#ifdef MATLAB_MEX_FILE
#include "tmwtypes.h"
#else
#include "rtwtypes.h"
#endif
extern void bftest_wrapper_Output( uint16_T* y1, const boolean_T u1, const boolean_T u2, const boolean_T u3, const boolean_T u4, const boolean_T u5, const boolean_T u6, const boolean_T u7, const boolean_T u8, const boolean_T u9, const boolean_T u10, const boolean_T u11, const boolean_T u12, const boolean_T u13, const boolean_T u14, const boolean_T u15, const boolean_T u16);
#endif
%closefile hFile
%assign cFileName = FcnGenerateUniqueFileName("bftest_wrapper", "source")
%openfile cFile = "%<cFileName>.c"
%selectfile cFile
#include <string.h>
#ifdef MATLAB_MEX_FILE
#include "tmwtypes.h"
#else
#include "rtwtypes.h"
#endif
void bftest_wrapper_Output( uint16_T* y1, const boolean_T u1, const boolean_T u2, const boolean_T u3, const boolean_T u4, const boolean_T u5, const boolean_T u6, const boolean_T u7, const boolean_T u8, const boolean_T u9, const boolean_T u10, const boolean_T u11, const boolean_T u12, const boolean_T u13, const boolean_T u14, const boolean_T u15, const boolean_T u16) {
*y1 = bftest((boolean_T)(u1), (boolean_T)(u2), (boolean_T)(u3), (boolean_T)(u4), (boolean_T)(u5), (boolean_T)(u6), (boolean_T)(u7), (boolean_T)(u8), (boolean_T)(u9), (boolean_T)(u10), (boolean_T)(u11), (boolean_T)(u12), (boolean_T)(u13), (boolean_T)(u14), (boolean_T)(u15), (boolean_T)(u16));
}
%closefile cFile
%<LibAddToCommonIncludes("%<hFileName>.h")>
%<LibAddToModelSources("%<cFileName>")>
%else
%endif
%endfunction
%% Function: BlockInstanceSetup ===========================================
%function BlockInstanceSetup (block, system) void
%if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
%else
%<LibBlockSetIsExpressionCompliant(block)>
%endif
%endfunction
%% Function: Outputs ======================================================
%function Outputs (block, system) Output
%if IsModelReferenceSimTarget() || CodeFormat=="S-Function" || ::isRAccel
%assign y1_ptr = LibBlockOutputSignalAddr(0, "", "", 0)
%assign u1_val = LibBlockInputSignal(0, "", "", 0)
%assign u2_val = LibBlockInputSignal(1, "", "", 0)
%assign u3_val = LibBlockInputSignal(2, "", "", 0)
%assign u4_val = LibBlockInputSignal(3, "", "", 0)
%assign u5_val = LibBlockInputSignal(4, "", "", 0)
%assign u6_val = LibBlockInputSignal(5, "", "", 0)
%assign u7_val = LibBlockInputSignal(6, "", "", 0)
%assign u8_val = LibBlockInputSignal(7, "", "", 0)
%assign u9_val = LibBlockInputSignal(8, "", "", 0)
%assign u10_val = LibBlockInputSignal(9, "", "", 0)
%assign u11_val = LibBlockInputSignal(10, "", "", 0)
%assign u12_val = LibBlockInputSignal(11, "", "", 0)
%assign u13_val = LibBlockInputSignal(12, "", "", 0)
%assign u14_val = LibBlockInputSignal(13, "", "", 0)
%assign u15_val = LibBlockInputSignal(14, "", "", 0)
%assign u16_val = LibBlockInputSignal(15, "", "", 0)
%%
/* %<Type> (%<ParamSettings.FunctionName>): %<Name> */
bftest_wrapper_Output(%<y1_ptr>, %<u1_val>, %<u2_val>, %<u3_val>, %<u4_val>, %<u5_val>, %<u6_val>, %<u7_val>, %<u8_val>, %<u9_val>, %<u10_val>, %<u11_val>, %<u12_val>, %<u13_val>, %<u14_val>, %<u15_val>, %<u16_val>);
%else
%if !LibBlockOutputSignalIsExpr(0)
%assign y1_val = LibBlockOutputSignal(0, "", "", 0)
%assign u1_val = LibBlockInputSignal(0, "", "", 0)
%assign u2_val = LibBlockInputSignal(1, "", "", 0)
%assign u3_val = LibBlockInputSignal(2, "", "", 0)
%assign u4_val = LibBlockInputSignal(3, "", "", 0)
%assign u5_val = LibBlockInputSignal(4, "", "", 0)
%assign u6_val = LibBlockInputSignal(5, "", "", 0)
%assign u7_val = LibBlockInputSignal(6, "", "", 0)
%assign u8_val = LibBlockInputSignal(7, "", "", 0)
%assign u9_val = LibBlockInputSignal(8, "", "", 0)
%assign u10_val = LibBlockInputSignal(9, "", "", 0)
%assign u11_val = LibBlockInputSignal(10, "", "", 0)
%assign u12_val = LibBlockInputSignal(11, "", "", 0)
%assign u13_val = LibBlockInputSignal(12, "", "", 0)
%assign u14_val = LibBlockInputSignal(13, "", "", 0)
%assign u15_val = LibBlockInputSignal(14, "", "", 0)
%assign u16_val = LibBlockInputSignal(15, "", "", 0)
%%
%<y1_val> = bftest(%<u1_val>, %<u2_val>, %<u3_val>, %<u4_val>, %<u5_val>, %<u6_val>, %<u7_val>, %<u8_val>, %<u9_val>, %<u10_val>, %<u11_val>, %<u12_val>, %<u13_val>, %<u14_val>, %<u15_val>, %<u16_val>);
%endif
%endif
%endfunction
%% Function: BlockOutputSignal ============================================
%function BlockOutputSignal (block,system,portIdx,ucv,lcv,idx,retType) void
%assign u1_val = LibBlockInputSignal(0, "", "", 0)
%assign u2_val = LibBlockInputSignal(1, "", "", 0)
%assign u3_val = LibBlockInputSignal(2, "", "", 0)
%assign u4_val = LibBlockInputSignal(3, "", "", 0)
%assign u5_val = LibBlockInputSignal(4, "", "", 0)
%assign u6_val = LibBlockInputSignal(5, "", "", 0)
%assign u7_val = LibBlockInputSignal(6, "", "", 0)
%assign u8_val = LibBlockInputSignal(7, "", "", 0)
%assign u9_val = LibBlockInputSignal(8, "", "", 0)
%assign u10_val = LibBlockInputSignal(9, "", "", 0)
%assign u11_val = LibBlockInputSignal(10, "", "", 0)
%assign u12_val = LibBlockInputSignal(11, "", "", 0)
%assign u13_val = LibBlockInputSignal(12, "", "", 0)
%assign u14_val = LibBlockInputSignal(13, "", "", 0)
%assign u15_val = LibBlockInputSignal(14, "", "", 0)
%assign u16_val = LibBlockInputSignal(15, "", "", 0)
%%
%switch retType
%case "Signal"
%if portIdx == 0
%return "bftest(%<u1_val>, %<u2_val>, %<u3_val>, %<u4_val>, %<u5_val>, %<u6_val>, %<u7_val>, %<u8_val>, %<u9_val>, %<u10_val>, %<u11_val>, %<u12_val>, %<u13_val>, %<u14_val>, %<u15_val>, %<u16_val>)"
%else
%assign errTxt = "Block output port index not supported: %<portIdx>"
%<LibBlockReportError(block,errTxt)>
%endif
%default
%assign errTxt = "Unsupported return type: %<retType>"
%<LibBlockReportError(block,errTxt)>
%endswitch
%endfunction
%% [EOF]

Answers (1)

Kausthub
Kausthub on 26 Dec 2023
Edited: Kausthub on 28 Dec 2023
Hi Jakub Pojsl,
I understand that you would like to configure and inline a level 2 C S-Function with variable number of inputs.
Yes, we can configure the “OutputFcnSpec” with dynamic number of inputs/outputs. A solution could be to represent the inputs as an array of dynamic size. This can be done by changing the "OutputFcnSpec" to:
def.OutputFcnSpec = 'uint16 y1 = bftest(boolean u1[])';
Another example for dynamic sized inputs/outputs is provided in the below documentation:
The “OutputFcnSpec” of the above example looks like:
def.OutputFcnSpec = ['void array3d_add(double y1[size(u1,1)][size(u1,2)][size(u1,3)], ',...
'double u1[][][], double u2[][][], ' ...
'int32 size(u1,1), int32 size(u1,2), int32 size(u1,3))'];
In this example the legacy function computes the addition of the 2 input signals:
  • Input1 and Input2 are dynamically sized 3D arrays.
  • Output1 is a dynamically sized 3D array of same size as Input1.
  • The last 3 function's arguments allow to pass the Input1's dimensions to the legacy function.
Few other documentations that might be helpful to resolve your query would be:
  • Configuring Input and Output Ports of a S-Function:
  • mdlInititializeSizes:
Hope it helps!

Categories

Find more on Deployment, Integration, and Supported Hardware in Help Center and File Exchange

Products


Release

R2021a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!