User manual › Exporting code — the ten targets, what each produces, and what verification proves
kind: manual#code-export#deploy#c#cpp#python#rust#java#matlab#vhdl#verilog#systemverilog#plc#structured-text#verification#residual#q16.16#synthesizable

Exporting code — the ten targets, what each produces, and what verification proves#

ICoreBlocks turns a diagram level (a subsystem, or the whole Home level) into a stand-alone program in one of ten languages: Python, MATLAB, Java, Rust, C++, C, System Verilog, Verilog, VHDL and PLC - ST (IEC 61131-3 Structured Text). The list is fixed in src/ICoreSDK/ICoreCodegen/ICoreCodegen.cpp (AVAILABLE_CODE_TYPES); every one of the ten can also be verified — compiled and run on your machine and compared against ICoreBlocks' own simulation of the same diagram (VERIFIABLE_CODE_TYPES, same file). Whatever the language, the export is the same shape: a deployable core that advances the model by one sample per call, plus a testbench that drives it through the simulation window, and where the language has a build system, a manifest that makes the folder build as-is.

What you do in the app#

  1. Set the global solver to fixed-step or discrete (Solver Configuration, or setModelConfig in the console); a variable-step solver refuses to export (message under the traps below).
  2. Open the Deploy page of the left panel — the Deploy To Hardware panel — or the menu Code Engine → Export → Deploy Targets. Press Create Target.
  3. Fill in the target's five fields: Target Name, Scripting (the language), Verification (None, Output Gates Only, Output Gates and Sink Blocks, Strict - All Signals), Source Subsystem (defaults to :/Home, i.e. the whole model) and Target Path (Select Folder; defaults to your ICoreBlocks documents folder, ~/Documents/ICore Blocks).
  4. Press Deploy on the target, or Deploy All. Failures are reported as Code Deploy Failed — check the Quick Code logger for details.

Targets are saved next to the project in exportTargets.ini, so they come back with it. The same targets are what Code Engine → Code Export Verifier (Fetch Targets, Verify This Target, Verify All) verifies; that window is where the extra knobs live: Residual Tolerance (%) (default 1 %), Testing Pulses Width (sample), Testing Amplitude Min / Max (default −1 … 1), and a Compiler section with Auto-scan or a hand-picked toolchain path.

The rules the export enforces#

  • Fixed-step or discrete solver only. The generated core has no notion of a variable step; export refuses otherwise (message under the traps).
  • One sampling rate across the exported subtree. A block or nested subsystem running at a different rate than the source stops the export: "The subsystem is multi-rate; code export verification needs a uniform sampling time." (ICoreCodeExportVerifier.cpp, emulateTarget; the plain export runs the same check in fireTarget).
  • The model name becomes the file and identifier stem. Anything that is not [A-Za-z0-9] collapses to a single _, leading/trailing _ are dropped, a leading digit is prefixed with ICore_, and an empty result becomes ExportedModel (ICoreCodeEngine::sanitizeCodeIdentifier, ICoreCodegen.h). VHDL and Structured Text are the strictest of the ten and set these rules for all of them.
  • A verification level other than None runs the verification before writing. If it fails, or the residual is above the tolerance, the app asks "Would you like to continue exporting? Choosing No aborts the whole run, not just this target." — headless, that question is answered No (ICoreCodegen.h, QuestionHandler fallback).

What each target produces#

<name> is the sanitized subsystem name. Every language writes into the Target Path folder you chose (ICoreCodegen.cpp, fireTarget; the per-language writers are in src/ICoreSDK/ICoreCodegen/CodeParsers/).

