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