Commit 857483c0 authored by Jan Pospisil's avatar Jan Pospisil

added test script; reformatted Ffpg class (added comments)

parent 6e97c2da
......@@ -31,7 +31,7 @@ The "!->" determines what to check further when check fails.
1) Plug FMC mezzanine into SVEC carrier board and into VME crate (and configure properly)
2) When using FMC mezzanine version 2 (EDA-03339-V2), check that all power LEDs (LD1, LD2, LD3) are on (!-> SVEC fuses, power supplies)
3) Connect clock (CLK IN) f_CLK = 400.789 MHz and trigger (TRIG IN) f_TRIG = 11.245 kHz - trigger has to be phase aligned with clock
4) Use /sw/FFPG_driver.py to configure the GW and run some test
4) Use /sw/FFPG_driver.py or /sw/FFPG_test.py to configure the GW and run some test
5) Check:
a) There is f_CLK/2 clock present on CLK OUT (!-> AD9512 divider)
b) LED CLK IN is on (!-> clock source is not stable?)
......
#!/usr/bin/env python
# coding: utf8
##-----------------------------------------------------------------------------
## Title : Test Script
## Project : FMC DEL 1ns 2cha (FFPG)
## URL : http://www.ohwr.org/projects/fmc-del-1ns-2cha
##-----------------------------------------------------------------------------
## File : FFPG_test.py
## Author(s) : Jan Pospisil <j.pospisil@cern.ch>
## Company : CERN (BE-BI-QP)
## Created : 2017-09-14
## Last update: 2017-09-14
## Standard : Python
##-----------------------------------------------------------------------------
## Description: Script for testing new boards
##-----------------------------------------------------------------------------
## Copyright (c) 2017 CERN (BE-BI-QP)
##-----------------------------------------------------------------------------
## GNU LESSER GENERAL PUBLIC LICENSE
##-----------------------------------------------------------------------------
## This source file is free software; you can redistribute it and/or modify it
## under the terms of the GNU Lesser General Public License as published by the
## Free Software Foundation; either version 2.1 of the License, or (at your
## option) any later version. This source 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 Lesser General Public License for more details. You should have
## received a copy of the GNU Lesser General Public License along with this
## source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html
##-----------------------------------------------------------------------------
## Revisions :
## Date Version Author Comment
## 2017-09-14 1.0 Jan Pospisil Created (based on FFPG_driver)
##-----------------------------------------------------------------------------
import time
import sys
from Ffpg import *
calibrationData = [ # -1 = uncalibrated
# channel 1, channel 2
[-1, -1], # FMC slot 0
[-1, -1] # FMC slot 1
]
def Init(pg):
global calibrationData
pg.Reset()
pg.SelectClock("external", 400.789)
pg.SetRatio(2)
pg.SetOverflow(17820)
pg.Ad9512Sync()
pg.SetTriggerThreshold(0.5)
pg.SetVcxoFrequency(-0.2105) # 125.0000 MHz
# calibration
fmcSlot = pg.GetFmcSlot()
for channel in [1,2]:
triggerPhase = calibrationData[fmcSlot][channel-1]
print("Calibrating channel "+str(channel)+" on FMC slot "+str(fmcSlot)+" to "+str(triggerPhase)+" ns")
if triggerPhase > -1:
pg.SetTriggerPhase(channel, triggerPhase)
else:
print(" (Skipping this calibration, no calibration data (-1) provided.)")
def TestSinglePulse(pg, channel, start, width, polarity):
pg.ResetBadState(channel)
pg.CreateSinglePulse(channel, start, width, polarity)
pg.EnableChannel(channel)
pg.StartChannel(channel)
def StopAndDisable(pg, channel):
pg.StopChannel(channel)
pg.DisableChannel(channel)
def PrintFrequency(pg):
frequency = pg.GetFrequency()
if frequency is not None:
print("RF clock frequency: "+str(frequency/1.0e6) + " MHz")
else:
print("RF clock frequency: (unstable)")
def StrPolarity(polarity):
if polarity == 1:
return "positive polarity"
elif polarity == 0:
return "negative polarity"
else:
return "unknown polarity"
while True:
fmcSlot = -1
while (fmcSlot < 0) or (fmcSlot > 1):
try:
fmcSlot = int(raw_input("Enter FMC slot to test (0 - lower FMC1, 1 - upper FMC2): "))
except KeyboardInterrupt:
print ""
sys.exit()
except:
fmcSlot = -1
channel = -1
while (channel < 1) or (channel > 2):
try:
channel = int(raw_input("Enter channel number to test (1, 2): "))
except KeyboardInterrupt:
print ""
sys.exit()
except:
channel = -1
# create FFPG driver for FMC slot
pg = Ffpg(fmcSlot)
# initialize the driver
Init(pg)
# test LEDs
pg.LedModeTest()
raw_input("All LEDs should be blinking... (press Enter)")
pg.LedModeNormal()
# test pulses
start = 10
width = 10
polarity = 1
TestSinglePulse(pg, channel, start, width, polarity)
raw_input("Basic pulse generated (start = "+str(start)+" ns, width = "+str(width)+" ns, "+StrPolarity(polarity)+") (press Enter)")
for start in range (10, 13+1):
TestSinglePulse(pg, channel, start, width, polarity)
raw_input("Shifting start of the pulse (start = "+str(start)+" ns, width = "+str(width)+" ns, "+StrPolarity(polarity)+") (press Enter)")
for width in range (10, 13+1):
TestSinglePulse(pg, channel, start, width, polarity)
raw_input("Shifting width of the pulse (start = "+str(start)+" ns, width = "+str(width)+" ns, "+StrPolarity(polarity)+") (press Enter)")
# stop and disable channel
StopAndDisable(pg, channel)
# print some info
pg.PrintVersion()
print("Actual temperature: "+str(pg.temp.ReadTemperature()) + " °C")
PrintFrequency(pg)
pg.PrintControl()
pg.PrintStatus()
pg.PrintDebug()
cont = raw_input("Next test? (n - quit, Enter - next test): ")
if cont == "n":
break
......@@ -31,6 +31,7 @@
## 2016-09-06 1.0 Jan Pospisil Derived from the initial example driver
## 2016-09-07 1.1 Jan Pospisil added AD9512 OUT4 fine delay
## 2016-09-21 1.2 Jan Pospisil added GetFmcSlot and LED test methods
## 2017-09-14 1.2.1 Jan Pospisil reformatted, added comments
##-----------------------------------------------------------------------------
import time
......@@ -62,40 +63,15 @@ class Ffpg(object):
def GetFmcSlot(self):
return self.fmcSlot
def GetVersion(self):
version = self.wb.Read("version")
major = (version >> 22) & ((2**10)-1)
minor = (version >> 12) & ((2**10)-1)
revision = version & ((2**12)-1)
return [major, minor, revision]
def PrintVersion(self):
[major, minor, revision] = self.GetVersion()
print("Gateware version: "+str(major)+"."+str(minor)+"."+str(revision))
def CompareVersion(self, major, minor, revision):
"""
check actual version against provided version
returns:
0 - versions are same
1 - actual version is newer than argument provided
-1 - actual version is older than argument provided
"""
[actualMajor, actualMinor, actualRevision] = self.GetVersion()
if actualMajor > major:
return 1
if actualMajor < major:
return -1
if actualMinor > minor:
return 1
if actualMinor < minor:
return -1
if actualRevision > revision:
return 1
if actualRevision < revision:
return -1
return 0
#######################
# Common Configuration
#######################
def Reset(self):
self.ClearMemory(0, "*")
self.wb.Write("control", 0)
def SelectClock(self, clockType, frequency):
"""
......@@ -123,14 +99,14 @@ class Ffpg(object):
def GetRatio(self):
ratiom1 = self.wb.Read("clock_ratio_m1")
return ratiom1+1
def SetOverflow(self, overflow):
self.wb.Write("overflow", overflow)
def Ad9512Sync(self):
""" synchronize AD9512 dividers """
self.wb.SetBits("control", 1<<9)
def Ad9512ActivateWbAccess(self):
self.wb.SetBits("control", 1<<11)
......@@ -155,134 +131,31 @@ class Ffpg(object):
code &= (2**bits)-1
self.wb.Write("vcxo_voltage", code)
def GetFrequency(self):
# return frequency of RF clock in Hz, if stable, None otherwise
status = self.wb.Read("status")
if (status & (1<<8) == 0):
return None
else:
return self.wb.Read("frequency")
def ClearMemory(self, channel, memory, memoryPart = -1):
def SetClockFineDelay(self, fineDelay, current = 0, capacitors = 0):
"""
channel = 1 | 2 | 0 (for both)
memory = 'set' | 'res' | '*' (for both memories)
if memoryPart > -1: clear only first memoryPart words
enable and set AD9512 OUT4 output fine delay
fineDelay = <0, 31>
"""
if channel == 0:
self.ClearMemory(1, memory, memoryPart)
self.ClearMemory(2, memory, memoryPart)
return
assert (memory == "set") or (memory == "res") or (memory == "*"), "Bad memory!"
if memory == "*":
self.ClearMemory(channel, "set", memoryPart)
self.ClearMemory(channel, "res", memoryPart)
return
partToClear = 2048
to = -1
if memoryPart > -1:
partToClear = memoryPart
to = memoryPart
self.wb.WriteMulti(self._GetChannelRegName(channel, memory+"_mem"), (0,)*partToClear, to = to)
# enable fine delay
self.wb.SetBits("control", (1<<10))
# set fine delay
fineDelayReg = fineDelay & 0x1F
fineDelayReg |= (current & 0x7) << 5
fineDelayReg |= (capacitors & 0x7) << 8
self.wb.Write("fine_delay", fineDelayReg)
def Reset(self):
self.ClearMemory(0, "*")
self.wb.Write("control", 0)
def DisableClockFineDelay(self):
self.wb.SetBits("control", (1<<10), 0)
#######################
# Pulse Channel Configuration
#######################
def _GetChannelRegName(self, channel, name):
assert (channel == 1) or (channel == 2), "Bad channel \"" + str(channel) + "\", we have only 2 channels!"
return "ch" + str(channel) + "_" + name
def GetClockPeriod(self):
""" return clock period in [ns] of the RF clock coming to the FPGA """
return 1000.0/(self.inputClockFrequency/self.GetRatio())
def SetFineDelay(self, channel, memory, delayValue):
"""
channel = 1 | 2
memory = "set" | "res"
delayValue in [ns]
"""
# compensate for calibration
delayValue += self.fineTriggerPhase[channel]
# calculate delay binary value
binaryValue = int(round(delayValue * 100)) # step is 10 ps
assert binaryValue < 2**10, "Fine delay is larger than the IC can handle!"
# set delay
self.wb.Write(self._GetChannelRegName(channel, "delay_"+memory), binaryValue)
def SetTriggerPhase(self, channel, phase):
""" phase in [ns] - depends on setup, cable length... """
clockPeriod = self.GetClockPeriod()
bitPhase = int(phase/clockPeriod) # advance output pulse
finePhase = phase - bitPhase * clockPeriod # delay output pulse
if finePhase > 0:
bitPhase += 1
finePhase = clockPeriod - finePhase
self.wb.Write(self._GetChannelRegName(channel, "trig_latency"), bitPhase)
self.fineTriggerPhase[channel] = finePhase
self.SetFineDelay(channel, "set", 0)
def PrintStatus(self):
print("Status register:")
status = self.wb.Read("status")
PrintBit(status, 0, "Clock infrastructure configuration", "busy", "idle")
PrintBit(status, 1, "VCXO DAC", "busy", "idle")
PrintBit(status, 2, "Trigger threshold DAC", "busy", "idle")
PrintBit(status, 3, "Delay configuration", "busy", "idle")
PrintBit(status, 4, "Channel 1 output", "enabled", "disabled")
PrintBit(status, 5, "Channel 2 output", "enabled", "disabled")
PrintBit(status, 6, "Channel 1", "running", "stopped")
PrintBit(status, 7, "Channel 2", "running", "stopped")
PrintBit(status, 8, "Input clock stability", "stable", "not stable/present")
def PrintControl(self):
print("Control register: ")
control = self.wb.Read("control")
PrintBits(control, 0, 1, "Clock selection", ("external clock", "FPGA LOOP", "on-board VCXO"))
PrintBit(control, 2, "Channel 1 output", "enabled", "disabled")
PrintBit(control, 3, "Channel 2 output", "enabled", "disabled")
PrintBits(control, 4, 5, "Channel 1 mode", ("stopped", "continuous", "one-shot"))
PrintBits(control, 6, 7, "Channel 2 mode", ("stopped", "continuous", "one-shot"))
PrintBit(control, 8, "LEDs", "blinking", "normal operation")
PrintBit(control, 9, "AD9512 Synchronization", "in progress", "done")
def PrintDebug(self):
print("Debug:")
debug = self.wb.Read("debug")
PrintBits(debug, 0, 2, "CH1 FSM state", ("Stop", "WaitForTrigger", "Generating", "Outputting"))
PrintBits(debug, 3, 5, "CH2 FSM state", ("Stop", "WaitForTrigger", "Generating", "Outputting"))
def EnableChannel(self, channel):
channel -= 1
channel %= 2
self.wb.SetBits("control", 0x4<<channel, 0x4<<channel) # set output enable
def DisableChannel(self, channel):
channel -= 1
channel %= 2
self.wb.SetBits("control", 0x4<<channel, 0) # set output disable
def StartChannel(self, channel):
channel -= 1
channel %= 2
channel *= 2
self.wb.SetBits("control", 0x30<<channel, 0x10<<channel) # set mode CONT.
def StartChannelOnce(self, channel):
channel -= 1
channel %= 2
channel *= 2
self.wb.SetBits("control", 0x30<<channel, 0x20<<channel) # set mode ONE-SHOT
def StopChannel(self, channel):
channel -= 1
channel %= 2
channel *= 2
self.wb.SetBits("control", 0x30<<channel, 0) # set mode STOP
def _ConfigurePulse(self, channel, setIndex, resIndex, pulse, which = 0):
"""
Manipulate the SET and RESET pulse generator arrays:
......@@ -322,19 +195,55 @@ class Ffpg(object):
shift = resIndex%32
self.wb.SetBitsMulti(self._GetChannelRegName(channel, "res_mem"), ((1<<shift),), ((pulse<<shift),), start=position, to=position+1)
def ResetBadState(self, channel):
""" robust reset function to get out of the bad `state 1` """
self.DisableChannel( channel ) #Outputs Hi-Z
self.StopChannel( channel ) #Stop clock
self.ClearMemory( channel, "*" ) #Clear the pulse generator arrays
# Define a single SET pulse per cycle,
# after two cycles, the MC100EP140 should be in the good
# state number 3 (output high)
self._ConfigurePulse(channel, 10, 50, 1, 1)
self.StartChannel( channel ) #Enable clock
time.sleep( 1e-3 )
self.StopChannel( channel )
self.ClearMemory( channel, "*" )
def SetTriggerPhase(self, channel, phase):
""" phase in [ns] - depends on setup, cable length... """
clockPeriod = self.GetClockPeriod()
bitPhase = int(phase/clockPeriod) # advance output pulse
finePhase = phase - bitPhase * clockPeriod # delay output pulse
if finePhase > 0:
bitPhase += 1
finePhase = clockPeriod - finePhase
self.wb.Write(self._GetChannelRegName(channel, "trig_latency"), bitPhase)
self.fineTriggerPhase[channel] = finePhase
self.SetFineDelay(channel, "set", 0)
def SetFineDelay(self, channel, memory, delayValue):
"""
channel = 1 | 2
memory = "set" | "res"
delayValue in [ns]
"""
# compensate for calibration
delayValue += self.fineTriggerPhase[channel]
# calculate delay binary value
binaryValue = int(round(delayValue * 100)) # step is 10 ps
assert binaryValue < 2**10, "Fine delay is larger than the IC can handle!"
# set delay
self.wb.Write(self._GetChannelRegName(channel, "delay_"+memory), binaryValue)
def ClearMemory(self, channel, memory, memoryPart = -1):
"""
channel = 1 | 2 | 0 (for both)
memory = 'set' | 'res' | '*' (for both memories)
if memoryPart > -1: clear only first memoryPart words
"""
if channel == 0:
self.ClearMemory(1, memory, memoryPart)
self.ClearMemory(2, memory, memoryPart)
return
assert (memory == "set") or (memory == "res") or (memory == "*"), "Bad memory!"
if memory == "*":
self.ClearMemory(channel, "set", memoryPart)
self.ClearMemory(channel, "res", memoryPart)
return
partToClear = 2048
to = -1
if memoryPart > -1:
partToClear = memoryPart
to = memoryPart
self.wb.WriteMulti(self._GetChannelRegName(channel, memory+"_mem"), (0,)*partToClear, to = to)
def CreateSinglePulse(self, channel, start, width, polarity = 1):
"""
......@@ -362,7 +271,7 @@ class Ffpg(object):
self.SetFineDelay(channel, "res", stopFineDelay)
self._ConfigurePulse(channel, startBit, stopBit, 1)
def CreateBunchPulses(self, channel, pulseDelay, pulseWidth, bunches, polarity = 1):
"""
create set of pulses inside LHC bunches
......@@ -424,21 +333,130 @@ class Ffpg(object):
else:
self._ConfigurePulse(channel, 0, 0, 1, 1) # only one set bit
def SetClockFineDelay(self, fineDelay, current = 0, capacitors = 0):
def ResetBadState(self, channel):
""" robust reset function to get out of the bad `state 1` """
self.DisableChannel( channel ) #Outputs Hi-Z
self.StopChannel( channel ) #Stop clock
self.ClearMemory( channel, "*" ) #Clear the pulse generator arrays
# Define a single SET pulse per cycle,
# after two cycles, the MC100EP140 should be in the good
# state number 3 (output high)
self._ConfigurePulse(channel, 10, 50, 1, 1)
self.StartChannel( channel ) #Enable clock
time.sleep( 1e-3 )
self.StopChannel( channel )
self.ClearMemory( channel, "*" )
def EnableChannel(self, channel):
channel -= 1
channel %= 2
self.wb.SetBits("control", 0x4<<channel, 0x4<<channel) # set output enable
def DisableChannel(self, channel):
channel -= 1
channel %= 2
self.wb.SetBits("control", 0x4<<channel, 0) # set output disable
def StartChannel(self, channel):
channel -= 1
channel %= 2
channel *= 2
self.wb.SetBits("control", 0x30<<channel, 0x10<<channel) # set mode CONT.
def StartChannelOnce(self, channel):
channel -= 1
channel %= 2
channel *= 2
self.wb.SetBits("control", 0x30<<channel, 0x20<<channel) # set mode ONE-SHOT
def StopChannel(self, channel):
channel -= 1
channel %= 2
channel *= 2
self.wb.SetBits("control", 0x30<<channel, 0) # set mode STOP
#######################
# Reporting, Debugging
#######################
def GetVersion(self):
version = self.wb.Read("version")
major = (version >> 22) & ((2**10)-1)
minor = (version >> 12) & ((2**10)-1)
revision = version & ((2**12)-1)
return [major, minor, revision]
def PrintVersion(self):
[major, minor, revision] = self.GetVersion()
print("Gateware version: "+str(major)+"."+str(minor)+"."+str(revision))
def CompareVersion(self, major, minor, revision):
"""
enable and set AD9512 OUT4 output fine delay
fineDelay = <0, 31>
check actual version against provided version
returns:
0 - versions are same
1 - actual version is newer than argument provided
-1 - actual version is older than argument provided
"""
# enable fine delay
self.wb.SetBits("control", (1<<10))
# set fine delay
fineDelayReg = fineDelay & 0x1F
fineDelayReg |= (current & 0x7) << 5
fineDelayReg |= (capacitors & 0x7) << 8
self.wb.Write("fine_delay", fineDelayReg)
def DisableClockFineDelay(self):
self.wb.SetBits("control", (1<<10), 0)
[actualMajor, actualMinor, actualRevision] = self.GetVersion()
if actualMajor > major:
return 1
if actualMajor < major:
return -1
if actualMinor > minor:
return 1
if actualMinor < minor:
return -1
if actualRevision > revision:
return 1
if actualRevision < revision:
return -1
return 0
def GetFrequency(self):
# return frequency of RF clock in Hz, if stable, None otherwise
status = self.wb.Read("status")
if (status & (1<<8) == 0):
return None
else:
return self.wb.Read("frequency")
def GetClockPeriod(self):
""" return clock period in [ns] of the RF clock coming to the FPGA """
return 1000.0/(self.inputClockFrequency/self.GetRatio())
def PrintStatus(self):
print("Status register:")
status = self.wb.Read("status")
PrintBit(status, 0, "Clock infrastructure configuration", "busy", "idle")
PrintBit(status, 1, "VCXO DAC", "busy", "idle")
PrintBit(status, 2, "Trigger threshold DAC", "busy", "idle")
PrintBit(status, 3, "Delay configuration", "busy", "idle")
PrintBit(status, 4, "Channel 1 output", "enabled", "disabled")
PrintBit(status, 5, "Channel 2 output", "enabled", "disabled")
PrintBit(status, 6, "Channel 1", "running", "stopped")
PrintBit(status, 7, "Channel 2", "running", "stopped")
PrintBit(status, 8, "Input clock stability", "stable", "not stable/present")
def PrintControl(self):
print("Control register: ")
control = self.wb.Read("control")
PrintBits(control, 0, 1, "Clock selection", ("external clock", "FPGA LOOP", "on-board VCXO"))
PrintBit(control, 2, "Channel 1 output", "enabled", "disabled")
PrintBit(control, 3, "Channel 2 output", "enabled", "disabled")
PrintBits(control, 4, 5, "Channel 1 mode", ("stopped", "continuous", "one-shot"))
PrintBits(control, 6, 7, "Channel 2 mode", ("stopped", "continuous", "one-shot"))
PrintBit(control, 8, "LEDs", "blinking", "normal operation")
PrintBit(control, 9, "AD9512 Synchronization", "in progress", "done")
PrintBit(control, 10, "AD9512 Fine Delay", "enabled", "disabled")
PrintBit(control, 11, "AD9512 SPI mode", "WB bridge", "automatic mode")
def PrintDebug(self):
print("Debug:")
debug = self.wb.Read("debug")
PrintBits(debug, 0, 2, "CH1 FSM state", ("Stop", "WaitForTrigger", "Generating", "Outputting"))
PrintBits(debug, 3, 5, "CH2 FSM state", ("Stop", "WaitForTrigger", "Generating", "Outputting"))
def LedModeTest(self):
self.wb.SetBits("control", (1<<8))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment