from __future__ import print_function
'''
# Copyright (C) 2015, Elphel.inc.
# Fit DQ/DQS timing parameters using Levenberg-Marquardt algorithm
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
@author: Andrey Filippov
@copyright: 2015 Elphel, Inc.
@license: GPLv3.0+
@contact: andrey@elphel.coml
@deffield updated: Updated
'''
__author__ = "Andrey Filippov"
__copyright__ = "Copyright 2015, Elphel, Inc."
__license__ = "GPL"
__version__ = "3.0+"
__maintainer__ = "Andrey Filippov"
__email__ = "andrey@elphel.com"
__status__ = "Development"
import math
import numpy as np
"""
For each byte lane:
tSDQS delay ps/step (~1/5 of datasheet value) - 1
tSDQi delay ps/step (~1/5 of datasheet value) - 8
tDQSHL=tDQSH-tDQSL (ps) - 1
tDQiHL=tDQiH-tDQiL (ps) - 8
tDQS=0 (not adjusted here) - 0
tDQi - DQi routing delay with respect to DQS - 8
tFDQS - array of 5 fine delay steps (here in ps) - 5
tFDQi - array of 5 fine delay steps (here in ps) for each bit - 5*8=40
error**2 here (y-tFDQi-fXi)**2
"""
PARAMETER_TYPES=(
{"name":"tSDQS", "size":1, "units":"ps","description":"DQS input delay per step (1/5 of the datasheet value)","en":1},
{"name":"tSDQ", "size":8, "units":"ps","description":"DQ input delay per step (1/5 of the datasheet value)","en":1},
{"name":"tDQSHL", "size":1, "units":"ps","description":"DQS HIGH minus LOW difference","en":1},
{"name":"tDQHL", "size":8, "units":"ps","description":"DQi HIGH minus LOW difference","en":1},
{"name":"tDQS", "size":1, "units":"ps","description":"DQS delay (not adjusted)","en":0},
{"name":"tDQ", "size":8, "units":"ps","description":"DQi delay","en":1},
{"name":"tFDQS", "size":4, "units":"ps","description":"DQS fine delays (mod 5)","en":1}, #only 4 are independent, 5-th is -sum of 4
{"name":"tFDQ", "size":32, "units":"ps","description":"DQ fine delays (mod 5)","en":1},
{"name":"anaScale","size":1, "dflt":20, "units":"ps","description":"Scale for non-binary measured results","en":1}, #should not be 0 - singular matrix
{"name":"tCDQS", "size":30, "units":"ps","description":"DQS primary delays (all but 8 and 24","en":1}, #only 4 are independent, 5-th is -sum of 4
)
FINE_STEPS=5
DLY_STEPS =FINE_STEPS * 32 # =160
def test_data(meas_delays,
compare_prim_steps,
quiet=1):
halfStep=0.5
if compare_prim_steps:
halfStep*=FINE_STEPS
if quiet < 2:
print ("DQS",end=" ")
for f in ('ir','if','or','of'):
for b in range (16):
print ("%s_%d"%(f,b),end=" ")
print()
for ldly, data in enumerate(meas_delays):
print("%d"%ldly,end=" ")
if data:
"""
for typ in ((0,0),(0,1),(1,0),(1,1)):
for pData in data: # 16 DQs, each None nor a pair of lists for inPhase in (0,1), each a pair of edges, each a pair of (dly,diff)
if pData and (not pData[typ[0]][typ[1]] is None):
print ("%d"%pData[typ[0]][typ[1]],end=" ")
else:
print ("x",end=" ")
"""
for typ in range(4):
for pData in data: # 16 DQs, each None nor a pair of lists for inPhase in (0,1), each a pair of edges, each a pair of (dly,diff)
if pData and (not pData[typ] is None):
if pData[typ][1] is None:
print ("%d"%(pData[typ][0]+halfStep),end=" ")
else:
print ("%d"%(pData[typ][0]),end=" ")
else:
print ("x",end=" ")
print()
def make_repeat(value,nRep):
if isinstance(value,(list,tuple)):
return value
else:
return (value,)*nRep
class X393LMA(object):
lambdas={"initial":0.1,"current":0.1,"max":100.0}
maxNumSteps=25
finalDiffRMS=0.001
parameters=None
# parameterMask={}
parameterMask={'tSDQS': True,
'tSDQ': [True, True, True, True, True, True, True, True],
'tDQSHL': True,
'tDQHL': [True, True, True, True, True, True, True, True],# 23.523465ps -> 23.315524 - too little difference?
'tDQS': False,
'tDQ': [True, True, True, True, True, True, True, True],
'tFDQS': [True, True, True, True],
'tFDQ': [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True],
'anaScale': True, # False,# True, # False # Broke?
# 'tCDQS': False # True #False #True # list of 30
'tCDQS': [True, True, True, True, True, True, True, True, # 8
True, True, True, True, True, True, True, True, True, True, True, True, True, True, True,
# False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, #15
True, True, True, True, True, True, True]
# False, False, False, False, False, False, False] #7
}
"""
parameterMask={'tSDQS': True,
'tSDQ': [True, True, True, True, True, True, True, True],
'tDQSHL': True, # False, # True,
'tDQHL': [True, True, True, True, True, True, True, True], # False, # [True, True, True, True, True, True, True, True], #OK
'tDQS': False,
'tDQ': [True, True, True, True, True, True, True, True], #BAD - without it 0 in JTbyJ for tFDQ
'tFDQS': [True, True, True, True], # False, # [True, True, True, True], # OK
'tFDQ': True, # False, # [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True],
'anaScale': False
}
"""
parameterVector=None
clk_period=None
analog_scale=20 # ps when there is analog result -0.5...+0.5, multiply it by analog_scale and add to result
# hist_estimated=None # DQ/DQS delay period,
# # DQ-DQS shift (and number of periods later) for averaged and individual bits,
# # for each of 4 edge types
def __init__(self):
pass
def createYandWvectors(self,
lane,
data_set,
compare_prim_steps,
scale_w=0.2, # multiply weight by this if fractions are undefined
periods=None,
quiet=1):
if quiet < 3:
print ("createYandWvectors(): scale_w=%f"%(scale_w))
def pythIsNone(obj):
return obj is None
isNone=pythIsNone
if isinstance(data_set,np.ndarray):
isNone=np.isnan
n=len(data_set)*32
# fx=np.zeros((DLY_STEPS*32,))
"""
use np.nan instead of the None data
np.isnan() test
, dtype=np.float
@param compare_prim_steps while scanning, compare this delay with 1 less by primary(not fine) step,
save None for fraction in unknown (previous -0.5, next +0.5)
"""
halfStep=0.5
if compare_prim_steps:
halfStep*=FINE_STEPS
# extra_Y=(0.0,halfStep)
y=np.zeros((n,), dtype=np.int) #[0]*n
w=np.zeros((n,)) #[0]*n
# f=np.full((n,),np.nan) # fractions
f=np.empty((n,)) # fractions
f[:]=np.nan
yf=np.zeros((n,)) # y with added fractions
if not periods is None:
p=np.zeros((n), dtype=np.int)#[0]*n
for dly,data in enumerate(data_set):
if data:
data_lane=data[lane*8:(lane+1)*8]
pm=[None]*8
for b,bData in enumerate(data_lane): # bdata for each bit is either None or has 4 (maybe None) DQ integer delay values
if bData:
pm[b]=[None]*4
for t,tData in enumerate(bData):
if not tData is None: #[dly],[b],[t] tData - int value
i=32*dly+8*t+b
y[i]=tData[0]
if not isNone(tData[1]):
f[i] = tData[1]
yf[i]=tData[0]
w[i]=1
else:
w[i]=scale_w
yf[i]=tData[0]+halfStep
if not periods is None:
p[i]=periods[dly][b][t]
#Normalize weights
S0=np.sum(w)
w*=1.0/S0
vectors={'y':y,'yf':yf,'w':w,'f':f} # yf - use for actual float value, y - integer
if not periods is None:
vectors['p']=p
return vectors
def showYOrVector(self,
ywp,
filtered=False,
vector=None,
showMode="IA"):
def pythIsNone(obj):
return obj is None
isNone=pythIsNone
# If vector is None - print y vector (skipping zero mask),
# otherwise print vector (should be the same length, using the same 'w' weight mask
v=vector
if v is None:
v= ywp['yf']
w=ywp['w']
try:
f=ywp['f']
noF=False
except:
f=None
noF=True
if not noF:
if isinstance(f,np.ndarray):
isNone=np.isnan
# print ("using np.isnan")
# print("filtered=",filtered)
n=len(v)/32
if 'A' in showMode.upper():
av=[]
for dly in range(n):
avd=[]
SAX=0.0
SA0=0.0
for t in range(4):
SX=0.0
S0=0.0
for b in range(8):
i=32*dly+8*t+b
if w[i] and ((not filtered) or noF or (not isNone(f[i]))):
SX+=w[i]*v[i]
S0+=w[i]
SAX+=SX
SA0+=S0
if S0>0:
SX/=S0
else:
SX=None
avd.append(SX)
if SA0>0:
SAX/=SA0
else:
SAX=None
avd.append(SAX)
av.append(avd)
print("DQS_dly", end= " ")
if "I" in showMode.upper():
for ft in ('ir','if','or','of'):
for b in range (8):
print ("%s_%d"%(ft,b),end=" ")
if "A" in showMode.upper():
for ft in ('ir','if','or','of','all'):
print ("%s"%(ft),end=" ")
print()
for dly in range(n):
print("%d"%dly,end=" ")
if "I" in showMode.upper():
for t in range(4):
for b in range(8):
i=32*dly+8*t+b
if w[i] and ((not filtered) or noF or (not isNone(f[i]))):
print("%s"%(str(v[i])),end=" ")
else:
print("?",end=" ")
if "A" in showMode.upper():
for a in av[dly]:
if not a is None:
print("%f"%(a),end=" ")
else:
print("?",end=" ")
print()
def showENLresults(self,
DQvDQS):
rslt_names=("early","nominal","late")
err_name='maxErrDqs'
numBits=0
for n in DQvDQS:
try:
for d in DQvDQS[n]:
try:
numBits=len(d)
# print ("d=",d)
break
except:
pass
break
except:
pass
if not numBits:
# print("showENLresults(): No no-None data provided")
# print("DQvDQS=",DQvDQS)
# return
raise Exception("showENLresults(): No no-None data provided")
numLanes=numBits//8
# print ("****numBits=%d"%(numBits))
enl_list=[]
for k in rslt_names:
if DQvDQS[k]:
enl_list.append(k)
print("DQS", end=" ")
for enl in enl_list:
for b in range(numBits):
print("%s%d"%(enl[0].upper(),b),end=" ")
for enl in enl_list:
for lane in range(numLanes):
if numLanes > 1:
print("%s%d_err"%(enl[0].upper(),lane),end=" ")
else:
print("%s_err"%(enl[0].upper()),end=" ")
print()
for dly in range(len(DQvDQS[enl_list[0]])):
print ("%d"%(dly),end=" ")
for enl in enl_list:
if DQvDQS[enl][dly] is None:
print ("? "*numBits,end="")
else:
for b in range(numBits):
if DQvDQS[enl][dly][b] is None:
print("?",end=" ")
else:
print("%d"%(DQvDQS[enl][dly][b]),end=" ")
for enl in enl_list:
# if numLanes>1:
# print ("DQvDQS[err_name]=",DQvDQS[err_name])
# print ("DQvDQS[err_name][enl]=",DQvDQS[err_name][enl])
# print ("DQvDQS[err_name][enl][dly]=",DQvDQS[err_name][enl][dly])
if DQvDQS[err_name][enl][dly] is None:
print ("? "*numLanes,end="")
else:
for lane in range(numLanes):
if DQvDQS[err_name][enl][dly] is None:
print("?",end=" ")
else:
if numLanes > 1:
if DQvDQS[err_name][enl][dly][lane] is None:
print("?",end=" ")
else:
print("%.1f"%(DQvDQS[err_name][enl][dly][lane]),end=" ")
else:
print("%.1f"%(DQvDQS[err_name][enl][dly]),end=" ")
print()
def normalizeParameters(self,
parameters,
isMask=False):
"""
Convert single/lists as needed
"""
if parameters is None:
parameters = self.parameters
for par in PARAMETER_TYPES:
name=par['name']
size=par["size"]
try:
v=parameters[name]
except:
if isMask:
v=par['en']
else:
try:
v=par['dflt']
except:
raise Exception("parameter['%s'] is not defined and PARAMETER_TYPES['%s'] does not provide default value"%(name,name))
if size == 1:
if isinstance(v,(list,tuple)):
v=v[0]
if isMask:
if v:
v=True
else:
v=False
else:
if isinstance(v,tuple):
v=list(v)
elif not isinstance(v,list):
v=[v]*size
if isMask:
for i in range(size):
if v[i]:
v[i]=True
else:
v[i]=False
parameters[name]=v
return parameters
def copyParameters(self,
parameters):
newPars={}
for k,v in parameters.items():
if isinstance(v,(list,tuple)):
newPars[k]=list(v)
else:
newPars[k]=v
return newPars
def createParameterVector(self,
parameters=None,
parameterMask=None):
# global PARAMETER_TYPES
if parameters is None:
parameters = self.parameters
if parameterMask is None:
parameterMask = self.parameterMask
vector=[]
for par in PARAMETER_TYPES:
name=par['name']
size=par["size"]
if par['en']:
try:
mask=parameterMask[name]
except:
mask=True
try:
parVal=parameters[name]
except:
parVal=None
# mask=False
if mask:
mask= make_repeat(mask,size)
parVal=make_repeat(parVal,size)
for m,p in zip(mask, parVal):
if m:
vector.append(p)
return np.array(vector)
def createParameterIndex(self,
parameters=None,
parameterMask=None):
"""
create dict as parameters, but instead of values - index in the parameter vector, or -1
"""
if parameters is None:
parameters = self.parameters
if parameterMask is None:
parameterMask = self.parameterMask
indices={}
parIndex=0
for par in PARAMETER_TYPES:
name=par['name']
size=par["size"]
if par['en']:
try:
mask=parameterMask[name]
except:
mask=True
if mask:
if size==1:
indices[name]=parIndex
parIndex += 1
else:
if not isinstance(mask,(list,tuple)):
mask=[mask]*size
indices[name]=[]
for m in mask:
if m:
indices[name].append(parIndex)
parIndex += 1
else:
indices[name].append(-1)
if not name in indices:
if size==1:
indices[name]=-1
else:
indices[name]=[-1]*size
indices['numPars']=parIndex # extra key with total number of parameters
return indices
def getParametersFromVector(self,
vector=None,
parameterMask=None,
parameters=None):# if not None, will be updated
# global PARAMETER_TYPES
if vector is None:
vector=self.parameterVector
if parameterMask is None:
parameterMask=self.parameterMask
if parameters is None:
parameters={}
index=0
for par in PARAMETER_TYPES:
name=par['name']
size=par["size"]
if par['en']:
try:
mask=parameterMask[name]
except:
mask=True
if mask:
mask= make_repeat(mask,size)
if size==1:
if mask[0]:
parameters[name]=vector[index]
index += 1
else:
if not name in parameters:
parameters[name]=[None]*size
for i,m in enumerate(mask):
if m:
parameters[name][i]=vector[index]
index += 1
return parameters
def dqi_dqsi_estimate_from_histograms(self,
lane, # byte lane
bin_size,
clk_period,
dly_step_ds,
primary_set,
data_set,
compare_prim_steps,
quiet=1):
"""
Prepare data by building and processing histograms to find
DQ/DQS period (in fine delay steps),
and for each data bit (and averaged) DQ-DQS shift (in fine steps) and
full periods - for primary set (and same phase) - starting from
closest to 0 (both signs) at DQS==0, for non-primary -\
from DQ 180 degrees later tha primary
using datasheet delay/step (without fine step delay)
and the data set (for each DQS delay - list of 16 bits,
each of 2x2 elements (DQ delay values) or null
Create data set template - for each DQS delay and inPhase
- branch - number of full periods to add
@param lane byte lane to process
@param bin_size bin size for the histograms (should be 5/10/20/40)
@param clk_period SDCLK period in ps
@param dly_step_ds IDELAY step (from the datasheet)
@param primary_set which of the data edge series to use as leading (other will be trailing by 180)
@param data_set measured data set
@param compare_prim_steps while scanning, compare this delay with 1 less by primary(not fine) step,
save None for fraction in unknown (previous -0.5, next +0.5)
@param quiet reduce output
"""
num_hist_steps=2*((DLY_STEPS+bin_size-1)//bin_size)
est_step_period=(clk_period/dly_step_ds)*FINE_STEPS
est_bin_period=est_step_period/bin_size
halfStep=0.5
if compare_prim_steps:
halfStep*=FINE_STEPS
extra_Y=(0.0,halfStep)
hist=[0.0]* num_hist_steps
hist4=[]
for _ in range(4):
hist4.append(list(hist))
hist8x4=[] # [8][4][num_hist_steps]
for _i in range(8):
l=[]
for _j in range(4):
l.append(list(hist))
hist8x4.append(l)
for dly,data in enumerate(data_set):
if data:
data_lane=data[lane*8:(lane+1)*8]
for b,bData in enumerate(data_lane):
if bData:
for t,tData in enumerate(bData):
if not tData is None:
binNum=int((tData[0]+extra_Y[tData[1] is None]-dly+DLY_STEPS+1) / bin_size)
hist8x4[b][t][binNum] += 1 # lowest bin will be 1 count shy
if binNum == 0:
hist8x4[b][t][binNum] += 1.0/(bin_size-1.0)
for t in range(4):
for i in range(num_hist_steps):
for b in range(8):
hist4[t][i]+=hist8x4[b][t][i]
hist4[t][i] /= 8.0
if quiet <1:
for i in range(num_hist_steps):
print ("%d"%i, end=" ")
for t in range(4):
for b in range(8):
print ("%f"%(hist8x4[b][t][i]), end=" ")
print ("%f"%(hist4[t][i]), end=" ")
print()
#Correlate
corr=[0.0]* num_hist_steps
for shft in range(num_hist_steps):
for x in range(0,num_hist_steps-shft):
for t in range(4):
corr[shft]+=hist4[t][x]*hist4[t][x+shft]
if quiet <1:
for i, c in enumerate(corr):
print("%d %f"%(i,c))
if quiet <2:
print ("est_step_period=%f\nest_bin_period=%f"%(est_step_period,est_bin_period))
# find actual period using correlation
if est_bin_period > (0.8*len(corr)):
raise Exception("Estimated DQS period %f is too high to measure with this data set correlation (%d)"%
(est_bin_period, len(corr)))
corr_low=int(0.5*est_bin_period)
corr_high=min(int(1.5*est_bin_period),len(corr))
if quiet <1:
print ("corr_low=%d, corr_high=%d"%(corr_low, corr_high))
xmx=corr_low
for x in range (corr_low,corr_high+1):
if corr[x] > corr[xmx]:
xmx=x
span=max(int(round(est_bin_period/8)),4)
corr_low=max(corr_low,xmx-span,-num_hist_steps//2)
corr_high=min(corr_high,xmx+span,num_hist_steps//2-1)
if quiet <1:
print ("corrected corr_low=%d, corr_high=%d, xmx=%d"%(corr_low, corr_high, xmx))
S0=0
SX=0
for x in range (corr_low,corr_high+1):
S0+=corr[x]
SX+=corr[x]*x
corr_bin_period=SX/S0
corr_period= corr_bin_period*bin_size # in finedelay steps
if quiet <2:
print ("Period by correlation=%f, (in bin steps: %f)"%(corr_period,corr_bin_period))
xSpan=min(int(corr_bin_period/2)+1,num_hist_steps//2)
corr_low= -xSpan
corr_high= xSpan
xmx=None
mx=0
if quiet <1:
print ("corr_low=%d, corr_high=%d"%(corr_low, corr_high))
for x in range(corr_low,corr_high+1):
y=hist4[primary_set][num_hist_steps//2 + x]
if y > mx:
mx=y
xmx=x
span=max(int(round(corr_bin_period/8)),4)
corr_low=max(corr_low,xmx-span,-num_hist_steps//2)
corr_high=min(corr_high,xmx+span,num_hist_steps//2-1)
if quiet < 1:
print ("corrected corr_low=%d, corr_high=%d, xmx=%d"%(corr_low, corr_high, xmx))
S0=0
SX=0
for x in range (corr_low,corr_high+1):
y=hist4[primary_set][num_hist_steps//2 + x]
S0+=y
SX+=y*x
primary_dly_shift= (SX/S0)* bin_size
if quiet < 1:
print ("tDQ-tDQS difference for primary set =%f (dly fine steps) (in bin steps %f)"%(primary_dly_shift,primary_dly_shift/bin_size))
# now for each of the other series find maximum closest to either primary or primary +-180 (sign here - opposite to the primary sign)
# do not forget to apply that sign later, so primary is always leading
b_series=[None]*4
for t in range(4):
if t==primary_set:
b_series[t]=(primary_dly_shift,0)
else:
b_start=primary_dly_shift # will search around b_start
periods=0
if ((t ^ primary_set) & 2):
if primary_dly_shift > 0:
b_start -= corr_period/2
periods=-1
else:
b_start += corr_period/2
periods=0 # as expected - primary is supposed to have lower DQ delay, than secondary
xSpan= corr_bin_period/2
#scanning in bin, not dly steps
corr_low= max(int(b_start/bin_size-xSpan),-(num_hist_steps//2))
corr_high=min(int(b_start/bin_size+xSpan), (num_hist_steps//2)-1)
xmx=None
mx=0
if quiet < 1:
print ("series=%d, b_start=%f, corr_low=%d, corr_high=%d, xSpan=%f"%(t, b_start, corr_low, corr_high,xSpan))
for x in range(corr_low,corr_high+1):
y=hist4[t][num_hist_steps//2 + x]
if y > mx:
mx=y
xmx=x
span=max(int(round(corr_bin_period/8)),4)
corr_low=max(corr_low,xmx-span)
corr_high=min(corr_high,xmx+span)
if quiet < 1:
print ("series=%d corrected corr_low=%d, corr_high=%d, xmx=%d"%(t, corr_low, corr_high, xmx))
S0=0
SX=0
for x in range (corr_low,corr_high+1):
y=hist4[t][num_hist_steps//2 + x]
S0+=y
SX+=y*x
b_series[t]= ((SX/S0)* bin_size,periods)
if quiet < 1:
print ("tDQ-tDQS difference for set%d =%s (dly fine steps) (in bin steps %d)"%(t, str(b_series[t]), b_series[t][0]))
if quiet < 2:
print ("b_series=%s"%(str(b_series)))
#Now find per-bit maximums closest to the average ones
b_indiv=[]
for b, hst in enumerate(hist8x4):
b_iseries=[None]*4
for t in range(4):
periods=b_series[t][1] # period shift of the averaged series
b_start=b_series[t][0] # will search around b_start
xSpan= corr_bin_period/2
#scanning in bin, not dly steps
corr_low= max(int(b_start/bin_size-xSpan),-(num_hist_steps//2))
corr_high=min(int(b_start/bin_size+xSpan), (num_hist_steps//2)-1)
xmx=None
mx=0
if quiet < 1:
print ("DQ[%d], series=%d, b_start=%f, corr_low=%d, corr_high=%d, xSpan=%f"%(b, t, b_start, corr_low, corr_high,xSpan))
for x in range(corr_low,corr_high+1):
y=hst[t][num_hist_steps//2 + x]
if y > mx:
mx=y
xmx=x
span=max(int(round(corr_bin_period/8)),4)
corr_low=max(corr_low,xmx-span,-num_hist_steps//2)
corr_high=min(corr_high,xmx+span,num_hist_steps//2-1)
if quiet < 1:
print ("DQ[%d], series=%d corrected corr_low=%d, corr_high=%d, xmx=%d"%(b, t, corr_low, corr_high, xmx))
S0=0
SX=0
for x in range (corr_low,corr_high+1):
y=hst[t][num_hist_steps//2 + x]
S0+=y
SX+=y*x
b_iseries[t]= ((SX/S0)* bin_size,periods)
if quiet < 1:
print ("DQ[%d], tDQ-tDQS difference for set%d =%s (dly fine steps) (in bin steps %d)"%(b, t, str(b_iseries[t]), b_iseries[t][0]))
b_indiv.append(b_iseries)
if quiet < 2:
print ("b_indiv=%s"%(str(b_indiv)))
return {'period':corr_period,
'b_series':b_series,
'b_indiv':b_indiv}
def get_periods_map(self,
lane,
data_set,
compare_prim_steps,
hist_estimated,
quiet=1):
"""
@param compare_prim_steps while scanning, compare this delay with 1 less by primary(not fine) step,
save None for fraction in unknown (previous -0.5, next +0.5)
"""
#assign most likely period shift for each data sample
halfStep=0.5
if compare_prim_steps:
halfStep*=FINE_STEPS
extra_Y=(0.0,halfStep)
period=hist_estimated['period']
data_periods_map=[]
for dly,data in enumerate(data_set):
if data:
data_lane=data[lane*8:(lane+1)*8]
pm=[None]*8
for b,bData in enumerate(data_lane): # bdata for each bit is either None or has 4 (maybe None) DQ integer delay values
if bData:
pm[b]=[None]*4
for t,tData in enumerate(bData):
if not tData is None: #[dly],[b],[t] tData - int value
he=hist_estimated['b_indiv'][b][t] # tuple (b, periods)
#find most likely period shift
pm[b][t]=int(round((tData[0]+extra_Y[tData[1] is None]-dly-he[0])/period))+he[1]
data_periods_map.append(pm)
else:
data_periods_map.append(None)
if quiet < 1:
print ("\nDQS%d measured data"%lane)
print ("DQS%d"%lane,end=" ")
for f in ('ir','if','or','of'):
for b in range (8):
print ("%s_%d"%(f,b),end=" ")
print()
for dly, data in enumerate(data_set):
print("%d"%dly,end=" ")
if data:
data_lane=data[lane*8:(lane+1)*8]
for typ in range(4):
for b, bData in enumerate(data_lane): # 8 DQs, each ...
if bData and (not bData[typ] is None):
d=bData[typ][0]+extra_Y[bData[typ][1] is None]
print ("%d"%(d),end=" ")
else:
print ("x",end=" ")
print()
if quiet < 1:
print ("\nDQS%d periods data"%lane)
print ("DQS%d"%lane,end=" ")
for f in ('ir','if','or','of'):
for b in range (8):
print ("%s_%d"%(f,b),end=" ")
print()
for dly, data in enumerate(data_set):
print("%d"%dly,end=" ")
if data:
data_lane=data[lane*8:(lane+1)*8]
for typ in range(4):
for b, bData in enumerate(data_lane): # 8 DQs, each ...
if bData and (not bData[typ] is None):
print ("%d"%(data_periods_map[dly][b][typ]),end=" ")
else:
print ("x",end=" ")
print()
if quiet < 2:
print ("\nDQS%d combined data"%lane)
print ("DQS%d"%lane,end=" ")
for f in ('ir','if','or','of'):
for b in range (8):
print ("%s_%d"%(f,b),end=" ")
print()
for dly, data in enumerate(data_set):
print("%d"%dly,end=" ")
if data:
data_lane=data[lane*8:(lane+1)*8]
for typ in range(4):
for b, bData in enumerate(data_lane): # 8 DQs, each ...
if bData and (not bData[typ] is None):
d=bData[typ][0]+extra_Y[bData[typ][1] is None]
print ("%f"%(d-period*data_periods_map[dly][b][typ]),end=" ")
else:
print ("x",end=" ")
print()
return data_periods_map
def lma_fit_dq_dqs(self,
lane, # byte lane
bin_size,
clk_period,
dly_step_ds,
primary_set,
data_set,
compare_prim_steps,
scale_w,
quiet=1):
"""
Initialize parameters and y-vector
using datasheet delay/step (without fine step delay)
and the data set (for each DQS delay - list of 16 bits,
each of 2x2 elements (DQ delay values) or null
Create data set template - for each DQS delay and inPhase
- branch - number of full periods to add
After initial parametersn are created - run LMA to find optimal ones,
then return up to 3 varints (early, nominal, late) providing the best
DQ input delay for each DQS one
@param lane byte lane to process (or non-number - process all byte lanes of the device)
@param bin_size bin size for the histograms (should be 5/10/20/40)
@param clk_period SDCLK period in ps
@param dly_step_ds IDELAY step (from the datasheet)
@param primary_set which of the data edge series to use as leading (other will be trailing by 180)
@param data_set measured data set
@param compare_prim_steps while scanning, compare this delay with 1 less by primary(not fine) step,
save None for fraction in unknown (previous -0.5, next +0.5)
@param scale_w weight for "uncertain" values (where samples chane from all 0 to all 1 in one step)
@param quiet reduce output
@return 3-element dictionary of ('early','nominal','late'), each being None or a 160-element list,
each element being either None, or a list of 3 best DQ delay values for the DQS delay (some mey be None too)
"""
if not isinstance(lane,(int, long)): # ignore content, process both lanes
lane_rslt=[]
numLanes=2
parametersKey='parameters'
errorKey='maxErrDqs'
tDQKey='tDQ'
earlyKey,nominalKey,lateKey=('early','nominal','late')
# periods={earlyKey:-1,nominalKey:0,lateKey:1} #t0~= +1216 t1~=-1245
for lane in range(numLanes):
lane_rslt.append(self.lma_fit_dq_dqs(lane, # byte lane
bin_size,
clk_period,
dly_step_ds,
primary_set,
data_set,
compare_prim_steps,
scale_w,
quiet))
#fix parameters if they have average tDQ different by +/- period(s)
tDQ_avg=[]
for lane in range(numLanes):
tDQ_avg.append(sum(lane_rslt[lane][parametersKey][tDQKey])/len(lane_rslt[lane][parametersKey][tDQKey]))
per1_0=int(round((tDQ_avg[1]-tDQ_avg[0])/clk_period))
if abs(tDQ_avg[1]-tDQ_avg[0]) > clk_period/2:
if quiet <5:
print ("lma_fit_dq_dqs: Data lanes tDQ average differs by %.1fps (%d clocks), shifting to match"%(abs(tDQ_avg[1]-tDQ_avg[0]),per1_0))
if abs(per1_0) > 1:
raise Exception ("BUG: lma_fit_dq_dqs: dta lanes differ by more than a period (per1_0=%d periods) - should not happen"%(per1_0))
#see number of valid items in early,nominal,late branches of each lane
numInVar=[{},{}]
for lane in range(numLanes):
for k in lane_rslt[lane].keys():
if (k != parametersKey) and (k != errorKey):
numValid=0
try:
for ph in lane_rslt[lane][k]:
if not ph is None:
numValid+=1
except:
pass
numInVar[lane][k]=numValid
if quiet < 2:
print ("numInVar=",numInVar)
late_lane=(0,1)[per1_0<0] # for late_lane E,N,L -> x, E, N, for not late_lane: E,N,L -> N, L, x
move_late_lane= (0,1)[numInVar[late_lane][earlyKey] <= numInVar[1-late_lane][lateKey]] # currently both are 0 - nothing is lost
tDQ_delta=clk_period*(-1,1)[move_late_lane]
lane_to_change=(1,0)[late_lane ^ move_late_lane]
if quiet < 2:
print ("late_lane= ",late_lane)
print ("move_late_lane=",move_late_lane)
print ("lane_to_change=",lane_to_change)
print ("tDQ_delta= ",tDQ_delta)
# modify tDQ:
for b in range(len(lane_rslt[lane_to_change][parametersKey][tDQKey])):
lane_rslt[lane_to_change][parametersKey][tDQKey][b]+=tDQ_delta
if quiet < 2:
print ("lane_rslt[%d]['%s']['%s']=%s"%(lane_to_change,parametersKey,tDQKey, str(lane_rslt[lane_to_change][parametersKey][tDQKey])))
# modify variants:
if move_late_lane:
lane_rslt[lane_to_change][earlyKey], lane_rslt[lane_to_change][nominalKey], lane_rslt[lane_to_change][lateKey]= (
lane_rslt[lane_to_change][nominalKey],lane_rslt[lane_to_change][lateKey], None)
else:
lane_rslt[lane_to_change][lateKey], lane_rslt[lane_to_change][nominalKey], lane_rslt[lane_to_change][earlyKey]= (
lane_rslt[lane_to_change][nominalKey],lane_rslt[lane_to_change][earlyKey], None)
# If there are only 2 ENL variants, make 'Nominal' - the largest
rslt={}
for k in lane_rslt[0].keys():
if (k != parametersKey) and (k != errorKey):
for r in lane_rslt: # lane_rslt is list of two dictionaries
try:
l=len(r[k])
break
except:
pass
else:
rslt[k]=None
continue
rslt[k]=[]
for dly in range(l):
w=[]
for lane in range(numLanes):
if (lane_rslt[lane][k] is None) or (lane_rslt[lane][k][dly] is None):
w += [None]*8
else:
w+=lane_rslt[lane][k][dly]
for b in w:
if not b is None:
rslt[k].append(w)
break
else:
rslt[k].append(None)
rslt[parametersKey] = []
for lane in range(numLanes):
rslt[parametersKey].append(lane_rslt[lane][parametersKey])
#per1_0
#print parameters?
if quiet <4:
print ("'%s' = ["%(parametersKey))
for lane_params in rslt[parametersKey]:
print("{")
for k,v in lane_params.items():
print('%s:%s'%(k,str(v)))
print("},")
print ("]")
rslt[errorKey]={}
for k in lane_rslt[0][errorKey].keys():
for r in lane_rslt: # lane_rslt is list of two dictionaries
try:
l=len(r[errorKey][k])
break
except:
pass
else:
rslt[errorKey][k]=None
continue
rslt[errorKey][k]=[]
for dly in range(l):
w=[]
for lane in range(numLanes):
if (lane_rslt[lane][errorKey][k] is None) or (lane_rslt[lane][errorKey][k][dly] is None):
w.append(None)
else:
w.append(lane_rslt[lane][errorKey][k][dly])
for lane in w:
if not lane is None:
rslt[errorKey][k].append(w)
break
else:
rslt[errorKey][k].append(None)
return rslt # byte lanes combined
if quiet < 3:
print ("init_parameters(): scale_w=%f"%(scale_w))
self.clk_period=clk_period
hist_estimated=self.dqi_dqsi_estimate_from_histograms(lane, # byte lane
bin_size,
clk_period,
dly_step_ds,
primary_set,
data_set,
compare_prim_steps,
quiet)
if quiet < 3:
print ("hist_estimated=%s"%(str(hist_estimated)))
data_periods_map=self.get_periods_map(lane,
data_set,
compare_prim_steps,
hist_estimated,
quiet) #+1)
ywp= self.createYandWvectors(lane,
data_set,
compare_prim_steps,
scale_w,
data_periods_map,
quiet)
# print("ywp=%s"%(str(ywp)))
if quiet < 2:
print("\nY-vector:")
self.showYOrVector(ywp,False,None)
print("\nY-vector(filtered):")
self.showYOrVector(ywp,True,None)
if quiet < 2:
print("\nperiods_map:")
self.showYOrVector(ywp,False,ywp['p'])
if quiet < 2:
print("\nweights_map:")
self.showYOrVector(ywp,False,ywp['w'])
step_ps=clk_period/hist_estimated['period'] #~15.6
tDQSHL=0;
tDQHL=[None]*8
for b, d in enumerate(hist_estimated['b_indiv']):
tDQSHL += (d[1][0]-d[0][0] +d[3][0]-d[2][0])*step_ps
tDQHL[b] = (d[0][0]-d[1][0] +d[3][0]-d[2][0])*step_ps
if quiet < 3:
print ("%d: S=%f, D=%f"%(b, d[1][0]-d[0][0] +d[3][0]-d[2][0], d[0][0]-d[1][0] +d[3][0]-d[2][0]))
tDQSHL /= 8.0
# calculate primary tDQ delays (primary - for the edges selected by 'primary_set'
tDQ=[0.0]*8
for b, d in enumerate(hist_estimated['b_indiv']):
for dp in d:
tDQ[b] += dp[0]-dp[1] * hist_estimated['period']
tDQ[b] = step_ps*0.25*(tDQ[b] - hist_estimated['period'])
parameters={
"tSDQS": step_ps,
"tSDQ": (step_ps,)*8,
"tDQSHL": tDQSHL, # 0.0, # improve Seems that initial value does not match final by sign!
"tDQHL": tDQHL, # (0.0)*8, # improve
"tDQS": 0.0,
"tDQ": tDQ,
"tFDQS": (0.0,)*4,
"tFDQ": (0.0,)*32,
"tCDQS": (0.0,)*30
# "anaScale":self.analog_scale
}
"""
Returns # best (early,nominal,late) for each bit for each delay ([3][160][8])
Outer list each has 160-element list, some of which are None, others hove 8 elements (including None ones)
"""
if quiet < 2:
print ("parameters=%s"%(str(parameters)))
self.normalizeParameters(parameters) #isMask=False)
if quiet < 3:
print ("normalized parameters=%s"%(str(parameters)))
"""
both ways work:
self.parameterMask={}
self.normalizeParameters(self.parameterMask,isMask=True)
and
"""
# self.parameterMask=self.normalizeParameters({},isMask=True)
self.parameterMask=self.normalizeParameters(self.parameterMask,isMask=True)
if quiet < 4:
print ("parameters mask=%s"%(str(self.parameterMask)))
create_jacobian=True
fxj= self.createFxAndJacobian(parameters,
ywp, # keep in self.variable?
primary_set,
create_jacobian,
None, #parMask
quiet)
if create_jacobian:
fx=fxj['fx']
else:
fx=fxj
if quiet < 2:
print("\nfx:")
self.showYOrVector(ywp,False,fx)
print("\nfx (filtered):")
self.showYOrVector(ywp,True,fx)
if quiet < 5:
arms = self.getParAvgRMS(parameters,
ywp,
primary_set, # prima
quiet+1)
print ("Before LMA (DQ lane %d): average(fx)= %fps, rms(fx)=%fps"%(lane,arms['avg'],arms['rms']))
if quiet < 3:
jByJT=np.dot(fxj['jacob'],np.transpose(fxj['jacob']))
print("\njByJT:")
for i,l in enumerate(jByJT):
print ("%d"%(i),end=" ")
for d in l:
print ("%f"%(d),end=" ")
print()
self.lambdas ['current']=self.lambdas ['initial']
for n_iter in range(self.maxNumSteps):
OK,finished=self.LMA_step(parameters,
ywp, # keep in self.variable?
primary_set, # prima
None, # parMask= None,
self.lambdas,
self.finalDiffRMS,
quiet)
if (quiet < 5) or ((quiet < 6) and finished):
arms = self.getParAvgRMS(parameters,
ywp,
primary_set, # prima
quiet+1)
print ("%d: LMA_step %s average(fx)= %fps, rms(fx)=%fps"%(n_iter,("FAILURE","SUCCESS")[OK],arms['avg'],arms['rms']))
if OK and quiet < 2:
print ("updated parameters=%s"%(str(parameters)))
if finished:
if quiet < 4:
print ("final parameters=%s"%(str(parameters)))
break
fx= self.createFxAndJacobian(parameters,
ywp, # keep in self.variable?
primary_set,
False,
None,
quiet)
if quiet < 3:
print("\nfx-postLMA:")
self.showYOrVector(ywp,False,fx)
print("\nfx-postLMA (filtered):")
self.showYOrVector(ywp,True,fx)
# calculate DQ[i] vs. DQS for -1, 0 and +1 period
DQvDQS_withErr=self.getBestDQforDQS(parameters,
primary_set,
quiet)
DQvDQS= DQvDQS_withErr['dqForDqs']
DQvDQS_ERR=DQvDQS_withErr['maxErrDqs']
rslt={}
rslt_names=("early","nominal","late")
for i, d in enumerate(DQvDQS):
rslt[rslt_names[i]] = d
rslt['parameters']=parameters
rslt['maxErrDqs']={} # {enl}[dly]
for i, d in enumerate(DQvDQS_ERR):
rslt['maxErrDqs'][rslt_names[i]] = d
if quiet < 3:
self.showDQDQSValues(parameters)
if quiet < 3:
print ("*** quiet=",quiet)
self.showENLresults(rslt) # here DQvDQS already contain the needed data
return rslt
def getBestDQforDQS(self,
parameters,
primary_set, # prima
quiet=1):
period=self.clk_period
tFDQS5=list(parameters['tFDQS'])
tFDQS5.append(-tFDQS5[0]-tFDQS5[1]-tFDQS5[2]-tFDQS5[3])
tSDQS=parameters['tSDQS']
tSDQ= parameters['tSDQ'] # list
tDQS =parameters['tDQS']#single value
tDQ= parameters['tDQ'] # list
tCDQS32=list(parameters['tCDQS'][0:8])+[0]+list(parameters['tCDQS'][8:23])+[0]+list(parameters['tCDQS'][23:30])
tDQSHL =parameters['tDQSHL']#single value
tDQHL= parameters['tDQHL'] # list
tFDQs=[] #corrections in steps?
for b in range(8):
tFDQi=list(parameters['tFDQ'][4*b:4*(b+1)])
tFDQi.append(-tFDQi[0]-tFDQi[1]-tFDQi[2]-tFDQi[3])
tFDQs.append(tFDQi)
#calculate worst bit error caused by duty cycles (asymmetry) of DQS and DQ lines
#bit delay error just adds to the 0.25*(abs(tDQSHL)+abs(tDQHL))
asym_err=[]
for i in range(8):
asym_err.append(0.25*(abs(tDQSHL)+abs(tDQHL[i])))
if quiet < 3:
print("asym_err=",asym_err)
dqForDqs=[]
maxErrDqs=[]
for enl in (0,1,2):
vDQ=[]
vErr=[]
someData=False
for dly in range(DLY_STEPS):
tdqs=dly * tSDQS - tDQS - tFDQS5[dly % FINE_STEPS] # t - time from DQS pad to internal DQS clock with zero setup/hold times to DQ FFs
tdqs-=tCDQS32[dly // FINE_STEPS]
tdqs3=tdqs +(-0.75+enl)*period # (early, nominal, late) in ps, centered in the middle
bDQ=[]
errDQ=None
# dbg_worstBit=None
# dbg_errs=[None]*8
for b in range(8): # use all 4 variants
tdq=(tdqs3+tDQ[b])/tSDQ[b]
itdq=int((tdqs3+tDQ[b])/tSDQ[b]) # in delay steps, approximate (not including tFDQ
bestDQ=None
if (itdq >= 0) and (itdq < DLY_STEPS):
bestDiff=None
for idq in range (max(itdq-FINE_STEPS,0),min(itdq+FINE_STEPS,DLY_STEPS-1)+1):
tdq=idq * tSDQ[b] - tDQ[b] - tFDQs[b][idq % FINE_STEPS]
diff=tdq - tdqs3 # idq-tFDQs[b][idq % FINE_STEPS]
if (bestDQ is None) or (abs(diff) < abs(bestDiff)):
bestDQ=idq
bestDiff=diff
if bestDQ is None:
bDQ=None
break
bDQ.append(bestDQ) #tuple(delay,signed error in ps)
fullBitErr=abs(bestDiff) +asym_err[b] #TODO: Restore the full error!
if (errDQ is None) or (fullBitErr > errDQ):
errDQ= fullBitErr
someData=True
vDQ.append(bDQ)
vErr.append(errDQ)
if someData:
dqForDqs.append(vDQ)
maxErrDqs.append(vErr)
else:
dqForDqs.append(None)
maxErrDqs.append(None)
return {'dqForDqs':dqForDqs,'maxErrDqs':maxErrDqs}
"""
ir = ir0 - s/4 + d/4 # ir - convert to ps from steps
if = if0 + s/4 - d/4
or = or0 - s/4 - d/4 # ir - convert to ps from steps
of = of0 + s/4 + d/4
(s-d)/2=if-ir
(s+d)/2=of-or
s=if-ir+of-or
d=ir-if+of-or
"""
def showDQDQSValues(self,
parameters):
tFDQS5=list(parameters['tFDQS'])
tFDQS5.append(-tFDQS5[0]-tFDQS5[1]-tFDQS5[2]-tFDQS5[3])
tSDQS=parameters['tSDQS']
tSDQ= parameters['tSDQ'] # list
tCDQS32=list(parameters['tCDQS'][0:8])+[0]+list(parameters['tCDQS'][8:23])+[0]+list(parameters['tCDQS'][23:30])
tFDQs=[] #corrections in steps?
for b in range(8):
tFDQi=list(parameters['tFDQ'][4*b:4*(b+1)])
tFDQi.append(-tFDQi[0]-tFDQi[1]-tFDQi[2]-tFDQi[3])
tFDQs.append(tFDQi)
print("\nRelative delay vs delay value")
print ("dly DSQS", end=" ")
for b in range(8):
print ("DQ%d"%(b),end=" ")
print ()
for dly in range(DLY_STEPS): # no constant delay - just scale and corrections
print ("%d"%(dly),end=" ")
tdqs=dly * tSDQS - tFDQS5[dly % FINE_STEPS] # t - time from DQS pad to internal DQS clock with zero setup/hold times to DQ FFs
tdqs-=tCDQS32[dly // FINE_STEPS]
print("%.3f"%(tdqs),end=" ")
for b in range(8):
tdq=dly * tSDQ[b] - tFDQs[b][dly % FINE_STEPS]
print("%.3f"%(tdq),end=" ")
print()
def createFxAndJacobian(self,
parameters,
y_data, # keep in self.variable?
primary_set, # prima
jacobian=False, # create jacobian, False - only fx
parMask=None,
quiet=1):
def pythIsNone(obj):
return obj is None
isNone=pythIsNone # swithch to np.isnan
y_vector = y_data['y']
yf_vector = y_data['yf'] # when no fractions available - half interval (0.5 or 2.5) is added, if available - nothing is added
periods_vector=y_data['p']
period=self.clk_period
try:
y_fractions = y_data['f']
except:
y_fractions = None
try:
w_vector = y_data['w']
except:
w_vector = None
anaScale = parameters['anaScale']
if y_fractions is None:
anaScale = 0
elif isinstance(y_fractions,np.ndarray):
isNone=np.isnan
# fx=[0.0]*DLY_STEPS*32
fx=np.zeros((DLY_STEPS*32,))
#self.clk_period
tFDQS5=list(parameters['tFDQS'])
tFDQS5.append(-tFDQS5[0]-tFDQS5[1]-tFDQS5[2]-tFDQS5[3])
tCDQS32=list(parameters['tCDQS'][0:8])+[0]+list(parameters['tCDQS'][8:23])+[0]+list(parameters['tCDQS'][23:30])
# print("*****tCDQS32=",tCDQS32)
tFDQ=[]
for b in range(8):
tFDQi=list(parameters['tFDQ'][4*b:4*(b+1)])
tFDQi.append(-tFDQi[0]-tFDQi[1]-tFDQi[2]-tFDQi[3])
tFDQ.append(tFDQi)
tSDQS=parameters['tSDQS']
tSDQ= parameters['tSDQ'] # list
tDQS =parameters['tDQS']#single value
tDQ= parameters['tDQ'] # list
tDQSHL =parameters['tDQSHL']#single value
tDQHL= parameters['tDQHL'] # list
for dly in range(DLY_STEPS):
tdqs=dly * tSDQS - tDQS - tFDQS5[dly % FINE_STEPS] # t - time from DQS pad to internal DQS clock with zero setup/hold times to DQ FFs
tdqs-=tCDQS32[dly // FINE_STEPS]
tdqs_r = tdqs - 0.25 * tDQSHL # sign opposite from: ir = ir0 - s/4 + d/4; or = or0 - s/4 - d/4 - NOT, but maybe other is wrong
tdqs_f = tdqs + 0.25 * tDQSHL # sign opposite from: if = if0 + s/4 - d/4; of = of0 + s/4 + d/4
tdqs_rf=(tdqs_r, tdqs_f)
#correct for DQS edge type
for b in range(8): # use all 4 variants
for t in range(4):
indx=32*dly+t*8+b
if (w_vector is None) or (w_vector[indx] > 0):
tdq=yf_vector[indx] * tSDQ[b] - tDQ[b] - tFDQ[b][y_vector[indx] % FINE_STEPS]
# correct for periods
tdq -= period*periods_vector[indx] # or should it be minus here?
# correct for edge types
if (t == 0) or (t == 3):
tdq -= 0.25*tDQHL[b]
else:
tdq += 0.25*tDQHL[b]
if anaScale:
if not isNone(y_fractions[indx]):
tdq-=anaScale*y_fractions[indx] # negative values mean that actual zero-point is not yet reached
if (t ^ primary_set) & 2:
tdq -= 0.5*period
fx[indx] = tdq - tdqs_rf[t & 1] # odd are falling DQS, even are rising DQS
if not jacobian:
return fx
if parMask is None:
parMask=self.normalizeParameters(self.parameterMask,isMask=True)
# pv= self.createParameterVector(parameters,parMask)
# numPars=len(pv)
# print("pv=%s"%(str(pv)))
parInd=self.createParameterIndex(parameters,parMask)
if quiet <2:
print("parInd=%s"%(str(parInd)))
numPars=parInd['numPars']
jacob=np.zeros((numPars,DLY_STEPS*32))
"""
fineM5=((1.0, 0.0, 0.0, 0.0, -0.25),
(0.0, 1.0, 0.0, 0.0, -0.25),
(0.0, 0.0, 1.0, 0.0, -0.25),
(0.0, 0.0, 0.0, 1.0, -0.25))
"""
fineM5=((1.0, 0.0, 0.0, 0.0, -1.0),
(0.0, 1.0, 0.0, 0.0, -1.0),
(0.0, 0.0, 1.0, 0.0, -1.0),
(0.0, 0.0, 0.0, 1.0, -1.0))
dqs_finedelay_en=parInd['tFDQS']
for e in dqs_finedelay_en:
if e>=0:
break
else:
dqs_finedelay_en=None
dqs_delay32_en=parInd['tCDQS']
for e in dqs_delay32_en:
if e>=0:
break
else:
dqs_delay32_en=None
# tCDQS32=list(parameters['tCDQS'][0:8])+[0]+list(parameters['tCDQS'][8:23])+[0]+list(parameters['tCDQS'][23:30])
if not dqs_delay32_en is None:
dqs_delay32_index=range(0,8)+[-1]+range(8,23)+[-1]+range(23,30)
for i,d in enumerate(dqs_delay32_index):
if d >= 0:
dqs_delay32_index[i] = dqs_delay32_en[d]
# print("*****dqs_delay32_index=",dqs_delay32_index)
dq_finedelay_en=[None]*8
for b in range(8):
dq_finedelay_en[b]=parInd['tFDQ'][4*b:4*(b+1)]
for e in dq_finedelay_en[b]:
if e>=0:
break
else:
dq_finedelay_en[b]=None
for dly in range(DLY_STEPS):
dlyMod5=dly % FINE_STEPS
dlyDiv5=dly // FINE_STEPS
dtdqs_dtSDQS = dly
dtdqs_dtDQS = -1.0
dtdqs_dtFDQS = (-fineM5[0][dlyMod5],-fineM5[1][dlyMod5],-fineM5[2][dlyMod5],-fineM5[3][dlyMod5])
dtdqs_dtDQSHL_rf=(-0.25,+0.25) # ign opposite from: ir = ir0 - s/4 + d/4; or = or0 - s/4 - d/4, ... - NOT, but maybe other is wrong
#correct for DQS edge type
# dbg=[0.0]*32
for b in range(8): # use all 4 variants
for t in range(4):
indx=32*dly+t*8+b
if (w_vector is None) or (w_vector[indx] > 0):
#dependencies of DQS delays
if parInd['tSDQS'] >= 0:
jacob[parInd['tSDQS'],indx]=-dtdqs_dtSDQS
if parInd['tDQS'] >= 0:
jacob[parInd['tDQS'],indx]=-dtdqs_dtDQS
if dqs_finedelay_en:
for i,pIndx in enumerate (dqs_finedelay_en):
if pIndx >= 0:
jacob[pIndx,indx]=-dtdqs_dtFDQS[i]
if dqs_delay32_en:
for i,pIndx in enumerate (dqs_delay32_index):
if pIndx >= 0:
jacob[pIndx,indx]=(0,1.0)[i==dlyDiv5]
# dbg[i]+=jacob[pIndx,indx]
if parInd['tDQSHL'] >= 0:
jacob[parInd['tDQSHL'],indx]=-dtdqs_dtDQSHL_rf[t & 1]
#dependencies of DQ delays
# tdq=y_vector[indx] * tSDQ[b] - tDQ[b] - tFDQ[b][y_vector[indx] % FINE_STEPS]
if parInd['tSDQ'][b] >= 0:
jacob[parInd['tSDQ'][b],indx]=y_vector[indx]
if parInd['tDQ'][b] >= 0:
jacob[parInd['tDQ'][b],indx] = -1
if dq_finedelay_en[b]:
yMod5=y_vector[indx] % FINE_STEPS
dtdq_dtFDQ = (-fineM5[0][yMod5],-fineM5[1][yMod5],-fineM5[2][yMod5],-fineM5[3][yMod5])
for i,pIndx in enumerate (dq_finedelay_en[b]):
if pIndx >= 0:
jacob[pIndx,indx]=dtdq_dtFDQ[i]
if parInd['tDQHL'][b] >= 0:
if (t == 0) or (t == 3):
jacob[parInd['tDQHL'][b],indx]=-0.25
else:
jacob[parInd['tDQHL'][b],indx]=+0.25
if parInd['anaScale'] >= 0:
if anaScale and not isNone(y_fractions[indx]):
jacob[parInd['anaScale'],indx]=-y_fractions[indx]
# print("dbg: %d: "%(dly),dbg)
return {'fx':fx,'jacob':jacob}
def getParAvgRMS(self,
parameters,
ywp,
primary_set, # prima
quiet=1):
fx= self.createFxAndJacobian(parameters,
ywp, # keep in self.variable?
primary_set,
False, # jacobian
None,
quiet)
"""
SX=0.0
SX2=0.0
S0=0.0
for d,w in zip(fx,ywp['w']):
if w>0:
S0+=w
SX+=w*d
SX2+=w*d*d
"""
S0=np.sum(ywp['w'])
SX=np.sum(fx*ywp['w'])
SX2=np.sum(fx*fx*ywp['w'])
avg= SX/S0
rms= math.sqrt(SX2/S0)
return {"avg":avg,"rms":rms}
def LMA_step(self,
parameters,
ywp, # keep in self.variable?
primary_set, # prima
parMask,
lambdas, #single-element list to update value
finalDiffRMS,
quiet= 1):
parVector0=self.createParameterVector(parameters, parMask) # initial parameter vector
arms0 = self.getParAvgRMS(parameters,
ywp,
primary_set, # prima
quiet+1)
if quiet < 2:
print ("LMA_step : average(fx)= %fps, rms(fx)=%fps"%(arms0['avg'],arms0['rms']))
delta=self.LMA_solve(parameters,
ywp, # keep in self.variable?
primary_set, # prima
parMask,
lambdas["current"],
quiet)
parVector= parVector0+delta
# print ("\nparVector0=%s"%(str(parVector0)))
# print ("\ndelta=%s"%(str(delta)))
# print ("\nparVector=%s"%(str(parVector)))
# newPars = {}.update(parameters) # so fixed parameters will appear in the newPars
newPars = self.copyParameters(parameters) # so fixed parameters will appear in the newPars
# newPars = self.getParametersFromVector(parVector,
if quiet < 2:
print ("\nparameters=%s"%(str(parameters)))
# print ("\n1: newPars=%s"%(str(newPars)))
self.getParametersFromVector(parVector,
parMask,
newPars) # parameters=None):# if not None, will be updated
if quiet < 2:
print ("\n2: newPars=%s"%(str(newPars)))
# print ("\nparameters=%s"%(str(parameters)))
arms1 = self.getParAvgRMS(newPars,
ywp,
primary_set, # prima
quiet+1)
finished=False
if arms1['rms'] <= arms0['rms']:
parameters.update(newPars)
lambdas["current"]*=.5
success=True
if (arms0['rms'] - arms1['rms']) < finalDiffRMS:
finished=True
else:
lambdas["current"]*=8.0
success=False
if lambdas["current"] > lambdas["max"]:
finished=True
if (quiet < 2) or ((quiet < 4) and (not success)):
print ("LMA_step %s: average(fx)= %fps, rms(fx)=%fps, lambda=%f"%(('FAILURE','SUCCESS')[success],arms1['avg'],arms1['rms'],lambdas["current"]))
return (success,finished)
def LMA_solve(self,
parameters,
ywp, # keep in self.variable?
primary_set, # prima
parMask= None,
lmbda= 0.001,
quiet= 1):
fxj= self.createFxAndJacobian(parameters,
ywp, # keep in self.variable?
primary_set,
True, # jacobian
parMask,
quiet)
try:
w_vector = ywp['w']
except:
w_vector = np.full((len(fxj['fx']),),1.0)
wJ=fxj['jacob'] *w_vector
JT=np.transpose(fxj['jacob'])
#np.fill_diagonal(z3,np.diag(z3)*0.1)
jByJT=np.dot(wJ,JT)
for i,_ in enumerate(jByJT):
jByJT[i,i] += lmbda*jByJT[i,i]
jByDiff= -np.dot(wJ,fxj['fx'])
delta=np.linalg.solve(jByJT,jByDiff)
return delta
"""
ir = ir0 - s/4 + d/4 # ir - convert to ps from steps
if = if0 + s/4 - d/4
or = or0 - s/4 - d/4 # ir - convert to ps from steps
of = of0 + s/4 + d/4
(s-d)/2=if-ir
(s+d)/2=of-or
s=if-ir+of-or
d=ir-if+of-or
"""
def lma_fit_dqs_phase(self,
lane, # byte lane
bin_size_ps,
clk_period,
dqs_dq_parameters,
tSDQS, # use if dqs_dq_parameters are not available
data_set,
compare_prim_steps,
scale_w,
numPhaseSteps,
maxDlyErr=200.0, # ps - trying multiple overlapping branches
fallingPhase=False, # output mode - delays decrease when phase increases
shiftFracPeriod=0.5, # measured data is marginal, shift optimal by half period
quiet=1):
"""
Calculate linear approximation for DQS-in or DQS-out vs. phase, crossing periods
@param lane byte lane to use (0,1 or 'all')
@param bin_size_ps histogram bin size in ps
@param clk_period clock period, in ps
@param dqs_dq_parameters dq{i,0} vs dqs[i,o} parameters or Null if not yet available (after write levelling)
used to get initial delay scale and fine delay correction
@param tSDQS delay in ps for one finedelay step - used only if dqs_dq_parameters are not available
@param data_set data set number for hard-coded debug data or -1 to use actual just measured results
@param compare_prim_steps if True input data was calibrated with primary delay steps, False - if with fine delay
That means that delay data is on average is either 2.5 or 0.5 lower than unbiased average
@param scale_w weight for samples that have "binary" data with full uncertainty of +/-2.5 or +/-0.5 steps
@param numPhaseSteps Total number of delay steps (currently 5*32 = 160)
@param maxDlyErr Made for testing multiple overlapping branches, maximal error in ps to keep the result branch
@param fallingPhase input data is decreasing with phase increasing (command/addresses, data output), False - increasing
as for DQS in /DQ in
@param shiftFracPeriod When measured data is marginal, not optimal, result needs to be shifted by this fraction of the period
Currently it should be 0.5 for input, 0.0 - for output
@param quiet=1):
"""
# print("++++++lma_fit_dqs_phase(), quiet=",quiet)
phase_sign=(1,-1)[fallingPhase]
phase_add=(0,numPhaseSteps)[fallingPhase]
def show_input_data(filtered):
print(('unfiltered','filtered')[filtered])
for phase,d in enumerate(data_set):
print ("%d"%(phase),end=" ")
if not d is None:
for lane,dl in enumerate(d):
if (not dl is None) and (not dl[0] is None) and ((not filtered) or (not dl[1] is None)):
dly = dl[0]
if dl[1] is None:
dly+=halfStep
# print ("%f %f"%(dly, dly-phase*phase_step/dbg_tSDQS[lane]), end=" ")
# print ("%f %f"%(dly*dbg_tSDQS[lane]/phase_step, dly*dbg_tSDQS[lane]/phase_step-phase), end=" ")
print ("%f %f"%(dly*dbg_tSDQS[lane], dly*dbg_tSDQS[lane]+(phase_add-phase)*phase_step), end=" ")
else:
print ("? ?", end=" ")
print()
def get_shift_by_hist(span=5):
for phase,d in enumerate(data_set):
if not d is None:
dl=d[lane]
if (not dl is None) and (not dl[0] is None):
dly = dl[0]
if dl[1] is None:
dly+=halfStep
diff_ps=dly*tSDQS+(phase_add-phase)*phase_step
binArr[int((diff_ps-minVal)/bin_size_ps)]+=1
if quiet < 3:
for i,h in enumerate(binArr):
print ("%d %d"%(i,h))
indx=0
for i,h in enumerate(binArr):
if h > binArr[indx]:
indx=i
low = max(0,indx-span)
high = min(len(binArr)-1,indx+span)
S0=0
SX=0.0
for i in range (low, high+1):
S0+=binArr[i]
SX+=binArr[i]*i
if S0>0:
SX/=S0
if quiet < 3:
print ("SX=",SX)
return minVal+bin_size_ps*(SX+0.5) # ps
if not isinstance(lane,(int, long)): # ignore content, process both lanes
rslt_names=("dqs_optimal_ps","dqs_phase","dqs_phase_multi","dqs_phase_err","dqs_min_max_periods")
rslt= {}
for name in rslt_names:
rslt[name] = []
for lane in range(2):
rslt_lane=self.lma_fit_dqs_phase(lane= lane, # byte lane
bin_size_ps= bin_size_ps,
clk_period= clk_period,
dqs_dq_parameters= dqs_dq_parameters,
tSDQS= tSDQS,
data_set= data_set,
compare_prim_steps= compare_prim_steps,
scale_w= scale_w,
numPhaseSteps= numPhaseSteps,
maxDlyErr= maxDlyErr,
fallingPhase= fallingPhase,
shiftFracPeriod= shiftFracPeriod,
quiet= quiet)
for name in rslt_names:
rslt[name].append(rslt_lane[name])
if quiet<3:
print ('dqs_optimal_ps=%s'%(str(rslt['dqs_optimal_ps'])))
print ('dqs_phase=[')
for lane in rslt['dqs_phase']:
print(" [",end=" ")
for i,d in enumerate(lane):
last= i == (len(lane)-1)
print("%s"%(str(d)), end=" ")
if not last:
print(",",end=" ")
if ((i+1) % 16) ==0:
print("\n ",end="")
else:
print('],')
print(']')
print ('dqs_phase_multi=[')
for lane in rslt['dqs_phase_multi']:
print(" [",end=" ")
for i,d in enumerate(lane):
last= i == (len(lane)-1)
print("%s"%(str(d)), end=" ")
if not last:
print(",",end=" ")
if ((i+1) % 16) ==0:
print("\n ",end="")
else:
print('],')
print(']')
print ('dqs_phase_err=[')
for lane in rslt['dqs_phase_err']:
print(" [",end=" ")
for i,d in enumerate(lane):
last= i == (len(lane)-1)
print("%s"%(str(d)), end=" ")
if not last:
print(",",end=" ")
if ((i+1) % 16) ==0:
print("\n ",end="")
else:
print('],')
print(']')
# print(rslt)
return rslt
phase_step= phase_sign*clk_period/ numPhaseSteps
try:
tSDQS=dqs_dq_parameters[lane]['tSDQS'] # ~16.081739769147354
except:
if quiet < 2:
print("dqs_dq_parameters are not available, using datasheet value for tSDQS=%f ps"%(tSDQS))
try:
dbg_tSDQS=(dqs_dq_parameters[0]['tSDQS'],dqs_dq_parameters[1]['tSDQS'])
except:
dbg_tSDQS=(tSDQS,tSDQS)
halfStep=0.5*(1,compare_prim_steps)[compare_prim_steps]
#phase_step/tSDQS
# print("lma_fit_dqs_phase(): quiet=",quiet)
if quiet < 2:
print (phase_step,dbg_tSDQS)
for filtered in range(2):
show_input_data(filtered)
# all preliminary tests above
#phase_add
if fallingPhase:
maxVal= clk_period
minVal= -DLY_STEPS*abs(tSDQS)
else:
maxVal= DLY_STEPS*abs(tSDQS)
minVal= -clk_period
num_bins=int((maxVal-minVal)/bin_size_ps)+1
binArr=[0]*num_bins
if quiet < 2:
print("minVal=",minVal)
print("maxVal=",maxVal)
print("num_bins=",num_bins)
#find main max
dly_max0=get_shift_by_hist() # delay in ps, corresponding to largest maximum
if quiet < 2:
print("max0=%f, (%f)"%(dly_max0,dly_max0/tSDQS))
periods=[None]*len(data_set)
for phase,d in enumerate(data_set):
if not d is None:
dl=d[lane]
if (not dl is None) and (not dl[0] is None):
dly = dl[0]
if dl[1] is None:
dly+=halfStep
periods[phase]=int(round((dly*tSDQS+(phase_add-phase)*phase_step)/clk_period)) #############################
w=[0.0]*len(data_set)
if quiet < 2:
for i,p in enumerate(periods):
print ("%d %s"%(i,str(p)))
try:
tFDQS=dqs_dq_parameters[lane]['tFDQS'] # [-21.824409224001187, -10.830180678770162, 1.5698858542328959, 11.267851084349177]
tFDQS.append(-tFDQS[0]-tFDQS[1]-tFDQS[2]-tFDQS[3])
except:
tFDQS=[0.0]*FINE_STEPS
SY=0.0
S0=0.0
for phase,d in enumerate(data_set):
if not d is None:
dl=d[lane]
if (not dl is None) and (not dl[0] is None):
dly = dl[0]
w[phase]=1.0
if dl[1] is None:
dly+=halfStep
w[phase]=scale_w
d=dly*tSDQS-periods[phase]*clk_period-tFDQS[dl[0] % FINE_STEPS] ############################
y=d+(phase_add-phase)*phase_step
S0+=w[phase]
SY+=w[phase]*y
if S0 > 0.0:
SY /= S0
if quiet < 2:
print ("shift=",SY," ps")
for phase,d in enumerate(data_set):
print ("%d"%(phase),end=" ")
if not d is None:
dl=d[lane]
if (not dl is None) and (not dl[0] is None):
dly = dl[0]
if dl[1] is None:
dly+=halfStep
d=dly*tSDQS-periods[phase]*clk_period-tFDQS[dl[0] % FINE_STEPS] ############################
print ("%f %f"%(d, w[i]), end=" ")
if not dl[1] is None:
print(d,end=" ")
else:
print("?",end=" ")
else:
print ("? ?", end=" ")
print("%f"%(SY-(phase_add-phase)*phase_step),end=" ")
print()
# Now shift SY by half clk_period for each phase find the closest DQSI match (None if does not exist)
# Shift should only be for DQSI (middle between the errors), for WLEV - no shift
#shiftFracPeriod
dqsi_range=abs(tSDQS)*DLY_STEPS# full range of the DQSI delay for this lane
# dqsi_middle_in_ps = SY-clk_period*(round(SY/clk_period -0.5)+0.5)
dqsi_middle_in_ps = SY-clk_period*(round(SY/clk_period -shiftFracPeriod)+shiftFracPeriod)
if quiet < 3:
print("SY=",SY)
print("dqsi_middle_in_ps=",dqsi_middle_in_ps)
print("phase_add=",phase_add)
print("phase_step=",phase_step)
print("shiftFracPeriod=",shiftFracPeriod)
print("clk_period*shiftFracPeriod=",(clk_period*shiftFracPeriod))
dqs_phase=[None]*numPhaseSteps
for phase in range(numPhaseSteps):
dly_ps=-phase_step*(phase_add-phase)+dqsi_middle_in_ps
dly_ps-=clk_period*round(dly_ps/clk_period - 0.5) # positive, dqsi_range:
continue # no valid dqs_idelay for this phase
idly = int (round(dly_ps/tSDQS)) ###############?
low= max(0,idly-FINE_STEPS)
high=min(DLY_STEPS-1,idly+FINE_STEPS)
idly=low
for i in range(low,high+1):
if abs(i*tSDQS-tFDQS[i % FINE_STEPS]-dly_ps) < abs(idly*tSDQS-tFDQS[idly % FINE_STEPS]-dly_ps): ############################
idly=i
dqs_phase[phase]=idly
if quiet < 3:
for phase,d in enumerate(data_set):
print ("%d"%(phase),end=" ")
if (not d is None) and (not d[lane] is None) and (not d[lane][0] is None):
dly = d[lane][0]
if d[lane][1] is None:
dly+=halfStep
print ("%f"%(dly), end=" ")
else:
print ("?", end=" ")
if not dqs_phase[phase] is None:
print("%f"%(dqs_phase[phase]),end=" ")
else:
print ("?", end=" ")
print()
# maxDlyErr (ps)
dqsi_phase_multi=[None]*numPhaseSteps
dqsi_phase_err= [None]*numPhaseSteps
min_max_periods=None
for phase in range(numPhaseSteps):
dly_ps=phase_step*(phase-phase_add)+dqsi_middle_in_ps
periods=int(round((dly_ps+ +maxDlyErr)/clk_period - 0.5))
dly_ps-=clk_period*periods # positive, = DLY_STEPS:
high=DLY_STEPS - 1
low= DLY_STEPS - FINE_STEPS
idly=low
for i in range(low,high+1):
if abs(i*tSDQS-tFDQS[i % FINE_STEPS]-dly_ps) < abs(idly*tSDQS-tFDQS[idly % FINE_STEPS]-dly_ps): ###################
idly=i
dqsi_phase_multi[phase][periods]=idly
dqsi_phase_err[phase][periods]=idly*tSDQS-tFDQS[idly % FINE_STEPS]-dly_ps ######################
periods-=1
dly_ps+=clk_period
if quiet < 3:
print ("maxDlyErr=",maxDlyErr)
print ("min_max_periods=",min_max_periods)
print ("dqsi_phase_multi=",dqsi_phase_multi)
print ("dqsi_phase_err=",dqsi_phase_err)
for phase,d in enumerate(data_set):
print ("%d"%(phase),end=" ")
if (not d is None) and (not d[lane] is None) and (not d[lane][0] is None):
dly = d[lane][0]
if d[lane][1] is None:
dly+=halfStep
print ("%f"%(dly), end=" ")
else:
print ("?", end=" ")
if not dqsi_phase_multi[phase] is None:
for periods in range (min_max_periods[0],min_max_periods[1]+1):
try:
print("%f"%(dqsi_phase_multi[phase][periods]),end=" ")
except:
print ("?", end=" ")
for periods in range (min_max_periods[0],min_max_periods[1]+1):
try:
print("%.1f"%(dqsi_phase_err[phase][periods]),end=" ")
except:
print ("?", end=" ")
else:
print ("?", end=" ")
print()
return {"dqs_optimal_ps":dqsi_middle_in_ps,
"dqs_phase":dqs_phase,
"dqs_phase_multi":dqsi_phase_multi,
"dqs_phase_err":dqsi_phase_err,
"dqs_min_max_periods":min_max_periods
}