C MEX S-Function のインライン化
S-Function のインライン化の概要
Simulink® モデルに S-Function が含まれていて、対応する TLC ブロック ターゲット ファイルがその S-Function に対して存在している場合は、コード ジェネレーターは S-Function をインライン化します。S-Function をインライン化すると、S-Function API 層を生成コードから削除して、より効率的なコードを生成できます。
さまざまなタスクを実行できる S-Function では、インライン化すると、ブロックの各インスタンスに対して設定されている現在の操作モードでのみコードを生成できるようになります。この例として、S-Function が任意の信号幅を受け入れ、信号の各要素をループ処理する場合、インライン化されたコードを生成して、信号に 2 つ以上の要素がある場合にはループを使用するが、信号の要素が 1 つだけの場合にはループを使用しない単純な計算を生成するようにすることが可能です。
インライン化されないレベル 1 の C MEX S-Function (S-Function API の古い形式に書き込まれる) では、特定の S-Function のルーチンが空でも、生成コードが該当するすべての関数を呼び出します。
関数 | 目的 |
---|---|
| サイズ配列を初期化する |
| サンプル時間配列を初期化する |
| 状態を初期化する |
| 出力を計算する |
| 離散状態を更新する |
| 連続状態の導関数を計算する |
| シミュレーションの終了時にクリーンアップする |
インライン化されないレベル 2 の C MEX S-Function (最新の S-Function API に書き込まれるもの) では、以下の例外を除き、上記の関数を呼び出します。
mdlInitializeConditions
は、MDL_INITIALIZE_CONDITIONS
が#define
で宣言されている場合にのみ呼び出されます。mdlStart
は、MDL_START
が#define
で宣言されている場合にのみ呼び出されます。mdlUpdate
は、MDL_UPDATE
が#define
で宣言されている場合にのみ呼び出されます。mdlDerivatives
は、MDL_DERIVATIVES
が#define
で宣言されている場合にのみ呼び出されます。
S-Function をインライン化することで、シミュレーション ループで空になる可能性がある関数の呼び出しを削除できます。これにより、生成コードの効率を大幅に高めることができます。
という S-Function をインライン化するには、sfunc
_name
というカスタム S-Function ブロック ターゲット ファイルを作成して S-Function MEX ファイルと同じフォルダーに配置します。そうすると、ビルド時に、S-Function sfunc
_name.tlc.c
ファイルへの関数呼び出しの設定を行う代わりに、ターゲット ファイルが実行されます。S-Function ターゲット ファイルは、ターゲット ファイルに定義されているステートメントのみを挿入するように Target Language Compiler に指示して、S-Function を "インライン化" します。
一般的に、S-Function のインライン化は以下の場合に特に役立ちます。
S-Function の内容の実行に必要な時間が S-Function の呼び出しに必要なオーバーヘッドと比較して小さい。
特定の S-Function ルーチンが空である (たとえば、
mdlUpdate
)。S-Function の動作はシミュレーションとコード生成とで変化します。たとえば、デバイス ドライバー I/O S-Function は、シミュレーション時には MATLAB® ワークスペースから読み取る一方で、生成コードでは実際のハードウェア アドレスから読み取ることがあります。
S-Function パラメーター
S-Function は、次の 2 つの異なるタイプのパラメーターを
ファイルに書き込んで Target Language Compiler ファイルからアクセスできるようにすることが可能です。model
.rtw
パラメーター設定: これらは、
ssWriteRTWParamSettings
を使用して S-Function のmdlRTW
メソッドを介して書き込まれる調整不可のパラメーター (通常、マスクされた S-Function でチェック ボックスおよびメニューから設定) に対応します。この場合、S-Function TLC 実装ファイルでは、ブロック内のSFcnParamSettings
レコードからこれらのパラメーター設定の値に直接アクセスできます。調整可能なパラメーター: このクラスのパラメーターは、S-Function 内で実行時パラメーターとして登録するとアクセスできます。なお、このような調整可能なパラメーターは
ファイルに自動的に書き出されます。S-Function の TLC ファイル内で、ライブラリ関数model
.rtwLibBlockParameter
およびそのバリアントを使用して実行時パラメーターおよびその属性にアクセスできます。
実行時パラメーターの作成および使用方法の詳細については、Create and Update S-Function Run-Time Parametersを参照してください。2 つのクラスのパラメーターを作成して使用する方法については、S-Function の例にある sfcndemo_runtime
の例も参照してください。以下は、調べて変更できるソース ファイルの例です。
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime1.c
toolbox/simulink/sfuntemplates/tlc_c/sfun_runtime1.tlc
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime2.c
toolbox/simulink/sfuntemplates/tlc_c/sfun_runtime2.tlc
toolbox/simulink/simdemos/simfeatures/src/sfun_runtime3.c
toolbox/simulink/sfuntemplates/tlc_c/sfun_runtime3.tlc
S-Function のサンプル コード
Gain ブロックを模倣し、1 つの入力、1 つの出力、1 つのスカラー ゲインがある単純な S-Function があるものとします。つまり、y = u * p
です。Simulink ブロックの名前が foo
で、レベル 2 の S-Function の名前が foogain
の場合、C MEX S-Function には以下のコードを含める必要があります。
#define S_FUNCTION_NAME foogain #define S_FUNCTION_LEVEL 2 #include "simstruc.h" #define GAIN mxGetPr(ssGetSFcnParam(S,0))[0] static void mdlInitializeSizes(SimStruct *S) { ssSetNumContStates (S, 0); ssSetNumDiscStates (S, 0); if (!ssSetNumInputPorts(S, 1)) return; ssSetInputPortWidth (S, 0, 1); ssSetInputPortDirectFeedThrough(S, 0, 1); if (!ssSetNumOutputPorts(S, 1)) return; ssSetOutputPortWidth (S, 0, 1); ssSetNumSFcnParams (S, 1); ssSetNumSampleTimes (S, 0); ssSetNumIWork (S, 0); ssSetNumRWork (S, 0); ssSetNumPWork (S, 0); } static void mdlOutputs(SimStruct *S, int_T tid) { real_T *y = ssGetOutputPortRealSignal(S, 0); const InputRealPtrsType u = ssGetInputPortRealSignalPtrs(S, 0); y[0] = (*u)[0] * GAIN; } static void mdlInitializeSampleTimes(SimStruct *S){} static void mdlTerminate(SimStruct *S) {} #define MDL_RTW /* Change to #undef to remove function */ #if defined(MDL_RTW)&&(defined(MATLAB_MEX_FILE)||defined(NRT)) static void mdlRTW (SimStruct *S) { if (!ssWriteRTWParameters(S, 1,SSWRITE_VALUE_VECT,"Gain","", mxGetPr(ssGetSFcnParam(S,0)),1)) { return; } } #endif #ifdef MATLAB_MEX_FILE #include "simulink.c" #else #include "cg_sfun.h" #endif
以下の 2 つのセクションでは、S-Function foogain
のインライン化されていないバージョンとインライン化されたバージョンを含む
の生成コードにおける差異を示します。モデルには他の Simulink ブロックは含まれていません。model
.c
これらの S-Function 関連の C ライブラリ関数の詳細については、C/C++ S-Function 機能の構成を参照してください。コードを生成する方法については、モデルの構成とコードの生成およびSimulink モデルから生成されたコードのビルドに使用するアプローチを参照してください。
model.c のインライン化されていないバージョンとインライン化されたバージョンの比較
S-Function の仕様を定義する TLC ファイルがない場合、コード ジェネレーターは S-Function API を使用して MEX ファイル S-Function を呼び出す必要があります。以下のコードは、インライン化されていない S-Function の
ファイルです (つまり、対応する TLC ファイルが存在しない)。model
.c
インラインでない S-Function
/* * model.c . . . */ real_T untitled_RGND = 0.0; /* real_T ground */ /* Start the model */ void MdlStart(void) { /* (no start code required) */ } /* Compute block outputs */ void MdlOutputs(int_T tid) { /* Level2 S-Function Block: <Root>/S-Function (foogain) */ { SimStruct *rts = ssGetSFunction(rtS, 0); sfcnOutputs(rts, tid); } } /* Perform model update */ void MdlUpdate(int_T tid) { /* (no update code required) */ } /* Terminate function */ void MdlTerminate(void) { /* Level2 S-Function Block: <Root>/S-Function (foogain) */ { SimStruct *rts = ssGetSFunction(rtS, 0); sfcnTerminate(rts); } } #include "model_reg.h" /* [EOF] model.c */
インライン化された S-Function. このコードは、foogain
S-Function が完全にインライン化された
です。model
.c
/* * model.c . . . */ /* Start the model */ void MdlStart(void) { /* (no start code required) */ } /* Compute block outputs */ void MdlOutputs(int_T tid) /* S-Function block: <Root>/S-Function */ /* NOTE: There are no calls to the S-function API in the inlined version of model.c. */ rtB.S_Function = 0.0 * rtP.S_Function_Gain; } /* Perform model update */ void MdlUpdate(int_T tid) { /* (no update code required) */ } /* Terminate function */ void MdlTerminate(void) { /* (no terminate code required) */ } #include "model_reg.h" /* [EOF] model.c */
この S-Function ブロック用にこのターゲット ファイルを含めた場合、結果として生成される
コードは以下のとおりです。 model
.c
rtB.S_Function = 0.0 * rtP.S_Function_Gain;
TLC ファイルを含めることで、コード サイズが大幅に低下するとともに、生成コードの実行効率性が高まりました。以下のメモでは、TLC コードおよび生成される出力に関する情報を取り上げます。
TLC 命令
%implements
がブロック ターゲット ファイルに必要であり、ブロック ターゲット ファイル内の最初の実行可能ステートメントでなければなりません。この命令は、Target Language Compiler が S-Functionfoogain
用に不適切なターゲット ファイルを実行しないようにします。foo
の入力はrtGROUND
(0.0 に等しい Simulink Coder™ グローバル) です。これは、foo
はモデル内の唯一のブロックであり、その入力が接続されていないためです。foogain
用に TLC ファイルを含めると、foogain
の S-Function 登録セグメントが不要になります。これにより、コード サイズが大幅に削減されます。TLC コードは、ビルド プロセスがパラメーター値をインライン化するように構成されている場合、
gain
パラメーターをインライン化します。たとえば、S-Function ダイアログ ボックスで S-Function パラメーターが 2.5 に指定されている場合、TLC 関数Outputs
によって以下が生成されます。rtB.foo = input * 2.5;
ご使用のオペレーティング システムにファイル名サイズの制限があり、S-Function の名前が (制限を超えている)
foosfunction
である場合は、%generatefile
命令を使用します。この場合、システム ターゲット ファイル (該当する S-Function ブロック ターゲット ファイルが参照されるより前の任意の場所) に以下のステートメントを含めます。%generatefile foosfunction "foosfunc.tlc"
このステートメントは、
foosfunction.tlc
ではなくfoosfunc.tlc
を開くように Target Language Compiler に指示します。
model_reg.h のインライン化されていないバージョンとインライン化されたバージョンの比較
レベル 2 の S-Function をインライン化すると、
コードのサイズが大幅に削減されます。モデル登録関数は長いため、この例ではコードのほとんどを省略しています。以下のコードでは、model
_reg.h
のインライン化されていないバージョンとインライン化されたバージョンの差異を取り上げます。インライン化により、以下のコードが削除されます。model
_reg.h
/* * model_reg.h * */ /* Normal model initialization code independent of S-functions */ /* child S-Function registration */ ssSetNumSFunctions(rtS, 1); /* register each child */ { static SimStruct childSFunctions[1]; static SimStruct *childSFunctionPtrs[1]; (void)memset((char_T *)&childSFunctions[0], 0, sizeof(childSFunctions)); ssSetSFunctions(rtS, &childSFunctionPtrs[0]); { int_T i; for(i = 0; i < 1; i++) { ssSetSFunction(rtS, i, &childSFunctions[i]); } } /* Level2 S-Function Block: untitled/<Root>/S-Function (foogain) */ { extern void foogain(SimStruct *rts); SimStruct *rts = ssGetSFunction(rtS, 0); /* timing info */ static time_T sfcnPeriod[1]; static time_T sfcnOffset[1]; static int_T sfcnTsMap[1]; { int_T i; for(i = 0; i < 1; i++) { sfcnPeriod[i] = sfcnOffset[i] = 0.0; } } ssSetSampleTimePtr(rts, &sfcnPeriod[0]); ssSetOffsetTimePtr(rts, &sfcnOffset[0]); ssSetSampleTimeTaskIDPtr(rts, sfcnTsMap); ssSetMdlInfoPtr(rts, ssGetMdlInfoPtr(rtS)); /* inputs */ { static struct _ssPortInputs inputPortInfo[1]; _ssSetNumInputPorts(rts, 1); ssSetPortInfoForInputs(rts, &inputPortInfo[0]); /* port 0 */ { static real_T const *sfcnUPtrs[1]; sfcnUPtrs[0] = &untitled_RGND; ssSetInputPortWidth(rts, 0, 1); ssSetInputPortSignalPtrs(rts, 0, (InputPtrsType)&sfcnUPtrs[0]); } } /* outputs */ { static struct _ssPortOutputs outputPortInfo[1]; _ssSetNumOutputPorts(rts, 1); ssSetPortInfoForOutputs(rts, &outputPortInfo[0]); ssSetOutputPortWidth(rts, 0, 1); ssSetOutputPortSignal(rts, 0, &rtB.S_Function); } /* path info */ ssSetModelName(rts, "S-Function"); ssSetPath(rts, "untitled/S-Function"); ssSetParentSS(rts, rtS); ssSetRootSS(rts, ssGetRootSS(rtS)); ssSetVersion(rts, SIMSTRUCT_VERSION_LEVEL2); /* parameters */ { static mxArray const *sfcnParams[1]; ssSetSFcnParamsCount(rts, 1); ssSetSFcnParamsPtr(rts, &sfcnParams[0]); ssSetSFcnParam(rts, 0, &rtP.S_Function_P1Size[0]); } /* registration */ foogain(rts); sfcnInitializeSizes(rts); sfcnInitializeSampleTimes(rts); /* adjust sample time */ ssSetSampleTime(rts, 0, 0.2); ssSetOffsetTime(rts, 0, 0.0); sfcnTsMap[0] = 0; /* Update the InputPortReusable and BufferDstPort flags for each input port */ ssSetInputPortReusable(rts, 0, 0); ssSetInputPortBufferDstPort(rts, 0, -1); /* Update the OutputPortReusable flag of each output port */ } }
S-Function foogain をインライン化する TLC ファイル
S-Function の不要な呼び出しを回避し、S-Function に必要な最小限のコードを生成するために、以下の TLC ファイル foogain.tlc
が例として用意されています。
%implements "foogain" "C" %function Outputs (block, system) Output /* %<Type> block: %<Name> */ %% %assign y = LibBlockOutputSignal (0, "", "", 0) %assign u = LibBlockInputSignal (0, "", "", 0) %assign p = LibBlockParameter (Gain, "", "", 0) %<y> = %<u> * %<p>; %endfunction
コード生成を見据えたブロック インスタンス データの管理
インスタンス データは、Simulink モデル内のブロックの各インスタンスに固有の追加のデータまたは作業メモリです。これは、パラメーターや状態データ (それぞれモデル パラメーターと状態ベクトルに格納) を含みませんが、パラメーターおよびモードの中間結果や派生表現をキャッシュするために使用されます。インスタンス データの一例として、Transport Delay ブロックで使用されるバッファーが挙げられます。
レベル 2 の S-Function でインスタンスごとにメモリを割り当てて使用するには、ssSetUserData
、作業ベクトル (たとえば、ssSetRWorkValue
や ssSetIWorkValue
)、DWork
ベクトルと呼ばれるデータ型付き作業ベクトルを使用するなど、複数の方法を利用できます。S-Function およびブロック ターゲット ファイルの記述にかかる労力を最小限にし、grt
などのターゲットで静的と malloc
の両方のインスタンス データに自動的に準拠するには、インスタンス データを使用して S-Function を記述する際にデータ型付き作業ベクトルを使用します。
利点は 2 つあります。まず、メモリの割り当てと解放が Simulink によって自動的に処理されるため、S-Function を記述するとより効率的になります。次に、DWork
名、データ型、サイズなど、DWork
ベクトルが
ファイルに自動的に書き込まれます。これにより、model
.rtwDWork
メモリの割り当てと解放を行う TLC コードを記述する必要がなくなるため、ブロック ターゲット ファイルの記述が容易になります。
さらに、DWork
ベクトルのグループを、関数に渡すために構造体にバンドルする場合に、S-Function の関数 mdlStart
とブロック ターゲット ファイルの Start
メソッドの両方で DWork
配列へのポインターを構造体に設定し、S-Function と生成コードのデータ処理間の整合性を確保できます。
最後に、DWork
を使用すれば、S-Function の実装に一致するブロック インスタンスごとにコードの特定のバージョン (データ型、スカラーかベクトル化かなど) を作成するのが単純になります。両方の実装で DWork
を同じように使用することで、C MEX S-Function やブロック ターゲット ファイルを変更することなく、インライン化されたコードを Simulink Accelerator ソフトウェアで使用できるようにします。
Simulink Accelerator ソフトウェアでのインライン化コードの使用
既定では、Simulink Accelerator ソフトウェアは高速化されたモデルのシミュレーションの一部として C MEX S-Function を呼び出します。高速化されたモデルを実行する前にアクセラレータで S-Function をインライン化する場合は、該当する S-Function の関数 mdlInitializeSizes
で ssSetOptions()
の呼び出しに SS_OPTION_USE_TLC_WITH_ACCELERATOR
フラグを設定して S-Function をインライン化するためにブロック ターゲット ファイルを使用するようにアクセラレータに指示します。
TLC 生成コードと C MEX S-Function でメモリおよび作業ベクトルのサイズと使用法が同じでなければならない点に注意してください。同じになっていないと、Simulink Accelerator ソフトウェアではインライン化されたコードを適切に実行できません。これは、ブロックおよびその作業ベクトルを初期化するために C MEX S-Function が呼び出され、関数 mdlInitializeSizes
、mdlInitializeConditions
、mdlCheckParameters
、mdlProcessParameters
、および mdlStart
が呼び出されるためです。定数信号伝播の場合、モデル実行の初期化フェーズ中に mdlOutputs
が C MEX S-Function から呼び出されます。
高速化されたモデルの実行のタイム ステップ フェーズ中に、Output
および Update
ブロックの TLC メソッドで生成されたコードが実行されます。さらに Derivatives
およびゼロクロッシング メソッドが存在すればそれも加わります。ブロック ターゲット ファイルの Start
メソッドは、高速化されたモデルのコードの生成時に使用されません。