C Code — Control Systems/User Defined
Control_Systems/User_Defined/C_Code · 1 input / 1 output port(s) at insert · exports to C
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
C Code
Control Systems / User Defined
Runs C you write, once per simulation step, compiled by the system C compiler at simulation start. Use it for anything the library has no block for – a nonlinearity, a lookup, a hand-written controller – when the model must also export to C.
The contract
Your code defines one function:
void compute(double t, const ICoreCMatrix* u, int uCount,
ICoreCMatrix* y, int yCount)
- t – the current simulation time, in seconds.
- u – the input ports in order,
u[0]tou[uCount-1], each anICoreCMatrix(rows,cols, row-majordata). Treat them as read-only. - y – the output ports in order. Set each
y[i].rowsandy[i].colsand filly[i].data(capacityICORE_MAX_MATRIX_ELEMENTS= 4096 doubles). Element (i, j) of a matrixmisICORE_AT(m, i, j). - state – declare
staticvariables for anything the block must remember; they persist between steps and are reset at every simulation start.
Ports
- Inputs and Outputs – one of each by default, and both
counts are user-editable.
uandyfollow the port order on the canvas. Output sizes are learned from a trial call on zero inputs at build time and must stay fixed during a simulation.
Parameters
- C Code – edited in the code editor, opened from the
Edit Code button on the config dialog's toolbar rather than in the
parameter pane.
math.h,stdlib.handstring.hare pre-included. - Sampling Time (s) – how often
computeis called. Zero or less inherits the solver's rate.
Code export
C only – your code is embedded verbatim into the generated C
core (with compute renamed per block, so several C Code blocks
can share one export). The other nine targets (Python, MATLAB, Java, Rust,
C++, VHDL, Verilog, SystemVerilog, PLC Structured Text) are not
supported: hand-written C cannot be mechanically translated, so an export
to any of them fails loudly for this block rather than emitting something
that does not run. Replace the block with library blocks, or with a state
space, when the model has to reach those targets.
Simulink bridge
Both directions, to simulink/User-Defined Functions/C
Function. The ports cross as SymbolSpec symbols (u1…,
y1…, in port order) so the wiring survives, and the code crosses
into the block's Output Code as text – C Code ↔
OutputCode. The two contracts differ: ICore calls your
compute once per step, while the C Function block runs its
output code directly over the symbols, so the code needs adapting after
crossing before the model runs on the other side. Sampling Time (s)
→ SampleTime, as on every block.
Notes
- Discrete by nature: the block is stepped, never integrated, since its state lives in C statics that update per sample.
- A compile error in your code is reported with the block's path and stops the run. Your compiled code runs in-process: a crash in it (a bad pointer, a division by zero trap) is a crash of the app, so guard your own bounds.
- Helper functions and statics you define are file-scope in the generated C export – prefix them if the model carries more than one C Code block.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/User_Defined/C_Code |
| family | Control_Systems/User_Defined |
| solver environment class | ICoreBlock_0_Control_Systems_1_User_Defined_2_C_Code |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/User_Defined/C_Code/ICoreBlock_0_Control_Systems_1_User_Defined_2_C_Code.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/User_Defined/C_Code/ICoreBlock_0_Control_Systems_1_User_Defined_2_C_Code.h |
| default size on canvas | 100 × 80 px |
| ports at insert | 1 in, 1 out |
| code generators implemented | C |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | — |
| 2 | out | ICoreDouble | — |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
CONFIG_C_CODE (unresolved) | DEFAULT_CODE_TEMPLATE | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::Both |
| Simulink path | simulink/User-Defined Functions/C Function |
| port-count rule | PortsParam:: |
SampleTime parameter | yes |
| ports | derived from a SymbolSpec (addSymbol), not a port-count parameter |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
C Code | OutputCode | passes through |
Caveat (shown to the user): the code crosses into the C Function block's OutputCode as text, but the contracts differ: ICore calls compute(t, u, uCount, y, yCount) while Simulink runs the output code directly over its SymbolSpec symbols (u1.., y1..), so the code needs adapting after crossing
Catalog contract: src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
⚠ Mismatch. Fixed at the source — the description or the code, whichever is wrong (R-D9) — never explained away on a docs page:
B7no ICoreSimulinkBlockCatalog::registerEntry — the bridge treats an unregistered type as unsupported, so the block owes an entry
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
C Code block — user-editable compute(t, u, uCount, y, yCount) over row-major matrices The user's source lives in a private config variable (edited via the config dialog's "Edit Code" toolbar button, persisted base64 by the recipe serializer). Contract:
void compute(double t, const ICoreCMatrix* u, int uCount, ICoreCMatrix* y, int yCount)
- t: time [s]; u: input ports in order (read-only, row-major);
y: output ports in order — SET rows/cols and fill data. statics persist between steps and reset at every simulation start. */
Simulation: discrete-only block; compute_h_discrete runs the code compiled by the system C compiler and loaded in-process (ICoreCRuntime). Output port sizes are inferred at build time by a trial call on zero inputs; the compiled library is cached by source text since the build's port-size pass repeats and a compiler run per pass would crawl. Code export: C only — every other language keeps the base class's empty default, which the exporters report as unsupported.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|
| 0 | -2 | -2 |
| 0.4 | 0.5 | 0.5 |
| 0.8 | -2 | -2 |
| 1.2 | 0.5 | 0.5 |
| 1.6 | -2 | -2 |
| 2 | 0.5 | 0.5 |
| 2.4 | -2 | -2 |
| 2.8 | 0.5 | 0.5 |
| 3.2 | -2 | -2 |
| 3.6 | 0.5 | 0.5 |
| 4 | -2 | -2 |
| 4.4 | 0.5 | 0.5 |
| 4.8 | -2 | -2 |
| 5.2 | 0.5 | 0.5 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 1 |
ramp | Ramp: slope 1 from t = 0 | 0 … 5.8 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -1 … 0.9996 |
step | Step: 0 -> 1 at t = 1 s | 0 … 1 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/Control_Systems__User_Defined__C_Code.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).