forked from M-Labs/artiq
Merge pull request #1657 from pathfinder49/phaser
This commit is contained in:
commit
67d474e6cf
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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).
|
||||
|
@ -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 |
|
||||
|
Loading…
Reference in New Issue
Block a user