Main Content

式の畳み込みをサポートする S-Function

式の畳み込みを使用して、S-Function API で提供されるマクロを呼び出すことにより、独自のインライン化 S-Function ブロックによって生成されるコードの効率を上げます。

S-Function API を使用すると、指定の S-Function ブロックが指定の入力端子で式を名義的に受け入れるかどうかを指定できます。ブロックが必ず式を受け入れなければならないということではありません。たとえば、入力で信号のアドレスが使用されると、式は利用できないため、式がその入力で受け入れられなくなります。

また、S-Function API では、式が指定の出力端子に関連付けられている計算を表すかどうかを指定できます。ブロックの入出力端子で式を要求すると、Simulink® エンジンはブロックの内容に応じて、その要求に従うかどうかを決定します。たとえば、接続先ブロックがその入力で式を受け入れない場合や、接続先ブロックに更新関数がある場合や、複数の出力接続先が存在する場合、エンジンは式を出力するブロックの要求を拒否する可能性があります。

また、式の出力要求を受け入れるか拒否するかの決定も、ブロックで使用される出力式のカテゴリに応じて変わります。

S-Function で式の畳み込みを使用するには、特定のブロックについて、式の受け入れおよび生成の要求に適したタイミングを理解しなければなりません。Simulink エンジンがこれらの要求を受け入れるか拒否するかを決めるアルゴリズムについては、理解する必要はありません。ただし、モデルと生成コードを追跡するには、要求が拒否される一般的な状況を理解しておくと役立ちます。

出力式のカテゴリ

C MEX S-Function を実装する場合、ブロックの出力に対応するコードを式として生成するかどうかを指定できます。ブロックが式を生成したら、その式が "定数"、"自明"、または "一般" になるように指定しなければなりません。

"定数" 出力は、ブロックの出力が、ブロックのパラメーターにのみ依存しており、ブロックの入力または状態には依存していない場合にのみ有効です。

"自明" 出力式は、出力端子に複数の遷移先があるときに、性能を損なうことなく繰り返すことができる式です。出力式では、ブロックのどの入力値も参照してはなりません。これは、入力が式自体である可能性があるためです。たとえば、Unit Delay ブロックの出力は、出力式が単純にこのブロックの状態への直接アクセスであり、他のデータがないため、自明な式として定義されます。

"一般" 出力式は、繰り返すと性能が損なわれることを想定しなければならない式です。そのため、一般的な出力式は、出力端子に複数の出力先がある場合は反復に適しません。たとえば、Sum ブロックの出力式を複数のブロックの入力として再計算するにはコストが高くなるため、Sum ブロックの出力は、自明な式ではなく一般的な式となります。

自明および一般的な出力式の例

次のブロック線図について考えます。Delay ブロックには複数の遷移先があり、その出力は自明な出力式として指定されるため、コードの効率性を低下させることなく Delay ブロックを複数回使用できます。

このコードの抜粋は、このブロック線図内の Unit Delay ブロックから生成されたコードを示しています。3 つのルート出力は Unit Delay ブロックの状態から直接割り当てられ、グローバル データ構造体 rtDWork のフィールドに保存されます。割り当ては式に関係なく直接行われるため、複数の遷移先に自明な式を使用することで性能が損なわれることはありません。

void MdlOutputs(int_T tid)
{
   ...
  /* Outport: <Root>/Out1 incorporates:

   *   UnitDelay: <Root>/Unit Delay */
  rtY.Out1 = rtDWork.Unit_Delay_DSTATE;

  /* Outport: <Root>/Out2 incorporates:
   *   UnitDelay: <Root>/Unit Delay */
  rtY.Out2 = rtDWork.Unit_Delay_DSTATE;

  /* Outport: <Root>/Out3 incorporates:
   *   UnitDelay: <Root>/Unit Delay */
  rtY.Out3 = rtDWork.Unit_Delay_DSTATE;

   ...
}

