TDECQ Parallel

This Python example performs a TDECQ measurement on multiple optical channels of an N1092D DCA-M module. The script applies TDECQ equalizer math function to each of the input channels. Also, for each channel a SIRC 26.5625 GBd TDECQ (13.3 GHz) reference filter is selected. The script is designed with the goal of performing multiple measurement trials (to accommodate switching DUTs) while reducing measurement time. The time required for each trial is reported and, after all trials are completed, the mean trial measurement time is displayed. At the end of a trial run, the script pauses to request if another trial should be performed.

A measurement trial consists of the completion of the following tasks for each channel:

  • Measure TDECQ.
  • Report the number of precursors used by the TDECQ equalizer.
  • Report the tap values used by the TDECQ equalizer.

Between each trial, TDECQ equalizer's settings (tap values and precursors) are recalculated. The measurement time required for each trial is minimized by using the following techniques:

  • An autoscale is only performed before the first trial run. Because we assume that all signals from all DUTs are similar, additional autoscales are not required.
  • Waveform wrapping is turned off which saves the time required to write to Color-Grade Gray-Scale (database) memory.
  • No screen captures are saved.

Before Running the Script

  1. Connect the following equipment with valid source waveforms for input to each channel:
    • N1092D optical oscilloscope DCA-M
    • N1077A clock recovery DCA-M
    • Both DCA-Ms must be connected either to the PC where the script is running or to an 86100D which is remotely controlled from the PC that is running the script. A Flex-on-Flex connection is not allowed.
    • 26.5625 GBd PAM-4 signals connected to the N1092D inputs with 65535 symbols (SSPRQ) pattern and 6.6406 GHz clock output (1:4 sub-rate clock).
  2. Modify the following script constants to match the conditions of your setup:
    • ADDRESS. The VISA address of N1010A FlexDCA on your PC or of an 86100D.
    • CHANNELS. This list include each input channel on which TDECQ is to be measured.
    • N1077A_SLOT. The module slot number where the N1077A DCA-M is installed. This number is displayed on the N1077A's front panel.
    • SYMBOLRATE. This is the symbol rate of the input signal.

Example Script

Copy

TDECQ-parallel.py

""" Performs a TDECQ measurement on N1092D optical channels.
To minimize test time:
 - Autoscale only performed on first trial run. Similar signals are assumed.
 - Waveform wrapping is turned off.
 - No screen captures are taken.
Test setup:
 - Optical signal source with 26.5625 GBd PAM4 outputs with 65535 pattern
   and 6.6406 GHz clock output (1:4 sub-rate clock).
 - Script running on PC with connection to 86100D. (Not Flex-on-Flex)
 - N1092D DCA-M oscilloscope in any slot except 2.
 - N1077A DCA-M clock recovery in slot 2.
 - 26.5625GBd PAM-4 input signal from the signal source.
 - The trigger is the clock recovery in slot 2.
Script:
 - Specifies 26.5625 GBd symbol rate and 65535 pattern length.
 - Pattern lock turned on with waveform wrap off.
 - Creates TDECQ equalizer math function for each input signal.
 - For each channel, turns on SIRC Reference Filter and selects
   "26.5625 GBd TDECQ (13.3 GHz)" filter.
Modify the CHANNELS constant for the signals in your test setup.
All channels are tested in a single "trial run". Multiple trial runs
allow you to change DUTS. As long as the signals are similar this works
as the script does not take the time to autoscale for each new run.
"""

import time
import pyvisa as visa  # import VISA library

TIMEOUT = 30000
#ADDRESS = 'TCPIP0::K-86100D-00003::hislip0,4880::INSTR'
ADDRESS = 'TCPIP0::localhost::hislip0,4880::INSTR'
N1077A_SLOT = '5'
CHANNELS = ['1A', '1B', '1C', '1D']
SYMBOLRATE = '26.5625e9'


