Rust.rs
A crate you can cargo run the moment it lands on disk.
// 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); } }
What Deploy writes for the reference model below, with only the file's
header banner removed. 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.
From the diagram to your Rust build.
Deploy writes the folder, ICore checks it against the simulation, and your code calls it once per sample.
Deploy writes
<name>_deployableCore.rs<name>_testbench.rsCargo.toml
Built, run and compared
Built with rustc / cargo, run across the simulation window and compared with the solver sample by sample, against a 0.1 % tolerance.
One call, one sample
Numbers are f64, and 800 of 806 library blocks export to Rust.
core.execute_blocks()Where Rust fits.
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.
| Target class | Tolerance | Observed |
|---|---|---|
| Software · 7 languages | 0.1 % | ≈1e-11 % |
| HDL · Q16.16 | 1 % | ≈1e-3 % |
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.
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.