生成されたコードは、単一および複数の遷移先をもつ Sum ブロックでのコード生成方法を示します。

一方、次のモデルの Sum ブロックについて考えてみましょう。

このモデルの上部 Sum ブロックは、non_triv というラベルの付いた信号を生成します。この出力信号の計算には 2 つの除算と加算が含まれます。Sum ブロックの出力では複数の遷移先があっても式の生成が許可されていたため、生成コードでブロックの操作が重複することがありました。この場合、生成される式が 4 つの除算と 2 つの加算へと大幅に増加します。そのためにプログラムの効率性が低下します。これに応じて Sum ブロックの出力には複数の遷移先ができるため、式としては認められなくなります。

信号 non_triv が 2 つの出力遷移先にルーティングされるため、Simulink エンジンは上部 Sum ブロックの出力が式になることを許可しません。代わりに、以下のコードの抜粋で示されているように、除算および加算演算の結果が、後に続くステートメントで 2 回参照される一時変数 (rtb_non_triv) に保存されます。

その一方で、出力遷移先が Out2 の 1 つしかない下部の Sum ブロックでは式が生成されます。

void MdlOutputs(int_T tid)
{
  /* local block i/o variables */
  real_T rtb_non_triv;
  real_T rtb_Sine_Wave;

  /* Sum: <Root>/Sum incorporates:
   *   Gain: <Root>/Gain
   *   Inport: <Root>/u1
   *   Gain: <Root>/Gain1
   *   Inport: <Root>/u2
   *
   * Regarding <Root>/Gain:
   *   Gain value: rtP.Gain_Gain
   *
   * Regarding <Root>/Gain1:
   *   Gain value: rtP.Gain1_Gain
   */
  rtb_non_triv = (rtP.Gain_Gain * rtU.u1) + (rtP.Gain1_Gain * 
rtU.u2);

  /* Outport: <Root>/Out1 */
  rtY.Out1 = rtb_non_triv;

  /* Sin Block: <Root>/Sine Wave */

  rtb_Sine_Wave = rtP.Sine_Wave_Amp *
	sin(rtP.Sine_Wave_Freq * rtmGetT(rtM_model) + 
	rtP.Sine_Wave_Phase) + rtP.Sine_Wave_Bias;

  /* Outport: <Root>/Out2 incorporates:
   *   Sum: <Root>/Sum1
   */
  rtY.Out2 = (rtb_non_triv + rtb_Sine_Wave);
}

出力式のカテゴリの指定

S-Function API は、ブロックの出力を式にするかどうかを宣言できるマクロを提供し、宣言する場合は式のカテゴリを指定できます。この表は、ブロックの出力を、一定、自明または一般的な出力式に宣言するタイミングを示します。

出力式のタイプ

式のカテゴリ

使用時

定数

ブロックの出力がブロック パラメーターへの直接メモリ アクセスであり、ブロックのいずれの入力または状態の値にも依存しない場合にのみ使用します。

自明

ブロックの出力が、効率を下げることなくコード内で複数回使用できる式 (DWork ベクトルのフィールドへの直接メモリ アクセス、文字など) であり、ブロックのいずれの入力値にも依存しない場合にのみ使用します。

Generic

出力が定数または自明の式ではない場合に使用します。

S-Function API で定義されているマクロを使用して、関数 mdlSetWorkWidths で出力を式として宣言しなければなりません。マクロには次の引数が含まれます。

  • SimStruct *S: ブロックの SimStruct へのポインター。

  • int idx: 出力端子のゼロベースのインデックス。

  • bool value: 端子が出力式を生成する場合は、TRUE を渡します。

次のマクロは、出力を定数式、自明式または一般式に設定する際に使用できます。

  • void ssSetOutputPortConstOutputExprInRTW(SimStruct *S, int idx, bool value)

  • void ssSetOutputPortTrivialOutputExprInRTW(SimStruct *S, int idx, bool value)

  • void ssSetOutputPortOutputExprInRTW(SimStruct *S, int idx, bool value)

