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:
* WRPLL
* ``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:
* 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

View File

@ -110,7 +110,7 @@ class DAC34H84:
syncsel_mixercd = 0b1001 # sif_sync and register write
syncsel_nco = 0b1000 # sif_sync
syncsel_fifo_input = 0b10 # external lvds istr
sif_sync = 1
sif_sync = 0
syncsel_fifoin = 0b0010 # istr
syncsel_fifoout = 0b0100 # ostr
@ -178,7 +178,8 @@ class DAC34H84:
(self.collisiongone_ena << 12) | (self.sif4_ena << 7) |
(self.mixer_ena << 6) | (self.mixer_gain << 5) |
(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(
(0x07 << 16) |
(self.mask_alarm_from_zerochk << 15) | (1 << 14) |
@ -200,7 +201,7 @@ class DAC34H84:
mmap.append(
(0x0d << 16) |
(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))
mmap.append((0x0e << 16) | (self.qmc_gainc << 0))
mmap.append(

View File

@ -1,6 +1,8 @@
from numpy import int32, int64
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, ms, MHz, dB
from artiq.language.units import us, ns, ms, MHz
from artiq.language.types import TInt32
from artiq.coredevice.dac34h84 import DAC34H84
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
Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation,
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
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the
@ -184,6 +187,9 @@ class Phaser:
is_baseband = hw_rev & PHASER_HW_REV_VARIANT
gw_rev = self.read8(PHASER_ADDR_GW_REV)
if debug:
print(gw_rev)
self.core.break_realtime()
delay(.1*ms) # slack
# allow a few errors during startup and alignment since boot
@ -224,6 +230,8 @@ class Phaser:
for data in self.dac_mmap:
self.dac_write(data >> 16, data)
delay(40*us)
self.dac_sync()
delay(40*us)
# pll_ndivsync_ena disable
config18 = self.dac_read(0x18)
@ -272,6 +280,16 @@ class Phaser:
else:
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
self.set_cfg(clk_sel=self.clk_sel, dac_txena=0)
@ -299,6 +317,9 @@ class Phaser:
self.duc_stb()
delay(.1*ms) # settle link, pipeline and impulse response
data = channel.get_dac_data()
delay(1*us)
channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000,
clr=1)
delay(.1*ms)
sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
data_i = data & 0xffff
@ -318,6 +339,7 @@ class Phaser:
delay(.2*ms)
for data in channel.trf_mmap:
channel.trf_write(data)
channel.cal_trf_vco()
delay(2*ms) # lock
if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)):
@ -326,6 +348,7 @@ class Phaser:
if channel.trf_read(0) & 0x1000:
raise ValueError("TRF R_SAT_ERR")
delay(.1*ms)
channel.en_trf_out()
# enable dac tx
self.set_cfg(clk_sel=self.clk_sel)
@ -549,6 +572,48 @@ class Phaser:
"""
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
def get_dac_alarms(self):
"""Read the DAC alarm flags.
@ -684,6 +749,7 @@ class PhaserChannel:
self.phaser = phaser
self.index = index
self.trf_mmap = TRF372017(trf).get_mmap()
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
@kernel
@ -761,6 +827,12 @@ class PhaserChannel:
def set_nco_frequency_mu(self, ftw):
"""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)
"""
self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16)
@ -770,6 +842,12 @@ class PhaserChannel:
def set_nco_frequency(self, frequency):
"""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
to 400 MHz, wrapping around at +- 500 MHz)
"""
@ -780,6 +858,16 @@ class PhaserChannel:
def set_nco_phase_mu(self, pow):
"""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)
"""
self.phaser.dac_write(0x12 + self.index, pow)
@ -788,10 +876,20 @@ class PhaserChannel:
def set_nco_phase(self, phase):
"""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
"""
pow = int32(round(phase*(1 << 16)))
self.set_duc_phase_mu(pow)
self.set_nco_phase_mu(pow)
@kernel
def set_att_mu(self, data):
@ -886,6 +984,32 @@ class PhaserChannel:
return self.trf_write(0x00000008 | (cnt_mux_sel << 27),
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:
"""Phaser IQ channel oscillator (NCO/DDS).

View File

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