O-Matrix User Stories

Using Eye Diagrams to Investigate Modulation Schemes

As part of my job at a major aerospace corporation I have been investigating using various modulation schemes to send data over a very non-ideal path that has both poor frequency response and a nonlinear amplitude response. One technique I use to assess the merits of the various schemes is classic eye patterns. Even though Iím an absolute O-Matrix novice and a lousy programmer to boot, I found it very easy and fast to create the required tools for evaluating each modulation scheme, one of which is described below. In addition, I am able to easily distribute my applications to colleagues using the O-Matrix Development Kit.

Manchester is one of the simpler encoding schemes which is best described by Figure 1 where a NRZ one is transmitted as a one-zero pair and a zero is transmitted as a zero-one pair with the state change occurring at the middle of the NRZ bit period.

Manchester Encoding Scheme
Figure 1

A couple of itís useful characteristics are that itís easy to synchronize with as it always has a transition in the middle of a NRZ bit period and the other is that the spectrum doesnít contain any energy at DC allowing it to cohabitate on the same wire as a DC power.

Eye diagrams are a very useful tool for the qualitative assessment of signals used to transmit digital data. Itís an old technique and my first exposure to it was some 35+ years ago. Traditionally they were presented on an oscilloscope by triggering on a system bit rate clock and setting the oscilloscope time base or sweep speed such that 2 or 3 bit periods are displayed on the screen. The result, due both to the persistence of the oscilloscope display and the peculiarities of the human eye is a presentation similar to that shown in figure 2 which actually has over 1000 Manchester or BiPhase encoded bits overlaid. There is some rounding of the waveform due to the transmission paths finite high frequency response but other than that these would be very good eyes since theyíre ďwide openĒ making it easy to determine whether a bit is a one or a zero. This is very valuable in the development process since the simulation times required to verify a bit error rate of 1 part in 10^9 or better is just not practical, even a modest 1 part in 10^6 is just not practical except perhaps for very simple networks.

Eye Diagram at VN1
Figure 2