次のマクロは、上記のマクロよりも前の呼び出しで設定されるステータスをクエリする際に使用できます。

  • bool ssGetOutputPortConstOutputExprInRTW(SimStruct *S, int idx)

  • bool ssGetOutputPortTrivialOutputExprInRTW(SimStruct *S, int idx)

  • bool ssGetOutputPortOutputExprInRTW(SimStruct *S, int idx)

一般式のセットは自明な式のセットのスーパーセットであり、自明な式のセットは定数式のセットのスーパーセットです。

そのため、ssGetOutputPortTrivialOutputExprInRTW で定数式になるように設定されている出力をクエリすると、True が返されます。定数式は生成コードの効率を低下させずに反復できる直接メモリ アクセスなので、自明式とみなされます。

同様に、定数式または自明式になるように設定されている出力では、一般式としてステータスをクエリすると True が返されます。

入力式に対する要求の受け入れと拒否

ブロックは、出力がコードで式形式で表されるように要求することができます。遷移先のブロックの入力端子で式が受け入れられないと、そのような要求が拒否される可能性があります。さらに、要求する側のブロックと要求される側のブロックが依存し合っていないという状態では、式は受け入れられません。

次の状態では、ブロックの入力端子で式を受け入れるように設定することはできません。

  • ブロックが入力データのアドレスを取得しなければならない場合。入力式のほとんどのタイプからアドレスを取得することはできません。

  • ブロックで生成されたコードが入力を 1 回を超えて参照する場合 (Abs ブロック、Max ブロックなど)。式が複雑になって重複する可能性があり、そのためコードの効率が低下する原因となります。

ブロックが入力端子での式の受け入れを拒否すると、その入力端子に接続されているブロックでは一般式または自明式の出力が許可されません。

定数式は性能を低下させず、ソフトウェアはパラメーターのアドレスを取得できるため、定数式の出力要求が拒否されることはありません。

S-Function API による入力式受け入れの指定

S-Function API が提供するマクロでは、次の内容を実行できます。

  • ブロックの入力が非定数式 (つまり、自明式または一般式) を受け入れるかどうかの指定

  • ブロックの入力が非定数式を受け入れるかどうかのクエリ

既定の設定では、ブロックの入力は非定数式を受け入れません。

関数 mdlSetWorkWidths でマクロを呼び出す必要があります。マクロには以下の引数があります。

  • SimStruct *S: ブロックの SimStruct へのポインター。

  • int idx: 入力端子のゼロベースのインデックス。

  • bool value: 端子が入力式を受け入れる場合は TRUE を渡します。それ以外の場合は FALSE を渡します。

ブロックの入力が非定数式を受け入れるかどうかを指定する際に使用できるマクロは、次のようになります。

void ssSetInputPortAcceptExprInRTW(SimStruct *S, int portIdx, bool value)

前の ssSetInputPortAcceptExprInRTW の呼び出しによって設定されるステータスをクエリする際に使用できる対応するマクロは、次のようになります。

bool ssGetInputPortAcceptExprInRTW(SimStruct *S, int portIdx)

出力式に対するブロックの要求の拒否

特定のブロックが出力式の生成を認めるように要求しても、一般的な理由から、その要求が拒否される場合があります。その理由には次のものが含まれますが、それだけに限定されません。

  • 出力式が自明ではなく、出力に複数の遷移先がある。

  • 出力式が自明ではなく、入力端子で式の受け入れが行われない遷移先に少なくとも 1 つ接続されている。

  • 出力がテスト ポイント。

  • 出力が外部ストレージ クラスに割り当てられている。

  • 出力をグローバル データを使用して保存しなければならない (たとえば、Merge ブロックまたは状態のあるブロックの入力)。

  • 出力信号が複雑。

特定のブロックに式の畳み込みを使用するかどうかを決定する場合、上記の一般的な要因を考慮する必要がありません。ただし、生成コードを調査したり式の畳み込みが表示されない状況を解析したりするときには、このルールが役立つ場合があります。

