RAM mode on AD9910 Urukul mode may do what you want, depending on sweep specifications.

    4 months later

    There's an example frequency ramp using the AD9910's RAM in #1378.

    An alternative would be to use the integrated digital ramp generator (DRG, see datasheet). This functionality is not (yet) exposed in the coredevice API (#1344) and needs CPLD gateware changes to get full support for Urukul v1.4+ (no access to the physical DRG pins, in particular DRCTL). Some features are nevertheless already available (and used in AD9910.measure_io_update_alignment()). Below is a quick example for a one-shot ramp centered on 100 MHz (barely tested, see the datasheet for an explanation of the parameters):

    from artiq.experiment import *
    from artiq.coredevice import ad9910
    
    class AD9910DRG(EnvExperiment):
        def build(self):
            self.setattr_device("core")
            self.setattr_device("urukul0_ch0")
            self.dds = self.urukul0_ch0
    
            self.ramp_low = self.dds.frequency_to_ftw(97.5*MHz)
            self.ramp_high = self.dds.frequency_to_ftw(102.5*MHz)
            self.ramp_step = self.dds.frequency_to_ftw(5*kHz)
            self.ramp_rate = 1 << 15
    
        @kernel
        def run(self):
            self.core.reset()
    
            # init DDS
            self.dds.cpld.init()
            self.dds.init()
            self.dds.sw.on()
            self.dds.set_att(20.)
            self.dds.set_amplitude(1.0)
    
            # setup enable DRG
            cfr2 = (
                (1 << 19)  # enable DRG
                | (0 << 20)  # DRG destination = FTW, see datasheet table 11
            )
            self.dds.write32(ad9910._AD9910.REG_CFR2, cfr2)
    
            # set ramp limits
            self.dds.write64(
                ad9910._AD9910_REG_RAMP_LIMIT,
                self.ramp_high,
                self.ramp_low,
            )
    
            # set ramp step
            # DRCTL = 0 so only the negative slope increment matters
            # one can however work around that limitation by purposely overflowing
            # the 32-bit FTW decrement
            self.dds.write64(
                ad9910._AD9910_REG_RAMP_STEP,
                -self.ramp_step,  # destination parameter (here FTW) decrement (DRCTL = 0)
                0,  # destination parameter increment (DRCTL = 1)
            )
    
            # set ramp rate
            self.dds.write32(
                ad9910._AD9910_REG_RAMP_RATE,
                self.ramp_rate << 16,  # set negative slope rate
            )
    
            # load registers = start ramp
            self.dds.cpld.io_update.pulse_mu(8)

      airwoodix Thanks alot for the reply and sorry for my late response. I am actually looking to perform continous sinusoidal frequency sweep modulation e.g. CF =100Mhz , depth = 2 Mhz and modulation frequency = 50 Khz.

      1. From the data sheet it seems that the DRG will only produce a linear sweep and therefore would the use of RAM Continuous Recirculate Mode be the most appropriate?
      2. I have read on the analog device forums that in the case the DRG is used to produce the FTWs, the phase is not reset upon recirculation.
        "When you frequency modulate via the DRG, the DRG generates time varying FTWs via its accumulator. The DRG's output FTWs become the input to the DDS accumulator. The DDS accumulator, meanwhile, just keeps "humming along" independent of the FTWs generated by the DRG. It just keeps on accumulating phase, unless you clear it manually.
        The only way to ensure zero phase at the start of each sweep is to somehow keep track of the timing and clear the DDS accumulator when a sweep starts. Synchronizing the clearing operation, however, will be tricky."

      https://ez.analog.com/dds/f/q-a/28396/ad9910-frequency-sweep-initial-phase/156351#156351
      Would you know how to overcome this issue?

      5 days later

      I have been able to produce a working code, however when I set the address step rate to be 25 (it is defined in t_step) the DDS output cuts out. It seems that below 1e4 the step address causes problems. In the documentation on the ARTIQ website it states that the step address is defined in units of 4ns. the code is below, could someone please advise me

      `from artiq.experiment import *
      from artiq.coredevice import ad9910
      import numpy as np

      #there are 1024 values for each profile, and the control parameters are the CF, MF, and MD
      #The standard value has been set as N = 1024
      #first write a simple script and then looking to optimise it.

      N = 1000

      f1 = 99.MHz
      f2 = 100.
      MHz

      #CF, MD, and MF are all in MHz
      CF = 100
      MF = 0.01
      MD = 1.

      #Converting to time period in seconds.
      time_period = (1/MF)*1000
      print(time_period)
      time_step= (time_period/N)
      print(time_step)
      t_step = int(round(time_step/4))
      print(t_step)
      T = int(1e8)

      class DDS_freq_ramp(EnvExperiment):

      def build(self):
          self.setattr_device("core")
          self.setattr_device("urukul1_cpld") #2nd Urukul module
          self.setattr_device("urukul1_ch3") #Urukul module
          self.u = self.urukul1_ch3
       
      def prepare(self):
          
          #create list of frequencies in FTW format to write to RAM
          
          self.f = [0.]*N
          self.f_ram = [0]*N
          time = np.arange(0,1000,1)
          amplitude = np.sin(2*np.pi*time/999)
          amplitude2 = amplitude*MD
          amplitude3 = amplitude2+CF
          amplitude4 = amplitude3*MHz
          f_span = f2 - f1
          f_step = f_span / N
          #print(f_step)
          print(amplitude4)
          #I want to see the first step
          print(f1+f_step)
          for i in range(N):
              self.f[i] = amplitude4[i]
              self.u.frequency_to_ram(self.f,self.f_ram)
       
      @kernel
      def run(self):
          self.core.reset()
          
          '''initialize DDS channel'''
          self.u.cpld.init()
          self.u.init()
          self.u.cpld.io_update.pulse(100*ns)
          self.core.break_realtime()
          self.u.set_amplitude(1.)
          self.u.set_att(0.*dB)
          self.u.set(100.*MHz)
       
          '''prepare RAM profile:'''
          self.u.set_cfr1() #disable RAM for writing data
          self.u.cpld.io_update.pulse_mu(8) #I/O pulse to enact RAM change
          self.u.set_profile_ram(start=0, end=N-1, step=t_step, profile=0, mode=ad9910.RAM_MODE_CONT_RAMPUP)
          self.u.cpld.set_profile(0)
          self.u.cpld.io_update.pulse_mu(8)
       
          '''write data to RAM:'''
          delay(50*us)
          self.u.write_ram(self.f_ram)
          delay(100*us)
       
          '''enable RAM mode (enacted by IO pulse) and fix other parameters:'''
          self.u.set_cfr1(internal_profile=0,ram_destination=ad9910.RAM_DEST_FTW, ram_enable=1)
          self.u.set_amplitude(1.)
          self.u.set_att(10.*dB)
          self.u.cpld.io_update.pulse_mu(8)
       
          '''switch on DDS channel'''
          self.u.sw.on()    `
      4 days later

      Could some one please advise me on how to correct this issue with regards to the signal cutting out after decreasing the time step for the step address rate to below 1e4? I would really appreciate some guidance.

      a month later

      I am running into problems with the time step between the samples, in the example code given above the time step is 1e8 which would correspond to 1e8x4ns = 0.4 seconds however going below 1e3x4ns the signal cuts out. I don't understand why this is a problem given that the minimum time to playback ram samples is 4ns and the minimum time for I/O update is in nanoseconds. If I could get this simple ramp up mode working then I should be able to carry onto sinusoidal frequency sweep, could someone please advise me.

        3 months later

        han94ros

        I'm just starting to play around with the ram_mode functionality. I'm wondering if you've resolved your issue and how?

        Also, is there a way to individually set the DDS profiles on a particular urukul? It seems that self.u.cpld.set_profile(0) sets the profile to 0 for all of the dds chips on the board corresponding to that cpld??

          jbroz Yes I have created a completed and tested script. You should note there was an issue with the amplitude decreasing with decreasing time step between the RAM samples. This can be fixed from the solution proposed in this github issue. https://github.com/m-labs/artiq/issues/1554. I have attached my tested code below so feel free to test and improve this code.

          `

                  
                  from artiq.experiment import *
                  from artiq.coredevice import ad9910
                  import numpy as np
                  #there are 1024 values for each profile, and the control parameters are the CF, MF, and MD
                  #The standard value has been set as N = 1024
                  #first write a simple script and then looking to optimise it.
          
                  N  = 25
                  
                  #f1 = 99.*MHz
                  #f2 = 100.*MHz
                  
                  
                  #CF, MD, and MF are all in MHz
                  CF = 100
                  MF = 0.05
                  MD = 0.5
                  
                  #Converting to time period in seconds. 
                  time_period = (1/(MF*MHz))*(1e9)
                  print(time_period)
                  time_step= (time_period/N)
                  print(time_step)
                  t_step = int(round(time_step/4))
                  print(t_step, "t_step")
                  T = int(1e2)  
                   
                  class DDS_freq_ramp(EnvExperiment):
                   
                      def build(self):
                          self.setattr_device("core")
                          self.setattr_device("urukul1_cpld") #2nd Urukul module
                          self.setattr_device("urukul1_ch3") #Urukul module
                          self.u = self.urukul1_ch3
                   
                      def prepare(self):
                          
                          #create list of frequencies in FTW format to write to RAM
                  
                          self.f = [0.]*N
                          self.f_ram = [0]*N
                          time = np.arange(0,N,1)
                          amplitude = np.sin(2*np.pi*time/(N-1))
                          amplitude2 = amplitude*MD
                          amplitude3 = amplitude2+CF
                          amplitude4 = amplitude3*MHz
                          #f_span = f2 - f1
                          #f_step = f_span / N
                          #print(f_step)
                          print(amplitude4)
                          #I want to see the first step
                          #print(f1+f_step)
                          for i in range(N):
                              self.f[i] = amplitude4[i]
                              self.u.frequency_to_ram(self.f,self.f_ram)
                   
                      @kernel
                      def run(self):
                          #self.core.reset()
                          self.core.break_realtime()
                          '''initialize DDS channel'''
                          self.u.cpld.init()
                          self.u.init()
                          self.u.cpld.io_update.pulse(100*ns)
                          #self.core.break_realtime()
                          self.u.set_amplitude(1.)
                          self.u.set_att(0.*dB)
                          self.u.set(100.*MHz)
                   
                          '''prepare RAM profile:'''
                          self.u.set_cfr1(ram_enable=0) #disable RAM for writing data
                          self.u.cpld.io_update.pulse(100*ns) #I/O pulse to enact RAM change
                          self.u.set_profile_ram(start=0, end=N-1, step=t_step, profile=0, mode=ad9910.RAM_MODE_CONT_RAMPUP)
                          self.u.cpld.set_profile(0)
                          delay(10 * us)
                          self.u.cpld.io_update.pulse(100*ns)
                      
                   
                          '''write data to RAM:'''
                          delay(10*us)
                          self.u.write_ram(self.f_ram)
                          delay(10*us)
                   
                          '''enable RAM mode (enacted by IO pulse) and fix other parameters:'''
                          self.u.set_cfr1(internal_profile=0, ram_enable = 1, ram_destination=ad9910.RAM_DEST_FTW, manual_osk_external=0, osk_enable=1, select_auto_osk=0)
                          self.u.sw.on()
                          delay(10 *us)
                          self.u.cpld.io_update.pulse_mu(8)
                          delay(1 * ms)
                          self.u.set_cfr1(ram_enable =0)

          `

          han94ros Will do. So far, I've just been playing with the amplitude modulation. But for the most part I'm doing everything the same way as you and it seems to be working fine. I don't know how much there is to improve.

          I'm still surprised that you can't set the profiles individually for the different DDS chips on the board (if this is, in fact, the case -- though, I haven't tested it yet).

          Also, thanks for pointing out the osk_enable hack! I'm sure that will save me some time tomorrow.

            jbroz Sure no worries, I am also working on the amplitude modulation today and perhaps we can compare codes. I will post it here after I have completed it.

            jbroz Have you been able to find a workaround to setting different modulation profiles for the different chips/channels on the board?

            I can only seem to run this script on a single channel, it is suprising the RAM cannot be made to output to more than one channel simulatenously.

            jbroz seems to work if you do not disable the RAM when writing the waveform to another chip/channel. This allows for the setting of independent frequency modulated signals to the different chips.

            It says in the manual however that it is strongly recommended that RAM_enable = 0 when performing load/retrieve operations - do you believe writing to the RAM whilst it is still enabled would cause any damage.

            Ahh, that makes sense. I think the warning in the manual doesn't apply since the load/retrieve operations are just being performed on a single DDS (even though we've switched the profile setting on all of them). But I could be wrong.

            a month later

            No additional interest or development that I know of.

              airwoodix Thanks for the information.
              rjo Exposure of the DRG api would simplify the programming significantly in some frequency sweep tasks. Is there any advice on what I can do since I really need the function?

              As always: fund the development and talk to people that can implement it or if you feel up to it, do it yourself.