Merge pull request #1657 from pathfinder49/phaser

This commit is contained in:
Robert Jördens 2021-05-12 12:54:05 +02:00 committed by GitHub
commit 67d474e6cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 157 additions and 20 deletions

View File

@ -9,8 +9,17 @@ ARTIQ-7
Highlights: Highlights:
* WRPLL * WRPLL
* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912 * ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912
* Phaser:
- Improved documentation
- Expose the DAC coarse mixer and sif_sync
- Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs.
Breaking changes: Breaking changes:
* Updated Phaser-Upconverter default frequency 2.875 GHz. The new default uses the target PFD
frequency of the hardware design.
* `Phaser.init()` now disables all Kasli-oscillators. This avoids full power RF output being
generated for some configurations.
* Phaser: fixed coarse mixer frequency configuration
ARTIQ-6 ARTIQ-6

View File

@ -110,7 +110,7 @@ class DAC34H84:
syncsel_mixercd = 0b1001 # sif_sync and register write syncsel_mixercd = 0b1001 # sif_sync and register write
syncsel_nco = 0b1000 # sif_sync syncsel_nco = 0b1000 # sif_sync
syncsel_fifo_input = 0b10 # external lvds istr syncsel_fifo_input = 0b10 # external lvds istr
sif_sync = 1 sif_sync = 0
syncsel_fifoin = 0b0010 # istr syncsel_fifoin = 0b0010 # istr
syncsel_fifoout = 0b0100 # ostr syncsel_fifoout = 0b0100 # ostr
@ -178,7 +178,8 @@ class DAC34H84:
(self.collisiongone_ena << 12) | (self.sif4_ena << 7) | (self.collisiongone_ena << 12) | (self.sif4_ena << 7) |
(self.mixer_ena << 6) | (self.mixer_gain << 5) | (self.mixer_ena << 6) | (self.mixer_gain << 5) |
(self.nco_ena << 4) | (self.revbus << 3) | (self.twos << 1)) (self.nco_ena << 4) | (self.revbus << 3) | (self.twos << 1))
mmap.append((0x03 << 16) | (self.coarse_dac << 12) | (self.sif_txenable << 0)) mmap.append((0x03 << 16) | (self.coarse_dac << 12) |
(self.sif_txenable << 0))
mmap.append( mmap.append(
(0x07 << 16) | (0x07 << 16) |
(self.mask_alarm_from_zerochk << 15) | (1 << 14) | (self.mask_alarm_from_zerochk << 15) | (1 << 14) |
@ -200,7 +201,7 @@ class DAC34H84:
mmap.append( mmap.append(
(0x0d << 16) | (0x0d << 16) |
(self.cmix_fs8 << 15) | (self.cmix_fs4 << 14) | (self.cmix_fs8 << 15) | (self.cmix_fs4 << 14) |
(self.cmix_fs2 << 12) | (self.cmix_nfs4 << 11) | (self.cmix_fs2 << 13) | (self.cmix_nfs4 << 12) |
(self.qmc_gainb << 0)) (self.qmc_gainb << 0))
mmap.append((0x0e << 16) | (self.qmc_gainc << 0)) mmap.append((0x0e << 16) | (self.qmc_gainc << 0))
mmap.append( mmap.append(

View File

@ -1,6 +1,8 @@
from numpy import int32, int64
from artiq.language.core import kernel, delay_mu, delay from artiq.language.core import kernel, delay_mu, delay
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.language.units import us, ns, ms, MHz, dB from artiq.language.units import us, ns, ms, MHz
from artiq.language.types import TInt32 from artiq.language.types import TInt32
from artiq.coredevice.dac34h84 import DAC34H84 from artiq.coredevice.dac34h84 import DAC34H84
from artiq.coredevice.trf372017 import TRF372017 from artiq.coredevice.trf372017 import TRF372017
@ -84,7 +86,8 @@ class Phaser:
LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas
Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation, Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation,
quadrature modulator compensation, fine and coarse mixing as well as group quadrature modulator compensation, fine and coarse mixing as well as group
delay capabilities are available. delay capabilities are available. If desired, these features my be
configured via the `dac` dictionary.
The latency/group delay from the RTIO events setting The latency/group delay from the RTIO events setting
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the
@ -184,6 +187,9 @@ class Phaser:
is_baseband = hw_rev & PHASER_HW_REV_VARIANT is_baseband = hw_rev & PHASER_HW_REV_VARIANT
gw_rev = self.read8(PHASER_ADDR_GW_REV) gw_rev = self.read8(PHASER_ADDR_GW_REV)
if debug:
print(gw_rev)
self.core.break_realtime()
delay(.1*ms) # slack delay(.1*ms) # slack
# allow a few errors during startup and alignment since boot # allow a few errors during startup and alignment since boot
@ -224,6 +230,8 @@ class Phaser:
for data in self.dac_mmap: for data in self.dac_mmap:
self.dac_write(data >> 16, data) self.dac_write(data >> 16, data)
delay(40*us) delay(40*us)
self.dac_sync()
delay(40*us)
# pll_ndivsync_ena disable # pll_ndivsync_ena disable
config18 = self.dac_read(0x18) config18 = self.dac_read(0x18)
@ -272,6 +280,16 @@ class Phaser:
else: else:
raise ValueError("DAC alarm") raise ValueError("DAC alarm")
# avoid malformed output for: mixer_ena=1, nco_ena=0 after power up
self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2] | (1 << 4))
delay(40*us)
self.dac_sync()
delay(100*us)
self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2])
delay(40*us)
self.dac_sync()
delay(100*us)
# power up trfs, release att reset # power up trfs, release att reset
self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) self.set_cfg(clk_sel=self.clk_sel, dac_txena=0)
@ -299,6 +317,9 @@ class Phaser:
self.duc_stb() self.duc_stb()
delay(.1*ms) # settle link, pipeline and impulse response delay(.1*ms) # settle link, pipeline and impulse response
data = channel.get_dac_data() data = channel.get_dac_data()
delay(1*us)
channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000,
clr=1)
delay(.1*ms) delay(.1*ms)
sqrt2 = 0x5a81 # 0x7fff/sqrt(2) sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
data_i = data & 0xffff data_i = data & 0xffff
@ -318,6 +339,7 @@ class Phaser:
delay(.2*ms) delay(.2*ms)
for data in channel.trf_mmap: for data in channel.trf_mmap:
channel.trf_write(data) channel.trf_write(data)
channel.cal_trf_vco()
delay(2*ms) # lock delay(2*ms) # lock
if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)): if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)):
@ -326,6 +348,7 @@ class Phaser:
if channel.trf_read(0) & 0x1000: if channel.trf_read(0) & 0x1000:
raise ValueError("TRF R_SAT_ERR") raise ValueError("TRF R_SAT_ERR")
delay(.1*ms) delay(.1*ms)
channel.en_trf_out()
# enable dac tx # enable dac tx
self.set_cfg(clk_sel=self.clk_sel) self.set_cfg(clk_sel=self.clk_sel)
@ -549,6 +572,48 @@ class Phaser:
""" """
return self.dac_read(0x06, div=257) >> 8 return self.dac_read(0x06, div=257) >> 8
@kernel
def dac_sync(self):
"""Trigger DAC synchronisation for both output channels.
The DAC sif_sync is de-asserts, then asserted. The synchronisation is
triggered on assertion.
By default, the fine-mixer (NCO) and QMC are synchronised. This
includes applying the latest register settings.
The synchronisation sources may be configured through the `syncsel_x`
fields in the `dac` configuration dictionary (see `__init__()`).
.. note:: Synchronising the NCO clears the phase-accumulator
"""
config1f = self.dac_read(0x1f)
delay(.1*ms)
self.dac_write(0x1f, config1f & ~int32(1 << 1))
self.dac_write(0x1f, config1f | (1 << 1))
@kernel
def set_dac_cmix(self, fs_8_step):
"""Set the DAC coarse mixer frequency for both channels
Use of the coarse mixer requires the DAC mixer to be enabled. The mixer
can be configured via the `dac` configuration dictionary (see
`__init__()`).
The selected coarse mixer frequency becomes active without explicit
synchronisation.
:param fs_8_step: coarse mixer frequency shift in 125 MHz steps. This
should be an integer between -3 and 4 (inclusive).
"""
# values recommended in data-sheet
# 0 1 2 3 4 -3 -2 -1
vals = [0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0001, 0b1110]
cmix = vals[fs_8_step%8]
config0d = self.dac_read(0x0d)
delay(.1*ms)
self.dac_write(0x0d, (config0d & ~(0b1111 << 12)) | (cmix << 12))
@kernel @kernel
def get_dac_alarms(self): def get_dac_alarms(self):
"""Read the DAC alarm flags. """Read the DAC alarm flags.
@ -684,6 +749,7 @@ class PhaserChannel:
self.phaser = phaser self.phaser = phaser
self.index = index self.index = index
self.trf_mmap = TRF372017(trf).get_mmap() self.trf_mmap = TRF372017(trf).get_mmap()
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
@kernel @kernel
@ -761,6 +827,12 @@ class PhaserChannel:
def set_nco_frequency_mu(self, ftw): def set_nco_frequency_mu(self, ftw):
"""Set the NCO frequency. """Set the NCO frequency.
This method stages the new NCO frequency, but does not apply it.
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see
`__init__()`).
:param ftw: NCO frequency tuning word (32 bit) :param ftw: NCO frequency tuning word (32 bit)
""" """
self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16) self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16)
@ -770,6 +842,12 @@ class PhaserChannel:
def set_nco_frequency(self, frequency): def set_nco_frequency(self, frequency):
"""Set the NCO frequency in SI units. """Set the NCO frequency in SI units.
This method stages the new NCO frequency, but does not apply it.
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see
`__init__()`).
:param frequency: NCO frequency in Hz (passband from -400 MHz :param frequency: NCO frequency in Hz (passband from -400 MHz
to 400 MHz, wrapping around at +- 500 MHz) to 400 MHz, wrapping around at +- 500 MHz)
""" """
@ -780,6 +858,16 @@ class PhaserChannel:
def set_nco_phase_mu(self, pow): def set_nco_phase_mu(self, pow):
"""Set the NCO phase offset. """Set the NCO phase offset.
By default, the new NCO phase applies on completion of the SPI
transfer. This also causes a staged NCO frequency to be applied.
Different triggers for applying nco settings may be configured through
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see
`__init__()`).
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see
`__init__()`).
:param pow: NCO phase offset word (16 bit) :param pow: NCO phase offset word (16 bit)
""" """
self.phaser.dac_write(0x12 + self.index, pow) self.phaser.dac_write(0x12 + self.index, pow)
@ -788,10 +876,20 @@ class PhaserChannel:
def set_nco_phase(self, phase): def set_nco_phase(self, phase):
"""Set the NCO phase in SI units. """Set the NCO phase in SI units.
By default, the new NCO phase applies on completion of the SPI
transfer. This also causes a staged NCO frequency to be applied.
Different triggers for applying nco settings may be configured through
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see
`__init__()`).
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see
`__init__()`).
:param phase: NCO phase in turns :param phase: NCO phase in turns
""" """
pow = int32(round(phase*(1 << 16))) pow = int32(round(phase*(1 << 16)))
self.set_duc_phase_mu(pow) self.set_nco_phase_mu(pow)
@kernel @kernel
def set_att_mu(self, data): def set_att_mu(self, data):
@ -886,6 +984,32 @@ class PhaserChannel:
return self.trf_write(0x00000008 | (cnt_mux_sel << 27), return self.trf_write(0x00000008 | (cnt_mux_sel << 27),
readback=True) readback=True)
@kernel
def cal_trf_vco(self):
"""Start calibration of the upconverter (hardware variant) VCO.
TRF outputs should be disabled during VCO calibration.
"""
self.trf_write(self.trf_mmap[1] | (1 << 31))
@kernel
def en_trf_out(self, rf=1, lo=0):
"""Enable the rf/lo outputs of the upconverter (hardware variant).
:param rf: 1 to enable RF output, 0 to disable
:param lo: 1 to enable LO output, 0 to disable
"""
data = self.trf_read(0xc)
delay(0.1 * ms)
# set RF and LO output bits
data = data | (1 << 12) | (1 << 13) | (1 << 14)
# clear to enable output
if rf == 1:
data = data ^ (1 << 14)
if lo == 1:
data = data ^ ((1 << 12) | (1 << 13))
self.trf_write(data)
class PhaserOscillator: class PhaserOscillator:
"""Phaser IQ channel oscillator (NCO/DDS). """Phaser IQ channel oscillator (NCO/DDS).

