forked from M-Labs/artiq
phaser: init [wip]
This commit is contained in:
parent
3a79ef740b
commit
07418258ae
@ -1,6 +1,6 @@
|
||||
from artiq.language.core import kernel, delay_mu, delay
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
from artiq.language.units import us, ns, MHz
|
||||
from artiq.language.units import us, ns, ms, MHz, dB
|
||||
from artiq.language.types import TInt32
|
||||
|
||||
|
||||
@ -50,6 +50,8 @@ PHASER_STA_SPI_IDLE = 1 << 5
|
||||
PHASER_DAC_SEL_DUC = 0
|
||||
PHASER_DAC_SEL_TEST = 1
|
||||
|
||||
PHASER_HW_REV_VARIANT = 1 << 4
|
||||
|
||||
|
||||
class Phaser:
|
||||
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
|
||||
@ -130,14 +132,73 @@ class Phaser:
|
||||
def init(self):
|
||||
"""Initialize the board.
|
||||
|
||||
Verifies board presence by reading the board ID register.
|
||||
Does not alter any state.
|
||||
Verifies board and chip presence, resets components, performs communication
|
||||
and configuration tests and establishes initial conditions.
|
||||
"""
|
||||
board_id = self.read8(PHASER_ADDR_BOARD_ID)
|
||||
if board_id != PHASER_BOARD_ID:
|
||||
raise ValueError("invalid board id")
|
||||
delay(20*us) # slack
|
||||
|
||||
# allow a few errors during startup and alignment
|
||||
if self.get_crc_err() > 20:
|
||||
raise ValueError("large number of CRC errors")
|
||||
delay(.1*ms) # slack
|
||||
|
||||
self.set_cfg(dac_resetb=0, att0_rstn=0, att1_rstn=0)
|
||||
self.set_leds(0x00)
|
||||
self.set_fan_mu(0)
|
||||
self.set_cfg() # bring everything out of reset
|
||||
delay(.1*ms) # slack
|
||||
|
||||
# 4 wire SPI, sif4_enable
|
||||
self.dac_write(0x02, 0x0082)
|
||||
if self.dac_read(0x7f) != 0x5409:
|
||||
raise ValueError("DAC version readback invalid")
|
||||
delay(.1*ms)
|
||||
if self.dac_read(0x00) != 0x049c:
|
||||
raise ValueError("DAC config0 readback invalid")
|
||||
delay(.1*ms)
|
||||
|
||||
t = self.get_dac_temperature()
|
||||
if t < 10 or t > 90:
|
||||
raise ValueError("DAC temperature out of bounds")
|
||||
delay(.1*ms)
|
||||
|
||||
patterns = [
|
||||
[0xffff, 0xffff, 0x0000, 0x0000], # test channel
|
||||
[0xaa55, 0x55aa, 0x55aa, 0xaa5a], # test iq
|
||||
[0xaa55, 0xaa55, 0x55aa, 0x55aa], # test byte
|
||||
[0x7a7a, 0xb6b6, 0xeaea, 0x4545], # ds pattern a
|
||||
[0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # ds pattern b
|
||||
]
|
||||
delay(.5*ms)
|
||||
for i in range(len(patterns)):
|
||||
errors = self.dac_iotest(patterns[i])
|
||||
if errors:
|
||||
raise ValueError("iotest error")
|
||||
delay(.5*ms)
|
||||
|
||||
hw_rev = self.read8(PHASER_ADDR_HW_REV)
|
||||
has_upconverter = hw_rev & PHASER_HW_REV_VARIANT
|
||||
delay(.1*ms) # slack
|
||||
|
||||
for ch in range(2):
|
||||
# test attenuator write and readback
|
||||
self.channel[ch].set_att_mu(0x55)
|
||||
if self.channel[ch].get_att_mu() != 0x55:
|
||||
raise ValueError("attenuator test failed")
|
||||
delay(.1*ms)
|
||||
self.channel[ch].set_att(31.5*dB)
|
||||
|
||||
# dac test data readback
|
||||
dac_test = [0x10102020, 0x30304040]
|
||||
self.channel[ch].set_duc_cfg(select=1)
|
||||
self.channel[ch].set_dac_test(dac_test[ch])
|
||||
if self.channel[ch].get_dac_data() != dac_test[ch]:
|
||||
raise ValueError("DAC test data readback failed")
|
||||
delay(.1*ms)
|
||||
|
||||
@kernel
|
||||
def write8(self, addr, data):
|
||||
"""Write data to FPGA register.
|
||||
@ -201,7 +262,7 @@ class Phaser:
|
||||
"""
|
||||
pwm = int32(round(duty*255.))
|
||||
if pwm < 0 or pwm > 255:
|
||||
raise ValueError("invalid duty cycle")
|
||||
raise ValueError("duty cycle out of bounds")
|
||||
self.set_fan_mu(pwm)
|
||||
|
||||
@kernel
|
||||
@ -278,9 +339,9 @@ class Phaser:
|
||||
:param length: SPI transfer length (1 to 8 bits)
|
||||
"""
|
||||
if div < 2 or div > 257:
|
||||
raise ValueError("invalid divider")
|
||||
raise ValueError("divider out of bounds")
|
||||
if length < 1 or length > 8:
|
||||
raise ValueError("invalid length")
|
||||
raise ValueError("length out of bounds")
|
||||
self.write8(PHASER_ADDR_SPI_SEL, select)
|
||||
self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5))
|
||||
self.write8(PHASER_ADDR_SPI_CFG,
|
||||
@ -339,6 +400,52 @@ class Phaser:
|
||||
data |= self.spi_read()
|
||||
return data
|
||||
|
||||
@kernel
|
||||
def get_dac_temperature(self) -> TInt32:
|
||||
"""Read the DAC die temperature.
|
||||
|
||||
:return: DAC temperature in degree Celsius
|
||||
"""
|
||||
return self.dac_read(0x06, div=257) >> 8
|
||||
|
||||
@kernel
|
||||
def dac_iotest(self, pattern) -> TInt32:
|
||||
"""Performs a DAC IO test according to the datasheet.
|
||||
|
||||
:param patterm: List of four int32 containing the pattern
|
||||
:return: Bit error mask (16 bits)
|
||||
"""
|
||||
for addr in range(len(pattern)):
|
||||
self.dac_write(0x25 + addr, pattern[addr])
|
||||
self.dac_write(0x29 + addr, pattern[addr])
|
||||
delay(.1*ms)
|
||||
for ch in range(2):
|
||||
self.channel[ch].set_duc_cfg(select=1) # test
|
||||
# dac test data is i msb, q lsb
|
||||
self.channel[ch].set_dac_test(pattern[2*ch] | (pattern[2*ch + 1] << 16))
|
||||
self.dac_write(0x01, 0x8000) # iotest_ena
|
||||
errors = 0
|
||||
# A data delay of 3*50 ps heuristically matches FPGA+board+DAC skews.
|
||||
# There is plenty of margin and no need to tune at runtime.
|
||||
# Parity provides another level of safety.
|
||||
for dly in [-3]: # range(-7, 8)
|
||||
if dly < 0:
|
||||
dly = -dly << 3 # data delay, else clock delay
|
||||
self.dac_write(0x24, dly << 10)
|
||||
self.dac_write(0x04, 0x0000) # clear iotest_result
|
||||
delay(.2*ms) # let it rip
|
||||
# no need to go through the alarm register,
|
||||
# just read the error mask
|
||||
# self.dac_write(0x05, 0x0000) # clear alarms
|
||||
# alarm = self.dac_read(0x05)
|
||||
# delay(.1*ms) # slack
|
||||
# if alarm & 0x0080: # alarm_from_iotest
|
||||
errors |= self.dac_read(0x04)
|
||||
delay(.1*ms) # slack
|
||||
self.dac_write(0x24, 0) # reset delays
|
||||
# self.dac_write(0x01, 0x0000) # clear config
|
||||
return errors
|
||||
|
||||
|
||||
class PhaserChannel:
|
||||
"""Phaser channel IQ pair.
|
||||
@ -459,7 +566,7 @@ class PhaserChannel:
|
||||
# 2 lsb are inactive, resulting in 8 LSB per dB
|
||||
data = 0xff - int32(round(att*8))
|
||||
if data < 0 or data > 0xff:
|
||||
raise ValueError("invalid attenuation")
|
||||
raise ValueError("attenuation out of bounds")
|
||||
self.set_att_mu(data)
|
||||
|
||||
@kernel
|
||||
@ -583,6 +690,6 @@ class PhaserOscillator:
|
||||
"""
|
||||
asf = int32(round(amplitude*0x7fff))
|
||||
if asf < 0 or asf > 0x7fff:
|
||||
raise ValueError("invalid amplitude")
|
||||
raise ValueError("amplitude out of bounds")
|
||||
pow = int32(round(phase*(1 << 16)))
|
||||
self.set_amplitude_phase_mu(asf, pow, clr)
|
||||
|
Loading…
Reference in New Issue
Block a user