ICore Blocks / Targets / Software

C.c

A C core with fixed-size storage and no allocation at run time - the target most microcontrollers and RTOS tasks actually want.

Software Verified with gcc or clang double 801 of 806 blocks
✓ verified · 0.1 %
#ifndef DPT_FEEDBACK_DISCRETE_DEPLOYABLECORE_H
#define DPT_FEEDBACK_DISCRETE_DEPLOYABLECORE_H

/* Tunable parameters (editable from the testbench) */
typedef struct {
    double blk2_gain[1][1];
} Params;

/* Generated signal storage - one fixed-size matrix per output port */
typedef struct {
    /* sig0: ICore Blocks/Home/DPT_Feedback_Discrete/In1/ICoreDouble-Out-0 */
    double sig0[1][1];
    /* sig1: ICore Blocks/Home/DPT_Feedback_Discrete/Error/ICoreDouble-Out-0 */
    double sig1[1][1];
    /* sig2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain/ICoreDouble-Out-0 */
    double sig2[1][1];
    /* sig3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant/ICoreDouble-Out-0 */
    double sig3[1][1];
    /* sig4: ICore Blocks/Home/DPT_Feedback_Discrete/ICoreDouble-Out-0 */
    double sig4[1][1];
} Signals;

/* External inputs: set these before execute_blocks; each top-level input gate
   copies its field into signal storage. */
typedef struct {
    double sig0[1][1];
} Inputs;

/* Persistent block state */
typedef struct {
    double blk3_u_hist[1][1][2];
    double blk3_y_hist[1][1][1];
} State;

/* Deployable core: owns params, signals and persistent block state */
typedef struct {
    Params  params;
    Signals signals;
    Inputs  inputs;
    State   state;
} DeployableCore;

void DeployableCore_init(DeployableCore* core);
void execute_blocks(DeployableCore* core);

#endif
#include "DPT_Feedback_Discrete_deployableCore.h"
#include <string.h>
#include <math.h>
#include <stdio.h>

/* blk0: ICore Blocks/Home/DPT_Feedback_Discrete/In1 */
static void blk0_solve(DeployableCore* core) {
    memcpy(core->signals.sig0, core->inputs.sig0, sizeof(core->signals.sig0));
}


/* blk1: ICore Blocks/Home/DPT_Feedback_Discrete/Error */
static void blk1_solve(DeployableCore* core) {
    double output[1][1] = {0};
    double in0[1][1];
    memcpy(in0, core->signals.sig0, sizeof(in0));
    for (int i = 0; i < 1; i++) {
        for (int j = 0; j < 1; j++) {
            output[i][j] += in0[i][j];
        }
    }
    double in1[1][1];
    memcpy(in1, core->signals.sig3, sizeof(in1));
    for (int i = 0; i < 1; i++) {
        for (int j = 0; j < 1; j++) {
            output[i][j] -= in1[i][j];
        }
    }
    memcpy(core->signals.sig1, output, sizeof(output));
}


/* blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain */
static void blk2_solve(DeployableCore* core) {
    double input[1][1];
    memcpy(input, core->signals.sig1, sizeof(input));
    double output[1][1] = {0};
    for (int i = 0; i < 1; i++) {
        for (int j = 0; j < 1; j++) {
            output[i][j] = input[i][j] * core->params.blk2_gain[0][0];
        }
    }
    memcpy(core->signals.sig2, output, sizeof(output));
}


/* blk3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant */
static void blk3_solve(DeployableCore* core) {
    static const double num[2] = {0, 0.40000000000000002};
    static const double den[1] = {-0.59999999999999998};
    for (int r = 0; r < 1; r++) {
        for (int c = 0; c < 1; c++) {
            const double uk = core->signals.sig2[r][c];
            for (int k = 1; k > 0; k--) core->state.blk3_u_hist[r][c][k] = core->state.blk3_u_hist[r][c][k - 1];
            core->state.blk3_u_hist[r][c][0] = uk;
            double yk = 0.0;
            for (int i = 0; i < 2; i++) yk += num[i] * core->state.blk3_u_hist[r][c][i];
            for (int i = 0; i < 1; i++) yk -= den[i] * core->state.blk3_y_hist[r][c][i];
            core->state.blk3_y_hist[r][c][0] = yk;
            core->signals.sig3[r][c] = yk;
        }
    }
}