ScriptingFiles written by DeployEntry point, one call = one sample
Python<name>_deployableCore.py, <name>_testbench.pymodule-level execute_blocks(); needs numpy
MATLAB<name>_deployableCore.m, <name>_testbench.mclassdef <name>_deployableCore < handle, core.execute_blocks()
Java<name>_deployableCore.java, <name>_testbench.javapublic class <name>_deployableCore, executeBlocks()
Rust<name>_deployableCore.rs, <name>_testbench.rs, Cargo.tomlpub struct DeployableCore, execute_blocks(&mut self)
C++<name>_deployableCore.hpp, <name>_testbench.cpp, CMakeLists.txtstruct DeployableCore, execute_blocks()
C<name>_deployableCore.h / .c, <name>_testbench.c, CMakeLists.txtDeployableCore_init(&core), then execute_blocks(&core)
VHDLicore_pkg.vhd, <name>.vhd, <name>_tb.vhd, Makefile (GHDL)entity <name> — one rising clk edge = one sample
Verilogicore_defs.vh, <name>.v, <name>_tb.v, Makefile (Icarus)module <name> — one clk edge = one sample
System Verilogicore_defs.svh, <name>.sv, <name>_tb.sv, Makefile (Icarus, -g2012)module <name>
PLC - ST<name>_deployableCore.st, <name>_testbench.stFUNCTION_BLOCK FB_<name> — one call = one scan

Common to all: the core holds params (tunable after export without regenerating — a Gain's gain, say), signals (one fixed-size matrix per output port, sig0, sig1, …, each commented with its block and port), inputs (set before each call) and persistent block state; storage is fixed-size, nothing is allocated at run time, and the block order is generated from the diagram. Software targets compute in double (LREAL in ST); the three HDL targets compute in fixed point, see below.

Which blocks support which target#

Each block's page is the truth for that block. Every page under Block catalog — every library block carries a Code export paragraph naming the targets it implements, and that paragraph is checked against the block's code on every documentation build. Do not work from a table here; work from the block's page.

Measured 2026-08-17 over the 308 library blocks (a block supports a target when it, or a base it inherits from, implements generateBodyCode_<Lang> — the same rule the checker uses): 306 blocks support all ten targets. The two exceptions are the user-code blocks — Control_Systems/User_Defined/C_Code exports to C only, and Control_Systems/User_Defined/Python_Code to Python only — so the per-target totals are Python 307, C 307, and 306 for each of MATLAB, Java, Rust, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text.

"Simulation-only" on the HDL targets#

The three HDL targets carry every signal in one fixed-point format, Q16.16 (16 integer bits including sign, 16 fractional bits — a range of about ±32768 and a step of 1.5e-5), declared in the generated icore_pkg.vhd / icore_defs.vh / icore_defs.svh. The format, its rounding, and what it does to your numbers are on Numerics — what the solver will and will not do.

A block whose arithmetic exists in that format — add, multiply, compare, delay, saturate — gets a synthesizable HDL body. A block whose arithmetic does not — a square root, a four-quadrant arctangent, sin/cos, a division by a signal rather than a constant, a random-number generator, or a source that reads simulation time — gets a body written in the simulator's real type that quantizes only at the port boundary. Such a core simulates correctly under GHDL or Icarus and passes verification, but a synthesis tool will not build it into logic. Two places tell you which case a block is:

  • the block's page, whose Code export paragraph says whether the three HDL targets are genuinely synthesizable Q16.16 or simulation-only, and why (Cartesian To Polar: "A square root and a four-quadrant arctangent have no Q16.16 form to call");
  • the generated file itself, which carries a comment such as -- Cartesian To Polar: SIMULATION-ONLY real arithmetic at the block's step.

Measured 2026-08-17 by a text search of the descriptions: 133 of the 308 blocks say their HDL targets are simulation-only. Nothing about this affects the seven software targets, which use double throughout.

What verification proves — and what it does not#

Mechanism. For a target with a verification level, the app writes a random stimulus (<name>_input.csv, uniform in Testing Amplitude Min…Max, one fresh draw per sample by default; Testing Pulses Width holds each draw for that many samples) into <project>/CodeExportTests/, exports a recording testbench there, builds and runs it with your toolchain, then runs ICoreBlocks' own simulation of the same subsystem on the same CSV and compares the two tables column by column (ICoreCodeExportVerifier.cpp, emulateTarget, getSimulatedSourceData, verifyMatrices). The verdict is the residual: the largest absolute difference expressed as a percentage of the simulated signal's peak, against the Residual Tolerance (%). The Code Export Verifier window plots Emulated vs Simulated and Residuals (Emulated − Simulated), lists max / RMS / mean absolute error, and shows the whole toolchain conversation under Compiler Log.

Toolchains it needs, and how a missing one reads. Auto-scan looks on PATH and in the usual install places (/opt/homebrew/bin, /usr/local/bin, /usr/bin, ~/.cargo/bin, the JDKs, /Applications/MATLAB_*.app); or point the target at a binary under Compiler → Auto-scan off. What each language runs (emulateTarget):

ScriptingNeedsMessage when absent
Pythonpython3 with numpyNo Python installation with numpy was found. Install numpy and try again.
C / C++gcc / g++C compiler (gcc) not found. / C++ compiler (g++) not found.
Rustrustcrustc not found.
Javaa JDK (javac + java)No JDK found to compile Java.
MATLABmatlabMATLAB not found.
VHDLghdlghdl not found.
Verilog / System Verilogiverilog + vvpiverilog/vvp not found.
PLC - STmatiec's iec2c (built in the app's own tree) and gccmatiec (iec2c) not found. Build the vendored matiec (…)

