Source code for qdr

"""
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,
]


[docs]def logl0(msg): logging.log(LEVEL0, msg)
[docs]def logl1(msg): logging.log(LEVEL1, msg)
[docs]def logl2(msg): logging.log(LEVEL2, msg)
[docs]def logl3(msg): logging.log(LEVEL3, msg)
[docs]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
[docs]class Qdr(Memory): """ Qdr memory on an FPGA. """
[docs] 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
[docs] @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)
[docs] 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)
[docs] 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
[docs] 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)
[docs] 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
[docs] 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