ICore Blocks / Targets / Software

Rust

A crate you can cargo run the moment it lands on disk.

Deploy writes
  • <name>_deployableCore.rs
  • <name>_testbench.rs
  • Cargo.toml
One call, one sample

core.execute_blocks(&mut self)

Verified with

rustc / cargo

Numbers

f64

Library blocks

306 of 308

The export writes a Cargo.toml beside the core and the testbench, so the folder is a crate: build it, run it, or drop the core module into an existing workspace.

The core is a pub struct whose step function takes &mut self — the borrow checker sees exactly what it needs to see, because the model's state really is owned by one object and mutated once per sample.

What the export looks like

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 0.4z⁻¹ / (1 − 0.6z⁻¹), an output — and the plant's output fed back into the junction. Five blocks, in a diagram the export-verification suite calls DPT_Feedback_Discrete — which is where the names in the file come from. Below is what Deploy writes for this target, with only the file's header banner removed.

DPT_Feedback_Discrete_deployableCore.rs

// Tunable parameters (editable from the testbench)
pub struct Params {
    pub blk2_gain: [[f64; 1]; 1],
}

impl Params {
    pub fn new() -> Params {
        Params {
            blk2_gain: [[1.8_f64]],
        }
    }
}

// External inputs: set these (e.g. from a testbench) before execute_blocks;
// each top-level input gate copies its field into signal storage.
pub struct Inputs {
    pub sig0: [[f64; 1]; 1],
}

impl Inputs {
    fn new() -> Inputs {
        Inputs {
            sig0: [[0.0_f64; 1]; 1],
        }
    }
}

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

impl Signals {
    fn new() -> Signals {
        Signals {
            sig0: [[0.0_f64; 1]; 1],
            sig1: [[0.0_f64; 1]; 1],
            sig2: [[0.0_f64; 1]; 1],
            sig3: [[0.0_f64; 1]; 1],
            sig4: [[0.0_f64; 1]; 1],
            inputs: Inputs::new(),
        }
    }
}

// blk0: ICore Blocks/Home/DPT_Feedback_Discrete/In1
struct Blk0;

impl Blk0 {
    fn new() -> Blk0 { Blk0 }

    fn solve(&mut self, signals: &mut Signals, _params: &Params) {
        signals.sig0 = signals.inputs.sig0;
    }
}


// blk1: ICore Blocks/Home/DPT_Feedback_Discrete/Error
struct Blk1;

impl Blk1 {
    fn new() -> Blk1 { Blk1 }

    fn solve(&mut self, signals: &mut Signals, _params: &Params) {
        // Accumulate all inputs element-wise into the fixed-size output
        let mut output = [[0.0_f64; 1]; 1];
        let in0 = signals.sig0;
        for i in 0..output.len() {
            for j in 0..output[0].len() {
                output[i][j] += in0[i][j];
            }
        }
        let in1 = signals.sig3;
        for i in 0..output.len() {
            for j in 0..output[0].len() {
                output[i][j] -= in1[i][j];
            }
        }
        signals.sig1 = output;
    }
}


// blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain
struct Blk2 {}

impl Blk2 {
    fn new() -> Blk2 { Blk2 {} }

    fn solve(&mut self, signals: &mut Signals, params: &Params) {
        let gain = params.blk2_gain;
        let input = signals.sig1;
        let mut output = [[0.0_f64; 1]; 1];
        for i in 0..output.len() {
            for j in 0..output[0].len() {
                output[i][j] = input[i][j] * gain[0][0];
            }
        }
        signals.sig2 = output;
    }
}


// blk3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant
struct Blk3 {
    u_hist: [[[f64; 2]; 1]; 1],
    y_hist: [[[f64; 1]; 1]; 1],
}

impl Blk3 {
    fn new() -> Blk3 { Blk3 { u_hist: [[[0.0; 2]; 1]; 1], y_hist: [[[0.0; 1]; 1]; 1] } }

    fn solve(&mut self, signals: &mut Signals, _params: &Params) {
        let num: [f64; 2] = [0_f64, 0.40000000000000002_f64];
        let den: [f64; 1] = [-0.59999999999999998_f64];
        let _ = &den;
        let input = signals.sig2;
        for r in 0..1 {
            for c in 0..1 {
                let uk = input[r][c];
                for k in (1..2).rev() { self.u_hist[r][c][k] = self.u_hist[r][c][k - 1]; }
                self.u_hist[r][c][0] = uk;
                let mut yk = 0.0_f64;
                for i in 0..2 { yk += num[i] * self.u_hist[r][c][i]; }
                for i in 0..1 { yk -= den[i] * self.y_hist[r][c][i]; }
                self.y_hist[r][c][0] = yk;
                signals.sig3[r][c] = yk;
            }
        }
    }
}


// blk4: ICore Blocks/Home/DPT_Feedback_Discrete/Out1
struct Blk4;

impl Blk4 {
    fn new() -> Blk4 { Blk4 }

    fn solve(&mut self, signals: &mut Signals, _params: &Params) {
        signals.sig4 = signals.sig3;
    }
}


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

impl DeployableCore {
    pub fn new() -> DeployableCore {
        DeployableCore {
            params: Params::new(),
            signals: Signals::new(),
            blk0: Blk0::new(),
            blk1: Blk1::new(),
            blk2: Blk2::new(),
            blk3: Blk3::new(),
            blk4: Blk4::new(),
        }
    }

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

Each block is a struct holding its own state, with a solve(&mut Signals, &Params); the core owns all five and takes &mut self once per sample. Look at Blk3: the plant's u_hist and y_hist live inside the block rather than in a global, so ownership matches the model and the borrow checker never has to be worked around.

How it is checked

Every one of the ten targets is verifiable, and this one is no exception: the export is compiled with the toolchain above, run across the simulation window, and compared against the solver sample by sample. Software targets pass at around 1e-11 % against a 0.1 % tolerance; the HDL targets are bounded by their fixed-point quantum instead. See verification.

See also: Code export  ·  Multi-target, multi-rate deploy

Get started

See it run on your own model.

Download the application from the customer portal, or read the documentation first — the manual, every block with its measured response, and the full command reference are public.