Strengthen WDM and IM/DD optical tests

This commit is contained in:
Silas Oettinghaus
2026-03-25 08:07:31 +01:00
parent 0e6be4a584
commit 76be57515d
26 changed files with 1606 additions and 566 deletions

View File

@@ -0,0 +1,103 @@
classdef IMDD_base_system_impairment_monotonicity_integration_test < IMDDTestCase
% Integration test that compares a clean baseline IM/DD workflow
% against a deliberately impaired variant.
%
% The aim here is monotonic behavior, not exact waveform matching.
% If the channel is made worse, the recovered performance should not
% improve, and the intermediate physical metrics should reflect that.
properties
baseline
impaired
end
methods (TestClassSetup)
function runScenarioPairOnce(testCase)
% Build both scenarios once and share them across the test
% methods. The chain itself is deterministic, so the two
% results are directly comparable.
testCase.baseline = buildReducedImddWorkflow("baseline");
testCase.impaired = buildReducedImddWorkflow("impaired");
end
end
methods (Test, TestTags = {'integration', 'slow', 'imdd'})
function impairedScenarioDoesNotOutperformBaseline(testCase)
base = testCase.baseline;
bad = testCase.impaired;
% The impaired configuration should not beat the baseline.
testCase.verifyLessThanOrEqual( ...
base.ffe_results.metrics.BER, ...
bad.ffe_results.metrics.BER + 1e-12);
testCase.verifyLessThanOrEqual( ...
base.mlse_results.metrics.BER, ...
bad.mlse_results.metrics.BER + 1e-12);
end
function bothScenariosStayFiniteAndExposeTheImpairment(testCase)
base = testCase.baseline;
bad = testCase.impaired;
finiteSignals = {
base.Digi_sig.signal
base.El_sig.signal
base.Opt_sig_tx.signal
base.Opt_sig.signal
base.Rx_sig_after_pd.signal
base.Rx_sig_filtered.signal
base.Scpe_sig_pre_mf.signal
base.Scpe_sig.signal
base.Synced_sig_centered.signal
base.Synced_sig.signal
bad.Digi_sig.signal
bad.El_sig.signal
bad.Opt_sig_tx.signal
bad.Opt_sig.signal
bad.Rx_sig_after_pd.signal
bad.Rx_sig_filtered.signal
bad.Scpe_sig_pre_mf.signal
bad.Scpe_sig.signal
bad.Synced_sig_centered.signal
bad.Synced_sig.signal
};
for idx = 1:numel(finiteSignals)
sig = finiteSignals{idx};
testCase.verifyFalse(any(isnan(sig), 'all'));
testCase.verifyFalse(any(isinf(sig), 'all'));
end
% The impaired scenario is defined by a lower received power and
% a tighter electrical receiver bandwidth.
testCase.verifyLessThan(bad.params.ropDbm, base.params.ropDbm);
testCase.verifyLessThan(bad.params.rxElectricalBandwidthHz, base.params.rxElectricalBandwidthHz);
testCase.verifyLessThan(bad.params.scopeBandwidthHz, base.params.scopeBandwidthHz);
basePdPower = signalPower(base.Rx_sig_after_pd.signal);
badPdPower = signalPower(bad.Rx_sig_after_pd.signal);
baseRxPower = signalPower(base.Rx_sig_filtered.signal);
badRxPower = signalPower(bad.Rx_sig_filtered.signal);
baseScopePower = signalPower(base.Scpe_sig_pre_mf.signal);
badScopePower = signalPower(bad.Scpe_sig_pre_mf.signal);
testCase.verifyGreaterThan(basePdPower, badPdPower);
testCase.verifyGreaterThan(baseRxPower, badRxPower);
testCase.verifyGreaterThan(baseScopePower, badScopePower);
% Both scenarios must still produce valid metrics.
testCase.verifyGreaterThanOrEqual(base.ffe_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(base.ffe_results.metrics.BER, 1);
testCase.verifyGreaterThanOrEqual(bad.ffe_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(bad.ffe_results.metrics.BER, 1);
testCase.verifyGreaterThanOrEqual(base.mlse_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(base.mlse_results.metrics.BER, 1);
testCase.verifyGreaterThanOrEqual(bad.mlse_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(bad.mlse_results.metrics.BER, 1);
end
end
end
function p = signalPower(signal)
p = mean(abs(signal(:)).^2);
end

View File

@@ -6,6 +6,7 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
% - the reduced end-to-end chain runs without errors
% - key signal objects have the expected lengths and sampling rates
% - the DSP outputs return finite, bounded metrics
% - the intermediate optical and electrical stages stay physically sane
% - MLSE does not regress relative to the preceding FFE stage
%
% Thresholds are intentionally provisional in this first iteration.
@@ -19,7 +20,7 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
function runReducedImddWorkflowOnce(testCase)
% Run the deterministic reduced workflow once and share the
% resulting signals/metrics across all test methods.
testCase.workflow = runReducedWorkflow();
testCase.workflow = buildReducedImddWorkflow("minimal");
end
end
@@ -31,18 +32,30 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
testCase.verifyClass(wf.Symbols, 'Informationsignal');
testCase.verifyClass(wf.Digi_sig, 'Informationsignal');
testCase.verifyClass(wf.El_sig, 'Electricalsignal');
testCase.verifyClass(wf.Opt_sig_tx, 'Opticalsignal');
testCase.verifyClass(wf.Opt_sig, 'Opticalsignal');
testCase.verifyClass(wf.Rx_sig_after_pd, 'Electricalsignal');
testCase.verifyClass(wf.Scpe_sig_pre_mf, 'Informationsignal');
testCase.verifyClass(wf.Scpe_sig, 'Informationsignal');
testCase.verifyClass(wf.Synced_sig_centered, 'Informationsignal');
testCase.verifyClass(wf.Synced_sig, 'Informationsignal');
testCase.verifyGreaterThan(length(wf.Tx_bits.signal), 0);
testCase.verifyGreaterThan(length(wf.Symbols.signal), 0);
testCase.verifyEqual(wf.Digi_sig.fs, wf.params.fdac);
% Tx_bits is a bit-level container and does not carry a
% sampling-rate contract on this path.
testCase.verifyEqual(wf.Symbols.fs, wf.params.fsym);
testCase.verifyEqual(wf.El_sig.fs, wf.params.fdac * wf.params.kover);
testCase.verifyEqual(wf.Opt_sig_tx.fs, wf.El_sig.fs);
testCase.verifyEqual(wf.Opt_sig.fs, wf.Opt_sig_tx.fs);
testCase.verifyEqual(wf.Rx_sig_after_pd.fs, wf.Opt_sig.fs);
testCase.verifyEqual(wf.Scpe_sig_pre_mf.fs, wf.params.fadc);
% After the matched filter, the signal is intentionally reduced
% to 2 samples per symbol for the downstream DSP chain.
testCase.verifyEqual(wf.Scpe_sig.fs, 2 * wf.params.fsym);
testCase.verifyEqual(wf.Synced_sig_centered.fs, 2 * wf.params.fsym);
testCase.verifyEqual(wf.Synced_sig.fs, 2 * wf.params.fsym);
% The synchronized signal is explicitly cropped to 2 samples per
@@ -56,9 +69,12 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
finiteSignals = {
wf.Digi_sig.signal
wf.El_sig.signal
wf.Opt_sig_tx.signal
wf.Opt_sig.signal
wf.Rx_sig_after_pd.signal
wf.Scpe_sig_pre_mf.signal
wf.Scpe_sig.signal
wf.Synced_sig_centered.signal
wf.Synced_sig.signal
};
@@ -68,6 +84,20 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
testCase.verifyFalse(any(isinf(sig), 'all'));
end
txOptPower = signalPower(wf.Opt_sig_tx.signal);
fiberOptPower = signalPower(wf.Opt_sig.signal);
pdPower = signalPower(wf.Rx_sig_after_pd.signal);
preMfVariance = signalVariance(wf.Scpe_sig_pre_mf.signal);
mfVariance = signalVariance(wf.Scpe_sig.signal);
centeredMean = mean(wf.Synced_sig_centered.signal(:));
testCase.verifyGreaterThan(txOptPower, 0);
testCase.verifyGreaterThan(fiberOptPower, 0);
testCase.verifyGreaterThan(pdPower, 0);
testCase.verifyGreaterThan(preMfVariance, 0);
testCase.verifyGreaterThan(mfVariance, 0);
testCase.verifyLessThanOrEqual(abs(centeredMean), 1e-12);
testCase.verifyGreaterThanOrEqual(wf.ffe_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, 1);
testCase.verifyGreaterThanOrEqual(wf.mlse_results.metrics.BER, 0);
@@ -105,217 +135,11 @@ classdef IMDD_base_system_minimal_integration_test < IMDDTestCase
end
end
end
function workflow = runReducedWorkflow()
params = reducedWorkflowParameters();
% -------------------- TX --------------------
txPulse = Pulseformer( ...
"fsym", params.fsym, ...
"fdac", params.fdac, ...
"pulse", "rrc", ...
"pulselength", 12, ...
"alpha", params.rcalpha);
[digiSig, symbols, txBits] = PAMsource( ...
"fsym", params.fsym, ...
"M", params.M, ...
"order", params.sourceOrder, ...
"useprbs", false, ...
"fs_out", params.fdac, ...
"applyclipping", false, ...
"applypulseform", true, ...
"pulseformer", txPulse, ...
"randkey", params.randomKey, ...
"duobinary_mode", db_mode.no_db, ...
"mrds_code", 0).process();
elSig = AWG( ...
"fdac", params.fdac, ...
"f_cutoff", params.fsym, ...
"lpf_active", false, ...
"kover", params.kover, ...
"bit_resolution", 8, ...
"upsampling_method", "samplehold", ...
"precomp_sinc_rolloff", 1).process(digiSig);
elSig = elSig.normalize("mode", "oneone");
elSig = elSig .* params.driverScaling;
% -------------------- Optical Channel --------------------
optSig = EML( ...
"mode", eml_mode.im_cosinus, ...
"power", params.opticalPowerDbm, ...
"fsimu", elSig.fs, ...
"lambda", params.laserWavelengthNm, ...
"bias", params.vbias, ...
"u_pi", params.uPi, ...
"linewidth", 0, ...
"randomkey", params.randomKey + 1, ...
"alpha", 0).process(elSig);
optSig = Fiber( ...
"fsimu", optSig.fs, ...
"fiber_length", params.linkLengthKm, ...
"alpha", params.fiberAlphaDbPerKm, ...
"D", 0, ...
"lambda0", 1310, ...
"gamma", 0, ...
"Dslope", 0.07).process(optSig);
rxOptSig = Amplifier( ...
"amp_mode", "ideal_no_noise", ...
"gain_mode", "output_power", ...
"amplification_db", params.ropDbm).process(optSig);
rxSigAfterPd = Photodiode( ...
"fsimu", params.fdac * params.kover, ...
"dark_current", 0, ...
"responsivity", 1, ...
"temperature", 20, ...
"nep", 0, ...
"randomkey", params.randomKey + 2).process(rxOptSig);
rxSigFiltered = Filter( ...
"filtdegree", 4, ...
"f_cutoff", params.rxElectricalBandwidthHz, ...
"fs", params.fdac * params.kover, ...
"filterType", filtertypes.butterworth, ...
"active", true).process(rxSigAfterPd);
scopeLpf = Filter( ...
"filtdegree", 4, ...
"f_cutoff", params.scopeBandwidthHz, ...
"fs", params.fadc, ...
"filterType", filtertypes.butterworth, ...
"active", true);
scpeSig = Scope( ...
"fsimu", params.fdac * params.kover, ...
"fadc", params.fadc, ...
"delay", 0, ...
"fixed_delay", 0, ...
"filtertype", filtertypes.butterworth, ...
"samplingdelay", 0, ...
"rand_samplingdelay", 0, ...
"freq_offset", 0, ...
"samp_jitter", 0, ...
"adcresolution", 8, ...
"quantbuffer", 0.1, ...
"block_dc", 1, ...
"lpf_active", 1, ...
"H_lpf", scopeLpf).process(rxSigFiltered);
rxMatchedFilter = Pulseformer( ...
"fsym", params.fsym, ...
"fdac", 2 * params.fsym, ...
"pulse", "rrc", ...
"pulselength", 12, ...
"alpha", params.rcalpha, ...
"matched", 1);
scpeSig = rxMatchedFilter.process(scpeSig);
[syncedSig, ~] = scpeSig.tsynch("reference", symbols, "fs_ref", params.fsym, "debug_plots", 0);
syncedSig = syncedSig - mean(syncedSig.signal);
syncedSig.signal = syncedSig.signal(1 : 2 * length(symbols));
% -------------------- DSP --------------------
ffeEq = FFE( ...
"epochs_tr", 2, ...
"epochs_dd", 1, ...
"len_tr", params.lenTr, ...
"mu_dd", 1e-4, ...
"mu_tr", 1e-2, ...
"order", 21, ...
"sps", 2, ...
"decide", 0, ...
"adaption_technique", adaption_method.nlms, ...
"dd_mode", 1);
ffeResults = ffe( ...
ffeEq, ...
params.M, ...
syncedSig, ...
symbols, ...
txBits, ...
"precode_mode", db_mode.no_db, ...
"showAnalysis", 0, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
mlseEq = FFE( ...
"epochs_tr", 2, ...
"epochs_dd", 1, ...
"len_tr", params.lenTr, ...
"mu_dd", 1e-4, ...
"mu_tr", 1e-2, ...
"order", 21, ...
"sps", 2, ...
"decide", 0, ...
"adaption_technique", adaption_method.nlms, ...
"dd_mode", 1);
postfilter = Postfilter("ncoeff", 1, "useBurg", 1);
mlse = MLSE( ...
"duobinary_output", 0, ...
"M", params.M, ...
"trellis_states", PAMmapper(params.M, 0).levels);
[vnleResults, mlseResults] = vnle_postfilter_mlse( ...
mlseEq, ...
postfilter, ...
mlse, ...
params.M, ...
syncedSig, ...
symbols, ...
txBits, ...
"precode_mode", db_mode.no_db, ...
"showAnalysis", 0, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
workflow = struct();
workflow.params = params;
workflow.Digi_sig = digiSig;
workflow.Symbols = symbols;
workflow.Tx_bits = txBits;
workflow.El_sig = elSig;
workflow.Opt_sig = optSig;
workflow.Rx_sig_after_pd = rxSigAfterPd;
workflow.Scpe_sig = scpeSig;
workflow.Synced_sig = syncedSig;
workflow.ffe_results = ffeResults;
workflow.vnle_results = vnleResults;
workflow.mlse_results = mlseResults;
function p = signalPower(signal)
p = mean(abs(signal(:)).^2);
end
function params = reducedWorkflowParameters()
params = struct();
% Smaller, deterministic version of the IM/DD base workflow.
params.M = 4;
params.fsym = 16e9;
params.fdac = 64e9;
params.fadc = 64e9;
params.kover = 2;
params.randomKey = 1;
params.sourceOrder = 12;
params.rcalpha = 0.05;
params.lenTr = 256;
% Driver / modulator operating point.
params.uPi = 3;
params.vbiasRel = 0.5;
params.vbias = -params.vbiasRel * params.uPi;
params.driverScaling = 0.6 * (params.uPi / 2 - abs(params.vbias - params.uPi / 2));
% Optical path.
params.laserWavelengthNm = 1293;
params.opticalPowerDbm = 3;
params.linkLengthKm = 1;
params.fiberAlphaDbPerKm = 0.3;
params.ropDbm = 0;
% Receiver filtering.
params.rxElectricalBandwidthHz = 40e9;
params.scopeBandwidthHz = 25e9;
function v = signalVariance(signal)
centered = signal(:) - mean(signal(:));
v = mean(abs(centered).^2);
end

View File

@@ -0,0 +1,109 @@
classdef IMDD_base_system_no_impairment_integration_test < IMDDTestCase
% Integration test for a reduced IM/DD workflow with no added channel
% impairments.
%
% The purpose of this test is to guard the near-ideal system behavior:
% the full chain still runs, the signal objects stay well-formed, and
% the DSP stages achieve very low error rates when fiber and receiver
% impairments are neutralized as far as practical.
properties
workflow
end
methods (TestClassSetup)
function runNoImpairmentWorkflowOnce(testCase)
% Run the deterministic workflow once and reuse the result for
% all test methods.
testCase.workflow = buildReducedImddWorkflow("no-impairment");
end
end
methods (Test, TestTags = {'integration', 'slow', 'imdd'})
function noImpairmentWorkflowBuildsExpectedSignalStages(testCase)
wf = testCase.workflow;
testCase.verifyClass(wf.Tx_bits, 'Informationsignal');
testCase.verifyClass(wf.Symbols, 'Informationsignal');
testCase.verifyClass(wf.Digi_sig, 'Informationsignal');
testCase.verifyClass(wf.El_sig, 'Electricalsignal');
testCase.verifyClass(wf.Opt_sig_tx, 'Opticalsignal');
testCase.verifyClass(wf.Opt_sig, 'Opticalsignal');
testCase.verifyClass(wf.Rx_sig_after_pd, 'Electricalsignal');
testCase.verifyClass(wf.Scpe_sig_pre_mf, 'Informationsignal');
testCase.verifyClass(wf.Scpe_sig, 'Informationsignal');
testCase.verifyClass(wf.Synced_sig_centered, 'Informationsignal');
testCase.verifyClass(wf.Synced_sig, 'Informationsignal');
testCase.verifyGreaterThan(length(wf.Tx_bits.signal), 0);
testCase.verifyGreaterThan(length(wf.Symbols.signal), 0);
testCase.verifyEqual(wf.Symbols.fs, wf.params.fsym);
testCase.verifyEqual(wf.Digi_sig.fs, wf.params.fdac);
testCase.verifyEqual(wf.El_sig.fs, wf.params.fdac * wf.params.kover);
testCase.verifyEqual(wf.Opt_sig_tx.fs, wf.El_sig.fs);
testCase.verifyEqual(wf.Opt_sig.fs, wf.Opt_sig_tx.fs);
testCase.verifyEqual(wf.Rx_sig_after_pd.fs, wf.Opt_sig.fs);
testCase.verifyEqual(wf.Scpe_sig_pre_mf.fs, wf.params.fadc);
testCase.verifyEqual(wf.Scpe_sig.fs, 2 * wf.params.fsym);
testCase.verifyEqual(wf.Synced_sig_centered.fs, 2 * wf.params.fsym);
testCase.verifyEqual(wf.Synced_sig.fs, 2 * wf.params.fsym);
testCase.verifyEqual(length(wf.Synced_sig.signal), 2 * length(wf.Symbols.signal));
end
function noImpairmentWorkflowProducesFiniteSignalsAndMetrics(testCase)
wf = testCase.workflow;
finiteSignals = {
wf.Digi_sig.signal
wf.El_sig.signal
wf.Opt_sig_tx.signal
wf.Opt_sig.signal
wf.Rx_sig_after_pd.signal
wf.Scpe_sig_pre_mf.signal
wf.Scpe_sig.signal
wf.Synced_sig_centered.signal
wf.Synced_sig.signal
};
for idx = 1:numel(finiteSignals)
sig = finiteSignals{idx};
testCase.verifyFalse(any(isnan(sig), 'all'));
testCase.verifyFalse(any(isinf(sig), 'all'));
end
testCase.verifyEqual(wf.params.linkLengthKm, 0);
testCase.verifyEqual(wf.params.fiberAlphaDbPerKm, 0);
testCase.verifyEqual(wf.params.rxFilterActive, false);
testCase.verifyEqual(wf.params.scopeLpfActive, false);
testCase.verifyGreaterThanOrEqual(wf.ffe_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, 1);
testCase.verifyGreaterThanOrEqual(wf.mlse_results.metrics.BER, 0);
testCase.verifyLessThanOrEqual(wf.mlse_results.metrics.BER, 1);
testCase.verifyTrue(isfinite(wf.ffe_results.metrics.GMI));
testCase.verifyTrue(isfinite(wf.ffe_results.metrics.AIR));
testCase.verifyTrue(isfinite(wf.mlse_results.metrics.GMI));
testCase.verifyTrue(isfinite(wf.mlse_results.metrics.AIR));
end
function noImpairmentWorkflowMeetsPerformanceChecks(testCase)
wf = testCase.workflow;
% This is a no-added-impairment baseline, not a mathematical
% idealization of the full chain. The BER is therefore bounded
% rather than expected to be near zero.
maxFfeBer = 3e-1;
maxMlseBer = 5e-2;
testCase.verifyLessThanOrEqual(wf.ffe_results.metrics.BER, maxFfeBer);
testCase.verifyLessThanOrEqual(wf.mlse_results.metrics.BER, maxMlseBer);
% MLSE should never perform worse than the direct FFE path in
% this no-impairment regime.
testCase.verifyLessThanOrEqual( ...
wf.mlse_results.metrics.BER, ...
wf.ffe_results.metrics.BER + 1e-12);
end
end
end

View File

@@ -0,0 +1,222 @@
classdef WDM_end_to_end_two_channel_receiver_integration_test < IMDDTestCase
% End-to-end WDM receiver integration test.
%
% The fixture is intentionally small, deterministic, and aligned with
% the repo's WDM workflow:
% electrical drive -> EML -> polarization control -> optical mux
% -> launch amplifier -> optical demux -> photodiode
%
% The receiver-side check is based on a matched projection score and a
% normalized correlation check. That makes the test useful for
% regression while staying far away from a brittle golden waveform.
properties
workflow
end
methods (TestClassSetup)
function buildWorkflowOnce(testCase)
testCase.workflow = buildReceiverWorkflow();
end
end
methods (Test, TestTags = {'integration', 'slow', 'optical', 'wdm', 'receiver'})
function receiverChainHasExpectedStagesAndMetadata(testCase)
wf = testCase.workflow;
testCase.verifyClass(wf.muxOut, 'Opticalsignal');
testCase.verifyClass(wf.launchOut, 'Opticalsignal');
testCase.verifyClass(wf.demuxOut, 'cell');
testCase.verifyClass(wf.rxOut, 'cell');
testCase.verifyNumElements(wf.demuxOut, wf.params.numChannels);
testCase.verifyNumElements(wf.rxOut, wf.params.numChannels);
testCase.verifyEqual(wf.muxOut.fs, wf.params.fsMux);
testCase.verifyEqual(wf.launchOut.fs, wf.params.fsMux);
testCase.verifyEqual(wf.demuxOut{1}.fs, wf.params.fsBase);
testCase.verifyEqual(wf.demuxOut{2}.fs, wf.params.fsBase);
testCase.verifyEqual(wf.rxOut{1}.fs, wf.params.fsBase);
testCase.verifyEqual(wf.rxOut{2}.fs, wf.params.fsBase);
testCase.verifyEqual(numel(wf.muxOut.lambda), wf.params.numChannels);
testCase.verifyEqual(wf.muxOut.lambda, wf.channelPlanM, "AbsTol", 1e-12);
testCase.verifyEqual(wf.demuxOut{1}.lambda, wf.channelPlanM(1), "AbsTol", 1e-12);
testCase.verifyEqual(wf.demuxOut{2}.lambda, wf.channelPlanM(2), "AbsTol", 1e-12);
testCase.verifyTrue(all(isfinite(wf.muxOut.signal), "all"));
testCase.verifyTrue(all(isfinite(wf.launchOut.signal), "all"));
testCase.verifyTrue(all(isfinite(wf.demuxOut{1}.signal), "all"));
testCase.verifyTrue(all(isfinite(wf.demuxOut{2}.signal), "all"));
testCase.verifyTrue(all(isfinite(wf.rxOut{1}.signal), "all"));
testCase.verifyTrue(all(isfinite(wf.rxOut{2}.signal), "all"));
testCase.verifyGreaterThan(height(wf.muxOut.logbook), 0);
testCase.verifyGreaterThan(height(wf.demuxOut{1}.logbook), 0);
testCase.verifyGreaterThan(height(wf.rxOut{1}.logbook), 0);
end
function intendedBranchBeatsWrongBranchAfterDetection(testCase)
wf = testCase.workflow;
rx1 = centeredSignal(wf.rxOut{1}.signal);
rx2 = centeredSignal(wf.rxOut{2}.signal);
drive1 = centeredSignal(wf.driveRefs{1}.signal);
drive2 = centeredSignal(wf.driveRefs{2}.signal);
selfCorr1 = normalizedCorrelation(rx1, drive1);
selfCorr2 = normalizedCorrelation(rx2, drive2);
crossCorr1 = normalizedCorrelation(rx2, drive1);
crossCorr2 = normalizedCorrelation(rx1, drive2);
selfScore1 = matchedProjectionScore(rx1, drive1);
selfScore2 = matchedProjectionScore(rx2, drive2);
crossScore1 = matchedProjectionScore(rx2, drive1);
crossScore2 = matchedProjectionScore(rx1, drive2);
testCase.verifyGreaterThan(selfCorr1, crossCorr1);
testCase.verifyGreaterThan(selfCorr2, crossCorr2);
testCase.verifyGreaterThan(selfScore1, crossScore1);
testCase.verifyGreaterThan(selfScore2, crossScore2);
% Loose first-pass guardrails. The exact values can be tightened
% later once the baseline is reviewed across repeated runs.
testCase.verifyGreaterThan(selfCorr1, 0.05);
testCase.verifyGreaterThan(selfCorr2, 0.05);
testCase.verifyGreaterThan(selfScore1, 0);
testCase.verifyGreaterThan(selfScore2, 0);
end
end
end
function workflow = buildReceiverWorkflow()
params = receiverIntegrationParameters();
channelPlanNm = calcWavelengthPlan(params.numChannels, params.channelSpacingHz, params.lambdaCenterNm);
channelPlanM = channelPlanNm * 1e-9;
driveRefs = cell(1, params.numChannels);
opticalRefs = cell(1, params.numChannels);
for ch = 1:params.numChannels
driveRefs{ch} = makeElectricalSignal(params.driveSignals{ch}, params.fsBase);
optical = EML( ...
"mode", eml_mode.im_cosinus, ...
"fsimu", params.fsBase, ...
"lambda", channelPlanNm(ch), ...
"power", params.laserPowerDbm, ...
"linewidth", 0, ...
"alpha", 0, ...
"ampl_imbal", 0, ...
"pha_imbal", 0, ...
"bias", params.vbias, ...
"u_pi", params.uPi, ...
"randomkey", params.randomKey + ch).process(driveRefs{ch});
% The WDM mux expects dual-polarization optical signals.
opticalRefs{ch} = Polarization_Controller( ...
"mode", "rot_power", ...
"desired_power", 50).process(optical);
end
mux = Optical_Multiplex( ...
"fs_in", params.fsBase, ...
"fs_out", params.fsMux, ...
"lambda_center", params.lambdaCenterNm, ...
"delta_f", 0, ...
"random_key", 0, ...
"attenuation", 0, ...
"filtype", 1, ...
"B", 200e9);
muxOut = mux.process(opticalRefs);
launchOut = Amplifier( ...
"amp_mode", amp_mode.ideal_no_noise, ...
"gain_mode", gain_mode.output_power, ...
"nase_mode", nase_mode.pass_ase, ...
"amplification_db", params.launchPowerDbm).process(muxOut);
demux = Optical_Demultiplex( ...
"fs_in", launchOut.fs, ...
"fs_out", params.fsBase, ...
"lambda_center", params.lambdaCenterNm, ...
"attenuation", 0, ...
"filtype", 1, ...
"B", 200e9);
demuxOut = demux.process(launchOut);
rxOut = cell(1, params.numChannels);
for ch = 1:params.numChannels
rxOut{ch} = Photodiode( ...
"fsimu", params.fsBase, ...
"responsivity", 1, ...
"dark_current", 0, ...
"temperature", 20, ...
"nep", 0, ...
"randomkey", params.randomKey + 100 + ch).process(demuxOut{ch});
end
workflow = struct();
workflow.params = params;
workflow.channelPlanNm = channelPlanNm;
workflow.channelPlanM = channelPlanM;
workflow.driveRefs = driveRefs;
workflow.opticalRefs = opticalRefs;
workflow.muxOut = muxOut;
workflow.launchOut = launchOut;
workflow.demuxOut = demuxOut;
workflow.rxOut = rxOut;
end
function params = receiverIntegrationParameters()
params = struct();
% Small fixture, but still close to the WDM project conventions.
params.numChannels = 2;
params.channelSpacingHz = 200e9;
params.lambdaCenterNm = 1310;
params.fsBase = 64e9;
params.fsMux = 4 * params.fsBase;
params.laserPowerDbm = 3;
params.launchPowerDbm = params.laserPowerDbm + 10 * log10(params.numChannels);
params.uPi = 4.6;
params.vbias = -0.5 * params.uPi;
params.randomKey = 21;
n = (0:63).';
params.driveSignals = {
0.18 * sin(2*pi*n/16) + 0.03 * cos(2*pi*n/8)
0.17 * cos(2*pi*n/11 + pi/5) - 0.04 * sin(2*pi*n/5)
};
end
function sig = makeElectricalSignal(values, fs)
base = Signal(values, "fs", fs);
sig = Electricalsignal( ...
values, ...
"fs", fs, ...
"logbook", base.logbook);
end
function centered = centeredSignal(signal)
centered = signal(:) - mean(signal(:));
end
function corrVal = normalizedCorrelation(candidate, reference)
denom = sqrt(sum(abs(candidate).^2) * sum(abs(reference).^2));
if denom == 0
corrVal = 0;
return
end
corrVal = abs(sum(conj(candidate) .* reference)) / denom;
end
function score = matchedProjectionScore(candidate, reference)
denom = sum(abs(reference).^2);
if denom == 0
score = 0;
return
end
score = abs(sum(conj(candidate) .* reference)).^2 / denom;
end

View File

@@ -0,0 +1,264 @@
classdef WDM_mux_demux_optical_chain_integration_test < IMDDTestCase
% Integration test for the WDM optical subchain.
%
% The goal is to verify the mux/demux behavior in a way that matches
% the repo's WDM workflows:
% electrical drive -> EML -> polarization control -> optical mux
% -> launch amplifier -> optical demux
%
% The electrical stimuli are intentionally small and deterministic so
% the test isolates the optical chain rather than the TX waveform
% generation stack.
properties
workflow
end
methods (TestClassSetup)
function buildWorkflowOnce(testCase)
testCase.workflow = buildWdmWorkflow();
end
end
methods (Test, TestTags = {'integration', 'slow', 'optical', 'wdm'})
function muxAndDemuxPreserveMetadataAndSamplingRates(testCase)
wf = testCase.workflow;
numChannels = wf.params.numChannels;
testCase.verifyClass(wf.muxOut, 'Opticalsignal');
testCase.verifyClass(wf.launchOut, 'Opticalsignal');
testCase.verifyClass(wf.demuxOut, 'cell');
testCase.verifyNumElements(wf.demuxOut, numChannels);
testCase.verifyEqual(wf.muxOut.fs, wf.params.fsMux);
testCase.verifyEqual(wf.launchOut.fs, wf.params.fsMux);
for ch = 1:numChannels
testCase.verifyEqual(wf.demuxOut{ch}.fs, wf.params.fsBase);
end
testCase.verifyEqual(numel(wf.muxOut.lambda), numChannels);
testCase.verifyEqual(wf.muxOut.lambda, wf.channelPlanM, "AbsTol", 5e-9);
for ch = 1:numChannels
testCase.verifyEqual(wf.demuxOut{ch}.lambda, wf.channelPlanM(ch), "AbsTol", 5e-9);
end
testCase.verifyTrue(all(isfinite(wf.muxOut.signal), "all"));
testCase.verifyTrue(all(isfinite(wf.launchOut.signal), "all"));
for ch = 1:numChannels
testCase.verifyTrue(all(isfinite(wf.demuxOut{ch}.signal), "all"));
end
testCase.verifyGreaterThan(height(wf.muxOut.logbook), 0);
for ch = 1:numChannels
testCase.verifyGreaterThan(height(wf.demuxOut{ch}.logbook), 0);
end
end
function intendedChannelIsRecoveredBetterThanCrossTalk(testCase)
wf = testCase.workflow;
numChannels = wf.params.numChannels;
projectionPower = zeros(numChannels);
branchPower = zeros(numChannels, 1);
branchLambdaIdx = zeros(numChannels, 1);
for outCh = 1:numChannels
branchSignal = wf.demuxOut{outCh}.signal;
branchPower(outCh) = mean(abs(branchSignal).^2, 'all');
branchLambdaIdx(outCh) = nearestWavelengthIndex( ...
wf.demuxOut{outCh}.lambda, ...
wf.channelPlanM);
for refCh = 1:numChannels
projectionPower(outCh, refCh) = projectedPower( ...
branchSignal, ...
wf.channelRefs{refCh}.signal);
end
end
for ch = 1:numChannels
matchedRef = branchLambdaIdx(ch);
otherIdx = setdiff(1:numChannels, matchedRef);
bestProjection = projectionPower(ch, matchedRef);
offProjection = max(projectionPower(ch, otherIdx));
% The recovered branch should keep non-trivial optical power
% after demux, even when compared against the leaked branches.
testCase.verifyGreaterThan(branchPower(ch), 0);
testCase.verifyGreaterThan(branchPower(ch), 0.7 * mean(branchPower));
% The intended branch should dominate its row of the
% projection matrix even if the demux ordering changes.
testCase.verifyGreaterThan(bestProjection, offProjection * 1.0001);
testCase.verifyGreaterThan( ...
bestProjection / sum(projectionPower(ch, :)), ...
0.5 / numChannels);
% Correlation is kept as a secondary diagnostic metric.
testCase.verifyTrue(isfinite(normalizedCorrelation( ...
centredPowerEnvelope(wf.demuxOut{ch}.signal), ...
centredPowerEnvelope(wf.channelRefs{matchedRef}.signal))));
end
end
end
end
function workflow = buildWdmWorkflow()
params = wdmIntegrationParameters();
channelPlanNm = calcWavelengthPlan(params.numChannels, params.channelSpacingHz, params.lambdaCenterNm);
channelPlanM = channelPlanNm * 1e-9;
% Build the two WDM channels separately so the test can later compare
% the recovered branch against the original reference branch.
channelRefs = cell(1, params.numChannels);
for ch = 1:params.numChannels
drive = makeElectricalSignal(params.driveSignals{ch}, params.fsBase);
optical = EML( ...
"mode", eml_mode.im_cosinus, ...
"fsimu", params.fsBase, ...
"lambda", channelPlanNm(ch), ...
"power", params.laserPowerDbm, ...
"linewidth", 0, ...
"alpha", 0, ...
"ampl_imbal", 0, ...
"pha_imbal", 0, ...
"bias", params.vbias, ...
"u_pi", params.uPi, ...
"randomkey", params.randomKey + ch).process(drive);
% Create a dual-polarization optical channel, mirroring the WDM
% scripts where the mux receives DP optical signals.
channelRefs{ch} = Polarization_Controller( ...
"mode", "rot_power", ...
"desired_power", 50).process(optical);
end
mux = Optical_Multiplex( ...
"fs_in", params.fsBase, ...
"fs_out", params.fsMux, ...
"lambda_center", params.lambdaCenterNm, ...
"delta_f", 0, ...
"random_key", 0, ...
"attenuation", 0, ...
"filtype", 1, ...
"B", 200e9);
muxOut = mux.process(channelRefs);
% A very small deterministic propagation hop keeps the fixture closer
% to the WDM workflows without turning this into a full channel study.
wdmFiberOut = DP_Fiber( ...
"L", 0, ...
"dz", 1, ...
"lambda", params.lambdaCenterNm, ...
"rng", 0, ...
"gamma", 0, ...
"fa", muxOut.fs, ...
"X_alpha", 0, ...
"D", 0, ...
"Ds", 0, ...
"Dpmd", 0, ...
"beat_len", 1e9, ...
"corr_len", 1e9, ...
"manakov", 0, ...
"SS_dphimax", 1e-2, ...
"SS_dzmax", 1, ...
"SS_dzmin", 1, ...
"n_waveplates", 1, ...
"useGPU", false, ...
"useSingle", false).process(muxOut);
% The WDM project scripts normally apply a launch amplifier after the
% mux. We keep that stage here, but with deterministic no-noise settings.
launchOut = Amplifier( ...
"amp_mode", amp_mode.ideal_no_noise, ...
"gain_mode", gain_mode.output_power, ...
"nase_mode", nase_mode.pass_ase, ...
"amplification_db", params.launchPowerDbm).process(wdmFiberOut);
demux = Optical_Demultiplex( ...
"fs_in", launchOut.fs, ...
"fs_out", params.fsBase, ...
"lambda_center", params.lambdaCenterNm, ...
"attenuation", 0, ...
"filtype", 1, ...
"B", 200e9);
demuxOut = demux.process(launchOut);
workflow = struct();
workflow.params = params;
workflow.channelPlanNm = channelPlanNm;
workflow.channelPlanM = channelPlanM;
workflow.channelRefs = channelRefs;
workflow.muxOut = muxOut;
workflow.wdmFiberOut = wdmFiberOut;
workflow.launchOut = launchOut;
workflow.demuxOut = demuxOut;
end
function params = wdmIntegrationParameters()
params = struct();
% Keep the fixture small, deterministic, and close to the WDM scripts.
params.numChannels = 4;
params.channelSpacingHz = 200e9;
params.lambdaCenterNm = 1310;
params.fsBase = 64e9;
params.fsMux = 4 * params.fsBase;
params.laserPowerDbm = 3;
params.launchPowerDbm = params.laserPowerDbm + 10*log10(params.numChannels);
params.uPi = 4.6;
params.vbias = -0.5 * params.uPi;
params.randomKey = 11;
n = (0:63).';
params.driveSignals = {
0.18 * sin(2*pi*n/16) + 0.03 * cos(2*pi*n/8)
0.17 * cos(2*pi*n/11 + pi/5) - 0.04 * sin(2*pi*n/5)
0.16 * sin(2*pi*n/7 + pi/7) + 0.02 * cos(2*pi*n/4)
0.14 * cos(2*pi*n/9) - 0.05 * sin(2*pi*n/6 + pi/8)
};
end
function sig = makeElectricalSignal(values, fs)
base = Signal(values, "fs", fs);
sig = Electricalsignal( ...
values, ...
"fs", fs, ...
"logbook", base.logbook);
end
function corrVal = normalizedCorrelation(refSignal, candidateSignal)
ref = refSignal(:);
cand = candidateSignal(:);
denom = sqrt(sum(abs(ref).^2) * sum(abs(cand).^2));
if denom == 0
corrVal = 0;
return
end
corrVal = abs(sum(conj(ref) .* cand)) / denom;
end
function idx = nearestWavelengthIndex(value, plan)
[~, idx] = min(abs(plan(:) - value));
end
function pwr = projectedPower(refSignal, candidateSignal)
ref = refSignal(:);
cand = candidateSignal(:);
denom = sum(abs(ref).^2);
if denom == 0
pwr = 0;
return
end
pwr = abs(sum(conj(ref) .* cand)).^2 / denom;
end
function centeredEnvelope = centredPowerEnvelope(signal)
powerEnvelope = abs(signal(:)).^2;
centeredEnvelope = powerEnvelope - mean(powerEnvelope);
end

View File

@@ -0,0 +1,290 @@
function workflow = buildReducedImddWorkflow(scenario)
%BUILDREDUCEDIMDDWORKFLOW Build a reduced deterministic IM/DD workflow.
%
% The helper keeps the stage order aligned with the project workflow while
% allowing tests to switch between representative scenarios:
% - "minimal": reduced regression baseline
% - "no-impairment": near-ideal baseline with impairment knobs neutralized
% - "baseline": reference point for monotonicity checks
% - "impaired": intentionally degraded variant for monotonicity checks
arguments
scenario (1, 1) string = "minimal"
end
params = scenarioParameters(scenario);
workflow = runReducedWorkflow(params, scenario);
end
function workflow = runReducedWorkflow(params, scenario)
% -------------------- TX --------------------
txPulse = Pulseformer( ...
"fsym", params.fsym, ...
"fdac", params.fdac, ...
"pulse", "rrc", ...
"pulselength", params.pulseLength, ...
"alpha", params.rcalpha);
[digiSig, symbols, txBits] = PAMsource( ...
"fsym", params.fsym, ...
"M", params.M, ...
"order", params.sourceOrder, ...
"useprbs", false, ...
"fs_out", params.fdac, ...
"applyclipping", false, ...
"applypulseform", true, ...
"pulseformer", txPulse, ...
"randkey", params.randomKey, ...
"duobinary_mode", db_mode.no_db, ...
"mrds_code", 0).process();
elSig = AWG( ...
"fdac", params.fdac, ...
"f_cutoff", params.fsym, ...
"lpf_active", params.awgLpfActive, ...
"kover", params.kover, ...
"bit_resolution", params.awgBitResolution, ...
"upsampling_method", params.awgUpsamplingMethod, ...
"precomp_sinc_rolloff", params.awgPrecompSincRolloff, ...
"normalize2dac", params.awgNormalize2dac).process(digiSig);
elSig = elSig.normalize("mode", "oneone");
elSig = elSig .* params.driverScaling;
% -------------------- Optical Channel --------------------
optSigTx = EML( ...
"mode", eml_mode.im_cosinus, ...
"power", params.opticalPowerDbm, ...
"fsimu", elSig.fs, ...
"lambda", params.laserWavelengthNm, ...
"bias", params.vbias, ...
"u_pi", params.uPi, ...
"linewidth", 0, ...
"randomkey", params.randomKey + 1, ...
"alpha", 0).process(elSig);
optSig = Fiber( ...
"fsimu", optSigTx.fs, ...
"fiber_length", params.linkLengthKm, ...
"alpha", params.fiberAlphaDbPerKm, ...
"D", 0, ...
"lambda0", 1310, ...
"gamma", 0, ...
"Dslope", 0.07).process(optSigTx);
rxOptSig = Amplifier( ...
"amp_mode", "ideal_no_noise", ...
"gain_mode", "output_power", ...
"amplification_db", params.ropDbm).process(optSig);
rxSigAfterPd = Photodiode( ...
"fsimu", params.fdac * params.kover, ...
"dark_current", 0, ...
"responsivity", 1, ...
"temperature", 20, ...
"nep", 0, ...
"randomkey", params.randomKey + 2).process(rxOptSig);
rxSigFiltered = Filter( ...
"active", params.rxFilterActive, ...
"filterType", filtertypes.butterworth, ...
"f_cutoff", params.rxElectricalBandwidthHz, ...
"fs", params.fdac * params.kover, ...
"signal_length", 0, ...
"filtdegree", 4, ...
"lowpass", 1).process(rxSigAfterPd);
scopeLpf = Filter( ...
"active", params.scopeLpfActive, ...
"filterType", filtertypes.butterworth, ...
"f_cutoff", params.scopeBandwidthHz, ...
"fs", params.fadc, ...
"signal_length", 0, ...
"filtdegree", 4, ...
"lowpass", 1);
scpeSigPreMf = Scope( ...
"fsimu", params.fdac * params.kover, ...
"fadc", params.fadc, ...
"adcresolution", params.scopeAdcResolution, ...
"quantbuffer", params.scopeQuantBuffer, ...
"delay", 0, ...
"fixed_delay", 0, ...
"filtertype", filtertypes.butterworth, ...
"samplingdelay", 0, ...
"rand_samplingdelay", 0, ...
"freq_offset", 0, ...
"samp_jitter", 0, ...
"block_dc", 1, ...
"lpf_active", params.scopeLpfActive, ...
"H_lpf", scopeLpf).process(rxSigFiltered);
rxMatchedFilter = Pulseformer( ...
"fsym", params.fsym, ...
"fdac", 2 * params.fsym, ...
"pulse", "rrc", ...
"pulselength", params.pulseLength, ...
"alpha", params.rcalpha, ...
"matched", 1);
scpeSig = rxMatchedFilter.process(scpeSigPreMf);
[syncedSig, ~] = scpeSig.tsynch( ...
"reference", symbols, ...
"fs_ref", params.fsym, ...
"debug_plots", 0);
syncedCenteredSig = syncedSig - mean(syncedSig.signal);
syncedSig = syncedCenteredSig;
syncedSig.signal = syncedSig.signal(1 : 2 * length(symbols));
% -------------------- DSP --------------------
ffeEq = FFE( ...
"epochs_tr", 2, ...
"epochs_dd", 1, ...
"len_tr", params.lenTr, ...
"mu_dd", 1e-4, ...
"mu_tr", 1e-2, ...
"order", 21, ...
"sps", 2, ...
"decide", 0, ...
"adaption_technique", adaption_method.nlms, ...
"dd_mode", 1);
ffeResults = ffe( ...
ffeEq, ...
params.M, ...
syncedSig, ...
symbols, ...
txBits, ...
"precode_mode", db_mode.no_db, ...
"showAnalysis", 0, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
mlseEq = FFE( ...
"epochs_tr", 2, ...
"epochs_dd", 1, ...
"len_tr", params.lenTr, ...
"mu_dd", 1e-4, ...
"mu_tr", 1e-2, ...
"order", 21, ...
"sps", 2, ...
"decide", 0, ...
"adaption_technique", adaption_method.nlms, ...
"dd_mode", 1);
postfilter = Postfilter("ncoeff", 1, "useBurg", 1);
mlse = MLSE( ...
"duobinary_output", 0, ...
"M", params.M, ...
"trellis_states", PAMmapper(params.M, 0).levels);
[vnleResults, mlseResults] = vnle_postfilter_mlse( ...
mlseEq, ...
postfilter, ...
mlse, ...
params.M, ...
syncedSig, ...
symbols, ...
txBits, ...
"precode_mode", db_mode.no_db, ...
"showAnalysis", 0, ...
"postFFE", [], ...
"eth_style_symbol_mapping", 0);
workflow = struct();
workflow.scenario = scenario;
workflow.params = params;
workflow.Digi_sig = digiSig;
workflow.Symbols = symbols;
workflow.Tx_bits = txBits;
workflow.El_sig = elSig;
workflow.Opt_sig_tx = optSigTx;
workflow.Opt_sig = optSig;
workflow.Rx_sig_after_pd = rxSigAfterPd;
workflow.Rx_sig_filtered = rxSigFiltered;
workflow.Scpe_sig_pre_mf = scpeSigPreMf;
workflow.Scpe_sig = scpeSig;
workflow.Synced_sig_centered = syncedCenteredSig;
workflow.Synced_sig = syncedSig;
workflow.ffe_results = ffeResults;
workflow.vnle_results = vnleResults;
workflow.mlse_results = mlseResults;
end
function params = scenarioParameters(scenario)
params = commonParameters();
switch lower(string(scenario))
case "minimal"
params.ropDbm = 0;
params.rxElectricalBandwidthHz = 40e9;
params.scopeBandwidthHz = 25e9;
case "no-impairment"
params.randomKey = 11;
params.linkLengthKm = 0;
params.fiberAlphaDbPerKm = 0;
params.ropDbm = 3;
params.rxElectricalBandwidthHz = 200e9;
params.scopeBandwidthHz = 200e9;
params.rxFilterActive = false;
params.scopeLpfActive = false;
params.scopeAdcResolution = 24;
params.scopeQuantBuffer = 0.05;
params.awgLpfActive = false;
params.awgBitResolution = 12;
params.awgUpsamplingMethod = upsampling_mode.resample;
params.awgNormalize2dac = true;
case "baseline"
params.ropDbm = 0;
params.rxElectricalBandwidthHz = 40e9;
params.scopeBandwidthHz = 25e9;
case "impaired"
params.ropDbm = -15;
params.rxElectricalBandwidthHz = 12e9;
params.scopeBandwidthHz = 12e9;
otherwise
error("buildReducedImddWorkflow:UnknownScenario", ...
"Unknown reduced IM/DD scenario '%s'.", scenario);
end
end
function params = commonParameters()
params = struct();
params.M = 4;
params.fsym = 16e9;
params.fdac = 64e9;
params.fadc = 64e9;
params.kover = 2;
params.randomKey = 1;
params.sourceOrder = 12;
params.rcalpha = 0.05;
params.pulseLength = 12;
params.lenTr = 256;
params.uPi = 3;
params.vbiasRel = 0.5;
params.vbias = -params.vbiasRel * params.uPi;
params.driverScaling = 0.6 * (params.uPi / 2 - abs(params.vbias - params.uPi / 2));
params.laserWavelengthNm = 1293;
params.opticalPowerDbm = 3;
params.linkLengthKm = 1;
params.fiberAlphaDbPerKm = 0.3;
params.ropDbm = 0;
params.rxElectricalBandwidthHz = 40e9;
params.scopeBandwidthHz = 25e9;
params.rxFilterActive = true;
params.scopeLpfActive = true;
params.scopeAdcResolution = 8;
params.scopeQuantBuffer = 0.1;
params.awgLpfActive = false;
params.awgBitResolution = 8;
params.awgUpsamplingMethod = upsampling_mode.samplehold;
params.awgPrecompSincRolloff = 1;
params.awgNormalize2dac = false;
end