ICore Blocks / Targets / Software

C++.hpp

A header-only core as a single struct - one object, one call per sample.

Software Verified with g++ or clang++ double 800 of 806 blocks
DPT_Feedback_Discrete_deployableCore.hpp
✓ verified · 0.1 %
#pragma once

#include <array>
#include <cmath>
#include <cstddef>
#include <iostream>

// Fixed-size matrix: R rows x C cols, stack/struct-allocated (no heap)
template <std::size_t R, std::size_t C>
using Mat = std::array<std::array<double, C>, R>;

// Tunable parameters (editable from the testbench)
struct Params {
    Mat<1, 1> blk2_gain = {{ {{1.8}} }};
};

// External inputs: set these before execute_blocks; each top-level input gate
// copies its field into signal storage.
struct Inputs {
    Mat<1, 1> sig0 {};
};

// Generated signal storage - one fixed-size matrix per output port
struct Signals {
    // sig0: ICore Blocks/Home/DPT_Feedback_Discrete/In1/ICoreDouble-Out-0
    Mat<1, 1> sig0 {};
    // sig1: ICore Blocks/Home/DPT_Feedback_Discrete/Error/ICoreDouble-Out-0
    Mat<1, 1> sig1 {};
    // sig2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain/ICoreDouble-Out-0
    Mat<1, 1> sig2 {};
    // sig3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant/ICoreDouble-Out-0
    Mat<1, 1> sig3 {};
    // sig4: ICore Blocks/Home/DPT_Feedback_Discrete/ICoreDouble-Out-0
    Mat<1, 1> sig4 {};
    Inputs inputs {};
};

// blk0: ICore Blocks/Home/DPT_Feedback_Discrete/In1
struct Blk0 {
    void solve(Signals& signals, const Params& params) {
        (void)params;
        signals.sig0 = signals.inputs.sig0;
    }
};


// blk1: ICore Blocks/Home/DPT_Feedback_Discrete/Error
struct Blk1 {
    void solve(Signals& signals, const Params& params) {
        (void)params;
        Mat<1, 1> output {};
        auto in0 = signals.sig0;
        for (std::size_t i = 0; i < output.size(); i++) {
            for (std::size_t j = 0; j < output[0].size(); j++) {
                output[i][j] += in0[i][j];
            }
        }
        auto in1 = signals.sig3;
        for (std::size_t i = 0; i < output.size(); i++) {
            for (std::size_t j = 0; j < output[0].size(); j++) {
                output[i][j] -= in1[i][j];
            }
        }
        signals.sig1 = output;
    }
};


// blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain
struct Blk2 {
    void solve(Signals& signals, const Params& params) {
        auto gain = params.blk2_gain;
        auto input = signals.sig1;
        Mat<1, 1> output {};
        for (std::size_t i = 0; i < output.size(); i++) {
            for (std::size_t j = 0; j < output[0].size(); j++) {
                output[i][j] = input[i][j] * gain[0][0];
            }
        }
        signals.sig2 = output;
    }
};


// blk3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant
struct Blk3 {
    double u_hist[1][1][2] {};
    double y_hist[1][1][1] {};
    void solve(Signals& signals, const Params& params) {
        static constexpr double num[2] = {0, 0.40000000000000002};
        static constexpr double den[1] = {-0.59999999999999998};
        auto input = signals.sig2;
        for (std::size_t r = 0; r < 1; r++) {
            for (std::size_t c = 0; c < 1; c++) {
                const double uk = input[r][c];
                for (std::size_t k = 1; k > 0; k--) u_hist[r][c][k] = u_hist[r][c][k - 1];
                u_hist[r][c][0] = uk;
                double yk = 0.0;
                for (std::size_t i = 0; i < 2; i++) yk += num[i] * u_hist[r][c][i];
                for (std::size_t i = 0; i < 1; i++) yk -= den[i] * y_hist[r][c][i];
                y_hist[r][c][0] = yk;
                signals.sig3[r][c] = yk;
            }
        }
    }
};


// blk4: ICore Blocks/Home/DPT_Feedback_Discrete/Out1
struct Blk4 {
    void solve(Signals& signals, const Params& params) {
        (void)params;
        signals.sig4 = signals.sig3;
    }
};


// Deployable core: owns params, signals and every stateful block instance
struct DeployableCore {
    Params  params {};
    Signals signals {};
    Blk0 blk0 {};
    Blk1 blk1 {};
    Blk2 blk2 {};
    Blk3 blk3 {};
    Blk4 blk4 {};

    // Execution order generated automatically from block diagram
    void execute_blocks() {
        // blk0: ICore Blocks/Home/DPT_Feedback_Discrete/In1
        blk0.solve(signals, params);
        // blk1: ICore Blocks/Home/DPT_Feedback_Discrete/Error
        blk1.solve(signals, params);
        // blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain
        blk2.solve(signals, params);
        // blk3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant
        blk3.solve(signals, params);
        // blk4: ICore Blocks/Home/DPT_Feedback_Discrete/Out1
        blk4.solve(signals, params);
    }
};

What Deploy writes for the reference model below, with only the file's header banner removed. The same core as one struct per block, with std::array for every matrix. Parameters carry their diagram values as member initialisers - blk2_gain is 1.8 the moment the object exists - so there is no init call to forget, and the whole thing still fits in a header you can include from anywhere.

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.hpp
  • <name>_testbench.cpp
  • CMakeLists.txt
→ code/<name>/
02 · VerifyICore

Built, run and compared

Built with g++ 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 800 of 806 library blocks export to C++.

DeployableCore core; core.execute_blocks();
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 whole core is a struct in one header, so adopting it is an include and a member. Params, signals, inputs and state are all members you can read and write directly; there is no wrapper class to learn and nothing to link.

The generated CMakeLists.txt builds the testbench as-is, which makes the exported folder a working project rather than a pile of sources to wire up yourself.

$ 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.