class TrialResults(object):
    """ Class used to collect results for all sources in a trial run.
    self.source is a tuple of channel and associated function number.
    """
    def __init__(self):
        self.source = ()
        self.TDECQ = ''
        self.precursors = 0
        self.taps = []


class InvalidOperationException(Exception):
    """ Exception raised if TDECQ measurement result is not valid."""
    pass


def open_flexdca_connection(address):
    """ Opens visa connection to FlexFlexDCA. """
    print('Connecting to Flexdca ...')
    try:
        rm = visa.ResourceManager()
        connection = rm.open_resource(address)
        connection.timeout = TIMEOUT
        connection.read_termination = '\n'
        connection.write_termination = '\n'
        inst_id = connection.query('*IDN?')
        print('\nFlexDCA connection established to:\n' + inst_id, flush=True)
    except (visa.VisaIOError, visa.InvalidSession):
        print('\nVISA ERROR: Cannot open instrument address.\n', flush=True)
        return None
    except Exception as other:
        print('\nVISA ERROR: Cannot connect to instrument:', other, flush=True)
        print('\n')
        return None
    return connection


def setup_test(flexdca, N107x_slot, symbol_rate):
    """ This assumes the input signal is a 26.5625GBd PAM-4
    signal from the source. And the trigger is the clock
    recovery in slot 2. """
    print('\nConfiguring the DCA.')
    flexdca.write('*CLS;*RST')
    flexdca.write(':ACQ:STOP')
    flexdca.write(':CRECovery{0}:CRATe {1}'.format(N107x_slot, symbol_rate))
    flexdca.query(':CRECovery{}:RELock;*OPC?'.format(N107x_slot))
    flexdca.write(':TRIGger:SRATe:AUTodetect OFF')  # Go into pattern lock
    flexdca.write(':TIMebase:SRATe ' + SYMBOLRATE)
    flexdca.write(':TRIGger:PLENgth:AUTodetect OFF')
    flexdca.write(':TRIGger:PLENgth 65535')
    flexdca.write(':SYST:MODE EYE')
    flexdca.write(':TRIG:PLOC ON')
    flexdca.write(':ACQuire:EPATtern ON')
    flexdca.write(':ACQuire:WRAPping OFF')
    flexdca.query('*OPC?')


def setup_channels_and_equalizers(flexdca, sources):
    """ For each source, configures the channel and installs
    a TDECQ equalizer at the channel's output. TDECQ measurement is
    turned on for equalizer's output waveform. An autoscale is performed.
    To save time, this autoscale is done only for the first measurement
    trial. This assumes that subsequent DUTs will have the same basic
    waveform attributes. The autoscale takes additional time as it
    must wait for optimization of each equalizer.
    """
    print('Configuring channels.')
    for source in sources:  # setup sources
        channel, function = source
        flexdca.write(':CHAN{0}:DISP ON'.format(channel))
        flexdca.write(':CHAN{0}:SIRC ON'.format(channel))
        flexdca.write(':CHAN{0}:SIRC:FBAN 13.28e9'.format(channel))
        flexdca.write(':CHAN{0}:SIGNal:TYPE:AUTO OFF'.format(channel))
        flexdca.write(':CHAN{0}:SIGNal:TYPE PAM4'.format(channel))
    for source in sources:  # setup sources
        channel, function = source
        flexdca.write(':FUNC{0}:FOPerator TEQualizer'.format(function))
        flexdca.write(':FUNC{0}:OPERand1 CHAN{1}'.format(function, channel))
        flexdca.write(':FUNC{0}:DISPlay ON'.format(function))
        flexdca.write(':MEAS:EYE:TDEQ:SOUR FUNC{0}'.format(function))
        flexdca.write(':MEAS:EYE:TDEQ')
        print('Applying equalizer to F{}.'.format(function))
    timeout = flexdca.timeout
    flexdca.timeout = 60000
    print('Autoscaling waveforms. Please wait...')
    flexdca.query(':SYSTem:AUToscale;*OPC?')
    flexdca.timeout = timeout