The message appears as a warning notification and in the Run Diagnosis log, and the same text is the row's verdict. (Builds before 2026-08-17 instead reported such a row as "Each matrix must have at least two columns (time + one signal)." — see the trap below.)

What a green result means, exactly: the exported code, compiled by your toolchain, reproduces ICoreBlocks' simulation of this subsystem on this stimulus, to within the tolerance. It does not mean the simulation is right (the reference is ICoreBlocks itself, not an independent model), it says nothing about inputs the stimulus never produced (a threshold the random signal never crossed, an exact tie, a value at ±π), it does not check what a sink block writes, and it cannot notice a parameter that changes nothing. Verification is a consistency check between the ten generators and the simulator — a strong one, but that is all it is.

Things that surprise people#

  • Every row of one language fails with the same toolchain message (rustc not found., C build failed.) — there is no output table to compare. Fix the toolchain, not the diagram. On a build older than 2026-08-17 the same rows read "Each matrix must have at least two columns (time + one signal)." with the real cause only in the log just above (emulateTarget: rustc not found.); that was a reporting defect (the empty-result test looked at the matrix's size, and an empty result is 1×1), fixed at the source since. (Observed 2026-08-17 on the pre-fix binary, run below: with no ~/.cargo visible all five Rust rows read this way; with rustc reachable, 5/5 pass.)
  • "Code export is not available for variable-time step. Deployment aborted; set the global solver to fixed-step or discrete and deploy again." — do exactly that; export never runs under a variable-step solver (ICoreCodegen.cpp, fireTarget).
  • "The subsystem is multi-rate." One block in the subtree carries a different sample time. Make them equal, or clear the block's own sample time so it inherits the solver's.
  • A HDL residual that is small but not zero. The software targets typically report a residual of 0 or ~1e-14 %; VHDL / Verilog / System Verilog report ~1e-3 % — that is the Q16.16 quantum, not a defect (rows in the run below).

Real runs (2026-08-17)#

Binary build-mac/ICoreBlocks.app, built 2026-08-16 23:39 (approximately commit 2e126fbf). The console command that exercises the whole path headlessly is exportVerify (see The command window — the command engine for a user): it builds a small test diagram per block configuration into a scratch project under the folder you give it, exports, builds and verifies every language, and writes export_verify_results.json there. Filtered to the Gain block, all ten languages, into a scratch folder:

$ HOME=<scratch>/home QT_QPA_PLATFORM=offscreen ICoreBlocks.app/Contents/MacOS/ICoreBlocks \
    --console "exportVerify <scratch>/verify --blocks Gain"
PRERELEASE-PROGRESS 1/50 PT_Gain / Python PASS
PRERELEASE-PROGRESS 2/50 PT_Gain / C++ PASS
PRERELEASE-PROGRESS 3/50 PT_Gain / C PASS
PRERELEASE-PROGRESS 4/50 PT_Gain / Rust FAIL
…  (one line per rig x language)
blocks: 5/529 selected by --blocks Gain
rigs: 24 block(s), 14 connection(s) created
45/50 passed  (5 rig(s) x 10 language(s))
results written to <scratch>/verify/export_verify_results.json

The rows for the first configuration, read back from that JSON (residual is a percentage of the simulated peak; tolerance is 0.1 % for software targets, 1 % for HDL):

rig      language        pass  residual%   maxAbs    tol%  reason
PT_Gain  Python          PASS  0           0         0.1
PT_Gain  C++             PASS  0           0         0.1
PT_Gain  C               PASS  0           0         0.1
PT_Gain  Rust            FAIL  -           -         0.1   Each matrix must have at least two columns (time + one signal).   ← pre-2026-08-17 binary; now reads "rustc not found."
PT_Gain  Java, MATLAB    PASS  0           0         0.1
PT_Gain  VHDL            PASS  0.00105     2.62e-05  1
PT_Gain  Verilog         PASS  0.001069    2.67e-05  1
PT_Gain  System Verilog  PASS  0.001062    2.65e-05  1
PT_Gain  PLC - ST        PASS  0           0         0.1

The Rust failure is the toolchain trap: the log holds emulateTarget: rustc not found. (the scratch home folder had no ~/.cargo). Re-run with rustc reachable, Rust only:

$ … --console "exportVerify <scratch>/verify Rust --blocks Gain"
PRERELEASE-PROGRESS 1/5 PT_Gain / Rust PASS
…
5/5 passed  (5 rig(s) x 1 language(s))
PT_Gain  Rust  PASS  residual%=1.76e-16  maxAbs=4.39e-18  seed=3721853574

What the export left in <scratch>/verify/ParityRigs/CodeExportTests/ for PT_Gain (ls, trimmed to that stem):

icore_defs.svh  icore_defs.vh  icore_pkg.vhd  PT_Gain.v  PT_Gain.sv  PT_Gain.vhd
PT_Gain_deployableCore.{c,h,hpp,java,m,py,rs}  PT_Gain_testbench.{c,cpp,java,m,py,rs}
PT_Gain_tb.{v,sv,vhd}  PT_Gain_input.csv  PT_Gain_output.csv
_plc_PT_Gain/PT_Gain_deployableCore.st  _plc_PT_Gain/PT_Gain_harness.c

The C core, PT_Gain_deployableCore.c (65 lines; the diagram is In1 → Gain 2.5 → Out1):

/* blk1: ICore Blocks/Home/PT_Gain/Gain */
static void blk1_solve(DeployableCore* core) {
    double input[1][1];
    memcpy(input, core->signals.sig0, 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.blk1_gain[0][0];
        }
    }
    memcpy(core->signals.sig1, output, sizeof(output));
}

