Generated reference › Modulator — Control Systems/Waveform Functions
kind: generated#block#control-systems-waveform-functions

Modulator — Control Systems/Waveform Functions

Control_Systems/Waveform_Functions/Modulator · 1 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.

Modulator

Control Systems / Waveform Functions

Puts a message signal onto a carrier, one sample at a time. The carrier phase is θ[k] = 2πFc·k·Ts and f is the carrier function (cosine or sine); the method decides what is done with it:

  • AM suppressed carrier – y = x·f(θ). The carrier disappears when the message does.
  • AM transmitted carrier – y = (x − c)·f(θ), with c the Carrier Offset. Shifting the message so it never changes sign leaves a carrier present at all times, which is what an envelope detector needs.
  • PM – y = f(θ + kp·x). The message moves the phase; the amplitude never changes.

These are the three methods of MATLAB's modulate that produce one output sample per input sample. The rest are covered elsewhere or cannot stream – see Notes.

Ports

  • x – the message, scalar. In the two AM methods it is the amplitude the carrier is multiplied by, so its units are the output's; in PM it is multiplied by the phase deviation and so is a phase in radians before it reaches the carrier. Any value is accepted.
  • y – the modulated carrier, scalar. In PM it never leaves [−1, +1]; in the AM methods its size follows the message's.

Parameters

  • Modulation – which of the three relations above is computed.
    • AM suppressed carrier – y = x·f(θ), the default. This is modulate's 'am' and its 'amdsb-sc': the two are one branch of the function and return identical values, so one setting covers both.
    • AM transmitted carrier – y = (x − c)·f(θ), 'amdsb-tc'. Reads Carrier Offset.
    • PM – y = f(θ + kp·x), 'pm'. Reads Phase Deviation (rad).
  • Carrier Frequency (Hz) – Fc. Any real value; a negative one runs the phase backwards. A frequency at or beyond half the block's sampling rate aliases, as any sampled oscillation does – MATLAB's function refuses that case outright and this block does not, because the rate is the model's rather than an argument.
  • Carrier Function – cos (MATLAB's, and the default) or sin, the same carrier a quarter cycle later. It is a setting rather than a fixed cosine so that a quadrature pair can be built from two of these blocks – see Notes.
  • Carrier Offset – c, subtracted from the message before it multiplies the carrier. Read by AM transmitted carrier only, and ignored by the other two. MATLAB's function defaults it to the smallest sample of the whole message; a block that sees one sample at a time cannot know that number, so it is entered here.
  • Phase Deviation (rad) – kp, radians of carrier phase per unit of message. Read by PM only, and ignored by the other two. MATLAB's function defaults it to π divided by the largest magnitude in the whole message – again a number a streaming block cannot have – so it is entered here.
  • 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.

Everything except the message is structural and is inlined at export time: the carrier step folds the frequency together with the block's own period, and the offset, the deviation and the choice of method and carrier function all become part of the emitted arithmetic rather than tunable parameters. The period is inside the carrier step, so a core exported at one rate does not carry the frequency to another – re-export after changing either.

The three HDL targets are simulation-only, and only because of the trigonometric call: the phase, its wrap and the product all stay in the Q16.16 datapath and just the carrier value converts at the port boundary and evaluates in real. A synthesizable carrier is a phase accumulator driving a lookup table, which is a different block.

Simulink bridge

No equivalent (Support::None). modulate is a Signal Processing Toolbox function, and that toolbox ships no Simulink library at all. The modulator blocks in the installed libraries belong to Communications Toolbox and are different blocks – they carry symbol mapping, pulse shaping and a complex baseband convention that this block does not – so mapping onto one would misreport what crossed. The bridge reports this block instead, and it carries no parity testbench; code export verification covers it across all ten languages.

Notes

  • Stateful (one phase register) and discrete by nature (setDiscreteOnlyBlock(true)): the carrier advances once per sample.
  • The first output sits at θ = 0, as modulate's first sample sits at t = 0. The carrier step is added after the sample is produced, never before.
  • The carrier is accumulated, not multiplied out. θ is a running sum wrapped into 0, 2π) rather than 2πFct computed from a growing t. The two agree to 2.2×10−16 over 8 samples and 2.9×10−14 over 500, measured at Fc = 3.4 Hz and Ts = 0.01 s; the accumulator is what keeps the argument inside the range the HDL cores carry.
  • QAM is two of these blocks. modulate's 'qam' is x·cos(θ) + x₂·sin(θ) over two messages: one modulator with Carrier Function cos, one with sin, and a Sum. That is why the carrier function is a setting.
  • FM is the Voltage Controlled Oscillator in this same family, which is modulate(…, 'fm') exactly – vco is implemented by calling it. Reach for that block rather than looking for an FM setting here.
  • PWM is the PWM block under Discontinuities, which is that method with the duty arriving on a port and one output sample per input sample.
  • Three methods are deliberately absent. 'amssb' needs the analytic signal of the whole message, which one sample cannot form; 'ptm' and 'ppm' emit Fs/Fc samples for every message sample, so they change the signal's rate where this block produces one output per input.
  • Scalar only: one message, one carrier phase. Wire one block per channel, so that two channels cannot silently share a phase.
  • No state space. The block multiplies its input by a time-varying quantity (and in PM is not even linear in it), so it carries none and model reduction correctly declines to merge it.