/* blk4: ICore Blocks/Home/DPT_Feedback_Discrete/Out1 */
static void blk4_solve(DeployableCore* core) {
    memcpy(core->signals.sig4, core->signals.sig3, sizeof(core->signals.sig4));
}


void DeployableCore_init(DeployableCore* core) {
    memset(&core->signals, 0, sizeof(core->signals));
    memset(&core->inputs,  0, sizeof(core->inputs));
    memset(&core->state,   0, sizeof(core->state));
    /* Default parameters (override from the testbench after init) */
    core->params.blk2_gain[0][0] = 1.800000; 
}

/* Execution order generated automatically from block diagram */
void execute_blocks(DeployableCore* core) {
    /* blk0: ICore Blocks/Home/DPT_Feedback_Discrete/In1 */
    blk0_solve(core);
    /* blk1: ICore Blocks/Home/DPT_Feedback_Discrete/Error */
    blk1_solve(core);
    /* blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain */
    blk2_solve(core);
    /* blk3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant */
    blk3_solve(core);
    /* blk4: ICore Blocks/Home/DPT_Feedback_Discrete/Out1 */
    blk4_solve(core);
}

What Deploy writes for the reference model below, with only the file's header banner removed. Params, signals, inputs and state are four plain structs inside one DeployableCore; each block is a static function over it, and execute_blocks is the order the diagram implies, resolved once at export time. The feedback is visible in blk1_solve: it reads sig3, the plant output that blk3_solve writes later in the same pass - so the loop carries the previous sample, which is what a discrete feedback loop means. Nothing here allocates, and the only headers are string.h, math.h and stdio.h.

Export · Verify · Integrate

From the diagram to your C build.

Deploy writes the folder, ICore checks it against the simulation, and your code calls it once per sample.

01 · ExportICore

Deploy writes

  • <name>_deployableCore.h / .c
  • <name>_testbench.c
  • CMakeLists.txt
→ code/<name>/
02 · VerifyICore

Built, run and compared

Built with gcc or clang, run across the simulation window and compared with the solver sample by sample, against a 0.1 % tolerance.

observed ≈1e-11 %
03 · IntegrateYour build

One call, one sample

Numbers are double, and 801 of 806 library blocks export to C.

DeployableCore_init(&core); … execute_blocks(&core);
With verification on, a failed comparison stops the export and says why, so a core that disagrees with the simulation never reaches your folder.
Software target

Where C fits.

The core is a plain struct plus functions. You initialise it once, set the inputs, and call execute_blocks once per sample; the outputs are struct members you read straight after. There is no allocator, no hidden state and no dependency beyond the standard library, so it drops into a bare-metal build as easily as a desktop one.

C is the only target that can carry a C block - the user-code block that runs your own source inside the loop. It supports 801 of the 806 library blocks: everything except the Python block, the two Python-model blocks, the Hit Scheduler, which steers a variable-step solver that exported code does not have, and the subsystem block, which writes no code of its own because its contents do.

$ Code Engine → Code Export Verifier → Verify All
Target classToleranceObserved
Software · 7 languages0.1 %≈1e-11 %
HDL · Q16.161 %≈1e-3 %
C runs the same double-precision arithmetic as the solver, so anything above noise would be a real defect. Passing runs measure about 1e-11 %. See verification.
The reference model

One model, ten targets.

Every target page shows the same model, so the ten are directly comparable: an input, an error junction, a gain of 1.8, a discrete plant and an output, with the plant's output fed back into the junction. The export-verification suite calls it DPT_Feedback_Discrete - which is where the names in the file come from.

DPT_Feedback_Discrete · 5 blocks
In1 Σ + − Error × 1.8 Ctrl_Gain 0.4z⁻¹ 1 − 0.6z⁻¹ Plant Out1 the previous sample, fed back
Get started

See it run on your own model.

Download the application from the customer portal, or read the documentation first - the manual, a page for every block, and the full command reference are public.