View File

@ -4,20 +4,21 @@ class TRF372017:
For possible values, documentation, and explanation, see the datasheet. For possible values, documentation, and explanation, see the datasheet.
https://www.ti.com/lit/gpn/trf372017 https://www.ti.com/lit/gpn/trf372017
""" """
rdiv = 21 # 13b rdiv = 2 # 13b - highest valid f_PFD
ref_inv = 0 ref_inv = 0
neg_vco = 1 neg_vco = 1
icp = 0 # 1.94 mA, 5b icp = 0 # 1.94 mA, 5b
icp_double = 0 icp_double = 0
cal_clk_sel = 12 # /16, 4b cal_clk_sel = 0b1110 # div64, 4b
ndiv = 420 # 16b # default f_vco is 2.875 GHz
pll_div_sel = 0 # /1, 2b nint = 23 # 16b - lowest value suitable for fractional & integer mode
prsc_sel = 1 # 8/9 pll_div_sel = 0b01 # div2, 2b
prsc_sel = 0 # 4/5
vco_sel = 2 # 2b vco_sel = 2 # 2b
vcosel_mode = 0 vcosel_mode = 0
cal_acc = 0b00 # 2b cal_acc = 0b00 # 2b
en_cal = 1 en_cal = 0 # leave at 0 - calibration is performed in `Phaser.init()`
nfrac = 0 # 25b nfrac = 0 # 25b
@ -27,9 +28,9 @@ class TRF372017:
pwd_vcomux = 0 pwd_vcomux = 0
pwd_div124 = 0 pwd_div124 = 0
pwd_presc = 0 pwd_presc = 0
pwd_out_buff = 1 pwd_out_buff = 1 # leave at 1 - only enable outputs after calibration
pwd_lo_div = 1 pwd_lo_div = 1 # leave at 1 - only enable outputs after calibration
pwd_tx_div = 0 pwd_tx_div = 1 # leave at 1 - only enable outputs after calibration
pwd_bb_vcm = 0 pwd_bb_vcm = 0
pwd_dc_off = 0 pwd_dc_off = 0
en_extvco = 0 en_extvco = 0
@ -59,8 +60,8 @@ class TRF372017:
ioff = 0x80 # 8b ioff = 0x80 # 8b
qoff = 0x80 # 8b qoff = 0x80 # 8b
vref_sel = 4 # 0.85 V, 3b vref_sel = 4 # 0.85 V, 3b
tx_div_sel = 1 # div2, 2b tx_div_sel = 0 # div1, 2b
lo_div_sel = 3 # div8, 2b lo_div_sel = 0 # div1, 2b
tx_div_bias = 1 # 37.5 µA, 2b tx_div_bias = 1 # 37.5 µA, 2b
lo_div_bias = 2 # 50 µA, 2b lo_div_bias = 2 # 50 µA, 2b
@ -84,6 +85,7 @@ class TRF372017:
setattr(self, key, value) setattr(self, key, value)
def get_mmap(self): def get_mmap(self):
"""Memory map for TRF372017"""
mmap = [] mmap = []
mmap.append( mmap.append(
0x9 | 0x9 |
@ -92,9 +94,10 @@ class TRF372017:
(self.cal_clk_sel << 27)) (self.cal_clk_sel << 27))
mmap.append( mmap.append(
0xa | 0xa |
(self.ndiv << 5) | (self.pll_div_sel << 21) | (self.prsc_sel << 23) | (self.nint << 5) | (self.pll_div_sel << 21) |
(self.vco_sel << 26) | (self.vcosel_mode << 28) | (self.prsc_sel << 23) | (self.vco_sel << 26) |
(self.cal_acc << 29) | (self.en_cal << 31)) (self.vcosel_mode << 28) | (self.cal_acc << 29) |
(self.en_cal << 31))
mmap.append(0xb | (self.nfrac << 5)) mmap.append(0xb | (self.nfrac << 5))
mmap.append( mmap.append(
0xc | 0xc |