import time
import ska_low_sps_tpm_api.boards.tpm_hw_definitions as tpm_hw_definitions
from ska_low_sps_tpm_api.base.definitions import *
from ska_low_sps_tpm_api.base.utils import *
from ska_low_sps_tpm_api.plugins.firmwareblock import FirmwareBlock
__author__ = "Alessio Magro"
[docs]
class TpmPll(FirmwareBlock):
"""TpmPll plugin"""
[docs]
@compatibleboards(BoardMake.TpmBoard)
@friendlyname("tpm_pll")
@maxinstances(1)
def __init__(self, board, logger=None, **kwargs):
"""
TpmPll initialiser.
:param board: Pointer to board instance
"""
super(TpmPll, self).__init__(board, logger=logger)
self._board_type = kwargs.get("board_type", "XTPM")
self._pll_out_config = []
fw_date_code = self.board["board.regfile.date_code"]
self._fpga_firmware = None
_use_jesd_refclk_only = False
if self.board.is_programmed(0):
self._fpga_firmware = self.board["fpga1.regfile.rev"]
if self.board.hw_rev == tpm_hw_definitions.TPM_HW_REV_1_2:
if self.board.has_register(
"fpga1.regfile.feature_extended.use_jesd_refclk_only"
):
_use_jesd_refclk_only = self.board[
"fpga1.regfile.feature_extended.use_jesd_refclk_only"
]
self._pps_invert = None
self._jesd_refclk_only = None
if self.board.hw_rev == tpm_hw_definitions.TPM_HW_REV_1_2:
self._pps_invert = 1
if _use_jesd_refclk_only == 1:
self._jesd_refclk_only = True
self.logger.debug("Not using JESD global clock!")
else:
self._jesd_refclk_only = False
if (
fw_date_code
>= tpm_hw_definitions.CPLD_FW_VER_REQUIRED_FOR_I2C_CMD_ACK_TPM_V1_2
):
self._pll_status_enabled = True
else:
self._pll_status_enabled = False
elif self.board.hw_rev >= tpm_hw_definitions.TPM_HW_REV_1_5:
self._pps_invert = 0
self._jesd_refclk_only = True
if fw_date_code > self.board.CPLD_FW_VER_LOCK_I2C_CHANGE:
self._pll_status_enabled = True
else:
self._pll_status_enabled = False
else:
raise PluginError("TpmPll: Invalid hw_rev detected")
# On TPM 1.6 check if we are using the special firmware supporting 800 MHz clock from PLL
self._config_800MHz = False
if (
self.board.hw_rev >= tpm_hw_definitions.TPM_HW_REV_1_5
and self._fpga_firmware is not None
):
if (
self._fpga_firmware
== tpm_hw_definitions.FPGA_FW_800MHZ_EXCEPTION_TPM_V1_5
):
self._config_800MHz = True
self.logger.info("FPGA FIRMWARE VERSION: %s" % hex(self._fpga_firmware))
self.logger.info("Using 800 MHz FPGA clock exception!")
if self._board_type == "XTPM":
if not self._config_800MHz:
self._pll_out_config = [
"sysref", # 0 - SYSRF1
"clk", # 1 - CLKB1
"clk", # 2 - CLKB0
"unused", # 3 - PLL_CLK_TST
"sysref", # 4 - SYSRF0
"unused", # 5 - NC
"vcxo"
if not self._jesd_refclk_only
else "unused", # 6 - v1.6 PLL_CLK_FPGA0, v2.0 NC
"unused", # 7 - 10M_FPGA0
"vcxo", # 8 - PLL_CLK_JESD_FPGA0
"sysref_pll1_retimed", # 9 - SYSREF_FPGA0
"sysref_pll1_retimed", # 10 - SYSREF_FPGA1
"unused", # 11 - 10M_FPGA1
"vcxo"
if not self._jesd_refclk_only
else "unused", # 12 - v1.6 PLL_CLK_FPGA1, v2.0 NC
"vcxo", # 13 - PLL_CLK_JESD_FPGA1
] # fmt: skip # TODO: Refactor _pll_out_config
else:
self.logger.warning(
"FPGA FIRMWARE VERSION: %s" % hex(self._fpga_firmware)
)
self.logger.warning(
"PLL configure in 800MHz mode [experimental] [exception for fw %s only]"
% hex(tpm_hw_definitions.FPGA_FW_800MHZ_EXCEPTION_TPM_V1_5)
)
self._pll_out_config = [
"sysref", # 0 - SYSRF1
"clk", # 1 - CLKB1
"clk", # 2 - CLKB0
"unused", # 3 - PLL_CLK_TST
"sysref", # 4 - SYSRF0
"unused", # 5 - NC
"unused", # 6 - NC
"unused", # 7 - 10M_FPGA0
"clk", # 8 - PLL_CLK_JESD_FPGA0
"sysref_pll1_retimed", # 9 - SYSREF_FPGA0
"sysref_pll1_retimed", # 10 - SYSREF_FPGA1
"unused", # 11 - 10M_FPGA1
"unused", # 12 - NC
"clk", # 13 - PLL_CLK_JESD_FPGA1
]
else:
raise PluginError("TpmPll: Board type not supported")
#######################################################################################
[docs]
def pll_out_set(self, idx):
"""
Set PLL out.
:param idx:
:return:
"""
type = self._pll_out_config[idx]
if type == "clk":
reg0 = 0x0
reg1 = 0x0 if not self._config_800MHz else 0x80
reg2 = 0x0
elif type == "clk_hstl":
reg0 = 0x0
reg1 = 0x80
reg2 = 0x0
elif type == "clk_div_2":
reg0 = 0x0
reg1 = 0x0
reg2 = 0x0 if not self._config_800MHz else 0x01
elif type == "clk_div_4":
reg0 = 0x0
reg1 = 0x0 if not self._config_800MHz else 0x80
reg2 = 0x3
elif type == "vcxo":
reg0 = 0x20
reg1 = 0x0 if not self._config_800MHz else 0x80
reg2 = 0x0
elif type == "vcxo_hstl":
reg0 = 0x20
reg1 = 0x80
reg2 = 0x0
elif type == "inverted_vcxo":
reg0 = 0xA0
reg1 = 0x0 if not self._config_800MHz else 0x80
reg2 = 0x0
elif type == "sysref":
reg0 = 0x40
reg1 = 0x0 if not self._config_800MHz else 0x80
reg2 = 0x0
elif type == "sysref_lvds_boost":
reg0 = 0x40
reg1 = 0x40
reg2 = 0x0
elif type == "sysref_pll1_retimed":
reg0 = 0x60
reg1 = 0x0 if not self._config_800MHz else 0x80
reg2 = 0x0
elif type == "sysref_lvds":
reg0 = 0x40
reg1 = 0x0
reg2 = 0x0
elif type == "sysref_hstl":
reg0 = 0x40
reg1 = 0x80
reg2 = 0x0
else:
reg0 = 0x0
reg1 = 0x0
reg2 = 0x0
return reg0, reg1, reg2
[docs]
def pll_config(self, fsample):
"""
Configure the PLL.
:param fsample:
"""
doubler = 0x0 # 0x0 0x10
if self._board_type == "XTPM":
# PLL1 config
self.board[("pll", 0x100)] = 0x1
self.board[("pll", 0x102)] = 0x1
self.board[("pll", 0x104)] = 10 if doubler == 0 else 20 # VCXO100MHz
self.board[("pll", 0x106)] = 0x14 # VCXO100MHz ##mod
self.board[("pll", 0x107)] = 0x13 # Not disable holdover
# Check if it is a TPM 2.0 which requires differential input
if self.board.hw_rev >= tpm_hw_definitions.TPM_HW_REV_2_0:
self.logger.warn("TpmPLL: PLL VCXO input set to differential")
self.board[("pll", 0x108)] = 0x29 # VCXO100MHz with differential input
else:
self.board[("pll", 0x108)] = 0x28 # VCXO100MHz
self.board[
("pll", 0x109)
] = 0x0 # setting PLL1 feedback source as PLL2 divider output # fmt: skip
self.board[("pll", 0x10A)] = 0x2 # 10MHZ: 0x2
# PLL2 config
self.board[("pll", 0x200)] = 0xE6
if fsample == 1000e6:
self.board[("pll", 0x201)] = 0x0A
self.board[("pll", 0x202)] = 0x13
self.board[("pll", 0x203)] = 0x00
self.board[("pll", 0x204)] = 0x4 # M1
self.board[("pll", 0x205)] = 0x7
self.board[("pll", 0x207)] = 0x2 # R1
self.board[("pll", 0x208)] = 0x9 # N2
elif fsample == 800e6:
self.board[("pll", 0x201)] = 0x0A if doubler == 0 else 0x05
self.board[("pll", 0x202)] = 0x13 if doubler == 0 else 0x23
self.board[("pll", 0x203)] = doubler
self.board[("pll", 0x204)] = (0 << 5) | (0 << 4) | 0x5 # M1
self.board[("pll", 0x205)] = 0x7
self.board[("pll", 0x207)] = 0x1 # R1
self.board[("pll", 0x208)] = 0x7 if doubler == 0 else 0x3 # N2
elif fsample == 700e6:
self.board[("pll", 0x201)] = 0xC8
self.board[("pll", 0x202)] = 0x13
self.board[("pll", 0x203)] = 0x00
self.board[("pll", 0x204)] = 0x5 # M1
self.board[("pll", 0x205)] = 0x7
self.board[("pll", 0x207)] = 0x0 # R1
self.board[("pll", 0x208)] = 0x6 # N2
else:
raise PluginError("TpmPll: Frequency not supported")
else:
raise PluginError("TpmPll: Board type not supported")
# Setting PLL Outputs
for n in range(14):
reg0, reg1, reg2 = self.pll_out_set(n)
self.board[("pll", 0x300 + 3 * n + 0)] = reg0
self.board[("pll", 0x300 + 3 * n + 1)] = reg1
self.board[("pll", 0x300 + 3 * n + 2)] = reg2
# Setting SYSREF
divider = 640 # sysref: divide by 640(*2)
self.board[("pll", 0x400)] = divider & 0xFF
self.board[("pll", 0x401)] = (divider >> 8) & 0xFF
self.board[("pll", 0x402)] = 0x0
self.board[("pll", 0x403)] = 0x96
# Power down unused output
self.board[("pll", 0x500)] = 0x10
pd = 0
for c in range(14):
if self._pll_out_config[c] == "unused":
pd |= 2**c
self.board[("pll", 0x501)] = pd & 0xFF
self.board[("pll", 0x502)] = (pd & 0xFF00) >> 8
self.board[("pll", 0x503)] = ~pd & 0xFF
self.board[("pll", 0x504)] = (~pd & 0xFF00) >> 8
# enable STATUS outputs
if self._pll_status_enabled:
self.board[("pll", 0x505)] = 0x2
self.board[("pll", 0x506)] = 0x3
self.board[("pll", 0x507)] = 0xC
# IO update command
self.board[("pll", 0xF)] = 0x1
for i in range(10):
if self.board[("pll", 0xF)] == 0:
break
if i == 9:
raise PluginError(
"PLL timeout error - IO_UPDATE (0xF): 0x%x"
% self.board[("pll", 0xF)]
)
time.sleep(0.1)
# Enable sysref
self.board[("pll", 0x403)] = 0x97
if (
do_until_eq(lambda: self.board[("pll", 0xF)], 0, ms_retry=100, s_timeout=10)
is None
):
raise PluginError(
"PLL timeout error - IO_UPDATE (0xF): 0x%x" % self.board[("pll", 0xF)]
)
self.board[("pll", 0xF)] = 0x1
# SYNC command
self.board[("pll", 0x32A)] = 0x1
if (
do_until_eq(lambda: self.board[("pll", 0xF)], 0, ms_retry=100, s_timeout=10)
is None
):
raise PluginError(
"PLL timeout error - IO_UPDATE (0xF): 0x%x" % self.board[("pll", 0xF)]
)
self.board[("pll", 0xF)] = 0x1
# SYNC command
self.board[("pll", 0x32A)] = 0x0
if (
do_until_eq(lambda: self.board[("pll", 0xF)], 0, ms_retry=100, s_timeout=10)
is None
):
raise PluginError(
"PLL timeout error - IO_UPDATE (0xF): 0x%x" % self.board[("pll", 0xF)]
)
self.board[("pll", 0xF)] = 0x1
# PLL2 VCO calibration
self.board[("pll", 0x203)] = doubler | 0x0
self.board[("pll", 0xF)] = 0x1
if (
do_until_eq(lambda: self.board[("pll", 0xF)], 0, ms_retry=100, s_timeout=10)
is None
):
raise PluginError(
"PLL timeout error - IO_UPDATE (0xF): 0x%x" % self.board[("pll", 0xF)]
)
self.board[("pll", 0x203)] = doubler | 0x1
self.board[("pll", 0xF)] = 0x1
if (
do_until_eq(lambda: self.board[("pll", 0xF)], 0, ms_retry=100, s_timeout=10)
is None
):
raise PluginError(
"PLL timeout error - IO_UPDATE (0xF): 0x%x" % self.board[("pll", 0xF)]
)
if (
do_until_eq(
lambda: self.board[("pll", 0x509)] & 0x1,
0x0,
ms_retry=100,
s_timeout=10,
)
is None
):
raise PluginError(
"PLL VCO calibration timeout - Status Readback 1 (0x509): 0x%x"
% self.board[("pll", 0x509)]
)
if (
do_until_eq(
lambda: self.board[("pll", 0x508)] in [0xF2, 0xE7],
0x1,
ms_retry=100,
s_timeout=10,
)
is None
):
raise PluginError(
"PLL not locked - Status Readback 0 (0x508): 0x%x"
% self.board[("pll", 0x508)]
)
if self._pll_status_enabled:
self.reset_pll_loss_of_lock()
[docs]
def get_pll_status(self):
if not self._pll_status_enabled:
return None
locks = self.board["board.regfile.pll.status"]
return locks
[docs]
def get_pll_loss_of_lock(self):
if not self._pll_status_enabled:
return None
return self.board["board.regfile.pll_lol"]
[docs]
def reset_pll_loss_of_lock(self):
if not self._pll_status_enabled:
return
# lol = self.board["board.regfile.pll_lol"]
self.board["board.regfile.pll_lol"] = 0 # lol & (~lol & 0xff)
[docs]
def pll_reset(self):
"""Perform the PLL reset"""
if self.board["board.regfile.enable.adc"] == 0:
self.logger.info("ADCs power disabled. Enabling")
self.board["board.regfile.enable.adc"] = 1
time.sleep(0.1)
else:
self.logger.info("ADCs power already enabled.")
if self.board["board.regfile.enable.sysr"] == 0:
self.logger.info("SYS_REF power disabled. Enabling")
self.board["board.regfile.enable.sysr"] = 1
time.sleep(0.1)
else:
self.logger.info("SYS_REF power already enabled.")
self.board["board.regfile.pll.resetn"] = 1
self.board["board.regfile.pll.resetn"] = 0
time.sleep(0.2)
self.board["board.regfile.pll.resetn"] = 1
time.sleep(0.2)
[docs]
def pll_start(self, fsample):
"""
Perform the PLL initialization procedure as implemented in ADI demo.
:param fsample: PLL output frequency in MHz. Supported frequency are 700, 800, 1000 MHz
"""
if fsample not in [1000e6, 800e6, 700e6]:
self.logger.warn(
"TpmPLL: Frequency "
+ str(fsample / 1e6)
+ " MHz is not currently supported."
)
fsample = 800e6
self.pll_reset()
self.board["fpga1.pps_manager.pps_in_invert"] = self._pps_invert
self.board["fpga1.pps_manager.sync_tc"] = 0x07090404
self.board["fpga1.pps_manager.sync_prescale"] = 0x0
self.board["fpga1.pps_manager.sync_cnt_enable"] = 0x7
self.board["fpga2.pps_manager.pps_in_invert"] = self._pps_invert
self.board["fpga2.pps_manager.sync_tc"] = 0x07090404
self.board["fpga2.pps_manager.sync_prescale"] = 0x0
self.board["fpga2.pps_manager.sync_cnt_enable"] = 0x7
if self._board_type == "XTPM":
self.board[("pll", 0xF)] = 0x1
self.pll_config(fsample)
self.pll_config(fsample)
self.board["fpga1.pps_manager.sync_cnt_enable"] = 0 # disable sync
self.board["fpga2.pps_manager.sync_cnt_enable"] = 0 # disable sync
if self._pll_status_enabled:
if self.get_pll_loss_of_lock() > 0:
raise PluginError("PLL Loss of lock detected!")
else:
raise PluginError("TpmPll: Board type not supported")
##################### Superclass method implementations #################################
[docs]
def initialise(self):
"""Initialise TpmPll"""
self.logger.info("TpmPll has been initialised")
return True
[docs]
def status_check(self):
"""Perform status check.
:return: Status
"""
self.logger.info("TpmPll : Checking status")
if self.board[("pll", 0x508)] not in [0xF2, 0xE7]:
self.logger.error("TpmPLL: PLL not initialised")
return Status.BoardError
if self._pll_status_enabled:
if self.get_pll_loss_of_lock() != 0:
self.logger.error("TpmPLL: PLL Loss of Lock Detected")
return Status.BoardError
return Status.OK
[docs]
def clean_up(self):
"""Perform cleanup.
:return: Success
"""
self.logger.info("TpmPll : Cleaning up")
return True