TLC ブロック実装での式の畳み込み

式の畳み込みを利用するには、インライン化 S-Function の TLC ブロック実装を変更し、Simulink エンジンに対し、次の位置で式の生成または受け入れを行うかどうかを通知できるようにします。

このトピックでは、TLC 実装に行わなければならない変更内容を説明します。

式の畳み込みの準拠

S-Function の関数 BlockInstanceSetup に、ブロックが式の畳み込みに準拠していることを登録します。そうしないと、ブロックの入出力で必要または許可されている式の畳み込みが無効となり、一時変数が使用されます。

式の畳み込みの準拠を登録するには、matlabroot/rtw/c/tlc/lib/utillib.tlc で定義されている、TLC ライブラリ関数 LibBlockSetIsExpressionCompliant(block) を呼び出します。以下に例を示します。

%% Function: BlockInstanceSetup ===========================================
%%
%function BlockInstanceSetup(block, system) void
  %%
  %<LibBlockSetIsExpressionCompliant(block)>
  %%
%endfunction

条件付きでこの関数の呼び出しを行うことによって、ブロックの入出力で式の畳み込みを条件付きで無効にすることができます。

コード ジェネレーターが提供する TLC ブロック実装のいずれかを独自の実装でオーバーライドする場合は、自分の実装を更新してから前述の呼び出しを実行してください。

出力式

関数 BlockOutputSignal は、スカラー出力式のコードまたは非スカラー出力式の単一の要素を生成するときに使用します。ブロックが式を出力する場合は、関数 BlockOutputSignal を追加しなければなりません。BlockOutputSignal のプロトタイプは次のようになります。

%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void

BlockOutputSignal の引数は以下のとおりです。

  • block: 出力式が生成されるブロックのレコード

  • system: ブロックを含んでいるシステムのレコード

  • portIdx: 式が生成される出力端子のゼロベースのインデックス

  • ucv: コードが生成される出力要素を定義するユーザー コントロール変数

  • lcv: コードが生成される出力要素を定義するループ制御変数

  • idx: コードが生成される出力要素を定義する信号インデックス

  • retType: 次の信号アクセスのタイプを定義する文字ベクトル

    "Signal" は出力信号の内容またはアドレスを指定します。

    "SignalAddr" は出力信号のアドレスを指定します。

関数 BlockOutputSignal は、出力信号またはアドレスに対応するテキスト文字ベクトルを返します。この文字ベクトルでは、式が関数呼び出しで構成されている場合を除き、開きかっこと閉じかっこを使って式に優先順位を付けなければなりません。式のアドレスは定数式に対してのみ返され、これはメモリにアクセスするパラメーターのアドレスです。Constant ブロックの関数 BlockOutputSignal を実装するコードを以下に示します。

%% Function: BlockOutputSignal =================================================
%% Abstract:
%%      Return the reference to the parameter.  This function *may*
%%      be used by Simulink when optimizing the Block IO data structure.
%%
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
  %switch retType
    %case "Signal"
      %return LibBlockParameter(Value,ucv,lcv,idx)
    %case "SignalAddr"
      %return LibBlockParameterAddr(Value,ucv,lcv,idx)
    %default
      %assign errTxt = "Unsupported return type: %<retType>"
      %<LibBlockReportError(block,errTxt)>
  %endswitch
%endfunction

Relational Operator ブロックに関数 BlockOutputSignal を実装するコードを以下に示します。

%% Function: BlockOutputSignal =================================================
%% Abstract:
%%      Return an output expression.  This function *may*
%%      be used by Simulink when optimizing the Block IO data structure.
%%
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
%switch retType
%case "Signal"
%assign logicOperator = ParamSettings.Operator
 %if ISEQUAL(logicOperator, "~=")
 %assign op = "!="
elseif ISEQUAL(logicOperator, "==") %assign op = "=="
  %else