Code facts#

FactValue
registered typeControl_Systems/Waveform_Functions/Modulator
familyControl_Systems/Waveform_Functions
solver environment classICoreBlock_0_Control_Systems_1_Waveform_Functions_2_Modulator
source[src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Waveform_Functions/Modulator/ICoreBlock_0_Control_Systems_1_Waveform_Functions_2_Modulator.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Waveform_Functions/Modulator/ICoreBlock_0_Control_Systems_1_Waveform_Functions_2_Modulator.h
default size on canvas124 × 76 px
ports at insert1 in, 1 out
code generators implementedPython, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text

Ports#

#DirectionSignal typeDescription label
1inICoreDoublex
2outICoreDoubley

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 variableDefaultSimulink parameter
ModulationAM suppressed carrier%~%AM transmitted carrier%~%PM~~AM s…—
Carrier Frequency (Hz)1—
Carrier Functioncos%~%sin~~cos—
Carrier Offset0—
Phase Deviation (rad)1—

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.

supportSupport::None
Simulink path—
port-count rulePortsParam::None
SampleTime parameteryes

Caveat (shown to the user): no Simulink equivalent: modulate() is a Signal Processing Toolbox FUNCTION and that toolbox ships no Simulink library at all. The modulator blocks in the installed libraries belong to Communications Toolbox and are different blocks -- they carry symbol mapping, pulse shaping and a complex baseband convention this block does not -- so mapping onto one would misreport what crossed. Reported rather than dropped, and it carries no parity testbench

Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h

Description vs code#

The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.

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).

Modulator -- one message sample onto one carrier sample, three methods, one phase register THE METHODS ARE READ OUT OF THE FUNCTION. MATLAB's modulate(x, Fc, Fs, method, opt) builds t = k/Fs from k = 0 and then, for the three methods a one-sample-in one-sample-out block can carry:

'am' / 'amdsb-sc' y = x .* cos(2*pi*Fc*t) 'amdsb-tc' y = (x - c) .* cos(2*pi*Fc*t) c the 5th argument 'pm' y = cos(2*pi*Fc*t + kp*x) kp the 5th argument

⚠ 'am' AND 'amdsb-sc' ARE ONE BRANCH OF THE FUNCTION, not two. Measured in R2026a at Fc = 3.4 Hz, Fs = 100 Hz over 8 samples: isequal(modulate(x,..,'am'), modulate(x,.., 'amdsb-sc')) is 1. So this block offers one setting for both and says so.

⚠ THE OTHER FIVE METHODS ARE NOT MISSING, THEY BELONG SOMEWHERE ELSE, and each is named in the description with its reason:

  • 'fm' is the Voltage Controlled Oscillator standing beside this block in this same

family -- vco() hands its work to modulate(..., 'fm'), so building it here would be a second copy of a block this library already has;

  • 'pwm' is the PWM block under Discontinuities, which is that method at a one-sample

rate with the duty on a port;

  • 'amssb' needs the analytic signal of the WHOLE message, which a block that sees one

sample cannot form;

  • 'ptm'/'ppm' emit Fs/Fc samples for every message sample -- they change the rate, and

a block here produces one output per input;

  • 'qam' takes two messages onto a cosine and a sine. Both halves ARE this block, which

is why the carrier function is a setting: two of these and one Sum is a QAM modulator.

THE CARRIER IS A WRAPPED PHASE ACCUMULATOR rather than a multiplication of a growing time: theta starts at 0, the step 2*pi*Fc*Ts is added after every output, and the result is wrapped into [0, 2*pi). That keeps the argument inside the fixed-point range the three hardware description targets carry, and it costs 2.2e-16 over 8 samples and 2.9e-14 over 500 against the function's own cos(2*pi*Fc*t) -- both measured, not estimated.

⚠ THE STEP IS ADDED AFTER THE OUTPUT, because the function's first sample sits at t = 0. The other order lags the reference by one carrier step on every sample, which a constant message cannot reveal -- which is why the block is verified against a message that moves.

⚠ THE THREE HARDWARE DESCRIPTION TARGETS ARE SIMULATION-ONLY, and only because of the trigonometric call: the phase, the wrap and the product all stay in the Q16.16 datapath, and just the carrier value converts at the port boundary and evaluates in real. The

Sample results#

Modulator — Step: 0 -> 1 at t = 1 sModulator — Step: 0 -> 1 at t = 1 s-1-0.500.51012345t (s)in ICoreDouble-Out-0out ICoreDouble-Out-0

The same rig also ran:

StimulusWhat it isOutput range
impulseImpulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair)-1 … 0
rampRamp: slope 1 from t = 0-5.5 … 5
sineSine Wave: amplitude 1, 2 rad/s, no phase, no bias-0.8415 … 1
tableRepeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample-3 … 2.427

Plotted: step — Step: 0 -> 1 at t = 1 s

Category dynamic · sample time 0.1 · 60 steps · commit 05a7af9545eeffe8cf9dc83de88a165a633fca2a · produced by docsSample --out <folder> --blocks Modulator Demodulator Hilbert_Transform --steps 60 · data docs/generated/samples/Control_Systems__Waveform_Functions__Modulator.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).