def recalculate_equalizers(flexdca, sources):
    """ For all measurement trials except for the first.
    Recalculates TDECQ equalizer tap values for each waveform. To save time,
    an autoscale is not performed as it is assumed that all waveforms
    have similar scaling attributes. A single pattern is acquired for
    all channels, which may take a long time if iterative optimization
    is used. However, iterative is not used in this script. Also, as the
    tap count increases, the optimization time increases.
    """
    flexdca.write(':ACQuire:CDISplay')
    for source in sources:
        channel, function = source
        flexdca.write(':SPRocess{0}:TEQualizer:TAPS:RECalculate'
                      .format(function))
        print('Recalulating equalizer for F{}.'.format(function))
    timeout = flexdca.timeout
    flexdca.timeout = 60000  # Wait for acquisition and equalizer optimization
    flexdca.query(':ACQuire:SINGle;*OPC?')
    flexdca.timeout = timeout


def print_trial_results(trial, results, trial_time):
    """ Print results for one complete trial run. Trial time includes the
    total time for measuring TDECQ for all sources in the run."""
    print('*'*30 + '\nTrial: {} Results'.format(trial))
    print('*'*30)
    print('Trial time: {}s'.format(round(trial_time, 3)))
    for result in results:
        channel, function = result.source
        print('\nWaveform: F{}'.format(function))
        print(' TDECQ: {} dB'.format(result.TDECQ))
        print(' # precursors: {}'.format(result.precursors))
        s = ''
        i = 1
        for tap in result.taps:
            s += str(tap).strip()
            if i % 3:
                s += ', '
            else:
                s += ',\n  '
            i += 1
        s = s[:s.rfind(',')]
        print(' Taps:\n  ', s, sep='')


def print_mean_time(times):
    """ Print the mean time for all of the trial runs. """
    avg_time = sum(times)/len(times)
    minutes, seconds = divmod(avg_time, 60)
    print('*' * 30)
    print('Mean measurement time: {0} minutes, {1} seconds'
          .format(minutes, round(seconds, 3)), flush=True)


print('Connecting to FlexDCA...')
FlexDCA = open_flexdca_connection(ADDRESS)
functions = [n for n in range(1, 17)]  # List of possible function IDs.
sources = list(zip(CHANNELS, functions))  # List of tuples (channel, function)
times = []  # List of times from each trial run
start_time = time.time()  # for measuring trial time
setup_test(FlexDCA, N1077A_SLOT, SYMBOLRATE)
setup_channels_and_equalizers(FlexDCA, sources)
trial = 1
while True:  # Run a trial
    trial_time = 0
    trial_results = []
    print('Starting trial {}.'.format(trial))
    if trial > 1:
        start_time = time.time()
        recalculate_equalizers(FlexDCA, sources)
    for source in sources:
        channel, function = source
        FlexDCA.write(':MEASure:EYE:TDEQ:SOUR FUNC{0}'.format(function))
        status = FlexDCA.query(':MEASure:EYE:TDEQ:STAT?')
        if ('CORR' not in status):
            reason = FlexDCA.query(':MEASure:EYE:TDEQ:STAT:REAS?')
            raise InvalidOperationException('TDECQ Result Invalid: {0}'
                                            .format(status + reason))
        results = TrialResults()
        results.source = source
        results.TDECQ = FlexDCA.query(':MEASure:EYE:TDEQ?')  # TDECQ meas
        results.taps = FlexDCA.query(':SPRocess{0}:TEQualizer:TAPS?'
                                     .format(function)).split(',')
        num = FlexDCA.query(':SPRocess{0}:TEQualizer:NPRecursors?'
                            .format(function))
        results.precursors = num
        trial_results.append(results)
    trial_time = time.time() - start_time
    print_trial_results(trial, trial_results, trial_time)
    times.append(trial_time)
    trial += 1
    s = input('Run another trial? [type q to quit]: ')
    if s in 'qQ':
        print_mean_time(times)
        break
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()