Skip to content

Commit

Permalink
Merge branch 'husky' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-dewar committed Apr 29, 2021
2 parents 7323abb + 3ead9a2 commit 02d3ed8
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 36 deletions.
67 changes: 67 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,73 @@ OpenADC Scope
:OSError: Raised when there is issues connecting to the hardware, such as
user not having the correct device permissions to access the hardware.

.. _api-scope-husky:

ChipWhisperer_Husky
===================

Mostly the same as the CWPro, with some additional upgraded settings.

It also includes a PLL class (which may be integrated into :code:`scope.clock`
in the future). This class's default setup is to use 7.37MHz for the target
clock and a x4 multiple for the ADC clock.

Like with the FPGA based clock, the target clock on the Husky
can be set directly::

scope.pll.target_freq = 7.37E6

The ADC clock is set as a positive integer multiple of the target clock::

scope.pll.adc_mul = 4

In order to ensure a clean multiple for the ADC, the PLL
settings for the whole chip are changed if :code:`adc_mul` or :code:`target_freq`
are changed. This means the target clock will drop out for a short period if
either are changed.

The PLL can use either an onboard XTAL, or a clock output from the onboard FPGA.
The FPGA setting can be set to use an external clock (HS1, usually). Otherwise,
the XTAL setting is recommended as it results in much less jitter on the ADC::

scope.pll.pll_src = 'xtal' # XTAL default
scope.pll.pll_src = 'fpga' # FPGA

Like with the other FPGA ChipWhisperers, the phase of the clock can be changed.
In this case, a positive unitless phase between 0 and 31 can be applied to either output clock::

# +5 phase to adc
scope.pll.adc_delay = 5

# +5 to both - same as both = 0
scope.pll.target_delay = 5

Additional functionality, such as resetting the chip and resynchronizing the output clocks
are available, but aren't typically needed.

.. attribute:: pll

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.pll_src
:annotation: scope.pll.pll_src

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.target_freq
:annotation: scope.pll.target_freq

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.adc_mul
:annotation: scope.pll.adc_mul

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.adc_freq
:annotation: scope.pll.adc_freq

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.pll_locked
:annotation: scope.pll.pll_locked

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.adc_delay
:annotation: scope.pll.adc_delay

.. autoattribute:: chipwhisperer.capture.scopes.cwhardware.ChipWhispererHuskyPLL.CDCI6214.target_delay
:annotation: scope.pll.target_delay


.. _api-scope-cwnano:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@
import time
import numpy as np


class CDCI6214:
"""PLL control for the Husky.
May be merged into scope.clock in the future.
Basic usage::
scope = cw.scope() # Husky only
scope.pll.target_freq = 7.37E6
scope.pll.adc_mul = 4 # any positive integer within reason that satisfies ADC specs
"""
def __init__(self, scope):
self._scope = scope
self.setup()
Expand All @@ -19,7 +30,14 @@ def __init__(self, scope):
self._fpga_clk_freq = 48E6

def write_reg(self, addr, data):
"""data can be a 16-bit integer or a 2 element list
"""Write to a CDCI6214 Register over I2C
Args:
addr (u8): Address to write to
data (u16 or list): Data to write. u16 is big endian, list is two elements long,
so write_reg(0x00, [0x10, 0x20]) is the same as write_reg(0x00, 0x2010)
data can be a 16-bit integer or a 2 element list
"""
if not hasattr(data, "__getitem__"):
tmp = [data & 0xFF, (data >> 8) & 0xFF]
Expand All @@ -28,9 +46,11 @@ def write_reg(self, addr, data):
self._scope._getNAEUSB().sendCtrl(0x29, data=[1, addr, 0x00, data[0], data[1]])

def read_reg(self, addr, as_int=False):
"""If as_int is True, return as a 16-bit integer
Else return as a bytearray
"""Read a CDCI6214 Register over I2C
Args:
addr (u8): Address to read from
as_int (bool): If true, return a big endian u16. Otherwise, return a two element list.
"""

self._scope._getNAEUSB().sendCtrl(0x29, data=[0, addr, 0x00, 0, 0])
Expand All @@ -44,7 +64,24 @@ def read_reg(self, addr, as_int=False):
return bytearray(data[1:])

def update_reg(self, addr, bits_to_set, bits_to_clear):
"""bits_to_set/bits_to_clear can be a 16-bit integer or a 2 element list.
"""Updates a CDCI6214 Register. Reads, clears bits, then sets bits.
This means bits_to_set will overwrite bits_to_clear. Effectively::
x = read_reg(addr)
x &= ~(bits_to_clear) # C bitwise not, illustration purposes only, not implemented like this
x |= bits_to_set
write_reg(addr, x)
Args:
addr (u8): Address to update
bits_to_set (u16 or list): Bits to set in the register, Overwrites bits_to_clear.
Big endian: 0x2010 == [0x10, 0x20]
bits_to_clear (u16 or list): Bits to set in the register
Big endian: 0x2010 == [0x10, 0x20]
bits_to_set/bits_to_clear can be a 16-bit integer or a 2 element list.
The clear is applied first, so you can clear a register with 0xffff to set
everything after.
"""
Expand All @@ -71,7 +108,17 @@ def update_reg(self, addr, bits_to_set, bits_to_clear):


def setup(self):
"""Do required initial setup
"""Do required initial setup.
Does the following:
* Sets GPIO1 and 4 to outputs
* Enables power to whole chip
* Enable sync
* Disable glitchless output
* Disable channel 2 and 4 (unused)
* Set ref as AC-differential, XIN == xtal
* Use register to select PLL input instead of pin
"""
# disable GPIO1/4 as inputs
self.update_reg(0x00, (1 << 13) | (1 << 12), 0)
Expand Down Expand Up @@ -110,6 +157,11 @@ def get_pll_input(self):
return (self.read_reg(0x01, True) & (1 << 8) == (0))

