Main Content

Code Architecture

Before investigating the specific code generation pieces of the Target Language Compiler (TLC), consider how Target Language Compiler generates code for a simple model. From the next figure, you see that blocks place code into Mdl routines. This shows MdlOutputs.

static void simple_output(int_T tid)
{

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

  simple_B.SineWave_d = simple_P.SineWave_Amp *
    sin(simple_P.SineWave_Freq * simple_M->Timing.t[0] +
    simple_P.SineWave_Phase) + simple_P.SineWave_Bias;

  /* Gain: '<Root>/Gain' */
  simple_B.Gain_d = simple_B.SineWave_d * simple_P.Gain_Gain;

  /* Outport: '<Root>/Out1' */
  simple_Y.Out1 = simple_B.Gain_d;
}

Blocks have inputs, outputs, parameters, states, plus other general properties. For example, block inputs and outputs are generally written to a block I/O structure (generated with identifiers of the type model_B, where model is the model name). Block inputs can also come from the external input structure (model_U) or the state structure when connected to a state port of an integrator (model_X), or ground (rtGround) if unconnected or grounded. Block outputs can also go to the external output structure, (model_Y). The following diagram shows the general block data mappings.

This discussion should give you a general sense of what the block object looks like. Now, you can look at specific pieces of the code generation process that are specific to the Target Language Compiler.

Code Generation Process

The code generator invokes the Target Language Compiler after a model is compiled into a partial representation of the model (model.rtw) suitable for code generation. To generate code, the Target Language Compiler uses its library of functions to transform two classes of target files:

  • System target files

  • Block target files

System target files are used to specify the overall structure of the generated code, tailoring for specific target environments. Block target files are used to implement the functionality of Simulink® blocks, including user-defined S-function blocks.

You can create block target files for C MEX, Fortran, and MATLAB® language S-functions to fully inline block functionality into the body of the generated code. C MEX S-functions can be noninlined, wrapper-inlined, or fully inlined. Fortran S-functions must be wrapper-inlined or fully inlined.

How TLC Determines S-Function Inlining Status

Whenever the Target Language Compiler encounters an entry for an S-function block in the model.rtw file, it must decide whether to generate a call to the S-function or to inline it.

Because they cannot use SimStructs, Fortran and MATLAB language S-functions must be inlined. This inlining can either be in the form of a full block target file or a one-line block target file that refers to a substitute C MEX S-function source file.

The Target Language Compiler selects a C MEX S-function for inlining if an explicit mdlRTW() function exists in the S-function code or if a target file for the current target language for the current block is in the TLC file search path. If a C MEX S-function has an explicit mdlRTW() function, there must be a corresponding target file or an error condition results.

The target file for an S-function must have the same root name as the S-function and must have the extension .tlc. For example, the target file for a C MEX S-function named sfix_bitop has the filename sfix_bitop.tlc.

Inlined and Noninlined S-Function Code

This example focuses on a C MEX S-function named sfix_bitop. The code generation options are set to allow reuse of signal memory for signal lines that were not set as tunable signals.

The code generated for the bit-wise operator block reuses a temporary variable that is set up for the output of the sum block to save memory. This results in one very efficient line of code, as seen here.

/* Bitwise Logic Block: <Root>/Bitwise Logical Operator */
/* [input] OR 'F00F' */
rtb_temp2 |= 0xF00F;

Initialization or setup code is not required for this inlined block.

If this block were not inlined, the source code for the S-function itself with its various options would be added to the generated code base, memory would be allocated in the generated code for the block’s SimStruct data, and calls to the S-function methods would be generated to initialize, run, and terminate the S-function code. To execute the mdlOutputs function of the S-function, code would be generated like this:

/* Level2 S-Function Block: <Root>/Bitwise Logical Operator (sfix_bitop) */
  {
    SimStruct *rts = ssGetSFunction(rtS, 0);
    sfcnOutputs(rts, tid);
  }

The entire mdlOutputs function is called and runs just as it does during simulation. That’s not everything, though. There is also registration, initialization, and termination code for the noninlined S-function. The initialization and termination calls are similar to the fragment above. Then, the registration code for an S-function with just one inport and one outport is 72 lines of C code generated as part of file model_reg.h.

/*Level2 S-Function Block: <Root>/Bitwise Logical Operator (sfix_bitop) */
    {
      extern void untitled_sf(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] = &rtU.In1;
          ssSetInputPortSignalPtrs(rts, 0, (InputPtrsType)&sfcnUPtrs[0]);
          _ssSetInputPortNumDimensions(rts, 0, 1);
          ssSetInputPortWidth(rts, 0, 1);
        }
      }
.
.
.

This continues until S-function sizes and methods are declared, allocated, and initialized. The amount of registration code generated is essentially proportional to the number and size of the input ports and output ports.

A noninlined S-function will typically have a significant impact on the size of the generated code, whereas an inlined S-function can be close to the handwritten size and performance of the generated code.

Related Topics