Comprehensive guide for exporting simulation models as C-code from multiple tools — with and without full licenses — for use with the Cal Suite FMU Generator and ASCMO-MOCA
The ETAS Cal Suite FMU Generator compiles C-code from any modeling environment into FMI 2.0 Co-Simulation FMUs that are directly loadable in ASCMO-MOCA for model-based calibration optimization.
| Component | Required? | Purpose |
|---|---|---|
Model C source (.c) | Yes | Contains model equations and step function |
Header files (.h) | Optional | For multi-file models, shared type/extern declarations |
initialize() function | Recommended | Called at FMU instantiation for state reset |
calculate(double dt) function | Yes | Called each co-simulation step |
inp_* / out_* / par_* naming | Yes | Enables automatic FMI variable detection |
Ve*_read, Ve*_write, Ke*) are also auto-detected.$$$ Requires: MATLAB + Simulink + Simulink Coder + Embedded Coder
%% Configure Simulink model for ERT code generation
model_name = 'MyCalibrationModel';
open_system(model_name);
cs = getActiveConfigSet(model_name);
set_param(cs, 'SystemTargetFile', 'ert.tlc'); % Embedded Coder target
set_param(cs, 'TargetLang', 'C');
set_param(cs, 'GenCodeOnly', 'on'); % Don't compile — Cal Suite does this
% MUST be fixed-step for FMU co-simulation
set_param(model_name, 'Solver', 'ode4');
set_param(model_name, 'FixedStep', '0.001'); % 1ms step size
% Clean code generation settings
set_param(cs, 'SupportNonFinite', 'off');
set_param(cs, 'SupportComplex', 'off');
set_param(cs, 'CombineOutputUpdateFcns', 'on');
set_param(cs, 'IncludeMdlTerminateFcn', 'on');
%% Use ExportedGlobal storage for Cal Suite auto-detection
% Naming: prefix with par_ for automatic FMI parameter mapping
par_thermal_mass = Simulink.Parameter;
par_thermal_mass.Value = 850.0;
par_thermal_mass.DataType = 'double';
par_thermal_mass.CoderInfo.StorageClass = 'ExportedGlobal';
par_thermal_mass.Description = 'Thermal mass [J/K]';
% For lookup tables:
par_spark_map = Simulink.Parameter;
par_spark_map.Value = zeros(10, 12); % [load x rpm] matrix
par_spark_map.DataType = 'double';
par_spark_map.CoderInfo.StorageClass = 'ExportedGlobal';
% Inport block names → Cal Suite detects as FMI inputs:
% inp_engine_speed, inp_map_kpa, inp_lambda ...
% Outport block names → Cal Suite detects as FMI outputs:
% out_exhaust_temp, out_mass_flow, out_cat_efficiency ...
% ALTERNATIVE (ETAS naming, also auto-detected):
% Inputs: VeRPMR_n_EngSpd (any Ve* without _write)
% Outputs: VeEXHR_T_UsCatTemp_write (Ve* with _write suffix)
% Params: KeEXHR_T_InitTemp (any Ke* prefix)
slbuild(model_name);
% Output directory: <model_name>_ert_rtw/
% ├── <model>.c ← Main step function
% ├── <model>.h ← I/O struct definitions
% ├── <model>_data.c ← Parameter default values
% ├── <model>_types.h ← Type definitions
% └── rtwtypes.h ← Simulink runtime types
%
% Upload ALL .c and .h files to Cal Suite
void <model>_step(void) and maps it to fmi2DoStep. Global variables with inp_/out_/par_ prefixes become FMI variables automatically.exportToFMU2CS) may produce FMUs that are NOT fully compatible with ASCMO-MOCA. Common issues: parameters not tunable, VendorAnnotations crash MOCA, structured naming confuses the variable browser. Recommended: Export C-code and let Cal Suite build the FMU.$$$ Requires: Full Simulink + SWIFT framework (production code path used by OEMs)
SWIFT (Simulink Wrapper for Integration Framework and Testing) generates a C wrapper file containing structured port arrays that the Cal Suite parser reads directly.
/* From swift_V2026_00_06_CWC.c — auto-parsed by Cal Suite */
SimulinkPortData InportData[] = {
{SS_DOUBLE, mxDOUBLE_CLASS, DYNAMICALLY_TYPED, 1, 1, sizeof(double), "F64"}, /* eng_spd */
{SS_DOUBLE, mxDOUBLE_CLASS, DYNAMICALLY_TYPED, 1, 1, sizeof(double), "F64"}, /* map_kpa */
{SS_SINGLE, mxSINGLE_CLASS, DYNAMICALLY_TYPED, 1, 4, sizeof(float), "F32"}, /* lambse_tgt */
...
};
SimulinkPortData OutportData[] = {
{SS_DOUBLE, mxDOUBLE_CLASS, DYNAMICALLY_TYPED, 1, 1, sizeof(double), "F64"}, /* cat_temp */
...
};
swift_<model_version>.c — The SWIFT wrapper (I/O port definitions)src/*.h — Calibration data headers (parameter declarations with extern CAL).c files from Simulink Coder/* comment */ after each array entry and dimensions from the dimM, dimN fields. No manual configuration needed.$$ Requires: MATLAB + Simulink (no Embedded Coder needed for file generation)
The stub file defines all model I/O signals with default values:
% ExhEstimr_stub.m — generated from Simulink model
% Stub Input list
VeRPMR_n_EngSpd_stub = 0; % scalar input, default=0
VeITKR_cmp_TrapdAirNormd_stub = 0; % scalar input
VaITKR_m_TrapdAir_stub = ones([1 10])*0.0; % array input, 10 elements
% Stub Output list
VeEXHR_T_UsTrbTempBnk1_stub = 0; % scalar output
VeEXHR_dm_ExhMassFlowBnk1_stub = 0; % scalar output
The cal file defines parameters with full metadata (dimensions, limits, units):
% ExhEstimr_cal.m — calibration parameter definitions
KtEXHI_T_ExhTempBas = struct(...
'VALUE', zeros(17,17), ...
'DESCRIPTION', 'Base exhaust temperature map', ...
'MIN', -64, ...
'MAX', 1650, ...
'UNIT', 'DegC', ...
'TYPE', 'Float32', ...
'DIM', [17 17], ...
'HBK_NAME', 'KyEXHI_n_EngSpd', ... % Y-axis (rows)
'VBK_NAME', 'KxEXHI_r_AirMassNormd'); % X-axis (cols)
*_BCAL_val.mat — MAT file with actual calibration table valuesgenerate_full_fmu.py parses these files automatically, generates C source implementing the model equations, creates modelDescription.xml, and builds the multi-platform FMU.$$ Requires: MATLAB + MATLAB Coder (cheaper than full Simulink + Embedded Coder)
function [out_exhaust_temp, out_wall_temp] = exhaust_model(...
inp_engine_speed, inp_load, inp_ambient_temp, inp_mass_flow, ...
par_thermal_mass, par_heat_coeff, par_pipe_length, par_pipe_diam)
%#codegen ← Required pragma for code generation
persistent state_gas state_wall
if isempty(state_gas)
state_gas = 25.0;
state_wall = 25.0;
end
dt = 0.001; % Must match FMU step size
T_comb = 400 + inp_load * 6.0 + (inp_engine_speed - 800) / 52;
A_pipe = pi * par_pipe_diam * par_pipe_length;
cp = 1100.0;
Q_in = inp_mass_flow * cp * (T_comb - state_gas);
Q_loss = par_heat_coeff * A_pipe * (state_gas - state_wall);
state_gas = state_gas + (Q_in - Q_loss) / par_thermal_mass * dt;
state_wall = state_wall + Q_loss / (par_thermal_mass * 2) * dt;
out_exhaust_temp = state_gas;
out_wall_temp = state_wall;
end
cfg = coder.config('lib');
cfg.TargetLang = 'C';
cfg.GenCodeOnly = true; % Let Cal Suite compile
cfg.GenerateReport = true;
% Define input types (all double scalars)
args = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
codegen -config cfg exhaust_model -args args
% Output: codegen/lib/exhaust_model/
% ├── exhaust_model.c ← Upload this
% ├── exhaust_model.h ← Upload this
% ├── exhaust_model_types.h
% └── rtwtypes.h
FREE No MathWorks license needed
If you have no MATLAB/Simulink license at all, here are your options:
If you have the model equations (from documentation, papers, or reference code), implement directly in C following Cal Suite naming conventions. See Section 9.
GNU Octave runs many MATLAB scripts but does NOT generate C-code. Use it to:
.mat files% In Octave — extract cal values for your C implementation
pkg load io;
data = load('model_BCAL_val.mat');
% Write parameter arrays to a .h file for C inclusion
fid = fopen('model_params.h', 'w');
fprintf(fid, 'const double par_spark_map[120] = {\n');
for i = 1:numel(data.spark_map)
fprintf(fid, ' %.6f,\n', data.spark_map(i));
end
fprintf(fid, '};\n');
fclose(fid);
The Cal Suite Agent can help translate MATLAB/Simulink model descriptions to C:
inp_*/out_*/par_* naming and build the FMU.FREE Full Modelica environment with FMU and C-code export
model ExhaustTempModel
// Inputs → become FMI inputs
input Real inp_engine_speed(start=800) "Engine speed [rpm]";
input Real inp_engine_load(start=0) "Engine load [%]";
input Real inp_ambient_temp(start=25) "Ambient temp [degC]";
input Real inp_mass_flow(start=0.01) "Exhaust flow [kg/s]";
// Outputs → become FMI outputs
output Real out_exhaust_temp(start=25) "Exhaust gas temp [degC]";
output Real out_wall_temp(start=25) "Pipe wall temp [degC]";
// Parameters → become FMI tunable parameters
parameter Real par_thermal_mass = 850 "Thermal mass [J/K]";
parameter Real par_heat_coeff = 45 "Heat transfer [W/(m2.K)]";
parameter Real par_pipe_length = 1.2 "Pipe length [m]";
parameter Real par_pipe_diameter = 0.06 "Pipe diameter [m]";
// State variables
Real T_gas(start=25);
Real T_wall(start=25);
equation
der(T_gas) = (inp_mass_flow * 1100 * (400 + inp_engine_load*6
+ (inp_engine_speed-800)/52 - T_gas)
- par_heat_coeff * Modelica.Constants.pi * par_pipe_diameter
* par_pipe_length * (T_gas - T_wall)) / par_thermal_mass;
der(T_wall) = (par_heat_coeff * Modelica.Constants.pi * par_pipe_diameter
* par_pipe_length * (T_gas - T_wall)
- par_heat_coeff * Modelica.Constants.pi * par_pipe_diameter
* par_pipe_length * 0.3 * (T_wall - inp_ambient_temp))
/ (par_thermal_mass * 2);
out_exhaust_temp = T_gas;
out_wall_temp = T_wall;
end ExhaustTempModel;
# Command line FMU export
omc --simCodeTarget=fmu ExhaustTempModel.mo
# → ExhaustTempModel.fmu
# Test in FMPy first:
python3 -c "from fmpy import simulate_fmu; simulate_fmu('ExhaustTempModel.fmu')"
# Generate C source only (no FMU packaging)
omc --simCodeTarget=C ExhaustTempModel.mo
# Output: ExhaustTempModel/
# ├── ExhaustTempModel.c
# ├── ExhaustTempModel_functions.c
# ├── ExhaustTempModel_records.c
# └── ExhaustTempModel.h
# Then simplify for Cal Suite:
# Extract the equations into a single .c file with inp_/out_/par_ globals
# (Cal Suite Agent can help with this transformation)
input Real inp_* and parameter Real par_* naming, OpenModelica preserves these names in the generated C code, making Cal Suite detection work with minimal post-processing.FREE Simulink-like block diagram modeling with C generation
Use the Xcos block diagram editor (similar to Simulink). Key blocks:
IN_f / OUT_f — Model inputs and outputsLOOKUP_c — 1D interpolation (equivalent to Simulink Lookup Table)INTEGRAL_m — Continuous integratorGAINBLK_f — Tunable gain parameter// Scilab 6.1+ with Code Generation module
importXcosDiagram('exhaust_model.zcos');
// Configure for standalone C generation
CodeGen = scicos_codegeneration();
CodeGen.target = 'C';
CodeGen.solver = 'Fixed'; // Required for FMU
CodeGen.step_size = 0.001;
CodeGen.gen_standalone = %T;
// Generate
do_code_generation('exhaust_model', CodeGen);
// Output: exhaust_model/exhaust_model.c
# Scilab generates generic variable names (u_1, y_1, rpar_1...)
# Rename to Cal Suite convention:
sed -i '' 's/u\[0\]/inp_engine_speed/g' exhaust_model.c
sed -i '' 's/u\[1\]/inp_engine_load/g' exhaust_model.c
sed -i '' 's/y\[0\]/out_exhaust_temp/g' exhaust_model.c
sed -i '' 's/rpar\[0\]/par_thermal_mass/g' exhaust_model.c
# ... rename all variables, then upload to Cal Suite
FREE For models implemented in Python/NumPy
# File: exhaust_model.pyx
# cython: boundscheck=False, wraparound=False
# Inputs (public = visible as C globals)
cdef public double inp_engine_speed = 800.0
cdef public double inp_engine_load = 0.0
cdef public double inp_ambient_temp = 25.0
cdef public double inp_mass_flow = 0.01
# Outputs
cdef public double out_exhaust_temp = 25.0
cdef public double out_wall_temp = 25.0
# Parameters (tunable in MOCA)
cdef public double par_thermal_mass = 850.0
cdef public double par_heat_coeff = 45.0
cdef public double par_pipe_length = 1.2
cdef public double par_pipe_diameter = 0.06
# Internal state
cdef double state_gas = 25.0
cdef double state_wall = 25.0
cpdef void initialize():
global state_gas, state_wall
state_gas = 25.0
state_wall = 25.0
cpdef void calculate(double dt):
global state_gas, state_wall
cdef double T_comb, A_pipe, Q_in, Q_loss
T_comb = 400.0 + inp_engine_load * 6.0 + (inp_engine_speed - 800.0) / 52.0
A_pipe = 3.14159265 * par_pipe_diameter * par_pipe_length
Q_in = inp_mass_flow * 1100.0 * (T_comb - state_gas)
Q_loss = par_heat_coeff * A_pipe * (state_gas - state_wall)
state_gas = state_gas + (Q_in - Q_loss) / par_thermal_mass * dt
state_wall = state_wall + Q_loss / (par_thermal_mass * 2.0) * dt
out_exhaust_temp = state_gas
out_wall_temp = state_wall
# Generate C from Cython (no Python runtime dependency):
cython --embed exhaust_model.pyx
# → exhaust_model.c (upload to Cal Suite)
For Python models that are primarily arithmetic (NumPy math without complex library calls), translate directly to C. The Cal Suite Agent can assist with this automatically.
# Simply upload your Python model to the Cal Suite chat:
# "Build an FMU from this Python model"
# [attach: exhaust_model.py]
#
# The agent will:
# 1. Parse your Python code
# 2. Translate to C with inp_/out_/par_ naming
# 3. Generate the FMI wrapper
# 4. Compile and validate
# 5. Return the .fmu file
FREE Full control, guaranteed Cal Suite compatibility
/* ═══════════════════════════════════════════════════════════
* Cal Suite FMU Generator — C Model Template
*
* RULES:
* 1. Use inp_* for inputs, out_* for outputs, par_* for parameters
* 2. Declare all as GLOBAL doubles at file scope
* 3. Implement initialize() and calculate(double dt)
* 4. Only use: math.h, string.h, stdlib.h (no stdio, no POSIX)
* ═══════════════════════════════════════════════════════════ */
#include <math.h>
/* ═══ INPUTS (set by MOCA each step) ═══ */
double inp_engine_speed = 800.0; /* rpm */
double inp_engine_load = 0.0; /* % */
double inp_ambient_temp = 25.0; /* degC */
double inp_mass_flow = 0.01; /* kg/s */
/* ═══ OUTPUTS (read by MOCA each step) ═══ */
double out_exhaust_temp = 25.0; /* degC */
double out_wall_temp = 25.0; /* degC */
double out_heat_loss = 0.0; /* W */
/* ═══ PARAMETERS (tunable by MOCA optimizer) ═══ */
double par_thermal_mass = 850.0; /* J/K */
double par_heat_coeff = 45.0; /* W/(m2.K) */
double par_pipe_length = 1.2; /* m */
double par_pipe_diameter = 0.06; /* m */
double par_bias_offset = 0.0; /* degC — correction term */
/* ═══ INTERNAL STATE (not visible to FMI) ═══ */
static double s_gas_temp = 25.0;
static double s_wall_temp = 25.0;
void initialize(void) {
s_gas_temp = inp_ambient_temp;
s_wall_temp = inp_ambient_temp;
out_exhaust_temp = inp_ambient_temp;
out_wall_temp = inp_ambient_temp;
out_heat_loss = 0.0;
}
void calculate(double dt) {
/* Guard against bad inputs */
double rpm = fmax(0.0, fmin(8000.0, inp_engine_speed));
double load = fmax(0.0, fmin(100.0, inp_engine_load));
double mdot = fmax(0.001, inp_mass_flow); /* avoid div-by-zero */
/* Physics */
double T_comb = 400.0 + load * 6.0 + (rpm - 800.0) / 52.0;
double A_pipe = 3.14159265 * par_pipe_diameter * par_pipe_length;
double cp = 1100.0;
double Q_in = mdot * cp * (T_comb - s_gas_temp);
double Q_gas_wall = par_heat_coeff * A_pipe * (s_gas_temp - s_wall_temp);
double Q_wall_amb = par_heat_coeff * A_pipe * 0.3 * (s_wall_temp - inp_ambient_temp);
/* Euler integration */
s_gas_temp += (Q_in - Q_gas_wall) / par_thermal_mass * dt;
s_wall_temp += (Q_gas_wall - Q_wall_amb) / (par_thermal_mass * 2.0) * dt;
/* Clamp to physical range */
s_gas_temp = fmax(-40.0, fmin(1200.0, s_gas_temp));
s_wall_temp = fmax(-40.0, fmin(900.0, s_wall_temp));
/* Set outputs */
out_exhaust_temp = s_gas_temp + par_bias_offset;
out_wall_temp = s_wall_temp;
out_heat_loss = Q_gas_wall + Q_wall_amb;
}
ETAS ETAS native tool with production-quality C generation
ASCET_Output/
├── Module.c ← Model implementation (step function)
├── Module.h ← Interface declarations
├── Module_CalPrm.c ← Calibration parameter definitions
├── Module_CalPrm.h ← Parameter declarations (extern)
└── Module_Impl.c ← Internal implementation details
| ASCET Element | ASCET Name Pattern | FMI Causality | Cal Suite Detection |
|---|---|---|---|
| Message (received) | Ve<Module>_<signal> | input | Auto (Ve* without _write) |
| Message (sent) | Ve<Module>_<signal>_write | output | Auto (Ve*_write) |
| Calibration Parameter | Ke<Module>_<param> | parameter (tunable) | Auto (Ke* prefix) |
| System Constant | Ke<Module>_<const> | parameter (fixed) | Auto (const qualifier) |
| Characteristic Curve | Ke<Module>_<curve>[N] | parameter (tunable) | Auto (array) |
| Characteristic Map | Ke<Module>_<map>[N*M] | parameter (tunable) | Auto (2D array) |
.c and .h files.$$$ 1D simulation tool for powertrain, thermal, and fluid systems
GT-SUITE → File → Export → FMU (Co-Simulation)
FMI Version: 2.0
Type: Co-Simulation
Solver: Built-in (GT's solver embedded in FMU)
GT-SUITE → ROM → Export → C Sourceinp_*, out_*, par_*)$$$ System simulation for vehicle and powertrain
Model → Export → FMU 2.0 (Co-Simulation) — mark parameters as "External" for tunability$$$ Production code generator from Simulink (alternative to Embedded Coder)
% In Simulink with TargetLink blocks:
% TargetLink → Code Generation → Generate Code
% Configure: "Generic Target" (not ECU-specific)
% Generated files:
% Model.c — Step function implementation
% Model.h — Interface (I/O struct declarations)
% Model_aa.c — Calibration parameters with A2L annotations
/* TargetLink generates struct-based I/O: */
typedef struct {
Float32 engine_speed; /* → rename to inp_engine_speed */
Float32 map_kpa; /* → rename to inp_map_kpa */
} Model_InType;
typedef struct {
Float32 exhaust_temp; /* → rename to out_exhaust_temp */
} Model_OutType;
/* Cal parameters in Model_aa.c: */
CAL Float32 KP_boost = 2.5; /* → rename to par_kp_boost */
/* Post-processing: flatten structs to global variables with inp_/out_/par_ prefixes.
Cal Suite Agent can perform this transformation automatically. */
| Prefix | FMI Role | FMI Attributes | Example |
|---|---|---|---|
inp_* | Input signal | causality="input" variability="continuous" | double inp_engine_speed = 800.0; |
out_* | Output signal | causality="output" variability="continuous" | double out_exhaust_temp = 25.0; |
par_* | Tunable parameter | causality="parameter" variability="tunable" initial="exact" | double par_heat_coeff = 45.0; |
| Pattern | FMI Role | Example |
|---|---|---|
Ve*_read or Ve* (no _write) | Input | double VeRPMR_n_EngSpd; |
Ve*_write | Output | double VeEXHR_T_UsCatTemp_write; |
Ke* | Parameter | double KeEXHR_T_InitialTemp; |
double type (FMI 2.0 uses fmi2Real = double)static on inp_/out_/par_ variables (makes them invisible to linker)static (e.g., static double s_filtered;)par_map[N] — Cal Suite expands to individual [0,0], [0,1]... in XML| Function | Signature | When Called |
|---|---|---|
| initialize | void initialize(void) | fmi2ExitInitializationMode — reset states |
| calculate / step | void calculate(double dt) | fmi2DoStep — execute one time step |
calculate, step, update, compute, and run are all auto-detected. The Cal Suite scans for void <name>(double) patterns.fmi2Instantiate, fmi2DoStep, fmi2GetReal, fmi2SetReal, and all required API functions| # | Check | What It Verifies |
|---|---|---|
| 1 | ZIP Structure | modelDescription.xml present, binaries present |
| 2 | Causality Correctness | Inputs/outputs/parameters properly classified |
| 3 | MOCA Readiness | Has ≥1 input, ≥1 output, ≥1 parameter; CoSimulation element present |
| 4 | Simulation Smoke Test | Instantiate → step → outputs are not NaN |
| 5 | Fidelity Check | All detected source variables present in FMU (100% coverage) |
| Symptom | Root Cause | Fix |
|---|---|---|
| "No Input available in FMU Model" in MOCA | No inp_* globals found | Declare inputs as double inp_* at file scope (global) |
| "No Output available in FMU Model" | No out_* globals found | Declare outputs as double out_* at file scope |
| Parameters not tunable in MOCA | Parameters declared inside functions or with static | Move to global scope: double par_* |
| Cross-compilation fails (win64) | Code uses POSIX headers (unistd.h, sys/*) | Only include math.h, string.h, stdlib.h |
| Outputs are all zero | calculate() not writing to out_* globals | Ensure step function assigns to global out_* variables |
| Parameter changes have no effect | Parameters read once in initialize() only | Read par_* globals inside calculate() each step |
| NaN/Inf in outputs | Division by zero or unbounded growth | Add input clamping (fmax/fmin) and guards |
| FMU works in FMPy but not MOCA | VendorAnnotations, wrong variability, missing CoSim element | Let Cal Suite generate XML (don't hand-edit) |
| MOCA crashes on load | <VendorAnnotations> in XML | Remove all VendorAnnotations (Cal Suite never adds them) |
| Array parameter naming mismatch | 1-based indexing in names | Use 0-based: par_map[0,0] not [1,1] |
| "GUID mismatch" error | XML rebuilt without recompiling binary | Regenerate both XML and binary from same build |
| Model runs but gives wrong values | Lookup table axis mismatch or wrong units | Verify axis values and interpolation order (row vs col major) |
#include <stdio.h> — printf/scanf fail on cross-compile#include <unistd.h> — POSIX only, not available on Windows#include <sys/time.h> — platform-specificmalloc() / free() — avoid dynamic allocation in model codefopen, fread) — not available in FMU sandboxmath.h, string.h, stdlib.h (for abs()), float.h, limits.h, stdint.h| Source Tool | License | Export Method | Post-Processing | Cal Suite Ready |
|---|---|---|---|---|
| Simulink + Embedded Coder | $$$ | ERT code generation | None (use inp_/out_/par_ naming) | ★★★★★ |
| Simulink + SWIFT | $$$ | SWIFT wrapper + headers | None (auto-parsed) | ★★★★★ |
| Simulink stub/cal files | $$ | Export .m files | None (auto-parsed) | ★★★★★ |
| MATLAB Coder | $$ | MATLAB function → C | Minimal (naming) | ★★★★☆ |
| ASCET-DEVELOPER | ETAS | Native C generation | None (ETAS naming) | ★★★★★ |
| dSPACE TargetLink | $$$ | TargetLink code gen | Flatten structs to globals | ★★★★☆ |
| GT-SUITE (ROM) | $$$ | ROM → C export | Rename variables | ★★★☆☆ |
| AVL CRUISE M | $$$ | FMU or Modelica export | May need rebuild via Cal Suite | ★★★☆☆ |
| OpenModelica | FREE | Modelica → C or FMU | Simplify generated C | ★★★☆☆ |
| Scilab/Xcos | FREE | Xcos code generation | Rename variables (sed) | ★★★☆☆ |
| Python (Cython) | FREE | Cython → C | Minimal | ★★★★☆ |
| Hand-written C | FREE | Direct implementation | None | ★★★★★ |
| AI Translation (Cal Suite) | FREE | Describe model → C | None (agent handles it) | ★★★★★ |