def get_outfreq(self, pll_out=3):
"""Get the output frequency of a PLL output.
Recommended to use :code:`scope.pll.adc_freq` and
:code:`scope.pll.target_freq` instead
"""
prescale_lut = [4, 5, 6]
if pll_out == 3:
prescale_reg = (self.read_reg(0x1E, True) >> 0) & 0b11
Expand Down Expand Up @@ -138,16 +190,22 @@ def get_outfreq(self, pll_out=3):
return self.input_freq / outdiv / prescale

def reset(self):
"""Do a reset of the PLL chip. Doesn't reset registers
"""Do a reset of the PLL chip. Doesn't reset registers.
Maybe need to do to lock PLL?
"""
self.update_reg(0x0, 1 << 2, 0x00)

def sync_clocks(self):
"""Resync clocks
"""Send a resync pulse to the internal synchronization blocks.
Resync clocks.
"""
self.update_reg(0x00, 1 << 5, 0x00)

def recal(self):
"""Perform a calibration. Typically unneeded.
"""
self.update_reg(0x0, 1 << 5, 1 << 5)

def set_pll_input(self, xtal=True):
Expand Down Expand Up @@ -210,34 +268,14 @@ def get_outdiv(self, pll_out=3):
elif pll_out == 1:
return self.read_reg(0x25, True) & 0x3FFF

# def set_outfreq(self, pll_out=3, freq=50E6):
# base_freq = self._pll_output_frequency
# if not freq:
# self.set_outdiv(pll_out, 0)
# if (freq < 100E3) or (freq > 250E6):
# raise ValueError("Max freq = 250MHz, min freq = 100kHz, given {}".format(freq))

# best_error = float('inf') #infinite error
# best_prescale = 4
# best_div = 100
# for prescale in range(5, 6):
# fin = base_freq / prescale
# div = int((fin / freq) + 0.5)
# actual_freq = fin / div
# error = abs(freq - actual_freq) / freq
# if error < best_error:
# best_error = error
# best_prescale = prescale
# best_div = div

# print(error, prescale, div)


# print("Found div {} prescale {}".format(best_div, best_prescale))
# self.set_prescale(pll_out, best_prescale)
# self.set_outdiv(pll_out, best_div)

def set_outfreqs(self, input_freq, target_freq, adc_mul):
"""Set an output target frequency for the target/adc using input_freq
Calculates the best PLL/divider settings for a target_freq
with an output div that can evenly divide adc_mul. Should
help keep clocks in sync. Recommended to just set scope.pll.target_freq
and scope.pll.adc_mul
"""
if (adc_mul < 1) or (adc_mul != int(adc_mul)):
raise ValueError("ADC must be >= 1 and an integer")
if (adc_mul * target_freq) > 200E6 or (adc_mul * target_freq < 10E6):
Expand Down Expand Up @@ -317,7 +355,13 @@ def set_bypass_adc(self, enable_bypass):

@property
def target_delay(self):
delay = (self.read_reg(0x26, True) >> 11) & 0b1111
"""Delays/phase shifts the target clock to the right (positive phase)
:getter: A 5 bit integer representing the delay
:setter: A 5 bit integer representing the delay. Must be between 0 and 31
"""
delay = (self.read_reg(0x26, True) >> 11) & 0b11111
return delay

@target_delay.setter
Expand All @@ -329,6 +373,12 @@ def target_delay(self, delay):

@property
def adc_delay(self):
"""Delays/phase shifts the target clock to the right (positive phase)
:getter: A 5 bit integer representing the delay
:setter: A 5 bit integer representing the delay. Must be between 0 and 31
"""
delay = (self.read_reg(0x32, True) >> 11) & 0b1111
return delay

Expand All @@ -341,6 +391,12 @@ def adc_delay(self, delay):

@property
def pll_src(self):
"""Get/set the PLL src. fpga is typically useful if using an external clock
:getter: 'xtal' or 'fpga'
:setter: 'xtal' or 'fpga'
"""
if self.get_pll_input():
return "xtal"
else:
Expand All @@ -357,10 +413,29 @@ def pll_src(self, src):

@property
def adc_mul(self):
"""The ADC clock output as a multiple of the target clock
Must be an integer multiple.
:getter: Last set multiplier
:setter: Multiplier to set. Recalculates both adc and target clock settings,
so setting this will result in a short disruption of the clock
to the target
"""
return self._adc_mul

@property
def target_freq(self):
"""The target clock frequency.
Due to PLL/adc_mul limitations, the actual value set will differ
from the requested value
:getter: The actual calculated target clock frequency that was set
:setter: The target frequency you want
"""
indiv = self.get_input_div()
outdiv = self.get_outdiv(1)
if not indiv:
Expand All @@ -373,6 +448,8 @@ def target_freq(self):

@property
def adc_freq(self):
"""The actual calculated adc_clock freq. Read only
"""
indiv = self.get_input_div()
outdiv = self.get_outdiv(3)
if not indiv:
Expand Down Expand Up @@ -431,6 +508,8 @@ def adc_mul(self, adc_mul):

@property
def pll_locked(self):
""" Returns True if the pll is locked, False otherwise
"""
return (self.read_reg(0x07, True) & (1 << 11)) == (1 << 11)

def _dict_repr(self):
Expand Down

0 comments on commit 02d3ed8

Please sign in to comment.