02 · Advanced Simulink Modeling¶
Verification note
MATLAB/Simulink was not available in the environment used to write this page. Block semantics, signal routing, and configuration options are documented, version-stable Simulink features, hand-traced against the Simulink documentation rather than built and simulated live.
Level 2 introduced Simulink as a block-diagram way to build the same kind of system a script would compute step by step. This module goes past a single flat diagram into the structuring tools real models need: subsystems, buses, state machines, and the settings that make a model scale beyond a toy example.
Subsystems: giving a diagram structure¶
A model with fifty blocks wired directly on one canvas is unreadable. Subsystems group a cluster of blocks behind a single block with its own inputs/outputs, the Simulink equivalent of factoring code into a function:
[Sum]->[Subsystem: PID Controller]->[Plant]->[Scope]
| ^
(In1) (Out1) -- ports inside map to the block's edges outside
Right-click a selection → Create Subsystem from Selection replaces
the selected blocks with one masked block; double-clicking it opens the
grouped diagram, with In1/Out1 port blocks marking where the outside
wires attach. This is purely organizational — simulation results are
identical before and after — but it is what makes a controls model with
hundreds of blocks navigable and reusable (the same "PID Controller"
subsystem can be copy-pasted or saved as a library block and dropped into
another model).
Masking a subsystem adds a custom dialog (parameters like Kp, Ki,
Kd exposed as fields) and a custom icon, so a reusable subsystem behaves
like a first-class block rather than exposing its internal wiring to
every user of the model.
Buses: bundling related signals¶
Passing a dozen separate wires between subsystems (temperature, pressure, flow rate, three status flags...) is as unwieldy as fifty scalar function arguments would be in code. A Bus groups related signals into one line, using a Bus Creator to pack and a Bus Selector to unpack:
[Temp] --\
[Pressure]--[Bus Creator]---[single bus wire]---[Bus Selector]--[Temp]-->[Scope]
[Flow] --/ \-[Pressure]->[...]
A Simulink.Bus object (defined once, e.g. in a model's PreLoadFcn or a
project setup script) formally names and types each element, the way a
struct with named fields formally documents what's inside it instead of
an unlabeled cell array of values in some agreed-upon order:
elems(1) = Simulink.BusElement;
elems(1).Name = 'Temp';
elems(1).DataType = 'double';
elems(2) = Simulink.BusElement;
elems(2).Name = 'Pressure';
elems(2).DataType = 'double';
sensorBus = Simulink.Bus;
sensorBus.Elements = elems;
Stateflow-style logic: the Switch and enable/trigger ports¶
Not every system is purely continuous math — many need mode-dependent
behavior ("run the controller only while Enable is true," "reset the
integrator when Reset pulses"). Two block-level tools cover the common
cases without a full state chart:
- Enabled subsystem: runs its contents only while its
Enableinput is nonzero; while disabled it holds (or optionally resets to zero, per block setting) its last output — the block-diagram version of anifguard wrapped around a whole chunk of a model. - Triggered subsystem: executes exactly once per rising (or falling,
or either) edge of a
Triggerinput, useful for sample-and-hold style behavior driven by an external event rather than every simulation step.
For genuinely mode-based systems (idle → ramping → running → fault, each
with different logic and edges between them triggered by conditions),
Stateflow provides a proper state-chart editor with states,
transitions, and guard conditions — the block-diagram analog of a switch
over an enum plus explicit transition rules, and the standard tool once
"enabled subsystem" checks start nesting more than one or two levels deep.
Signal attributes and the model's data dictionary¶
Every wire in Simulink carries a data type, dimension, and
sample time, usually inferred automatically but explicitly
overridable via a Signal Specification block or a bus element's
DataType field. This matters most when a model will later be compiled
with MATLAB Coder or deployed to fixed-point hardware (Level 4 covers
both): pinning int16 on a sensor signal early catches a type mismatch
in simulation, in the same design environment, rather than after
generating C code for an embedded target.
A Data Dictionary (.sldd file) centralizes these definitions —
signal types, bus objects, calibratable parameters — outside any single
model, so multiple models in one project reference the same shared
definitions instead of each redefining sensorBus independently and
risking drift between copies.
Solver configuration for accuracy vs. speed¶
Level 2 used the default variable-step solver without discussing it. Simulink's Model Configuration Parameters dialog exposes the trade-off explicitly:
| Solver | Behavior | When to use |
|---|---|---|
ode45 (variable-step) |
Adapts step size to the signal's rate of change | Default for smooth, continuous dynamics — usually the right first choice |
ode23 |
Lower-order, cheaper per step | Mildly stiff or when ode45 is unnecessarily fine-grained |
ode23s/ode15s |
Implicit, designed for stiff systems | Dynamics with widely separated time constants (fast electrical transient + slow thermal drift) |
Fixed-step (ode4, discrete) |
Constant Δt every step |
Required before generating code for real-time or embedded targets, where the target hardware runs on a fixed clock |
Switching from variable-step to fixed-step is a required step, not an
optional tune, before Level 4's code-generation workflow — real-time
targets execute one tick per hardware clock cycle and cannot honor a
solver that shrinks or grows Δt based on signal behavior.
Model referencing: composing models from models¶
A Model block embeds one whole .slx model inside another, the
system-level equivalent of one script calling another as a function
rather than being pasted inline. Large projects (a full vehicle model
built from separately-owned Engine.slx, Transmission.slx,
Brakes.slx models) use this to let teams work on subsystems
independently, version them separately, and simulate the top-level model
either against the full sub-model or a fast simplified "surrogate"
version swapped in during rapid top-level iteration.
How It Actually Works¶
Beyond a single flat diagram, Simulink's compilation phase (mentioned in Module 06) becomes the crux of how subsystems, buses, and masked blocks actually execute. A subsystem is not automatically its own simulation "unit" — by default it's inlined into the parent model's compiled execution list during the same sorting/scheduling pass that determines block execution order, so a Subsystem block adds no runtime overhead by itself; only an Atomic Subsystem (or a Model block referencing a separate model file) forces its own compiled unit with its own explicit execution boundary, which matters for things like enabling separate sample rates or code-generation partitioning.
Bus signals bundle multiple signals into one wire in the diagram, but underneath, Simulink still propagates each constituent signal's own data type and dimensions independently through the compiled model — a bus is a compile-time/diagram-time organizational convenience, not a genuinely different runtime signal type; connecting a bus to a scope or a block that expects a plain signal fails at compilation time (type/dimension propagation), not simulation time, because the mismatch is detected before the simulation loop ever starts.
Masked blocks (custom dialog UI wrapped around a subsystem) evaluate their mask parameters as MATLAB expressions in a workspace scoped to that mask instance, at model-update time — this is why a masked block's parameters can reference variables from the base workspace or use MATLAB expressions directly in its dialog fields, and why changing a mask parameter can trigger the model to recompile (re-run type/dimension propagation) if that parameter affects a block's dimensions or data type.
Note: describes Simulink's documented compilation/scheduling model; Simulink is unavailable in this environment to build or execute a model.
Summary¶
- Subsystems group blocks for readability and reuse, purely organizational; masking turns a subsystem into a reusable block with its own parameter dialog.
- Buses bundle related signals under one wire and one named type
(
Simulink.Bus), avoiding dozens of individually-routed scalar wires. - Enabled/triggered subsystems add lightweight mode-dependent execution; Stateflow is the full state-chart tool once mode logic gets genuinely complex.
- Signal types/dimensions and a shared Data Dictionary keep large models consistent and are prerequisites for later code generation.
- Solver choice (variable-step
ode45/ode15svs. fixed-step) trades simulation accuracy/speed and, for fixed-step, is mandatory before targeting real-time or embedded hardware. - Model referencing composes large models from independently-owned sub-models, mirroring modular software architecture at the block-diagram level.