In this instance the hardware doesnít yet exist so the system was modeled in Spice, some 5000+ components. For each modulation scheme I wrote a short O-Matrix script to generate a piecewise-linear time and amplitude representation of the signal to be used by Spice as the excitation for the time domain analysis. For this case it involved generating random ones and zeros, the Manchester carrier, sampling them at the desired sample density and finally Exclusive-OR them to create the PWL data. Hindsight being 20-20 it would have been smarter to Exclusive-OR them and then sample the result. For this technique to be valid the number of bits must be very large so itís not unusual to generate files 100ís of k-bytes long. The code to generate the Manchester data is shown below.
# Generate a random Manchester PWL input file for B2 Spice     mcc 07/14/2006
# output is ((1/fnrz)/t)n samples at sr=1/t.
ranseed    # seed random number generator from system clock
# get inputs
fnrz = atod(input("The NRZ bit rate in bits/sec"))
n = atod(input("The number of random NRZ bits in the sequence"))
sr = atod(input("The sample rate in samples/second"))
# generate and sample the NRZ bit stream
A=rand(n,1                        # load the array with random numbers 
tock=zeros(((1/fnrz)/t)*n,1)      # load the array with zeros
nrz=zeros(((1/fnrz)/t)*n,1)       # load the array with zeros
biphase=zeros(((1/fnrz)/t)*n,2)   # load the array with zeros 
for i = 1 to n begin              # make the random number either a 1 or a 0    
   if A(i)  < 0.5 then sample = 0 
   else sample = 1    
   for j = 1 to ((1/fnrz)/t) begin
      nrz((i-1)*((1/fnrz)/t)+j) = sample   # write the NRZ sample to the array
      tock((i-1)*((1/fnrz)/t)+j) = (((i-1)*((1/fnrz)/t))+j-1)*t   # write the time to the to the array  
# generate and sample the carrier
carrier=zeros(((1/fnrz)/t)*n,1)            # load the array with zeros
for i = 1 to n begin                  
   for j = 1 to ((1/fnrz)/t/2) begin
      carrier((i-1)*((1/fnrz)/t)+j) = 0  # first 1/2 cycle
   for j= (1+((1/fnrz)/t)/2) to ((1/fnrz)/t) begin
      carrier((i-1)*((1/fnrz)/t)+j) = 1   # second 1/2 cycle 
# XOR the NRZ bit stream and the carrier to generate the Manchester bit stream
f = input("Enter the path and output file name")   # specify output file name
if exists(f) then rmfile(f)                        # delete file if it already exists 
biphase = [tock, 2*int(xor(carrier, nrz))-1]       # exclusive OR the carrier and RZ bit stream 
write(f,biphase)                                   # write data to the designated file 
The output from Spice is a large text file of time and amplitude samples that are not at equal time intervals. Spice will ratchet down the size of the time step as required to generate a reasonable solution. Since my problem is very non-linear the result is very large files on the order of 5 mega-bytes for even a modest number of random bits. This data could of course be linearized and the density reduced via interpolation but doing so has the potential to discard the very kind of disturbances that would cause a bit error in the decode process. For this reason I decided to error on the side of caution and deal with the large files.

To analyze the results I wrote a script in O-Matrix to plot a traditional eye pattern from this data. While it was trivial to get a basic display up and running, there were some subtleties to deal with to obtain a clean plot. The resulting code is shown below.
# Plot "Eyes" from a B2 Spice or PSpice transient analysis output file       mcc 08/09/2006
# rev 1  changed size fudge factor from 1.1 to 1.2 
# rev 2 dropped fudge factor and now determine the max vector size required for tock & eyes.
# rev 3 fixed a minor problem that Brian Falk spotted in the second while loop of "make eyes"
width = 1                     
# get user inputs                        
fin = input("Enter the path and input file name")    # specify input file name
t = input("Enter the plot title")                    # specify plot title
fs = atod(input("Enter the NRZ bit rate in bits/sec"))
wl = atod(input("Enter the group size of NRZ bit periods to display"))
ng = atod(input("Enter the number of NRZ bit groups to skip before plotting"))
width = atoi(input("Set axis and line width in pixels: 1,3,5,7,9,11 or 13"))
nrow = nrows(fin)                # get file size
read(fin,"char", 1)              # read one line of text to skip the column titles
a=read(fin, "double",(nrow-1) )  # read the remaining rows as numbers
seconds=a.col(1)                 # extract time  
amplitude=a.col(2)               # extract amplitude 
# set up plot stuff
gcolor({"red"})                  # set trace color
gstyle("solid",width)            # set style and width of plot lines
gxaxis("linear",0, wl/fs,2*wl,10)# set x scale          
yscale = axiscale([amplitude])    # compute scale for Y axis
gyaxis("linear",yscale(1),yscale(2),yscale(3),yscale(4))    # scale Y axis
i = 1   # main index counter
j = 0   # window index counter 
m = 0   # window counter
# determine the maximum size for the the tock and eye vectors 
told = seconds(1) # time of previous window
kold = 0          # previous count
maxcount = 0      # maximum data points/window found
for k = 1 to nrow-1 begin
   if (seconds(k) >= (ng*wl)/fs) then begin      # skip specified settling time
      if seconds(k) - told >= wl/fs then begin   
	     if maxcount == 0 then begin
            maxcount = k + 1
            kold = k
         if k - kold + 1 > maxcount then begin       # if new size > maxcount
            maxcount = k - kold + 1                  # male maxcount = current size 
            kold = k                                 # store new old k 
            told = seconds(k)                        # store new old time
sizeof = maxcount    # make size of vectors = maxcount
# make Eyes
i = 1                # main index counter
j = 0                # window index counter 
m = 0                # window counter 
plottime=seconds(nrow-1)-(wl*ng)/fs   # total plot time, constant for window control
flag = 0                              # set flag to false
while i < nrow-1 begin                # outer loop, do till end of file
   if (seconds(i) >= (ng*wl)/fs) and (plottime - m*wl/fs >= wl/fs) then begin  # controls when to plot complete windows
      flag = -1                       # set flag to true
      j=0                             # reset inner loop index to zero
	  # inner loop, do till we have one full window         
      while seconds(i+j) - ((m*wl/fs) + (ng*wl)/fs) < wl/fs and i+j < nrow-1 begin              
         tock(j+1) = seconds(i+j) - ((m*wl/fs) + (ng*wl)/fs) # store normalized time
         eyes(j+1) = amplitude(i+j)   # store amplitude
         j=j+1                        # increment window index                
      end                             # end of inner loop
	  # a little kludge to set the unused vector locations to the last data point. 
      if j <> 0 and tock(j+1) == 1e30 then begin  
         for k = j to sizeof begin
            tock(k) = tock(j)
            eyes(k) = eyes(j)
      m=m+1     # increment number of windows counter  
   if flag==0 then i = i + 1  # count by 1 during settling time
   else i = i + j             # step i by j after settling time
end  # end of outer loop
Figures 3 and 4 are the results of one recent analysis that show the effects of passing the Manchester signal down this very large network, Figure 3 is near the beginning of the network and Figure 4 is towards the end and indeed shows considerably more distortion.

Eye Diagram at VN352
Figure 3

Eye Diagram at VN354
Figure 4
Once I had this basic work completed I used the O-Matrix Development Kit along with the The O-Matrix Virtual Machine Application Launcher to share these with others working on the project with me. I work in a very large company where many different computational tools are used so Iíve found the Development Kit quite useful for sharing similar small utilities with colleagues.

A partial list of some additional functions that Iíve shared this way recently is shown below.
  1. FET Bias, computes the component values required to achieve an operating point within a specified tolerance.
  2. Phase Noise to Jitter, computes the RMS jitter from phase noise data.
  3. QPSK bit error rate vs. SNR
  4. 128 QAM Symbol Error Rate vs. SNR
  5. MTI Clutter Rejection vs. PRI to PRI Amplitude Jitter
  6. MTI Clutter Rejection vs. PRI to PRI Timing Jitter
  7. MTI Clutter Rejection vs. Pulse Width Jitter
  8. MTI Clutter Rejection Limitation due to Quantization Errors
The most popular of these has been the conversion of phase noise data in dB relative to the carrier into RMS jitter in seconds. Those of us that design data acquisition systems are concerned with the amount of jitter on the clock for the A/D conversion process. However, off the shelf oscillators are almost always specified in terms of phase noise relative to the carrier (dBc) so I wrote this short utility in O-Matrix to make this calculation. There are a number of my colleagues using this utility thanks to the Development Kit and the Application Launcher. The code for this short utility is shown below.
# Calculate rms jitter from phase noise data in dBc        mcc 02/20/06
# Assumes that the noise is symmetric about the carrier
# A good rule of thumb is to integrate out to 2xFo 
format double "f15.4"
fzero=atod(input("Oscillator Frequency(Hz)=?"))
n = atoi(input("Number of data points=?"))
f = fill(0d0,n,1)                                  # create and fill vector with zero
a = fill(0d0,n,1)                                  # create and fill vector with zero 
Lf = fill(0d0,n,1)                                 # create and fill vector with zero 
Bf = fill(0d0,n,1)                                 # create and fill vector with zero 
sum = 0.0d0
for i = 1 to n begin                              # loop to get input data
   f(i,1) = atod(input("Offset in Hz?"))          # frequency offset from carrier
   Lf(i,1) = atod(input("Noise level in dBc?"))   # Noise level in dBc at the offset frequency
for i = 1 to n-1 begin
   a(i,1)=(Lf(i+1,1) -Lf(i,1))/(log10(f(i+1,1))-log10(f(i,1)))  # compute slopes (dBc/decade)
   if a(i,1) == -10.0 then a(i,1)=a(i,1)+1d-6                                 
   sum = sum + (10^(Lf(i,1)/10)*f(i,1)^(-a(i,1)/10)*(1/(a(i)/10 +1d0))*( f(i+1,1)^(a(i)/10 + 1.0d0)
   f(i,1)^(a(i,1)/10 +1.0d0)))
clc   # clear Command window screen
print "RMS Jitter =", rmsj/1d-12,"ps"
print "       Input Data"
print "Oscillator Frequency(Hz)=", fzero
print "Freq", f,"Phase Noise (dBc)",Lf                                                                              
# Test Values from Analog Devices App Note MT-008: 
# "Converting Oscillator Phase Noise to Time Jitter" rev 10-03-2005 page 5
# Walt Kester     
# Oscillator Frequency 2.25 Ghz   Jitter = 1.57ps
# 100 Hz    -82 dBc
# 1kHz      -80 dBc
# 10kHz     -77 dBc
# 100kHz    -112 dBc
# 1Mhz      -134 dBc
# 10 Mhz    -146 dBc
# 4.5 Ghz   -146 dBc
#  Calculated with this code the Jitter = 1.5658 ps 

This user story was contributed by M.C. ďMoeĒ Clayton, mclayton@twcny.rr.com

Company |  Products |  Showcase |  Support |  Ordering
Copyright© 1994-2009 Harmonic Software Inc. - All rights reserved.