diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 11a126813..1c668fd3f 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -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 diff --git a/artiq/coredevice/dac34h84.py b/artiq/coredevice/dac34h84.py index 155096a1e..4063551aa 100644 --- a/artiq/coredevice/dac34h84.py +++ b/artiq/coredevice/dac34h84.py @@ -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( diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index d5acf6501..c8c3c0938 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -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). diff --git a/artiq/coredevice/trf372017.py b/artiq/coredevice/trf372017.py index 40957db86..e09d355c2 100644 --- a/artiq/coredevice/trf372017.py +++ b/artiq/coredevice/trf372017.py @@ -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 |