What is SysDynLab?
SysDynLab is a browser-based block diagram simulator for building and running continuous and discrete-time dynamic systems — entirely in the browser, with no installation required.
Models are built visually by connecting blocks with wires on an infinite canvas. Each block represents a mathematical operation, signal source, or sink. Wires carry numeric signals between blocks in real time.
The simulation engine runs in WebAssembly for near-native speed. It supports variable-step ODE solvers (Euler, RK4, RK45, DOPRI), discrete-time blocks, nested subsystems, state machines, and live plotting via the Plotter block.
Key capabilities:
- Drag-and-drop block library: Sources, Continuous, Math, Discrete, Nonlinear, Logic, Signal Routing, Sinks
- Nested subsystems for hierarchical modeling
- State machines with hierarchical states, parallel regions, junctions, and entry/exit actions
- Live Plotter with pan, zoom, and multi-channel support
- Save/load models as XML; import CSV data; export C code
- Built-in demos covering control systems, oscillators, HVAC, and more
Building Your First Model
1
Add blocks from the sidebar
Click any block in the left sidebar to place it on the canvas. Or drag it to a specific position. Blocks are grouped by category: Sources, Continuous, Math, Discrete, and more.
2
Connect blocks with wires
Click an output port (right side of a block — blue) then click an input port (left side — orange) to draw a wire. Wires route automatically. To delete a wire, click it and press Delete.
3
Configure block parameters
Double-click any block to open its parameter popup. Change values (expressions like 2*pi are accepted) and click Apply. Right-click a block for more options (mirror, rotate, duplicate, help).
4
Set simulation time and run
Set T (duration in seconds) in the toolbar. Leave dt empty for auto step size. Press ▶ Run (or Space) — the button turns red while running. Press again to stop early.
5
View results in the Plotter
Add a Plotter block (Sinks section) and wire signals into it. Double-click the Plotter after running to open the signal viewer. Scroll to zoom, drag to pan. Click Zoom X / Zoom Y then draw a selection rectangle on the plot.
6
Save and load models
Use Ctrl+S to save your model as an XML file. Ctrl+O loads a saved model. Models include all blocks, wires, and parameter values.
Try a demo first! Click the Demo button in the toolbar and select any built-in example to see how a complete model looks. Demos are a great way to explore what SysDynLab can do.
Your First Simulation (step-by-step)
- Click Sine Wave in the Sources section of the sidebar.
- Click Plotter in the Sinks section.
- Click the output port of the Sine Wave block, then click the input port of the Plotter.
- Set T = 5 in the toolbar and press ▶ Run.
- Double-click the Plotter to see the sine wave plot.
Common Tasks
| Task | How |
| Select multiple blocks | Drag a selection box on empty canvas, or Shift+click blocks |
| Move blocks | Drag any block; wires follow automatically |
| Delete selection | Delete or Backspace |
| Undo / Redo | Ctrl+Z / Ctrl+Y |
| Copy & paste | Ctrl+C then Ctrl+V |
| Fit all blocks in view | Click Fit button in toolbar |
| Create a subsystem | Select blocks → right-click → Create Subsystem. Double-click to enter. |
| Export simulation data | Add a CSV Sink block — it saves a .csv file when simulation ends |
| Generate C code | Right-click any block or subsystem → Generate C Code |
Plotter Block (Scope)
The Plotter block records signals during simulation and lets you view them as time-series plots. It is the primary data visualization tool in SysDynLab.
Setting Up a Plotter
- Drag Plotter from the Sinks section of the sidebar onto the canvas.
- Double-click the block to set nInputs — the number of signal channels (1–10).
- Wire signals from other blocks into the input ports.
- Run the simulation (Space or the Run button).
- Double-click the Plotter block again to open the signal viewer.
Viewer Toolbar Controls
| Button / Action | What it does |
| Zoom X | Activate X-axis (time) zoom mode. Click once for the start point, click again for the end point — the time axis zooms to that range. Blue band previews the selection. |
| Zoom Y | Activate Y-axis (value) zoom mode. Same two-click selection, but locks the value axis. |
| ↔+ / ↔− | Zoom in / out horizontally (time axis) by a fixed factor. |
| ↕+ / ↕− | Zoom in / out vertically (value axis) by a fixed factor. |
| Reset | Return to the full auto-scaled view (all data visible). |
| Names | Toggle the channel-name panel. Type a custom label for each channel. Names appear on the plot legend and in cursor readouts, and are saved with the model. |
Mouse Interaction on the Plot
| Action | How |
| Pan left / right / up / down | Click and drag on the canvas |
| Zoom in / out | Scroll wheel anywhere on the canvas |
| Read cursor values | Move the mouse over the canvas — a vertical cursor snaps to the nearest data point and shows time + value for every channel |
Channel Names
Click Names in the viewer toolbar. A row of text boxes appears below the toolbar — one per physical input port. Type a descriptive label such as Speed or Error. The name appears:
- In the inline legend on the right side of the plot
- In the hover cursor readout (
Speed = 3.142)
- As the row label when subplot mode is active
Names are stored in the block parameters and saved with the model XML.
Mux Input → Overlay Mode
If the Plotter has nInputs = 1 and the single port is wired from a Mux block, SysDynLab automatically expands the vector and draws all elements as separate colored traces on one shared plot. This is the easiest way to compare several signals side-by-side without adding multiple Plotter blocks.
Example: Mux 3 signals (reference, output, error) → single Plotter input → three colored lines in the same plot.
Multi-Input → Subplot Mode
If the Plotter has nInputs > 1, each physical input port gets its own subplot row. All rows share the same time axis so you can compare timing across signals. Each row auto-scales its Y axis independently.
If a port in subplot mode is itself wired from a Mux, that row shows all Mux elements overlaid within its subplot — giving you both subplot isolation and overlay comparison simultaneously.
Typical Patterns
| Goal | Setup |
| Compare 3 signals on one plot | Mux 3 → Plotter (nInputs = 1) |
| Compare 3 signals each on its own row | Plotter nInputs = 3, one signal per port |
| Overlay a vector + show another signal separately | Plotter nInputs = 2; port 1 ← Mux(3); port 2 ← scalar signal |
| Name channels | Open viewer → click Names → type labels |
| Zoom into transient at t=5s | Click Zoom X → click at t=4.8 → click at t=5.5 |
XY Scope Block
The XY Scope block plots one signal against another in phase-plane style — there is no time axis. Input 1 maps to the X axis; Input 2 maps to the Y axis. The result is a parametric trajectory curve.
Common uses: visualizing limit cycles, attractors, Lissajous figures, and velocity vs. position phase portraits.
Example: Connect a Sine Wave to Input 1 and a Cosine (Sine with phase = π/2) to Input 2 to draw a circle.
Display Block
The Display block shows the current numeric value of its input in a green LCD-style readout — updated live at every simulation step. Use it to monitor a single value without opening a scope.
- If the input is a scalar, one row is shown.
- If the input is a vector (from a Mux), each element gets its own row. The block height grows automatically.
- Values use automatic precision: small/large values are shown in scientific notation.
- The display also shows the last value frozen at the end of simulation, useful for checking final steady-state.
CSV Sink
The CSV Sink records all input channels during simulation and saves them as a comma-separated file when the simulation ends. The filename is the block label.
- Set nInputs to the number of channels to record.
- Connect a Mux to record multiple signals as separate columns.
- The file has a
time column followed by one column per channel.
- Use this to post-process data in Excel, Python (pandas), or MATLAB.
What Is the State Machine Block?
The State Machine block implements statecharts with hierarchical states, parallel (AND) regions, junctions, entry/exit actions, transition conditions, local variables, and arbitrary input/output ports. The entire statechart runs inside the WASM simulation engine at each time step.
Adding a State Machine
- Drag State Machine from the Signal Routing section of the sidebar.
- Double-click the block to open the graphical editor.
- Use the Ports tab at the top to add input ports, output ports, and local variables.
- Build the statechart on the editor canvas.
- Close the editor — it auto-saves.
- Wire the block inputs and outputs like any other block and run the simulation.
Editor Layout
| Area | Purpose |
| Left palette | Drag elements onto the canvas: State, Junction (diamond), Initial marker (green circle) |
| Center canvas | Main editing area — place states, draw transitions, arrange the chart |
| Right properties panel | Shows editable fields for the selected element (name, actions, condition, order, etc.) |
| Ports tab (top) | Define input ports, output ports, and local variables for the statechart |
States
States are the building blocks of the chart. Only one state (per region) is active at any time.
| Operation | How |
| Create a state | Drag State from the left palette, or right-click empty canvas → Add State |
| Rename a state | Double-click the state name text and type a new name |
| Move a state | Drag the state header (top bar) |
| Resize a state | Drag the bottom-right corner handle |
| Set as Initial state | Right-click the state → Set as Initial (gives it a green dot). Every top-level chart needs one Initial state. |
| Entry action | Select the state → type in the Entry field in the properties panel. Runs once when the state is entered. |
| Exit action | Select the state → type in the Exit field. Runs once when the state is left. |
| Delete a state | Select it → press Delete or use the trash icon |
Tip: Every statechart must have exactly one element marked as Initial — either a state or a junction. The simulator enters that element first when the simulation starts. If a state is an AND superstate (parallel regions), no Initial marker is required inside it — all regions activate simultaneously.
Transitions
Transitions connect states. When a condition is true, the chart follows the transition: exits the source state (running exit actions), runs the transition action, then enters the target state (running entry actions).
| Operation | How |
| Draw a transition | Click and drag from the border of one state to another state (or to a junction) |
| Transition condition | Select the transition → type a boolean expression in the Condition field (e.g. error > 0.5). Leave empty to always fire. |
| Transition action | Select the transition → type assignment(s) in the Action field (e.g. output = 1; counter = 0) |
| Execution order | When multiple transitions leave the same state, each gets a number badge. The lowest-numbered transition whose condition is true fires first. Change the order in the Order field in the properties panel. |
| Bend the curve | Drag the circular midpoint handle on the transition to curve it |
| Move the label | Drag the transition label text to reposition it along the curve |
| Reattach endpoints | Drag the small circles at the start or end of the transition to reposition where it connects to the state border |
| Delete a transition | Click the transition to select it → press Delete |
Junctions
Junctions are diamond-shaped routing nodes. They do not have entry/exit actions and are never "active" — they are evaluation waypoints.
| Type | How it works | When to use |
| Routing junction |
Has multiple outgoing transitions with different conditions. When control reaches it, the first matching outgoing transition is followed. |
if/else branching without duplicating the source state; fan-out from one source to many targets based on conditions |
| Action junction (dead-end) |
Has no outgoing transitions whose condition matches (or no outgoing transitions at all). Runs all actions along the path from the source state, then stays in the source state — the current state does not change. |
Conditional side effects: run an action only under certain conditions without a state change (e.g. increment a counter) |
To set a junction as the Initial entry point, right-click it → Set as Initial. The chart evaluates the junction's outgoing transitions on start to find the initial state.
Parallel (AND) Regions
A state can be divided into parallel regions — all are active simultaneously, each running its own sub-statechart independently.
- To create parallel regions: right-click a composite state → Add Parallel Region, or drag the dashed region separator.
- Each region needs its own Initial marker.
- Regions can read and write the same output ports and local variables. This lets one region's transition condition check values set by another region's action.
- Transitions between different regions of the same parent state are not allowed — use shared variables to coordinate instead.
Hierarchical (Composite) States
States can contain other states, creating a hierarchy. The inner states are only active when the outer (composite) state is active.
- Create a composite state by dragging a new state inside an existing state.
- A transition to a composite state enters its Initial inner state.
- A transition from a composite state is a "super-transition" — it fires regardless of which inner state is currently active, exits the entire composite, and runs all exit actions from inside out.
Ports Tab — Inputs, Outputs & Locals
Click the Ports tab at the top of the editor to configure the statechart interface:
| Section | Purpose | Usage in expressions |
| Input ports | Signals flowing into the block from the canvas. Add with the + button, give each a name. | Read-only in conditions and actions: speed > 10 |
| Output ports | Signals the block sends out to connected blocks. Add with + button, give each a name. | Assign in actions: mode = 2. Read in conditions: mode == 1 |
| Local variables | Internal state variables that persist between time steps but are not visible outside the block. Zero-initialized; reset each time simulation starts. | Read and assign freely: counter = counter + 1 |
Port order in the list determines the pin position on the block (top = first pin). Rename ports by clicking their name field. The block on the canvas updates its pin count automatically when you add or remove ports.
Expression Language
Conditions and actions share a small expression language. Both paths (WASM runtime and C code generator) support the same operators.
Operators
| Category | Operators | Example |
| Arithmetic | + - * / | speed * 0.1 + bias |
| Unary minus | -x | -error |
| Comparison | > >= < <= == != | error >= threshold |
| Logic | && || ! | a > 0 && b < 1 |
| Bitwise | & | ^ ~ << >> | flags & 0x03 |
| Modulo | % | step % 4 (uses fmod internally) |
| Assignment (action only) | = | output = input + 1 |
| Multi-statement (action only) | ; | y = 1; counter = 0 |
Built-in Functions
| Function | Description |
abs(x) | Absolute value |
sqrt(x) | Square root |
sin(x) cos(x) tan(x) | Trigonometry (radians) |
asin(x) acos(x) atan(x) | Inverse trig (radians) |
atan2(y, x) | Four-quadrant arctangent |
exp(x) | ex |
log(x) | Base-10 logarithm |
ln(x) | Natural logarithm |
pow(x, y) | x raised to the power y |
min(a, b) max(a, b) | Minimum / maximum of two values |
floor(x) ceil(x) round(x) | Rounding functions |
sign(x) | −1, 0, or +1 based on sign |
rise(x) | Returns true on a rising edge of x (0→non-zero). Condition-only. |
fall(x) | Returns true on a falling edge of x (non-zero→0). Condition-only. |
pi e | Mathematical constants π and e |
Example Patterns
| Goal | Condition | Action |
| Transition when error exceeds threshold | abs(error) > 0.5 | output = 1 |
| Count steps in a state | (empty — always fires from junction) | counter = counter + 1 |
| Reset counter on entry | — | Entry: counter = 0 |
| Trigger on rising edge | rise(trigger) | output = 1; timer = 0 |
| Stay in state for N steps | counter >= N | counter = 0 |
| Two-condition if/else via junctions | Trans A: x > 0 Trans B: x <= 0 | Route from junction to StateA or StateB |
Running the Simulation
After closing the editor, the State Machine block looks like any other block — wire its input and output ports, set simulation time, and press Run. The statechart evaluates at every simulation step: reads inputs, checks transition conditions (in order), fires matching transitions, runs actions, and writes outputs.
- Output ports hold their last assigned value between steps — they are not reset to zero unless an action sets them.
- Local variables are zero-initialized at
t=0 and persist for the entire simulation.
- Parallel regions run concurrently — both are evaluated every step.
- Use a Display block on an output port to watch the active state value live.
Tip: To inspect which state is active, add an output port named mode and assign a distinct integer in each state's entry action (e.g. mode = 1, mode = 2). Connect it to a Display block to monitor the active state during simulation.
Generating C Code from a State Machine
SysDynLab can export the State Machine block as self-contained C or Python code — the same logic that runs in the WASM simulation engine, translated to portable source code ready for embedded targets or desktop applications.
Right-click the State Machine block on the canvas and select ▶ Generate Code. See the Code Generation tab for full details on options and output structure.
- The generated C code includes the full statechart logic: state transitions, guards, entry/exit actions, local variables, and output assignments.
- State is stored in a generated
struct — safe for multi-instance use (no global variables).
- The step function signature is:
void name_step(const Inputs *u, Outputs *y, State *s)
- An optional test
main() is generated to drive a simple step-response test.
Overview
SysDynLab can generate self-contained C (ANSI C89 or C99) or Python code from any Subsystem or State Machine block. The generated code can be compiled and run independently of SysDynLab — no library dependencies beyond the C standard library or NumPy.
The code generator performs the same topological sort and subsystem flattening as the WASM simulation engine, so generated code matches the simulation numerically.
How to Open the Code Generator
- Right-click a Subsystem block or a State Machine block on the canvas.
- Select ▶ Generate Code from the context menu.
- Configure options in the left panel.
- Click ▶ Generate.
- Use Copy to copy to clipboard or ↓ Download to save as a file.
You can also right-click a block that is inside a subsystem when you have entered that subsystem level — the generator targets the current subsystem context.
Generator Options — C Language
| Option | Values | Notes |
| Function / Class Name | Any identifier (default: subsystem_step) | Prefix for all generated function and struct names. Use a descriptive name like pid_controller or motor_drive. |
| Data Type | double (default), float | double matches the simulation engine precision. Use float for embedded targets where 64-bit FPU is not available. |
| C Standard | C99 (default), ANSI C89/C90 | C99 allows // comments and mixed declarations/code. ANSI mode is compatible with older compilers (e.g. MSVC without /std:c99, or older embedded toolchains). State machine blocks require C99 for designated initializers. |
| Optimization Level | -O0, -O1, -O2 (default), -O3, -Os | Embeds a #pragma GCC optimize or equivalent hint. -O2 is the recommended default. Use -Os for code-size-constrained flash targets. |
| Linkage | static (default), extern | static confines the functions to a single translation unit — safest for embedded. extern declares functions for use across multiple .c files. |
| always_inline hint | On (default) | Adds __attribute__((always_inline)) to helper functions. Eliminates call overhead on GCC/Clang targets. |
| const input pointer | On (default) | Marks the input struct pointer const, allowing the compiler to optimize reads. Disable if your toolchain does not support it. |
| volatile state | Off (default) | Marks the state struct volatile — required when the state is shared between an ISR and the main loop on bare-metal targets. |
| NaN / Inf guard | Off (default) | Adds isnan/isinf checks on outputs at the end of each step. Useful for debugging numerically unstable models but adds per-step overhead. |
| Generate header (.h) | Off (default) | Prepends a matching #ifndef-guarded header block with struct typedefs and the function prototype. Paste the header part into a separate .h file. |
| Generate test main() | Off (default) | Appends a main() function that calls the step function for 100 steps and prints output values. Useful for verifying the generated code compiles and runs correctly: gcc -o test model.c -lm && ./test |
| Inline comments | On (default) | Adds comments to the generated code identifying each block by name and type. Turn off for cleaner output on code-size-constrained targets. |
Generator Options — Python
| Option | Values | Notes |
| Backend | NumPy (default), Pure Python | NumPy generates vectorized operations using numpy — faster for batch simulation. Pure Python generates plain math with no dependencies — portable to MicroPython or constrained environments. |
| Inline comments | On (default) | Same as C — adds block-name comments to the generated code. |
Generated Code Structure — C
For a subsystem named my_ctrl with the default options, the generator produces:
| Section | Contents |
my_ctrl_Inputs | Struct with one field per subsystem input port (named after the connected inport block label). |
my_ctrl_Outputs | Struct with one field per subsystem output port. |
my_ctrl_State | Struct holding all persistent state: integrator values, filter states, PID integrator, delay buffers, state machine locals, etc. |
my_ctrl_init(State *s) | Initializes the state struct to zero / initial conditions. Call once before the first step. |
my_ctrl_step(const Inputs *u, Outputs *y, State *s) | Runs one simulation step: reads inputs, evaluates all blocks in topological order, writes outputs, updates state. Call at every sample period. |
Typical Embedded Integration
#include "my_ctrl.h"
static my_ctrl_State ctrl_state;
static my_ctrl_Inputs ctrl_in;
static my_ctrl_Outputs ctrl_out;
void system_init(void) {
my_ctrl_init(&ctrl_state);
}
/* Call from timer ISR at sample rate */
void control_isr(void) {
ctrl_in.reference = adc_read_reference();
ctrl_in.feedback = adc_read_feedback();
my_ctrl_step(&ctrl_in, &ctrl_out, &ctrl_state);
dac_write(ctrl_out.control_signal);
}
State Machine Code Generation
Right-click a State Machine block → ▶ Generate Code. The generator translates the full statechart to C:
- Each state becomes an integer constant (
STATE_IDLE = 0, etc.).
- Active state is stored as a field in the State struct (
s->active_state).
- Local variables become fields in the State struct.
- Entry/exit actions and transition conditions are emitted as inline
if chains.
- Parallel regions are emitted as independent active-state fields and evaluated sequentially.
- Hierarchical states are emitted with nested switch-case blocks for each level.
The step function evaluates every active state's outgoing transitions in order, runs matching actions, and exits/enters states in the correct sequence — identical logic to the WASM runtime.
Subsystem Code Generation
Right-click a Subsystem block → ▶ Generate Code. The generator:
- Flattens the subsystem hierarchy (resolves Port In / Port Out blocks to direct connections).
- Resolves Tag Send / Tag Read pairs within the subsystem scope.
- Topologically sorts the flattened block graph (respecting direct-feedthrough flags).
- Schedules stateful blocks (integrators, filters, delay buffers) into a separate phase that runs after the algebraic blocks.
- Emits
_init and _step functions.
Nested subsystems are inlined — the generated code is always a flat function, not recursive calls. This makes it suitable for real-time targets with no dynamic memory or recursion.
Algebraic Loop Warning
If the model has a feedback loop with no state-holding block in the loop (a true algebraic loop), the generator emits a // WARNING: algebraic loop detected comment and places blocks in a best-effort order. The generated code may not numerically match the simulation, and the loop may need an Integrator, Unit Delay, or similar block inserted to break it.
Compilation
The generated C file is self-contained — no SysDynLab headers are needed. Compile with any C99-compatible compiler:
gcc -O2 -std=c99 -o my_model my_model.c -lm
clang -O2 -std=c99 -o my_model my_model.c -lm
For embedded (ARM Cortex-M example):
arm-none-eabi-gcc -O2 -std=c99 -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard \
-c my_model.c -o my_model.o
Solver Selection
Auto (recommended) — SysDynLab analyzes your model and picks the best solver automatically.
It detects stiff dynamics from transfer functions, filters, and PID gains.
| Solver | Order | Type | Best For |
| Euler | 1 | Fixed, Non-Stiff |
Quick previews, educational. Fast but least accurate — needs very small dt. |
| Heun (RK2) | 2 | Fixed, Non-Stiff |
Moderate accuracy at low cost. Good balance for simple systems. |
| RK4 | 4 | Fixed, Non-Stiff |
Default choice. Excellent accuracy for most models. The workhorse solver. |
| Implicit Euler | 1 | Fixed, Stiff |
Unconditionally stable for stiff systems but low accuracy. Use when stability matters more than precision. |
| Trapezoidal | 2 | Fixed, Stiff |
Good accuracy + stiff stability. Recommended fixed-step choice for stiff models. |
| RK45 Dormand-Prince | 5 | Variable, Non-Stiff |
Highest accuracy. Adapts step size internally for optimal performance. Best for smooth, non-stiff dynamics. |
| ESDIRK23 | 3 | Variable, Stiff |
Adaptive + stiff-capable. For models with wide time-constant spread (fast & slow modes together). |
When Is a System “Stiff”?
A system is stiff when it has both very fast and very slow dynamics simultaneously. Examples:
- High-bandwidth low-pass filter (cutoff > 100 Hz) in a slow control loop
- Transfer function with widely separated poles (e.g., s + 0.1 and s + 1000)
- PID with large derivative gain driving a slow plant
Non-stiff solvers (Euler, Heun, RK4, RK45) require very small dt for stiff systems —
they can blow up or oscillate. Stiff solvers (Implicit Euler, Trapezoidal, ESDIRK23) remain stable
even with larger dt.
Step Size (dt) Guide
Leave dt empty (recommended) — SysDynLab auto-suggests dt from your model’s fastest dynamics
(sine frequencies, filter cutoffs, transfer function poles).
| Solver | Samples per Period | Rule of Thumb |
| Euler | ~50 | dt < 1 / (50 × fmax) |
| Heun | ~30 | dt < 1 / (30 × fmax) |
| RK4 | ~15 | dt < 1 / (15 × fmax) |
| Implicit Euler | ~15 | Stable at any dt, but accuracy needs ~15/period |
| Trapezoidal | ~10 | dt < 1 / (10 × fmax) |
| RK45 | ~10 | Adapts automatically — dt sets the output resolution |
| ESDIRK23 | ~7 | Adapts automatically — dt sets the output resolution |
fmax = fastest frequency in your model (highest sine freq, filter cutoff, or natural frequency).
Examples
| Model | Recommended Solver | Typical dt |
| Step → 1st order TF (1/(s+1)) → Scope | RK4 | 0.01 – 0.05 |
| Sine (10 Hz) → Gain → Scope | RK4 | 0.005 |
| Step → PID → Plant with fast pole (1/(0.001s+1)) | Trapezoidal or ESDIRK23 | 0.0001 – 0.001 |
| Chirp (0.1–100 Hz) → LP filter (50 Hz) → Scope | RK45 | auto (0.001) |
| Discrete PID + Sample Delay (no continuous blocks) | Euler (any) | Match sample time |
Discrete Blocks
Discrete blocks (Sample Delay, Discrete TF, Discrete PID, etc.) operate at every simulation step.
Set Ts = -1 (default) to use the simulation dt as sample time, or set a specific Ts value.
If your model is purely discrete with no continuous blocks, any solver works — dt determines the sample rate.
Troubleshooting
- Output explodes / NaN: dt is too large, or you need a stiff solver. Reduce dt or switch to Trapezoidal / ESDIRK23.
- Oscillating output: Euler on a stiff system. Switch to Implicit Euler or Trapezoidal.
- Simulation too slow: dt is too small or solver is inefficient. Try RK4 (fewer steps needed) or RK45 (auto-adapts).
- Step response looks stepped: dt is too large to capture the dynamics. Reduce dt.
- Plotter looks wrong at high zoom: Increase T (duration) or reduce dt for more data points.
Simulator & Solver Limitations
SysDynLab is designed for education and rapid prototyping. The following constraints apply:
Hard Limits (compile-time constants in the WASM engine)
| Resource | Limit | Notes |
| Blocks | 512 | Total flattened blocks after subsystem expansion. A subsystem with 50 blocks uses 50 of the 512 slots, not 1. |
| Wires | 2 048 | Total connections across the whole model. |
| Ports per block | 8 | Applies to both inputs and outputs. Sum, Mux, etc. are also capped at 8 inputs. |
| Continuous state slots | 6 144 (512 × 12) | Integrator, filter, and TF state variables combined. |
| Implicit solver state size | 16 | Newton iteration limit for stiff solvers (Implicit Euler / ESDIRK23). Systems with more than 16 simultaneous state variables may not converge. |
| SM states per block | 64 | Includes all hierarchy levels and parallel region states within one State Machine block. |
| SM transitions per block | 128 | Total transitions (all levels) within one State Machine block. |
| SM local variables | 16 | Per State Machine block. |
| Scope ring buffer | 2 M doubles (~16 MB) | Shared across all Plotter channels. Wraps (oldest data overwritten) when full on long runs. |
Numerical Precision
| Item | Limitation |
| Floating-point precision | All signals are 64-bit doubles (IEEE 754). Underflow near ±5×10−324 and overflow near ±1.8×10308 produce ±Inf or NaN — the simulation does not automatically detect or recover from this. |
| Fixed-step accuracy | Euler and Heun accumulate global error proportional to dt. Halving dt roughly halves (Euler) or quarters (Heun) the error. For high-accuracy results use RK4 or variable-step RK45. |
| Variable-step resolution | RK45 and ESDIRK23 adapt step size internally but still record output at the user-specified dt intervals. Very coarse dt hides the fine-grained solution — reduce dt to improve plot resolution even with adaptive solvers. |
| Stiff systems with non-stiff solvers | Euler/Heun/RK4 require dt < 2 / |λmax| for stability. A fast pole (e.g. τ = 0.001 s) forces dt < 0.002 — which is often impractical. Switch to a stiff solver. |
Model Constraints
| Constraint | Detail |
| Algebraic loops | A feedback path with no state-holding block (Integrator, Unit Delay, Transfer Function, etc.) is an algebraic loop. The simulator warns and uses best-effort ordering, but results may be incorrect. Insert an Integrator or Unit Delay to break the loop. |
| Transfer function order | The denominator polynomial must have degree ≥ numerator degree (proper or strictly proper). An improper TF (e.g. s/(s+1) is fine; s²/(s+1) is not) will cause incorrect or unstable behaviour. |
| Discrete sample time mixing | All discrete blocks share the simulation step dt as their base sample time. Blocks with different Ts values are evaluated at each step but only update when their counter triggers — this is not true multirate scheduling and can introduce up to one dt of sample-time error. |
| Mux / signal width | Mux blocks expand signals for the Plotter only. Arithmetic blocks (Gain, Sum, etc.) operate on scalar signals — vector math is not supported. |
| Delay block | The Transport Delay uses a ring buffer sized to ceil(delay / dt) samples. Very large delay-to-dt ratios consume memory proportionally. A delay of 10 s at dt = 0.0001 s allocates 100 000 doubles per delay block. |
| State machine expressions | Supports scalar arithmetic and logic only. No string handling, no arrays, no function calls to user code. The ternary operator ?: is not supported. |
| No zero-crossing detection | State machine transitions are evaluated at each time step, not at the exact moment a condition becomes true. A fast event shorter than dt may be missed. Reduce dt or use a discrete block with a matching Ts. |
Browser & WASM Constraints
| Constraint | Detail |
| WASM memory ceiling | Initial heap is 64 MB; the runtime can grow up to 512 MB as needed. Very large scope buffers or many delay blocks may push into this range on long simulations. |
| Plotter data retention | Scope data is held in browser memory. A 100 s simulation at dt = 0.0001 s stores 1 million data points per channel — this can slow plot rendering. |
| Single-threaded execution | The simulation runs on the main browser thread. Very long simulations will block the UI momentarily per frame. |
| No persistent storage | Models are not saved automatically. Use Ctrl+S to download an XML file. Closing or refreshing the tab loses all unsaved work. |
| File access | CSV import, model load/save, and code download all use the browser file picker — there is no direct filesystem access. |
What SysDynLab Does Not Support
- Partial differential equations (PDEs) — only lumped (ODE) systems.
- Distributed-parameter delay — only fixed transport delay with a ring buffer.
- Multi-body physics or 3-D simulation.
- Real-time hardware I/O — simulation is as-fast-as-possible, not wall-clock locked.
- Symbolic math — all parameters must be numeric constants evaluated before simulation starts.
- Automatic differentiation for sensitivity or gradient computation.
- Optimization or parameter sweeps — run multiple simulations manually and compare Plotter traces.
Tip: For most control and signal-processing models these limits are never hit. If you encounter NaN outputs, check for algebraic loops first, then reduce dt. If the simulation is unexpectedly slow, switch from Euler to RK4 — it needs far fewer steps for the same accuracy.
Keyboard Shortcuts
| Action | Shortcut |
| Run / Stop simulation | Space or click ▶ Run |
| Undo | Ctrl+Z |
| Redo | Ctrl+Y |
| Save model | Ctrl+S |
| Open model | Ctrl+O |
| Copy selection | Ctrl+C |
| Cut selection | Ctrl+X |
| Paste | Ctrl+V |
| Duplicate selection | Ctrl+D |
| Select all | Ctrl+A |
| Delete selection | Delete or Backspace |
| Cancel wire / exit subsystem | Escape |
| Apply block popup | Enter (while popup is open) |
Mouse & Canvas
| Action | How |
| Pan the canvas | Middle-click drag, or hold Space + drag |
| Zoom in / out | Scroll wheel on canvas |
| Select multiple blocks | Drag a selection box on empty canvas |
| Add block to selection | Shift+click a block |
| Open block properties | Double-click block |
| Open context menu | Right-click block or wire |
| Start a wire | Click an output port (blue, right side) |
| Complete a wire | Click an input port (orange, left side) |
| Enter subsystem | Double-click a Subsystem block |
| Exit subsystem | Click parent name in breadcrumb, or Escape |
| Open scope viewer | Double-click Plotter block after running |
| Zoom scope X or Y | Click Zoom X / Zoom Y → draw rectangle on plot |
| Pan scope plot | Drag on the scope canvas |
| Zoom scope with wheel | Scroll on scope canvas |
| Reset scope view | Click Reset in scope toolbar |
Block Context Menu
| Item | What it does |
| Properties | Open block parameter popup |
| Mirror H / Mirror V | Flip block horizontally or vertically |
| Rotate CW / CCW | Rotate block 90° |
| Duplicate | Copy block in place |
| Delete | Remove block and its wires |
| Block Help | Show documentation for this block type |
| Create Subsystem | Group selected blocks into a new Subsystem |
| Generate C Code | Open C code generator for this block/subsystem |
About SysDynLab
SysDynLab is a browser-based block diagram simulator for building and running continuous and discrete-time dynamic systems. It runs entirely in the browser using WebAssembly — no installation, no server-side computation.
Built with vanilla JavaScript, WebAssembly (Emscripten / C), and no external frameworks.
Disclaimer
No Warranty. Use at Your Own Risk.
SysDynLab is provided "as is", without warranty of any kind, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, or numerical accuracy.
The simulation results produced by SysDynLab are approximations based on the numerical methods and model parameters you configure. Results may differ from real-world behaviour due to solver discretization error, model simplifications, floating-point rounding, or incorrect parameter values.
The user assumes full responsibility for verifying the correctness of any simulation result before using it for design, engineering, safety analysis, or any other purpose. SysDynLab and its authors accept no liability for decisions made based on simulation output.
Do not rely on SysDynLab results in safety-critical applications without independent verification by qualified engineers using certified tools.
License
A note on the future of SysDynLab:
If there is enough community interest and people are willing to contribute, I am open to releasing SysDynLab as a fully open-source project on GitHub — so that everyone can improve it together. If you would like to see that happen, send a message via the Feedback button. Enough voices will make it happen.
SysDynLab is currently distributed under a Source-Available License. The source code is published for transparency and personal study. It is not open-source under OSI definitions.
SysDynLab Source-Available License
Copyright (c) 2025 SysDynLab. All rights reserved.
Permission is granted, free of charge, to any individual to:
- Use this software for personal, academic, or educational purposes.
- Read and study the source code for learning purposes.
- Build and run the software locally for non-commercial use.
- Create and share simulation models (.xml files) made with this software.
The following are expressly PROHIBITED without prior written permission
from the copyright holder:
1. Hosting or deploying this software (or any modified version of it)
as a public or private web service, website, or application.
2. Redistributing, sublicensing, or publishing the source code or any
substantial portion of it, whether modified or unmodified.
3. Using this software or its source code as the basis for a competing
product or service, commercial or otherwise.
4. Removing or altering this license notice or any copyright attribution.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. IN NO EVENT SHALL
THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY
ARISING FROM THE USE OF OR INABILITY TO USE THIS SOFTWARE.
To request a commercial license or special permission, contact:
sysdynlab.simulator@gmail.com
Third-Party Components
| Component | Purpose | License |
| Emscripten | Compiles the C simulation engine to WebAssembly | MIT / University of Illinois |
| WebAssembly | Browser runtime for the simulation engine | W3C open standard |