/* Execution order generated automatically from block diagram */
void execute_blocks(DeployableCore* core) {
    /* blk0: ICore Blocks/Home/PT_Gain/In1 */
    blk0_solve(core);
    /* blk1: ICore Blocks/Home/PT_Gain/Gain */
    blk1_solve(core);
    /* blk2: ICore Blocks/Home/PT_Gain/Out1 */
    blk2_solve(core);
}

The same block in the VHDL core, PT_Gain.vhd — the gain arrives on a port (blk1_gain : in Fx_matrix(0 to 0, 0 to 0)) and the multiply is fixed point:

                -- blk1: ICore Blocks/Home/PT_Gain/Gain
                for i in 0 to 0 loop
                    for j in 0 to 0 loop
                        s.sig1(i, j) := resize(s.sig0(i, j) * blk1_gain(0, 0), s.sig1(i, j));
                    end loop;
                end loop;

exportVerify is the pre-release harness, not Deploy: it writes the recording testbenches; a Deploy of the same subsystem also writes the build manifest (CMakeLists.txt, Cargo.toml, Makefile) from the table above. Residual, testbench, deployable core and the console vocabulary are defined in User glossary — the vocabulary of using ICoreBlocks; when a run does not go as this page says, start at Troubleshooting — the messages you will meet and what to do.

For contributors#

The generator seams, the recording testbenches and the tolerance bands are contributor material: the code-export and verification module pages and the guide on verifying export (all in related: above) hold the invariants, and the block-description checker in tools/docs/ is what keeps each block's support line true.