Matlab Design Dsphdl Ddc
MathWorksOfficialCreated by MathWorks
- Skill
- Tool integration
- Software development
- BSD-2-Clause
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for designing Digital Down Converters (DDC) in MATLAB using dsphdl System objects. It covers the end-to-end process of configuring NCOs, mixers, and decimation filter chains, simulating the design, and generating synthesizable Verilog or VHDL code via HDL Coder. The workflow includes specific guidance for integer and non-integer decimation, including the use of Farrow rate converters, while emphasizing hardware-ready practices such as valid signal propagation and fixed-point arithmetic.
What it does
- Configures NCO, mixer, and decimation filter chains using dsphdl objects.
- Supports design of integer and non-integer decimation architectures.
- Provides methods for CIC compensation filter design.
- Facilitates HDL code generation for FPGA and ASIC targets.
- Includes guidelines for fixed-point arithmetic and resource sharing.
- Requires interactive parameter gathering to ensure design consistency.
When to use it
- When designing a DDC or receiver front-end for FPGA or ASIC implementation.
- When building a decimation chain using NCO, mixer, and filter components.
- When the target output rate requires non-integer decimation.
- When converting floating-point algorithms to HDL-ready streaming architectures.
Advantages
- Ensures HDL-synthesizable design patterns.
- Provides a consistent approach to bit-accurate simulation.
- Automates the transition from algorithmic design to HDL code.
- Includes specific handling for complex carrier modulation and decimation staging.
Usage examples
- “I need to design a DDC for a 100 MHz input signal to a 6.25 MHz output.”
- The agent will initiate the requirements gathering process to define the sampling frequency, decimation factors, and filter specifications before providing the design script.
- “How do I handle non-integer decimation in my DDC design?”
- The agent will guide you through using a combination of integer CIC and FIR stages followed by a dsphdl.FarrowRateConverter to achieve the required fractional rate change.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/matlab/matlab-design-dsphdl-ddc/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip matlab-design-dsphdl-ddc.zip -d .agents/skills
Documentation by MathWorks
dsphdl DDC: Design, Simulate, Generate HDL
Overview
End-to-end MATLAB workflow for designing a Digital Down Converter (DDC) using dsphdl System objects — combining an NCO (local oscillator), complex mixer, and multi-stage decimation filter chain — then simulating the streaming HDL-optimized design and generating synthesizable HDL via HDL Coder.
Integer decimation (all stages have integer rate change):
RF Input → [Mixer] → [CIC Decimator] → [Compensation FIR Decimator] → Baseband Output
↑
[NCO] (generates cos + jsin at carrier frequency)Non-integer decimation (uses Farrow for fine rate adjustment):
RF Input → [Mixer] → [CIC Dec (xR)] → [Farrow (L/M)] → [FIR Dec (xD)] → Baseband Output
↑
[NCO]When to Use
Designing a DDC or receiver front-end for FPGA/ASIC
Building an NCO + mixer + decimation chain with
dsphdlobjectsNon-integer decimation — when the target output rate doesn't divide evenly into the input rate
Converting a floating-point DDC algorithm to HDL-ready streaming architecture
Generating Verilog/VHDL for a complete DDC subsystem
When Not To Use
Non-HDL workflows, e.g. embedded C code generation
Critical Conventions
NCO Phase Increment
ALWAYS compute as: phaseInc = round((-Fc * 2^AccumulatorWL) / Fs) where Fc is the carrier frequency, Fs is the input sample frequency, and AccumulatorWL is the NCO accumulator word length. This produces a negative integer. Never omit the negation or compute round(Fc / Fs * 2^AccumulatorWL) — the negative sign is required for correct down-conversion.
Mixer: direct multiply with fi() arithmetic — NO conjugate
The mixer multiplies the input by the NCO output directly. ALWAYS write it as a single fi() expression with NO conj():
% DO — correct, concise, HDL-synthesizable:
mixed = fi(dataIn * ncoSample, 1, 16, 14);
% DON'T — conjugate the NCO (wrong convention for this toolbox):
% mixed = fi(dataIn * conj(ncoSample), 1, 16, 14);
% DON'T — manual I/Q decomposition (verbose, error-prone):
% cosVal = real(ncoOut); sinVal = imag(ncoOut);
% iMixed = fi(dataIn * cosVal, 1, 16, 14);
% qMixed = fi(dataIn * sinVal, 1, 16, 14);
% DON'T — double() arithmetic (NOT synthesizable for HDL):
% mixed = fi(double(dataIn) * double(ncoOut), 1, 16, 14);OutputDataType — ALWAYS use "Same word length as input"
ALWAYS set OutputDataType to 'Same word length as input' on CIC and FIR stages. For Farrow, use 'Same as first input' (equivalent property value). Never use 'Full precision' — it causes excessive bit growth through the chain.
FIR NumCycles for resource sharing
ALWAYS set NumCycles on FIR stages — see references/numcycles.md for full details.
NCO PhaseIncrementSource
ALWAYS set PhaseIncrementSource to 'Property' for fixed-frequency DDC designs. The default is 'Input port', which changes the step method signature from nco(validIn) to nco(phaseInc, validIn).
Valid signal piping — NEVER use if-statements on valid
ALWAYS call every stage every clock cycle and pipe valid outputs to downstream valid inputs. This matches the actual HDL hardware behavior where all stages run every clock cycle and valid propagates as a signal.
% DO — pipe valid through the chain:
[ncoSample, ncoValid] = nco(validIn);
mixed = fi(dataIn * ncoSample, 1, 16, 14);
[cicSample, cicValid] = cicDec(mixed, ncoValid);
[dataOut, validOut] = firDec(cicSample, cicValid);
% DON'T — conditionally call downstream stages:
% [cicSample, cicV] = cicDec(mixed, true);
% if cicV
% [firSample, firV] = firDec(cicSample, true);
% endData-valid pairing — ALWAYS gate data with its corresponding valid
Data outputs are only meaningful when the corresponding valid signal is true. Always use dataOut together with validOut from the same stage. Never read, store, or process data samples without checking the valid that was returned alongside them.
% DO — collect only valid output samples:
outputData = dataOut(validOut);
% DO — gate downstream processing on the valid from the same step call:
[dataOut, validOut] = firDec(cicSample, cicValid);
if validOut
outputBuffer(idx) = dataOut;
idx = idx + 1;
end
% DON'T — use dataOut without checking validOut:
% outputBuffer(ii) = dataOut; % dataOut is garbage when validOut is false
% DON'T — mix valid from one stage with data from another:
% goodSamples = cicSample(validOut); % validOut is from firDec, not cicDecThis applies to all stages: NCO, CIC, FIR, and Farrow. When a stage returns [data, valid], those two outputs are paired — data is undefined when valid is false.
Test signals — sinusoid modulated onto complex carrier
DDC inputs represent a baseband signal modulated onto a complex carrier. ALWAYS construct the test signal as a real baseband waveform multiplied by a complex carrier exponential:
% DO — sinusoid modulated onto complex carrier:
inputSignal = cos(2*pi*Fsig*t) .* exp(1j*2*pi*Fc*t);
dataIn = fi(inputSignal, 1, 16, 14); % fi() of complex input stays complex
% DO — complex codegen input type:
dataType = complex(fi(0, 1, 16, 14));
% DON'T — pure complex exponential at offset frequency (not a modulated signal):
% inputSignal = exp(1j*2*pi*(Fc + Fsig)*t);
% DON'T — real cos() signal (loses negative frequency content, wrong DDC behavior):
% inputSignal = cos(2*pi*(Fc + Fsig)*t);After down-conversion, the expected baseband output is the original modulating waveform: cos(2*pi*Fsig*t).
Do NOT pre-allocate dataOut or validOut in testbenches — let MATLAB grow them dynamically so the data type propagates from the DDC function output.
This applies to all generated code: design scripts, testbenches, and HDL codegen argument types.
All System objects MUST be persistent
In the HDL function wrapper, declare all dsphdl objects as persistent and initialize inside if isempty(...).
Interactive Requirements Gathering (REQUIRED)
Before generating any code, ALWAYS use AskUserQuestion to gather the user's DDC specifications. Ask exactly ONE question per AskUserQuestion call. Do not assume defaults.
Questions to ask (one at a time, in order):
Sample rate and carrier: "What is the input sampling frequency (Fs) and the carrier/IF frequency to down-convert?"
Output rate: "How would you like to specify the output rate?" (decimation factor or output sample rate in Hz). Compute
totalDecim = Fs / Fs_out. Check whethertotalDecimis an integer (i.e.,mod(Fs, Fs_out) == 0). Only flag as non-integer if it truly is (e.g., 8.2, 12.5). Integer values like 25, 100, etc. are integer even if they are not powers of 2.Signal bandwidth: "What is the desired passband bandwidth at the output?"
Input data type: "What is the input word length and format?" (e.g., 16-bit signed, 14 fractional)
Decimation staging: Present factorization options based on whether
totalDecimis integer:If integer: Only offer integer staging options (CIC+FIR, CIC+FIR+FIR, All FIR). Compute valid integer factor pairs/triples of
totalDecimand present them. Do NOT offer Farrow-based options. Every stage must actually change the sample rate (decimation factor >= 2). Never offer non-decimating FIR stages (factor = 1).If non-integer: Offer Farrow-based options (CIC+FIR+Farrow) alongside integer approximations. Explain the Farrow handles the fractional remainder. The Farrow must always decimate (RateChange > 1, since RateChange = fsIn/fsOut). Choose integer stages so that CIC_R * FIR_R <= total_decimation. Never offer staging where CIC_R * FIR_R > total_decimation (that would require Farrow interpolation).
CIC parameters (if CIC used): "How many CIC sections?"
HDL language: "Verilog or VHDL?" — just present the two options; do NOT offer commentary on what each language is good for or when to choose one over the other.
After gathering requirements — compute derived parameters:
Phase increment =
round((-Fc * 2^AccumulatorWL) / Fs)CIC/FIR decimation factors (must be integer per stage)
FIR
NumCycles= cumulative decimation at that FIR's input (seereferences/numcycles.md)For non-integer:
total_decimation = CIC_R * FIR_R * farrowRateChange, wherefarrowRateChange = total_decimation / (CIC_R * FIR_R)(close to 1, specified as fsIn/fsOut)Validate:
signalBandwidth/2 < Fs_out/2— if not, the desired signal exceeds the output Nyquist rate and the specs are inconsistent. Tell the user to reduce bandwidth or increase output rate.
Complete Workflow
Step 1: Compute DDC Parameters
%% DDC System Parameters
Fs = 100e6; % Input sample rate (Hz)
Fc = 25e6; % Carrier/IF frequency (Hz)
totalDecim = 16; % Total decimation factor
Fs_out = Fs / totalDecim; % Output sample rate
% Decimation staging: CIC handles bulk, FIR refines
cicDecimFactor = 8; % CIC decimation
firDecimFactor = totalDecim / cicDecimFactor; % FIR decimation = 2
% NCO phase increment for carrier frequency
accWL = 32; % Accumulator word length (32-bit gives ~0.023 Hz resolution at 100 MHz)
phaseInc = round((-Fc * 2^accWL) / Fs);
fprintf('Phase increment: %d\n', phaseInc);
fprintf('Actual frequency: %.6f MHz\n', abs(phaseInc) * Fs / 2^accWL / 1e6);Step 2: Design CIC Compensation FIR
ALWAYS use dsp.CICCompensationDecimator to design the compensation filter. This designs a filter that inverts the CIC's passband droop (sinc^N rolloff) while providing the stopband rejection needed for the FIR decimation. Never use fir1() — it produces a generic lowpass that does not compensate CIC droop.
%% Design CIC compensation filter using dsp.CICCompensationDecimator
cicNumSections = 4;
cicDiffDelay = 1;
Fs_afterCIC = Fs / cicDecimFactor; % Sample rate after CIC (= FIR input rate)
Fpass = signalBandwidth / 2; % Passband edge (half of desired signal BW)
Fstop = Fs_out / 2; % Stopband edge (output Nyquist)
cicDroopComp = dsp.CICCompensationDecimator(firDecimFactor, ...
'SampleRate', Fs_afterCIC, ...
'CICRateChangeFactor', cicDecimFactor, ...
'CICNumSections', cicNumSections, ...
'PassbandFrequency', Fpass, ...
'StopbandFrequency', Fstop, ...
'PassbandRipple', 0.1, ...
'StopbandAttenuation', 50);
compCoeffs = cicDroopComp.coeffs.Numerator;Step 3: Instantiate dsphdl System Objects
%% Create the DDC components
% NCO — generates complex exponential at carrier frequency
nco = dsphdl.NCO( ...
'DesignMethod', 'NCO parameter', ...
'PhaseIncrementSource', 'Property', ...
'PhaseIncrement', phaseInc, ...
'Waveform', 'Complex exponential', ...
'AccumulatorWL', accWL, ...
'OutputWL', 16, ...
'OutputFL', 14, ...
'NumDitherBits', 4, ...
'PhaseQuantization', true, ...
'NumQuantizerAccumulatorBits', 12);
% CIC Decimator — bulk decimation (efficient, no multipliers)
cicDec = dsphdl.CICDecimator( ...
'DecimationFactor', cicDecimFactor, ...
'DifferentialDelay', cicDiffDelay, ...
'NumSections', cicNumSections, ...
'OutputDataType', 'Same word length as input', ...
'GainCorrection', true);
% FIR Decimator — compensation + final decimation
% NumCycles = cicDecimFactor: FIR input arrives every 8 clocks, so reuse multipliers
firDec = dsphdl.FIRDecimator( ...
'DecimationFactor', firDecimFactor, ...
'Numerator', compCoeffs, ...
'NumCycles', cicDecimFactor, ...
'OutputDataType', 'Same word length as input', ...
'FilterStructure', 'Direct form systolic');Step 4: Simulate the DDC
%% Simulate DDC end-to-end
numSamples = 1000 * totalDecim;
t = (0:numSamples-1)' / Fs;
Fsig = 1e6;
inputSignal = cos(2*pi*Fsig*t) .* exp(1j*2*pi*Fc*t);
dataIn = fi(inputSignal, 1, inputWL, inputFL);
for ii = 1:numSamples
[ncoSample, ncoValid] = nco(true);
mixed = fi(dataIn(ii) * ncoSample, 1, inputWL, inputFL);
[cicSample, cicValid] = cicDec(mixed, ncoValid);
[ddcOut(ii), ddcValid(ii)] = firDec(cicSample, cicValid);
end
outputData = ddcOut(ddcValid);
fprintf('DDC produced %d output samples from %d input samples\n', numel(outputData), numSamples);Step 5: Generate HDL
5a. Create the Design Function
IMPORTANT: dsp.CICCompensationDecimator is NOT supported for HDL code generation. You MUST pre-compute the FIR coefficients by running the design script (Step 2) in MATLAB first, then hardcode the resulting numeric vector in the HDL function. Never call dsp.CICCompensationDecimator inside an HDL function — it will fail at codegen time.
function [dataOut, validOut] = myDDC(dataIn, validIn)
%myDDC HDL-optimized Digital Down Converter
persistent nco cicDec firDec;
if isempty(nco)
phaseInc = round((-25e6 * 2^32) / 100e6);
nco = dsphdl.NCO( ...
'DesignMethod', 'NCO parameter', ...
'PhaseIncrementSource', 'Property', ...
'PhaseIncrement', phaseInc, ...
'Waveform', 'Complex exponential', ...
'AccumulatorWL', 32, ...
'OutputWL', 16, ...
'OutputFL', 14, ...
'NumDitherBits', 4, ...
'PhaseQuantization', true, ...
'NumQuantizerAccumulatorBits', 12);
cicDec = dsphdl.CICDecimator( ...
'DecimationFactor', 8, ...
'DifferentialDelay', 1, ...
'NumSections', 4, ...
'OutputDataType', 'Same word length as input', ...
'GainCorrection', true);
% Coefficients pre-computed from dsp.CICCompensationDecimator in design script
compCoeffs = [ ... ]; % <-- paste numeric vector from Step 2
firDec = dsphdl.FIRDecimator( ...
'DecimationFactor', 2, ...
'Numerator', compCoeffs, ...
'NumCycles', 8, ...
'OutputDataType', 'Same word length as input', ...
'FilterStructure', 'Direct form systolic');
end
[ncoSample, ncoValid] = nco(validIn);
mixed = fi(dataIn * ncoSample, 1, 16, 14);
[cicSample, cicValid] = cicDec(mixed, ncoValid);
[dataOut, validOut] = firDec(cicSample, cicValid);
endWorkflow for obtaining coefficients: After running the design script (Step 2), execute fprintf('%.15g, ', compCoeffs) in MATLAB to get the numeric values, then paste them into the compCoeffs vector in the HDL function.
5b. Create the Testbench
The testbench should run the DDC, print sample counts, and plot time-domain I/Q and frequency-domain output. Do NOT include correlation checks, normalization, or numeric pass/fail verification — just plot and let the user visually inspect.
After running the testbench: Print the sample counts and the rough decimation factor (numInputSamples / numOutputSamples). Do NOT add commentary or interpretation about the decimation factor — just print the numbers.
%% DDC Testbench
clear myDDC;
totalDecim = 16; numSamples = 1000 * totalDecim;
Fs = 100e6; Fc = 25e6; Fs_out = Fs / totalDecim;
t = (0:numSamples-1)' / Fs;
Fsig = 1e6;
inputSignal = cos(2*pi*Fsig*t) .* exp(1j*2*pi*Fc*t);
dataIn = fi(inputSignal, 1, 16, 14);
for ii = 1:numSamples
[dataOut(ii), validOut(ii)] = myDDC(dataIn(ii), true);
end
outputData = dataOut(validOut);
fprintf('DDC produced %d output samples from %d input samples\n', numel(outputData), numSamples);
%% Plot — time-domain I/Q and frequency-domain PSD
tOut = (0:numel(outputData)-1)' / Fs_out;
figure; subplot(2,1,1);
plot(tOut*1e6, real(double(outputData)), tOut*1e6, imag(double(outputData)));
xlabel('Time (\mus)'); ylabel('Amplitude'); title('DDC Output (I/Q)'); legend('I','Q'); grid on;
subplot(2,1,2); nfft = min(256, numel(outputData));
[psd, f] = pwelch(double(outputData), hanning(nfft), floor(nfft/2), nfft, Fs_out, 'centered');
plot(f/1e6, 10*log10(psd)); xlabel('Frequency (MHz)'); ylabel('PSD (dB/Hz)'); title('Output Spectrum'); grid on;5c. Generate HDL Code
%% Generate HDL for DDC
hdlcfg = coder.config("hdl");
hdlcfg.TargetLanguage = 'Verilog'; % or 'VHDL'
hdlcfg.GenerateHDLTestBench = true;
hdlcfg.TestBenchName = 'myDDC_tb';
dataType = complex(fi(0, 1, 16, 14)); % scalar complex fixed-point input
codegen -config hdlcfg myDDC -args {dataType, false} -d hdl_outputAfter HDL generation: Do not display the resource utilization report or resource summary to the user. Just confirm that HDL generation succeeded (number of files, conformance errors) and list the key generated file paths.
Non-Integer Decimation with Farrow Rate Converter
For non-integer decimation factors (e.g., 8.2, 12.5, 7.68), use integer CIC + FIR stages for bulk decimation and a dsphdl.FarrowRateConverter for the fractional fine adjustment.
Staging strategy (DDC — Farrow must always decimate, RateChange > 1):
Choose integer CIC_R and FIR_R such that CIC_R * FIR_R <= total_decimation (integer stages under-decimate; Farrow handles the remainder). **Never** choose CIC_R * FIR_R > total_decimation — that would require Farrow interpolation (RateChange < 1), which is wrong for a DDC.
Compute farrowRateChange = total_decimation / (CIC_R * FIR_R) (must be > 1 for DDC, since RateChange = fsIn/fsOut)
Verify: CIC_R * FIR_R * farrowRateChange = total_decimation
Example: 8.2x decimation — CIC(x4) + FIR(x2) + Farrow(RateChange=41/40) = 4 * 2 * (41/40) = 8.2
function [dataOut, validOut] = myDDC_fractional(dataIn, validIn)
%myDDC_fractional DDC with non-integer 8.2x decimation
persistent nco cicDec firDec farrow;
if isempty(nco)
phaseInc = round((-25e6 * 2^32) / 100e6);
nco = dsphdl.NCO( ...
'DesignMethod', 'NCO parameter', ...
'PhaseIncrementSource', 'Property', ...
'PhaseIncrement', phaseInc, ...
'Waveform', 'Complex exponential', ...
'AccumulatorWL', 32, ...
'OutputWL', 16, 'OutputFL', 14, ...
'NumDitherBits', 4, ...
'PhaseQuantization', true, ...
'NumQuantizerAccumulatorBits', 12);
cicDec = dsphdl.CICDecimator( ...
'DecimationFactor', 4, ...
'DifferentialDelay', 1, ...
'NumSections', 4, ...
'OutputDataType', 'Same word length as input', ...
'GainCorrection', true);
% Coefficients pre-computed from dsp.CICCompensationDecimator in design script
compCoeffs = [ ... ]; % <-- paste numeric vector from design script
firDec = dsphdl.FIRDecimator( ...
'DecimationFactor', 2, ...
'Numerator', compCoeffs, ...
'NumCycles', 4, ...
'OutputDataType', 'Same word length as input', ...
'FilterStructure', 'Direct form systolic');
farrowCoeffs = [-1/6, 1/2, -1/3, 0; ...
1/2, -1, -1/2, 1; ...
-1/2, 1/2, 1, 0; ...
1/6, 0, -1/6, 0];
farrow = dsphdl.FarrowRateConverter( ...
'RateChangeSource', 'Property', ...
'RateChange', 41/40, ...
'Coefficients', farrowCoeffs, ...
'OutputDataType', 'Same as first input', ...
'FilterStructure', 'Direct form systolic');
end
[ncoSample, ncoValid] = nco(validIn);
mixed = fi(dataIn * ncoSample, 1, 16, 14);
[cicSample, cicValid] = cicDec(mixed, ncoValid);
[firSample, firValid] = firDec(cicSample, cicValid);
% Farrow always outputs 3: [data, valid, ready]
[dataOut, validOut, ~] = farrow(firSample, firValid);
endKey Farrow conventions:
RateChange= fsIn / fsOut. In a DDC, Farrow must always decimate (RateChange > 1). Never use RateChange < 1 in a DDC — that would be interpolation.Farrow always outputs 3 values:
[data, valid, ready]. Capturereadywith~if not using backpressure.Farrow must be called every clock cycle — it manages its own internal timing.
Default 3rd-order Lagrange coefficients work well for most DDC applications.
For additional architecture variants (three-stage, Farrow vs FIR Rate Converter comparison, real I/Q mixing), see references/architecture-variants.md.
Common Mistakes
These supplement the Critical Conventions above — only items not already covered there.
| Mistake | Fix |
|---|---|
Using mfilt objects for filter design | NEVER use mfilt (e.g., mfilt.cicdecim, mfilt.firinterp) — it is deprecated. Use dsp.CICCompensationDecimator, designMultirateFIR, or dsp.FIRDecimator/dsp.FIRInterpolator for filter design |
Using fir1() for CIC compensation filter | ALWAYS use dsp.CICCompensationDecimator — it inverts CIC droop; fir1() is a generic lowpass that ignores CIC response |
Calling dsp.CICCompensationDecimator inside HDL function | dsp.CICCompensationDecimator is NOT supported for HDL code generation. Pre-compute coefficients in the design script, then hardcode the numeric vector in the HDL function |
| CIC decimation factor not integer | Each stage must be integer. For non-integer totals, use Farrow for the fractional part |
| Farrow misuse (wrong rate, missing output, not called every cycle) | RateChange = fsIn/fsOut (> 1 for DDC decimation). Always capture 3 outputs [data, valid, ~]. Call every cycle. Choose integer stages so CIC_R * FIR_R <= total_decimation — never > total (that requires interpolation) |
For component property tables and step method signatures, see references/component-properties.md. |
Copyright 2026 The MathWorks, Inc.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSELicence file added by AutoGineer1.4 KiB
- SKILL.md21.9 KiB
- manifest.yaml559 B
- references/architecture-variants.md3.2 KiB
- references/component-properties.md5.7 KiB
- references/numcycles.md6.1 KiB
references/numcycles.md
dsphdl NumCycles: Resource Sharing in Decimation Chains
Overview
The NumCycles property on dsphdl System objects controls resource sharing — it tells the filter how many clock cycles elapse between valid input samples, so the hardware can reuse multipliers across those idle cycles instead of instantiating one multiplier per tap.
Key principle: In a decimation chain, each stage's input arrives at a slower rate than the system clock. NumCycles should match that rate ratio so the filter exploits every available idle cycle for multiplier reuse.
Objects with NumCycles
| System Object | Default | Required FilterStructure | Typical Use |
|---|---|---|---|
dsphdl.FIRDecimator | 1 | 'Direct form systolic' | Decimation chains — set to cumulative decimation at input |
dsphdl.FIRFilter | 2 | 'Partly serial systolic' | Standalone filters in multi-rate chains |
dsphdl.FarrowRateConverter | 1 | — | Fractional rate converters after integer decimation stages |
IMPORTANT — FilterStructure differs by object:
dsphdl.FIRFilter:NumCyclesis only active with'Partly serial systolic'. Using'Direct form systolic'silently ignoresNumCycles(produces a warning).dsphdl.FIRDecimator:NumCyclesis active with'Direct form systolic'(the default).dsphdl.FIRFilteralso requires settingSerializationOptionto either'Minimum number of cycles between valid input samples'(then setNumCycles) or'Maximum number of multipliers'(then setNumberOfMultipliers).
Critical Convention
ALWAYS set NumCycles to the number of system clock cycles between valid input samples at that stage. In a decimation chain, this equals the cumulative decimation factor at the stage's input:
First filter after CIC(xR):
NumCycles = RSecond filter after CIC(xR) + FIR(xD):
NumCycles = R * DFarrow after CIC(xR) + FIR(xD):
NumCycles = R * D
How It Works
Without resource sharing (NumCycles = 1), an N-tap FIR needs N multipliers running in parallel — one multiply per tap per clock. With NumCycles = K, the filter serializes computation across K clocks, needing only ceil(N/K) multipliers.
Example: A 32-tap FIR after a CIC that decimates by 16:
NumCycles = 1→ 32 multipliers (no sharing)NumCycles = 16→ ceil(32/16) = 2 multipliers (16x reduction)
The filter accepts one sample, computes for 16 clocks using 2 multipliers, then is ready for the next sample — which arrives exactly 16 clocks later because of the CIC decimation.
Decimation Chain Examples
Two-Stage: CIC + FIR
cicR = 8;
cicDec = dsphdl.CICDecimator('DecimationFactor', cicR, 'NumSections', 4);
compCoeffs = coeffs(dsp.CICCompensationDecimator(2, 'CICRateChangeFactor', cicR, 'CICNumSections', 4)).Numerator;
firDec = dsphdl.FIRDecimator( ...
'DecimationFactor', 2, ...
'Numerator', compCoeffs, ...
'NumCycles', cicR, ...
'FilterStructure', 'Direct form systolic');Three-Stage: CIC + FIR + FIR
cicR = 16;
cicDec = dsphdl.CICDecimator('DecimationFactor', cicR, 'NumSections', 5);
compCoeffs = coeffs(dsp.CICCompensationDecimator(2, 'CICRateChangeFactor', cicR, 'CICNumSections', 5)).Numerator;
firDec1 = dsphdl.FIRDecimator( ...
'DecimationFactor', 2, ...
'Numerator', compCoeffs, ...
'NumCycles', cicR, ...
'FilterStructure', 'Direct form systolic');
chanCoeffs = designMultirateFIR(1, 2);
firDec2 = dsphdl.FIRDecimator( ...
'DecimationFactor', 2, ...
'Numerator', chanCoeffs, ...
'NumCycles', cicR * 2, ...
'FilterStructure', 'Direct form systolic');
% Total: 16 * 2 * 2 = 64x decimation
% firDec1: 16 clocks/sample → ceil(32/16) = 2 multipliers
% firDec2: 32 clocks/sample → ceil(64/32) = 2 multipliersWith Farrow Rate Converter
cicR = 4; firR = 2;
cicDec = dsphdl.CICDecimator('DecimationFactor', cicR, 'NumSections', 4);
compCoeffs = coeffs(dsp.CICCompensationDecimator(firR, 'CICRateChangeFactor', cicR, 'CICNumSections', 4)).Numerator;
firDec = dsphdl.FIRDecimator( ...
'DecimationFactor', firR, ...
'Numerator', compCoeffs, ...
'NumCycles', cicR, ...
'FilterStructure', 'Direct form systolic');
farrow = dsphdl.FarrowRateConverter( ...
'RateChangeSource', 'Property', ...
'RateChange', 41/40, ...
'NumCycles', cicR * firR);
% Farrow input rate = CIC(x4) * FIR(x2) = 8 clocks between samplesFIRFilter in a Multi-Rate Chain
% Standalone FIR filter running after 8x decimation
fir = dsphdl.FIRFilter( ...
'Numerator', fir1(63, 0.5), ...
'FilterStructure', 'Partly serial systolic', ...
'SerializationOption', 'Minimum number of cycles between valid input samples', ...
'NumCycles', 8);
% 'Partly serial systolic' is required for NumCycles on FIRFilter
% ceil(64/8) = 8 multipliers instead of 64Common Mistakes
| Mistake | Fix |
|---|---|
| Not setting NumCycles (defaults to 1) | Set to cumulative decimation at that stage's input |
| Setting NumCycles larger than available clocks | NumCycles must not exceed the actual inter-sample gap — filter will miss samples |
| Setting NumCycles on CICDecimator | CICDecimator does not have NumCycles — it is inherently serial by design |
| Confusing NumCycles with DecimationFactor | NumCycles = clock cycles between valid inputs (for sharing); DecimationFactor = rate change ratio |
| Using NumCycles > 1 with frame (vector) input on FIRDecimator | Frame input with NumCycles > 1 is not supported on Direct form systolic — use scalar input |
Using 'Direct form systolic' with NumCycles on dsphdl.FIRFilter | FIRFilter requires 'Partly serial systolic' for NumCycles — 'Direct form systolic' silently ignores it (warning only). Also set SerializationOption. |
Forgetting SerializationOption on dsphdl.FIRFilter | Set 'Minimum number of cycles between valid input samples' (for NumCycles) or 'Maximum number of multipliers' (for NumberOfMultipliers) |
| Assuming all objects use the same FilterStructure for NumCycles | FIRDecimator uses 'Direct form systolic'; FIRFilter uses 'Partly serial systolic' — they differ |
Copyright 2026 The MathWorks, Inc.
Requirements and compatibility
Written for: Claude Code
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Names Claude Code tools
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- MathWorks
- Publisher
- MathWorks (Official)
- Source
- matlab/matlab-agentic-toolkit · skills-catalog/signal-processing/matlab-design-dsphdl-ddc · commit 2a7f215
- Licence
- BSD 2-Clause License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by MathWorks.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-design-dsphdl-ddc.zip