-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathqdr.py
585 lines (505 loc) · 21.2 KB
/
qdr.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
"""
Created on Fri Mar 7 07:15:45 2014
@author: paulp
"""
import logging
import numpy
import struct
import register
from memory import Memory
LOGGER = logging.getLogger(__name__)
LOGGER.propagate = True
USE_JACK_CAL = True
LEVEL0 = logging.INFO
LEVEL1 = LEVEL0 - 1
LEVEL2 = LEVEL1 - 1
LEVEL3 = LEVEL2 - 1
QDR_WORD_WIDTH = 36
QDR_WW_LIMITED = 32
NUM_DELAY_TAPS = 32
MINIMUM_WINDOW_LENGTH = 4
CAL_DATA = [
[0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555,
0xAAAAAAAA, 0x55555555, 0xAAAAAAAA, 0x55555555],
[0, 0, 0xFFFFFFFF, 0, 0, 0, 0, 0],
numpy.arange(256) << 0,
numpy.arange(256) << 8,
numpy.arange(256) << 16,
numpy.arange(256) << 24,
]
def logl0(msg):
logging.log(LEVEL0, msg)
def logl1(msg):
logging.log(LEVEL1, msg)
def logl2(msg):
logging.log(LEVEL2, msg)
def logl3(msg):
logging.log(LEVEL3, msg)
def find_cal_area(a):
"""
Given a vector of pass (1) and fail (-1), find contiguous chunks of 'pass'.
:param a: Vector input (list?)
:return: Tuple - (max_so_far, begin_index, end_index)
"""
max_so_far = a[0]
max_ending_here = a[0]
begin_index = 0
begin_temp = 0
end_index = 0
for i in range(len(a)):
if max_ending_here < 0:
max_ending_here = a[i]
begin_temp = i
else:
max_ending_here += a[i]
if max_ending_here >= max_so_far:
max_so_far = max_ending_here
begin_index = begin_temp
end_index = i
return max_so_far, begin_index, end_index
class Qdr(Memory):
"""
Qdr memory on an FPGA.
"""
def __init__(self, parent, name, address, length_bytes,
device_info, ctrlreg_address):
"""
Make the QDR instance, given a parent, name and info from Simulink.
* Most often called from_device_info
:param parent: Parent device who owns this Qdr
:param name: A unique device name
:param address: Address of the Qdr in memory
:param length_bytes: Length of the Qdr in memory
:param device_info: Information about this Qdr device
:param ctrlreg_address:
"""
super(Qdr, self).__init__(name=name, width_bits=32,
address=address, length_bytes=length_bytes)
self.parent = parent
self.block_info = device_info
self.which_qdr = self.block_info['which_qdr']
self.ctrl_reg = register.Register(
self.parent, self.which_qdr+'_ctrl', address=ctrlreg_address,
device_info={'tag': 'xps:sw_reg', 'mode': 'one\_value',
'io_dir': 'From\_Processor', 'io_delay': '0',
'sample_period': '1', 'names': 'reg',
'bitwidths': '32', 'arith_types': '0',
'bin_pts': '0', 'sim_port': 'on',
'show_format': 'off'})
self.memory = self.which_qdr + '_memory'
self.control_mem = self.which_qdr + '_ctrl'
# self.qdr_cal()
# some readability tweaks
self.p_write_int = self.parent.write_int
self.p_read_uint = self.parent.read_uint
self.p_bwrite = self.parent.blindwrite
self.p_read = self.parent.read
# print('QDR %s logger name(%s) id(%i) level(%i)' % ()
# self.name, LOGGER.name, id(LOGGER), LOGGER.level)
# print('qdr logger handlers:', LOGGER.handlers)
LOGGER.debug('New Qdr %s' % self)
# TODO - Link QDR ctrl register to self.registers properly
@classmethod
def from_device_info(cls, parent, device_name, device_info, memorymap_dict, **kwargs):
"""
Process device info and the memory map to get all necessary info and
return a Qdr instance.
:param parent:
:param device_name: the unique device name
:param device_info: information about this device
:param memorymap_dict: a dictionary containing the device memory map
:return: a Qdr object
"""
def find_info(suffix):
address, length = -1, -1
fullname = device_info['which_qdr'] + suffix
for mem_name in memorymap_dict.keys():
if mem_name == fullname:
address = memorymap_dict[mem_name]['address']
length = memorymap_dict[mem_name]['bytes']
break
if address == -1 or length == -1:
raise RuntimeError('QDR %s: could not find memory address and'
'length for device %s' % (device_name,
fullname))
return address, length
mem_address, mem_length = find_info('_memory')
ctrlreg_address, ctrlreg_length = find_info('_memory')
# TODO - is the ctrl reg a register or the whole 256 bytes?
return cls(parent, device_name, mem_address, mem_length,
device_info, ctrlreg_address)
def __repr__(self):
"""
:return: a string representation of the Qdr Class
"""
return '%s:%s' % (self.__class__.__name__, self.name)
def reset(self):
"""
Reset the QDR controller by toggling the lsb of the control register.
Sets all taps to zero (all IO delays reset).
"""
LOGGER.debug('qdr reset')
self.ctrl_reg.write_int(1, blindwrite=True)
self.ctrl_reg.write_int(0, blindwrite=True)
def _control_mem_write(self, value, offset):
"""
Write to this QDR's control memory on the parent's mem bus
:param value:
:param offset:
"""
self.p_write_int(self.control_mem, value,
blindwrite=True, word_offset=offset)
def _disable_fabric(self):
"""
Disable the fabric write to the QDR.
"""
self._control_mem_write(1, 2)
def _enable_fabric(self):
"""
Enable the fabric write to the QDR.
"""
self._control_mem_write(0, 2)
def _add_extra_latency(self, extra_lat):
"""
If input argument is True, add an extra cycle of latency to QDR
input data. Useful for clock rates <~200.
If false, remove any extra latency already applied.
:param extra_lat:
"""
self._control_mem_write(1 if extra_lat else 0, 9)
def qdr_reset(self):
"""
Resets the QDR and the IO delays (sets all taps=0).
"""
self._control_mem_write(1, 0)
self._control_mem_write(0, 0)
# these two should just do nothing if support is not compiled into
# the running fpg
self._add_extra_latency(False)
self._enable_fabric()
def _qdr_delay_clk_step(self, step):
"""
Steps the output clock by 'step' amount.
:param step:
"""
if step == 0:
return
self._control_mem_write(0 if step < 0 else 0xffffffff, 7)
logl1('Applying clock delay: {}'.format(step))
for _ctr in range(abs(step)):
self._control_mem_write(0, 5)
self._control_mem_write(1 << 8, 5)
def _qdr_delay_inout_step(self, inout, bitmask, step):
"""
Steps all bits in bitmask by 'step' number of taps.
:param bitmask:
:param step:
"""
if step == 0:
return
self._control_mem_write(0 if step < 0 else 0xffffffff, 7)
if inout == 'in':
offset = 4
maskval = 0xf & (bitmask >> 32)
elif inout == 'out':
offset = 6
maskval = (0xf & (bitmask >> 32)) << 4
else:
raise ValueError('Unknown delay type: {}'.format(inout))
for _ctr in range(abs(step)):
self._control_mem_write(0, offset)
self._control_mem_write(0, 5)
self._control_mem_write(0xffffffff & bitmask, offset)
self._control_mem_write(maskval, 5)
def _qdr_delay_out_step(self, bitmask, step):
"""
Steps all bits in bitmask by 'step' number of taps.
:param bitmask:
:param step:
"""
self._qdr_delay_inout_step('out', bitmask, step)
def _qdr_delay_in_step(self, bitmask, step):
"""
Steps all bits in bitmask by 'step' number of taps.
:param bitmask:
:param step:
:return:
"""
self._qdr_delay_inout_step('in', bitmask, step)
def _qdr_delay_clk_get(self):
"""
Gets the current value for the clk delay.
"""
raw = self.p_read_uint(self.control_mem, word_offset=8)
if (raw & 0x1f) != ((raw & (0x1f << 5)) >> 5):
raise RuntimeError('Counter values not the same -- logic error! '
'Got back %i.' % raw)
return raw & 0x1f
def qdr_cal_check(self, step=-1, quickcheck=True):
"""
Checks calibration on a qdr. Raises an exception if it failed.
:param step: the current step
:param quickcheck: if True, return after the first error, else process
all test vectors before returning
"""
patfail = 0
for pattern in CAL_DATA:
data_format = '>%iL' % len(pattern)
self.p_bwrite(
self.memory, struct.pack(data_format, *pattern))
curr_val = self.p_read(self.memory, len(pattern)*4)
curr_val = struct.unpack(data_format, curr_val)
for word_n, word in enumerate(pattern):
faildiff = word ^ curr_val[word_n]
# logl2('step({}) written({:032b}) read({:032b}) '
# 'faildiff({:032b})'.format(step, word,
# curr_val[word_n], patfail))
if faildiff and quickcheck:
return False, faildiff
patfail |= faildiff
return patfail == 0, patfail
def _find_in_delays(self):
"""
:return:
"""
per_step_fail = []
per_bit_cal = [[] for _ in range(QDR_WW_LIMITED)]
logl2('QDR({}) checking cal over {} steps'.format(
self.name, NUM_DELAY_TAPS))
for step in range(NUM_DELAY_TAPS):
# check this step
res, fail_pattern = self.qdr_cal_check(step, False)
per_step_fail.append(fail_pattern)
# check each bit of the failure pattern
for bit in range(QDR_WW_LIMITED):
masked_bit = (fail_pattern >> bit) & 0x01
bit_value = -1 if masked_bit else 1
per_bit_cal[bit].append(bit_value)
logl3('\tstep input delays to {}'.format(step+1))
self._qdr_delay_in_step(0xfffffffff, 1)
# print(the failure patterns)
logl2('Eye for QDR {:s} (0 is pass, 1 is fail):'.format(self.name))
for step, fail_pattern in enumerate(per_step_fail):
logl2('\tdelay_step {:2d}: {:032b}'.format(step, fail_pattern))
# print(the per-bit eye diagrams)
logl3('Per-bit cal:')
for bit, bit_eye in enumerate(per_bit_cal):
logl3('\tbit_{:d}: {}'.format(bit, bit_eye))
# # find indices where calibration passed and failed:
# for bit in range(n_bits):
# try:
# bit_cal[bit].index(1)
# except ValueError:
# raise RuntimeError('Calibration failed for bit %i.' % bit)
# logl0('valid_steps for bit {} {}'.format(bit, valid_steps[bit]))
cal = {
'steps': numpy.array([0] * (QDR_WW_LIMITED + 4)),
'area': numpy.array([0] * QDR_WW_LIMITED),
'start': numpy.array([0] * QDR_WW_LIMITED),
'stop': numpy.array([0] * QDR_WW_LIMITED)
}
# for each bit, calculate the area that is best calibrated
for bit, bit_eye in enumerate(per_bit_cal):
area, start, stop = find_cal_area(bit_eye)
if area < MINIMUM_WINDOW_LENGTH:
raise RuntimeError('Could not find a robust calibration '
'setting for QDR %s' % self.name)
# original was 1/2
# running on /4 for some months, but seems to be giving errors
# with hot roaches
cal['start'][bit] = start
cal['stop'][bit] = stop
cal['steps'][bit] = (start + stop) // 2
# cal['steps'][bit] = (start + stop) // 3
# cal['steps'][bit] = (start + stop) // 4
# since we don't have access to bits 32-36, we guess the number of
# taps required based on the lower 32 bits:
# MEDIAN
median_taps = numpy.median(cal['steps'])
logl1('Median taps: {}'.format(median_taps))
for bit in range(QDR_WW_LIMITED, QDR_WORD_WIDTH):
cal['steps'][bit] = median_taps
logl1('Selected per-bit delays: {}'.format(cal['steps']))
# # MEAN
# mean_taps = numpy.mean(cal_steps)
# logl1('Mean taps: {}'.format(mean_taps))
# for bit in range(32, QDR_WORD_WIDTH):
# cal_steps[bit] = mean_taps
# logl1('Selected tap for bit {}: {}'.format(
# bit, cal_steps[bit]))
return cal['steps'], cal['area'], cal['start'], cal['stop']
def _apply_calibration(self, in_delays, out_delays, clk_delay, extra_clk=0):
"""
:param in_delays:
:param out_delays:
:param clk_delay:
:param extra_clk:
"""
assert len(in_delays) == QDR_WORD_WIDTH
assert len(out_delays) == QDR_WORD_WIDTH
# reset all the taps to zero
self.qdr_reset()
if USE_JACK_CAL:
self._add_extra_latency(extra_clk)
self._qdr_delay_clk_step(clk_delay)
for delays, delaypref in [(in_delays, 'in'), (out_delays, 'out')]:
_maxdelay = int(max(delays))
if _maxdelay == 0:
logl2('No {}put delays to apply'.format(delaypref))
else:
logl1('Applying {}put delays up to {}:'.format(
delaypref, _maxdelay))
for step in range(_maxdelay):
mask = 0
for bit in range(len(delays)):
mask += (1 << bit if (step < delays[bit]) else 0)
logl1('\tstep {} {} {:036b}'.format(delaypref, step, mask))
self._qdr_delay_inout_step(delaypref, mask, 1)
def qdr_cal(self, fail_hard=True):
if not USE_JACK_CAL:
return self._qdr_cal_ours(fail_hard)
else:
return self._qdr_cal_jacks(fail_hard)
def _qdr_cal_ours(self, fail_hard=True):
"""
Calibrates a QDR controller, stepping input delays and (if that fails)
output delays. Returns True if calibrated, raises a runtime
exception if it doesn't.
:param fail_hard:
"""
cal = False
failure_pattern = 0xffffffff
out_step = 0
while (not cal) and (out_step < NUM_DELAY_TAPS - 1):
# reset all the in delays to zero, and the out delays to
# this iteration.
in_delays = [0] * QDR_WORD_WIDTH
_out_delays = [out_step] * QDR_WORD_WIDTH
_current_step = self._qdr_delay_clk_get()
logl1('Output delay: current({}) set_to({})'.format(
_current_step, out_step))
self._apply_calibration(in_delays=in_delays,
out_delays=_out_delays,
clk_delay=out_step)
try:
in_delays, area, start, stop = self._find_in_delays()
except AssertionError:
in_delays = [0] * QDR_WORD_WIDTH
except Exception as e:
raise RuntimeError('Unknown exception in qdr_cal - '
'{!s}'.format(e.message))
# update the out delays with the current input delays
_out_delays = [out_step] * QDR_WORD_WIDTH
self._apply_calibration(in_delays=in_delays,
out_delays=_out_delays,
clk_delay=out_step)
cal, failure_pattern = self.qdr_cal_check()
out_step += 1
if (not cal) and fail_hard:
raise RuntimeError('QDR %s calibration failed.' % self.name)
return cal, failure_pattern
def qdr_check_cal_any_good(self, current_step, checkoffset=2**22):
"""
Checks calibration on a qdr.
:param current_step: what is the current output delay step
:param checkoffset: where to write the test data
:return: True if *any* of the bits were good
"""
patfail = 0
for pn, pattern in enumerate(CAL_DATA):
logl2('pattern[0]: {:x}'.format(pattern[0]))
_patternstr = '>%iL' % len(pattern)
_wrdata = struct.pack(_patternstr, *pattern)
self.p_bwrite(self.memory, _wrdata, offset=checkoffset)
_rdata = self.p_read(self.memory, len(pattern) * 4,
offset=checkoffset)
retdat = struct.unpack(_patternstr, _rdata)
for word_n, word in enumerate(pattern):
patfail |= word ^ retdat[word_n]
# logl2('\tcurstep({}) written({:032b}) read({:032b}) '
# 'faildiff({:032b})'.format(current_step, word,
# retdat[word_n], patfail))
if patfail == 0xffffffff:
# none of the bits were correct, so bail
logl2('No good bits found, bailing.')
return False
return True
def _scan_out_to_edge(self):
"""
Step through the possible output delays. When any of the bits are OK,
use this as the start point for the input delay scan. This makes life
a little simpler than letting the input and output delays of all bits
be completely independent.
If no good bits are found, that's fine, we'll just leave the output
delay set to the maximum allowed
"""
out_step = 0
for out_step in range(NUM_DELAY_TAPS):
if self.qdr_check_cal_any_good(out_step):
break
if out_step < NUM_DELAY_TAPS - 1:
self._qdr_delay_out_step(2**QDR_WORD_WIDTH - 1, 1)
self._qdr_delay_clk_step(1)
return out_step
def _qdr_cal_jacks(self, fail_hard=True, min_eye_width=8):
"""
Calibrates a QDR controller
Step output delays until some of the bits reach their eye.
Then step input delays
Returns True if calibrated, raises a runtime exception if it doesn't.
:param fail_hard: throw an exception on cal fail if True, else
return False
:param min_eye_width: What is the minimum eye width we'll accept?
"""
def _find_out_delay(add_extra_latency=False):
# reset all delays and set extra latency to zero.
self.qdr_reset()
self._disable_fabric()
if add_extra_latency:
self._add_extra_latency(1)
# find the first output delay that may work with no input delay
out_step = self._scan_out_to_edge()
logl1('Output delays set to {}'.format(out_step))
# find input delays for this output delay
in_dels, areas, starts, stops = self._find_in_delays()
return out_step, in_dels, areas, starts, stops
# find an initial solution
out_step0, in_delays0, good_area0, good_starts0, \
good_stops0 = _find_out_delay()
# if any of the calibration eyes are less than some minimum width,
# and there is a chance that adding an extra cycle of latency will
# help, give that a go!
# Default values to replace if we rescan
in_delays = in_delays0
out_delay = out_step0
extra_clk = False
max_input_delay_required = numpy.any(good_stops0 == NUM_DELAY_TAPS-1)
eyes_too_small = numpy.any(good_area0 < min_eye_width)
if max_input_delay_required and eyes_too_small:
logl1('Adding extra latency and checking for better solutions')
out_step1, in_delays1, good_area1, good_starts1, \
good_stops1 = _find_out_delay()
if numpy.all(good_area1 > good_area0):
logl1('New solutions with extra latency are better')
in_delays = in_delays1
out_delay = out_step1
extra_clk = True
else:
logl1('Original solution without extra latency was better')
logl1('Using in delays: {}'.format(in_delays))
self._apply_calibration(in_delays=in_delays,
out_delays=[out_delay] * QDR_WORD_WIDTH,
clk_delay=out_delay,
extra_clk=extra_clk)
cal, failure_pattern = self.qdr_cal_check()
self._enable_fabric()
if (not cal) and fail_hard:
raise RuntimeError('QDR %s calibration failed.' % self.name)
return cal, failure_pattern
# end