ICore Blocks / Features / User code

Your code,
in the loop.

Some of a model is never going to be a block. Write it in C or Python, wire it onto the canvas, and it runs every step like anything else there: it reads its inputs, runs your compute(), and writes its outputs.

C · compiled by your compiler Python · numpy, bundled Called once per step Saved in the project
One rate limiter, written twice Sampling Time 0.1 s
C PYTHON Step at 0.5 s .c C Code .py Python Code 01 01
C Code · Edit Code.c
/* Slew-rate limiter: the output chases u[0]
   at no more than 2 units/s. */
void compute(double t, const ICoreCMatrix* u, int uCount,
             ICoreCMatrix* y, int yCount) {
    /* statics persist between steps, reset every run */
    static double last = 0.0, tPrev = 0.0;
    (void)uCount; (void)yCount;
    double step = 2.0 * (t - tPrev);
    double d = ICORE_AT(u[0], 0, 0) - last;
    if (d >  step) d =  step;
    if (d < -step) d = -step;
    last += d;
    tPrev = t;
    y[0].rows = 1;
    y[0].cols = 1;
    ICORE_AT(y[0], 0, 0) = last;
}
Python Code · Edit Code.py
import numpy as np

def compute(t, u, state):
    # Slew-rate limiter: the output chases u[0]
    # at no more than 2 units/s.
    last = state.get("last", 0.0)
    step = 2.0 * (t - state.get("t", 0.0))
    d = float(np.clip(u[0][0, 0] - last, -step, step))
    state["last"], state["t"] = last + d, t
    return [np.array([[last + d]])]
Calls to compute()21 per lane
Output0 → 1 in 0.5 s
C vs Pythonidentical at every sample

One limiter written twice, once for each block. A unit step arrives at 0.5 s, and each lane's compute() is called once per 0.1 s sample: one dot per call. For this page, the C body was compiled against the block's own prelude and the Python body run with numpy, and the two agree at every sample. State lives in a C static or in Python's state dict, and both are reset at every simulation start.

01 · The contract

One function. That's the whole API.

Each block calls a single compute() of yours. Everything it needs arrives as arguments, and everything it produces goes out through the output ports.

C CodePython Code
Signaturevoid compute(double t, const ICoreCMatrix* u, int uCount, ICoreCMatrix* y, int yCount)def compute(t, u, state):
Inputsu[0] … u[uCount-1], read-only, row-major; element (i, j) is ICORE_AT(m, i, j)a list of numpy matrices, one per input port, in canvas order
Outputsset y[i].rows and .cols, fill .data (room for 4,096 doubles)return a list, one entry per output port; float, int or bool all work
Memorystatic variables, reset at every simulation startthe state dict, persisting between steps
Port typeseach matrix's read-only kind: floating, integer, boolean, string or buseach array's own dtype; a String port arrives as a str
Runs onyour system C compiler, loaded in-process; math.h, stdlib.h, string.h pre-includedthe Python inside ICore Blocks, with numpy

Both blocks start with one input and one output, and you can change both counts. Every port is a double unless you retype it, and the new type reaches your code without anything extra to declare.

02 · Every run

A block like any other.

Once it's on the canvas, the solver treats your code the way it treats a library block.

01 · Build

Sizes, learned

Output sizes come from a trial call on zero inputs when the model is built. After that they stay fixed for the run.

02 · Start

A clean slate

C statics are re-zeroed and the state dict starts empty at every simulation start. The compiled library is cached by its source text, so the build does not compile the same code twice.

03 · Step

Once per sample

compute() runs at the block's Sampling Time, or at the solver's rate when that is zero or less. It is stepped, never integrated.

04 · Fail

Loudly, by path

A compile error, or a Python exception, is reported with the block's path and stops the run.

03 · Where it goes

Exports to its own language. Nowhere it would break.

Hand-written C can't be translated mechanically, and none of the other targets can carry a Python interpreter. So each block exports to the one language it's written in, and refuses the other nine by name rather than emitting code that won't run.

BlockCC++RustPythonMATLABJavaVHDLVerilogSVPLC ST
C Code✓---------
Python Code---✓------
C → C

Embedded verbatim

Your code goes into the generated C core as written. Each block's compute is renamed, so several C Code blocks can share one export.

Python → Python

Embedded, indented

Your function is embedded, indented, into the generated Python module, so the exported model carries your code as written.

Simulink · both ways

Carried as text

The blocks cross to Simulink's C Function and Python Code blocks. The wiring survives, and your source moves into their Output Code. The two contracts differ, so the code needs adapting on the other side. The MATLAB bridge →

If a subsystem has to reach an FPGA, keep hand-written code out of that subtree. Or, once the code settles, promote it to a real block with its own code for every target. That's what the Block Wizard is for.

04 · Part of the model

It travels with the project.

The source you type is stored in the model itself, in the project file and in the diagram's recipe text. A model with user code in it is still one file to save, share, script, or hand to an agent.

  • LibraryControl Systems › User Defined: C Code and Python Code
  • EditorThe Edit Code button on the block's config dialog opens the code editor
  • TemplateCustom Code Blocks places both, fed from one sine, with a scope for each
  • CompilerChosen on Configuration › Toolchains, or with toolchain set cc <path>
C block · Python block
A diagram with a C code block and a Python code block side by side A diagram with a C code block and a Python code block side by side
The shipped Custom Code Blocks template. The same sine drives both lanes, so the two scopes agree while the two bodies do. Change one body and the lanes separate on screen.
Honest edges

What to know before you rely on it.

  • C runs in-processYour compiled C runs inside the application, and there is no sandbox. A bad pointer or a division-by-zero trap is a crash of the app, so guard your own bounds.
  • A compiler, for CThe C Code block needs a C compiler on the machine. Python needs nothing extra: it ships inside the application with numpy, and can't be swapped for another installation.
  • Fixed shapesOutput sizes are set when the model is built and can't change during a run. A C output holds at most 4,096 elements.
  • Discrete onlyBoth blocks are stepped at a sample rate. For continuous dynamics, use library blocks or a state space.
  • One target eachC Code exports only to C, and Python Code only to Python. An export to any other target fails for these blocks, and says so.
  • Shared scope in CHelpers and statics you define are file-scope in the generated C, so prefix them when a model carries more than one C Code block.

In the documentation: C Code  ·  Python Code  ·  Toolchains  ·  Templates

See also: Block Wizard - your own library  ·  Code export

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.