%assign op = logicOperator
%endif
 %assign u0 = LibBlockInputSignal(0, ucv, lcv, idx)
%assign u1 = LibBlockInputSignal(1, ucv, lcv, idx)
  %return "(%<u0> %<op> %<u1>)"
 %default
 %assign errTxt = "Unsupported return type: %<retType>"
 %<LibBlockReportError(block,errTxt)>
%endswitch
%endfunction

複数の出力があるブロックの式の畳み込み

ブロックの出力端子が 1 つの場合、出力端子が式でない場合に限って、ブロックの TLC ファイルにある関数 Outputs が呼び出されます。それ以外の場合は関数 BlockOutputSignal が呼び出されます。

ブロックに複数の出力があると、いずれかの出力端子が式でない場合に関数 Outputs が呼び出されます。関数 Outputs は、式である出力端子でコードが生成されないように防がなければなりません。これは、LibBlockOutputSignalIsExpr() の呼び出しをもつ個々の出力端子に対応したコード セクションを保護することによって実現されます。

たとえば、2 つの入力と 2 つの出力をもつ S-Function を考えてみましょう。ここでは次のようになります。

  • 最初の出力 y0 は、最初の入力の 2 倍に等しい。

  • 2 番目の出力 y1 は、2 番目の入力の 4 倍に等しい。

S-Function の関数 Outputs および関数 BlockOutputSignal を、次のコードの抜粋に示します。

%% Function: BlockOutputSignal =================================================
%% Abstract:
%%      Return an output expression.  This function *may*
%%      be used by Simulink when optimizing the Block IO data structure.
%%
%function BlockOutputSignal(block,system,portIdx,ucv,lcv,idx,retType) void
%switch retType
%case "Signal"
   %assign u = LibBlockInputSignal(portIdx, ucv, lcv, idx)
 %case "Signal"
 %if portIdx == 0
  %return "(2 * %<u>)"
%elseif portIdx == 1
 %return "(4 * %<u>)"
%endif
%default
%assign errTxt = "Unsupported return type: %<retType>"
 %<LibBlockReportError(block,errTxt)>
%endswitch
%endfunction  
%%
%% Function: Outputs =================================================
%% Abstract:
%%      Compute output signals of block
%%
%function Outputs(block,system) Output
%assign rollVars = ["U", "Y"]
%roll sigIdx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars 
%assign u0 = LibBlockInputSignal(0, "", lcv, sigIdx)
 %assign u1 = LibBlockInputSignal(1, "", lcv, sigIdx)
 %assign y0 = LibBlockOutputSignal(0, "", lcv, sigIdx)
 %assign y1 = LibBlockOutputSignal(1, "", lcv, sigIdx)
%if !LibBlockOutputSignalIsExpr(0)
%<y0> = 2 * %<u0>;
%endif
%if !LibBlockOutputSignalIsExpr(1)
 %<y1> = 4 * %<u1>;
%endif
%endroll
%endfunction

式の畳み込みに準拠するブロックのコメント

従来、ブロックは次の形式のコメントで出力コードの前に置かれていました。

/* %<Type> Block: %<Name> */

ブロックが式の畳み込みに準拠していると、上記の最初の行が自動的に生成されます。ブロックの TLC 実装の一部としてコメントを含めないでください。追加情報は関数 LibCacheBlockComment を使用して登録します。

関数 LibCacheBlockComment は入力を文字ベクトルとして取得し、先頭のヘッダーを除き、コメント本文、1 行または複数行のコメントの最後の改行および末尾のトレーラーを定義します。

次の TLC コードはブロック コメントの登録を示します。文字ベクトルを返す関数 LibBlockParameterForComment は、ブロック パラメーターの値を指定するブロック コメントに対する使用に適しています。

%openfile commentBuf
  $c(*) Gain value: %<LibBlockParameterForComment(Gain)>
  %closefile commentBuf
  %<LibCacheBlockComment(block, commentBuf)>

関連するトピック