Product — Control Systems/Base Blocks
Control_Systems/Base_Blocks/Product · 2 input / 1 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
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.
Product
Control Systems / Base Blocks
Multiplies its inputs, either entry by entry or as matrices, chosen by the Multiplication Type parameter: y = u₁ · u₂ … element by element, or y = u₁ × u₂ as a true matrix product. The choice decides the block's shape as well as its arithmetic.
Ports
- Inputs – two by default. In Element-wise the count is user-editable and every input must carry the SAME signal size; this block does not expand a scalar over a matrix. In Matrix there are exactly two, and their inner dimensions must agree: a [m,n] first input needs a [n,p] second.
- Output – the product y. The shared input size in Element-wise; [m,p] in Matrix, so a matrix product may change the signal's dimensions.
Operations
An input's operation is its port description label, not a parameter: click the label on the canvas and type * or /. A blank label counts as *. A fresh block comes up as * then *, matching Simulink's default for this block – Divide is the same block with * then /. A leading / is a reciprocal, so a /* port list computes (1/u₁)·u₂. Because the operation lives on the port it travels with copy, undo and save, and can never fall out of step with the port count – the same arrangement Sum uses for its signs. Only Element-wise accepts a /; see Parameters.
Parameters
- Multiplication Type – which product is computed. This selects
the arithmetic rather than retuning it, so each option is a separate code path.
- Element-wise (.*) – entry by entry, over any number of inputs, each taken with its own * or /. This is the default, as in Simulink. All inputs share one size and the output takes it.
- Matrix (*) – the matrix product y = u₁ × u₂, summing over the inner index. Exactly two inputs, both *. A third input, or a / on either port, is reported when the model is built: chain a second Product block for a longer product, and use Divide or Math Function for a quotient. A [m,n] and a [n,p] input give a [m,p] output, so this is the one mode that can change the signal's dimensions.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. Both the multiplication type and the per-port operations are fixed into the generated arithmetic at export time; there is nothing left to retune on the generated core.
The seven software targets agree exactly, including division by zero in Element-wise, which gives ±Inf (or NaN for 0/0) in all seven.
The three HDL targets differ by mode, and deliberately.
Matrix is ordinary Q16.16 fixed point and is synthesizable: it is
a sum of products, the same arithmetic Gain's matrix modes emit.
Element-wise is simulation-only, because a / is reachable
there and Q16.16 cannot hold a quotient whose divisor has come near zero –
one quantum of the divisor is 1.5×10⁻⁵, so an ordinary signal
crossing zero produces a result far outside the format. Those cores convert at
the port boundary and evaluate in real arithmetic; having no
infinity, they answer 0 where a divisor is exactly zero.
Simulink bridge
Import and export, mapped to simulink/Math Operations/Product.
"Multiplication Type" to Multiplication, one option for one option
(Element-wise(.*), Matrix(*)), so that round trip is
lossless. There is no parameter behind the per-port operations on either side:
the port labels become Simulink's Inputs string
(**), and an imported string – or an imported input
count, which Simulink also accepts there – becomes this port list.
"Sampling Time (s)" goes to SampleTime, as on every block.
Simulink's Divide is this same block with an Inputs of
*/, and imports as ICore's Divide; its Product of
Elements is a different block, and is ICore's Product Of Elements.
A Simulink Product set to Matrix(*) with more than two inputs, or
with a / among them, is reported on import rather than silently
truncated.
Notes
- Algebraic, with no state.
- Not linear in either mode – a product of two signals is not – so the block deliberately carries no state space and model reduction reports it as unmergeable. Gain is the linear sibling, a signal times a constant, and that one can be absorbed; it is also the block to reach for when one operand is a constant matrix, since its Matrix (K*u) mode is synthesizable in every target.
- At least two inputs are required, as for Sum and Divide.
- Matrix is not commutative: swapping the two inputs generally changes the answer, and changes the output's shape unless both are square.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Base_Blocks/Product |
| family | Control_Systems/Base_Blocks |
| solver environment class | ICoreBlock_0_Control_Systems_1_Base_Blocks_2_Product |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/Base_Blocks/Product/ICoreBlock_0_Control_Systems_1_Base_Blocks_2_Product.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/Base_Blocks/Product/ICoreBlock_0_Control_Systems_1_Base_Blocks_2_Product.h |
| default size on canvas | 70 × 70 px |
| ports at insert | 2 in, 1 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | OP_MULTIPLY |
| 2 | in | ICoreDouble | OP_MULTIPLY |
| 3 | 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 |
|---|---|---|
Multiplication Type | Element-wise (.*)%~%Matrix (*)~~Element-wise (.*) | Multiplication |
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/Math Operations/Product |
| port-count rule | PortsParam::DivideSigns |
SampleTime parameter | yes |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Multiplication Type | Multiplication | Element-wise (.*) → Element-wise(.*), Matrix (*) → Matrix(*) |
Caveat (shown to the user): the two multiplication types map 1:1 onto Simulink's Multiplication values, so the choice is lossless in both directions; the per-input operations are port labels rather than config, and cross as Simulink's Inputs string. Matrix() here takes exactly two '' inputs -- a longer chain or a '/' among them is reported rather than silently truncated, and is expressed by cascading a second Product block
Catalog contract: src/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B0Ports lists 2 entries for 3 ports (2 in, 1 out) — grouped, or one undocumented? a reader must say
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).
Product block -- the multiplying half of Simulink's Product/Divide pair "Multiplication Type" selects WHICH code runs, so it is a mode rather than a tuning, and it decides the block's SHAPE as well as its arithmetic:
Element-wise (.) y = u1 (op) u2 (op) ... entry by entry, any number of inputs, each carrying "" or "/" in its port DESCRIPTION LABEL. Identical arithmetic to Divide, deliberately: the two are ONE Simulink block, and a user moving between them should not meet a second convention. Matrix () y = u1 * u2, the true matrix product of two SIGNALS. [m,n] x [n,p] leaves as [m,p]. Exactly two inputs, both "".
WHY THE MATRIX MODE TAKES EXACTLY TWO INPUTS. Simulink chains N matrices left to right, and each step of such a chain produces an intermediate of a DIFFERENT shape than both its operands. The seven software targets could hold those intermediates; a VHDL block body cannot -- it runs inside one clocked process and may declare nothing, and the shared scratch (acc, acc2, iacc) is scalar. Emitting a chain the HDL backends could not follow would mean either a language that silently does something else or a mode with no rig behind it, and ADDING_NEW_BLOCKS.md is explicit that an untested branch is worse than a documented limit. So the third input is refused with a message that names the cascade as the way to get it.
HDL, and why the two modes differ there:
Element-wise is SIMULATION-ONLY real arithmetic, for exactly the reason Divide gives. A "/" port is reachable on this block too, and a Q16.16 quotient overflows as soon as the divisor drops below |numerator| / 32768 -- one quantum of the divisor is 1.5e-5, so an ordinary signal crossing zero produces a quotient the datapath cannot hold. One emitted body per target, converting at the port boundary, rather than a fixed-point path selected only by an all-"*" port list.
Matrix is FIXED POINT and synthesizable. It is "*"-only by construction, so it has no dynamic-range argument against it -- it is the same sum-of-products Gain's Matrix (K*u) already emits and already passes. It is also a rig of its own (PT_Product__Matrix), so unlike the branch Divide declined to write, this one is selected and compared in all ten languages.
Algebraic and stateless. NO state space in either mode: a product of two SIGNALS is not linear. See the header.
Sample results#
| t | in ICoreDouble-Out-0 | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|---|
| 0 | -2 | -2 | 4 |
| 0.4 | 0.5 | 0.5 | 0.25 |
| 0.8 | -2 | -2 | 4 |
| 1.2 | 0.5 | 0.5 | 0.25 |
| 1.6 | -2 | -2 | 4 |
| 2 | 0.5 | 0.5 | 0.25 |
| 2.4 | -2 | -2 | 4 |
| 2.8 | 0.5 | 0.5 | 0.25 |
| 3.2 | -2 | -2 | 4 |
| 3.6 | 0.5 | 0.5 | 0.25 |
| 4 | -2 | -2 | 4 |
| 4.4 | 0.5 | 0.5 | 0.25 |
| 4.8 | -2 | -2 | 4 |
| 5.2 | 0.5 | 0.5 | 0.25 |
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 … 33.64 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 1 |
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__Base_Blocks__Product.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).