Post Go back to editing

FMCOMMS8 FHM through GPIO timing and phase sync query

Hello Team,  
We are using Kuiper Linux drivers for ZCU102 with Fmcomms8. We are able to change LO using iio_attr and sysfs. For this test, phases for the 2 chips are getting maintained in multiple powerons with +/- 2.5 degree changes from poweron to poweron. The time taken for this is around 400ms for each chip. 
When we wrote a shell script for the FHM. Hopping seems to be there but the phases are changing with each poweron as well as each run. This shell script takes around 50ms for each chip to hop between frequencies.
Next we used the python script mentioned in the following forum post:
IIO Usage: Multi-Chip Sync (MCS) in Frequency Hopping (FHM) Mode using GPIO pin trigger - Q&A - Design Support ADRV9008-1/ADRV9008-2/ADRV9009 - EngineerZone (analog.com)

Since we are using ZCU102 instead of ZU11EG, I modified the code as below:

# Copyright (C) 2019 Analog Devices, Inc.
#
# SPDX short identifier: ADIBSD

import time

import adi
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
import random

def measure_phase(chan0, chan1):
    errorV = np.angle(chan0 * np.conj(chan1)) * 180 / np.pi
    error = np.mean(errorV)
    return error


# Plot config
plot_time_domain = False

# Use FHM
fhm = True

# Create radio
sdr = adi.adrv9009_zu11eg_fmcomms8 (uri="ip:10.0.0.206") #changed IP and also used adrv900_zu11eg_fmcomms8 instead of adrv9009_zu11eg
sdr._rxadc.set_kernel_buffers_count(1)

#devices are named as chip_c and chip_d so changed the names in the below properties
# Configure properties
sdr.rx_enabled_channels = [0, 1, 2, 3]
sdr.tx_enabled_channels = [0, 1]
sdr.trx_lo_chip_c = 2000000000
sdr.trx_lo_chip_d = 2000000000
sdr.tx_hardwaregain_chan0_chip_c = -10
sdr.tx_hardwaregain_chan1_chip_c = -10
sdr.tx_hardwaregain_chan0_chip_d = -10
sdr.tx_hardwaregain_chan1_chip_d = -10
sdr.gain_control_mode_chan0_chip_c = "slow_attack"
sdr.gain_control_mode_chan1_chip_c = "slow_attack"
sdr.gain_control_mode_chan0_chip_d = "slow_attack"
sdr.gain_control_mode_chan1_chip_d = "slow_attack"
sdr.rx_buffer_size = 2 ** 14

if fhm:
    # setr devicetree power up TRX lo frequency for all devices
    sdr.set_trx_lo_frequency = 2500000000
    # Setup FHM
    print("setting up FHM Chip A")
    print("Setting FHM trigger to SPI")
    sdr._ctrl_c.debug_attrs["adi,fhm-mode-fhm-trigger-mode"].value = "1"
    print("Setting FHM init frequency")
    sdr._ctrl_c.debug_attrs["adi,fhm-mode-fhm-init-frequency_hz"].value = "2450000000"
    print("Setting FHM Min frequency")
    sdr._ctrl_c.debug_attrs["adi,fhm-config-fhm-min-freq_mhz"].value = "200"
    print("Setting FHM Max frequency")
    sdr._ctrl_c.debug_attrs["adi,fhm-config-fhm-max-freq_mhz"].value = "6000"
    print("Set MCS+FHM")
    sdr._ctrl_c.debug_attrs["adi,fhm-mode-enable-mcs-sync"].value = "1"
    sdr._ctrl_c.debug_attrs["adi,fhm-config-fhm-gpio-pin"].value = "8"


    print("setting up FHM Chip B")
    print("Setting FHM trigger to SPI")
    sdr._ctrl_d.debug_attrs["adi,fhm-mode-fhm-trigger-mode"].value = "1"
    print("Setting FHM init frequency")
    sdr._ctrl_d.debug_attrs["adi,fhm-mode-fhm-init-frequency_hz"].value = "245000000"
    print("Setting FHM Min frequency")
    sdr._ctrl_d.debug_attrs["adi,fhm-config-fhm-min-freq_mhz"].value = "200"
    print("Setting FHM Max frequency")
    sdr._ctrl_d.debug_attrs["adi,fhm-config-fhm-max-freq_mhz"].value = "5000"
    print("Set MCS+FHM")
    sdr._ctrl_d.debug_attrs["adi,fhm-mode-enable-mcs-sync"].value = "1"
    sdr._ctrl_d.debug_attrs["adi,fhm-config-fhm-gpio-pin"].value = "8"
    

#print("Syncing")
# this will renitialize both ADRV9009s from reset and sync them together using the jesd204-fsm
#sdr.jesd204_fsm_ctrl = "1" #trying to enable this function throws an exception so checked the property using iio_attr -d iio:device3 and 			    #iio_attr -d iio:device4, this property already set to 1
#print("Done syncing")

print("FH Mode Enable", sdr.frequency_hopping_mode_en_chip_c)
sdr.frequency_hopping_mode_en_chip_c = 1
print("FH Mode Enable", sdr.frequency_hopping_mode_en_chip_c)

print("FH Mode Enable Chip B", sdr.frequency_hopping_mode_en_chip_d)
sdr.frequency_hopping_mode_en_chip_d = 1
print("FH Mode Enable Chip B", sdr.frequency_hopping_mode_en_chip_d)

print("Setting first frequency")
nextLO = 3000000000
sdr.frequency_hopping_mode_chip_c = nextLO
sdr.frequency_hopping_mode_chip_d = nextLO
sdr.dds_single_tone(500000, 0.8)

# Collect data
M = 150
N = 1
p1 = np.zeros(M)
p2 = np.zeros(M)
p1v = np.zeros(M)
p2v = np.zeros(M)
for k in range(M):
    pf1 = np.zeros(N)
    pf2 = np.zeros(N)
    #hopping between 2 static LOs first
    next_freq_lj = 3000000000  #random.randrange(200000000, 5999000000, 10)
    #if k % 5 == 0:
    next_freq_sj = 2000000000  #random.randrange(2000000000, 2001000000, 10000)

    if True:
        ############################
        start_time_lo = time.time();
        print(f"start time: {start_time_lo * 1e6} microseconds")
        print("RUN #", k)
        print("Off tune to ", nextLO)
        tobemovedto = sdr.frequency_hopping_mode_chip_c
        # Tell LO to move and set next frequency
        sdr.frequency_hopping_mode_chip_c = next_freq_sj
        sdr.frequency_hopping_mode_chip_d = next_freq_sj
        nextLO = sdr.frequency_hopping_mode_chip_c
        print("Current LO", tobemovedto)
        time.sleep(0.0001)
        ############################
        print("Tune back to", nextLO)
        tobemovedto = sdr.frequency_hopping_mode_chip_c
        # Tell LO to move and set next frequency
        sdr.frequency_hopping_mode_chip_c = next_freq_lj
        sdr.frequency_hopping_mode_chip_d = next_freq_lj
        nextLO = sdr.frequency_hopping_mode_chip_c
        print("Current LO", tobemovedto)
        time.sleep(0.0001)
        ############################
        print("FH Mode Enable", sdr.frequency_hopping_mode_en_chip_c)
        print("FH Mode Enable Chip B", sdr.frequency_hopping_mode_en_chip_d)
        end_time_lo = time.time()
        print(f"end time: {end_time_lo * 1e6} microseconds")
        total_time=int((end_time_lo - start_time_lo) * 1e6)
        print("total time:",total_time)
     else:
         if fhm:
             sdr.frequency_hopping_mode_chip_c = next_freq_sj
             sdr.frequency_hopping_mode_chip_d = next_freq_sj
             sdr.frequency_hopping_mode_chip_c = next_freq_lj
             sdr.frequency_hopping_mode_chip_d = next_freq_lj
         else:
             sdr.trx_lo_chip_c = next_freq_sj
             sdr.trx_lo_chip_d = next_freq_sj

    # Flush
    #for r in range(N):
        #x = sdr.rx()

     for r in range(N):
         x = sdr.rx()
         pf1[r] = measure_phase(x[0], x[1])
         pf2[r] = measure_phase(x[0], x[2])

     if plot_time_domain:
         plt.clf()
         plt.plot(np.real(x[0][:1000]))
         plt.plot(np.real(x[1][:1000]))
         plt.plot(np.real(x[2][:1000]))
         plt.show()
         plt.draw()
         plt.pause(2)

     p1[k] = np.mean(pf1)
     p2[k] = np.mean(pf2)
     p1v[k] = np.var(pf1)
     p2v[k] = np.var(pf2)
     print("Phases", p1[k], p2[k])
     print("Variances", p1v[k], p2v[k])
     print("\n")
     plt.clf()
     x = np.array(range(0, k + 1))
     plt.errorbar(x, p1[x], yerr=p1v[x], label="Channel 0/1)")
     plt.errorbar(x, p2[x], yerr=p2v[x], label="Channel 0/2")
     plt.xlim([-1, x[-1] + 1])
     plt.xlabel("Measurement Index")
     plt.ylabel("Phase Difference (Degrees)")
     plt.legend()
     plt.draw()
     plt.pause(0.05)


plt.show()

In adrv_zu11eg_fmcomms8.py file located in usr/local/lib/python3.9/dist-packages/adi we also changed some few lines for frequency hopping mode for making it compatible to our board as below:
def mcs_chips(self):
        """mcs_chips: MCS Synchronize all four transceivers """
        try:
            _ = self.jesd204_fsm_ctrl
            # We're JESD204-fsm enabled - do nothing
        except:  # noqa: E722
            # Turn off continuous SYSREF, and enable GPI SYSREF request
            # self._clock_chip_carrier.reg_write(0x5A, 0) commented since only fmc clock is present
            self._clock_chip_fmc.reg_write(0x5A, 0)
            #self._ctrl and self._ctrl_b removed in below loop since only fmcomms8 is present
            chips = [self._ctrl_c, self._ctrl_d]
            for i in range(12):
                for chip in chips:
                    try:
                        self._set_iio_dev_attr_str("multichip_sync", i, chip)
                    except OSError:
                        pass



    @property
    def frequency_hopping_mode_chip_c(self):
          """frequency_hopping_mode_chip_c: Set Frequency Hopping Mode"""
          return self._get_iio_attr(
              "TRX_LO", "frequency_hopping_mode", True, self._ctrl_c
          )

    @frequency_hopping_mode_chip_c.setter
    def frequency_hopping_mode_chip_c(self, value):
        self._set_iio_attr(
            "TRX_LO", "frequency_hopping_mode", True, value, self._ctrl_c
        )

    @property
    def frequency_hopping_mode_en_chip_c(self):
        """frequency_hopping_mode_en: Enable Frequency Hopping Mode"""
        return self._get_iio_attr(
             "TRX_LO", "frequency_hopping_mode_enable", True, self._ctrl_c
        )

    @frequency_hopping_mode_en_chip_c.setter
    def frequency_hopping_mode_en_chip_c(self, value):
        self._set_iio_attr(
            "TRX_LO", "frequency_hopping_mode_enable", True, value, self._ctrl_c
        )

    @property
    def frequency_hopping_mode_chip_d(self):
        """frequency_hopping_mode_chip_d: Set Frequency Hopping Mode"""
        return self._get_iio_attr(
            "TRX_LO", "frequency_hopping_mode", True, self._ctrl_d
            )

    @frequency_hopping_mode_chip_d.setter
    def frequency_hopping_mode_chip_d(self, value):
        self._set_iio_attr(
                "TRX_LO", "frequency_hopping_mode", True, value, self._ctrl_d
                )


    @property
    def frequency_hopping_mode_en_chip_d(self):
        """frequency_hopping_mode_en: Enable Frequency Hopping Mode"""
        return self._get_iio_attr(
            "TRX_LO", "frequency_hopping_mode_enable", True, self._ctrl_d
            )

    @frequency_hopping_mode_en_chip_d.setter
    def frequency_hopping_mode_en_chip_d(self, value):
        self._set_iio_attr(
            "TRX_LO", "frequency_hopping_mode_enable", True, value, self._ctrl_d
             )        

In the same file we had to comment 4 recievers and 4 transceivers out of 8. since we have only 2 chips.

After these particular changes, we are able to get frequency hopping mode and get phases maintained around +/- 2.5 degrees in different power-cycles. But the issue is that it takes around 400ms for both chips combined in the GPIO mode to switch from 1 LO to another using the above python script. This time should be around 70us as stated in the ADRV9009 datasheet. Our questions are as below
What could be done to reduce the time for switching and getting constant phases in different powerons?
Why do we get exception when we try to set sdr.jesd204_fsm_ctrl = "1" when using the python script?

Thank you in advance for your time and support.
Regards
Meghraj



.
[edited by: MeghrajT at 5:22 AM (GMT -5) on 15 Nov 2023]
  • 70usec is the PLL lock time in FHM mode(time taken for the PLL to lock after the trigger is applied).

    his shell script takes around 50ms for each chip to hop between frequencies.

    With 50msec wait time after each hop, how much phase difference are you seeing?

  • Thank you for the quick reply.
    How much time should it ideally take to hop to another frequency including the PLL lock time and switching to another frequency in FHM GPIO mode?

    With 50msec wait time the phase difference keeps jumping for the second chip for each LO switch. Between Chip A Rx1 and Rx2 phase difference seems constant around 173 degrees with slight variation of +/- 2.5 degrees. For chip A Rx1 and chip B Rx1 phase difference keeps randomly changing, sometimes -119, then -104, 30.7, 51.2, etc. This is true for Chip A Rx1 and Chip B Rx2 as well.


  • Hello, there is an update. I changed my shell script according to the python script provided in the other forum. Now the shell script takes around 25ms to execute and it shows better consistency than before with regards to the phase. Still in some cases the phase drifts to 15 to 20 degrees variation as per my testing data. 
    I am running configuration script once and then the frequency hopping script in a loop. I have attached the shell scripts for configuration and frequency hopping below. For each hop the frequency hopping frequency and GPIO trigger needs to be set and toggled twice for the change in LO switching.
    Could you please explain why we are getting this phase difference on different power cycles?
    Also what could be done to reduce the time for each hop? We were expecting this time to be in microseconds.

    Thank you for your time and support
    Regards 
    Meghraj


  • I was not able to add code section in my reply so here are the scripts for configuration of FHM and running it.

    For configuration Frequency Hopping Mode:

    #!/bin/bash

    #chip C
    iio_attr -D iio:device3 adi,fhm-mode-fhm-trigger-mode 0
    iio_attr -D iio:device3 adi,fhm-mode-fhm-init-frequency_hz 2450000000
    iio_attr -D iio:device3 adi,fhm-config-fhm-min-freq_mhz 200
    iio_attr -D iio:device3 adi,fhm-config-fhm-max-freq_mhz 6000
    iio_attr -D iio:device3 adi,fhm-mode-enable-mcs-sync 1
    iio_attr -D iio:device3 adi,fhm-config-fhm-gpio-pin 8
    #chip D
    iio_attr -D iio:device4 adi,fhm-mode-fhm-trigger-mode 0
    iio_attr -D iio:device4 adi,fhm-mode-fhm-init-frequency_hz 2450000000
    iio_attr -D iio:device4 adi,fhm-config-fhm-min-freq_mhz 200
    iio_attr -D iio:device4 adi,fhm-config-fhm-max-freq_mhz 6000
    iio_attr -D iio:device4 adi,fhm-mode-enable-mcs-sync 1
    iio_attr -D iio:device4 adi,fhm-config-fhm-gpio-pin 8

    iio_attr -c iio:device3 altvoltage0 frequency_hopping_mode_enable 1
    iio_attr -c iio:device4 altvoltage0 frequency_hopping_mode_enable 1

    echo 456 > /sys/class/gpio/export

    echo out > /sys/class/gpio/gpio456/direction

    echo 471 > /sys/class/gpio/export

    echo out > /sys/class/gpio/gpio471/direction

    cat /sys/class/gpio/gpio456/direction

    cat /sys/class/gpio/gpio471/direction

    #gain params
    echo 5 > /sys/bus/iio/devices/iio:device3/in_voltage0_hardwaregain

    echo 5 > /sys/bus/iio/devices/iio:device3/in_voltage1_hardwaregain

    echo 5 > /sys/bus/iio/devices/iio:device4/in_voltage0_hardwaregain

    echo 5 > /sys/bus/iio/devices/iio:device4/in_voltage1_hardwaregain

    echo "chip1 Rx 1 gain"
    cat /sys/bus/iio/devices/iio:device3/in_voltage0_hardwaregain
    echo "chip1 Rx 2 gain"
    cat /sys/bus/iio/devices/iio:device3/in_voltage1_hardwaregain

    echo "chip1 Rx 1 gain"
    cat /sys/bus/iio/devices/iio:device3/in_voltage0_hardwaregain
    echo "chip1 Rx 2 gain"
    cat /sys/bus/iio/devices/iio:device3/in_voltage1_hardwaregain
    ------------------------------------------------------------------------------------------------------------

    For Frequency Hopping:

    #!/bin/bash
    #For Frequency hopping through GPIO 8
    delay=0.000000

    desired_frequency=2000000000
    desired_frequency_2=5000000000

    freq_dev3_path="/sys/bus/iio/devices/iio:device3/out_altvoltage0_TRX_LO_frequency_hopping_mode"
    freq_dev4_path="/sys/bus/iio/devices/iio:device4/out_altvoltage0_TRX_LO_frequency_hopping_mode"

    gpio_dev3_path="/sys/class/gpio/gpio456/value"
    gpio_dev4_path="/sys/class/gpio/gpio471/value"

    while true; do

    echo "start"
    sleep $delay
    #both chips LO 1
    while true; do
    start_time_lo_1=$(date +%s.%6N)
    echo "start tuning lo 1"
    echo "$desired_frequency" > "$freq_dev3_path"
    echo "$desired_frequency" > "$freq_dev4_path"
    echo "1" > "$gpio_dev3_path"
    echo "1" > "$gpio_dev4_path"
    sleep $delay
    echo "0" > "$gpio_dev3_path"
    echo "0" > "$gpio_dev4_path"
    sleep $delay
    if [ "$(cat "$freq_dev3_path")" -eq "$desired_frequency" ] && [ "$(cat "$freq_dev4_path")" -eq "$desired_frequency" ]; then
    echo "freq1 $(cat "$freq_dev3_path") for chip1 written"
    echo "freq1 $(cat "$freq_dev4_path") for chip2 written"
    end_time_lo_1=$(date +%s.%6N)
    elapsed_time_lo_1=$(echo "scale=6; ($end_time_lo_1 - $start_time_lo_1) * 1000000" | bc)
    echo "Time taken for LO 1 both chips: $elapsed_time_lo_1 microseconds"
    break
    fi
    done
    sleep $delay
    #both chips LO 2
    while true; do
    start_time_lo_2=$(date +%s.%6N)
    echo "start tuning lo 2"

    echo "$desired_frequency_2" > "$freq_dev3_path"
    echo "$desired_frequency_2" > "$freq_dev4_path"

    echo "1" > "$gpio_dev3_path"
    echo "1" > "$gpio_dev4_path"
    sleep $delay
    echo "0" > "$gpio_dev3_path"
    echo "0" > "$gpio_dev4_path"
    sleep $delay
    if [ "$(cat "$freq_dev3_path")" -eq "$desired_frequency_2" ] && [ "$(cat "$freq_dev4_path")" -eq "$desired_frequency_2" ]; then
    echo "freq2 $(cat "$freq_dev3_path") for chip1 written"
    echo "freq2 $(cat "$freq_dev4_path") for chip2 written"
    end_time_lo_2=$(date +%s.%6N)
    elapsed_time_lo_2=$(echo "scale=6; ($end_time_lo_2 - $start_time_lo_2) * 1000000" | bc)
    echo "Time taken for LO 2 both chips: $elapsed_time_lo_2 microseconds"
    break
    fi
    done
    sleep $delay

    done

    ---------------------------------------------------------------------------------------------------------------------------------------

  • It is not possible to hop and achieve phase sync within usec's of time.

  • Thank you for the response  . I understand it is not possible in usec's of time to maintain phase sync but even when I am using a 5 sec delay between frequency hopping, in higher bands like 5 GHz, I am still getting 10 to 15 degree variation from poweron to poweron and sometimes in the same poweron in multiple runs.
    Generally how much time does it require for phase to be maintained after switching LO with FHM GPIO mode?

    I modified the script as below:

    #!/bin/bash
    delay=5
    delay_small=0.001

    desired_frequency=2000000000
    desired_frequency_2=5000000000

    freq_dev3_path="/sys/bus/iio/devices/iio:device3/out_altvoltage0_TRX_LO_frequency_hopping_mode"
    freq_dev4_path="/sys/bus/iio/devices/iio:device4/out_altvoltage0_TRX_LO_frequency_hopping_mode"

    gpio_dev3_path="/sys/class/gpio/gpio456/value"
    gpio_dev4_path="/sys/class/gpio/gpio471/value"

    while true; do

    echo "start"
    sleep $delay_small
    #both chips LO 1
    while true; do
    start_time_lo_1=$(date +%s.%6N)
    echo "start tuning lo 1"
    echo "$desired_frequency" > "$freq_dev3_path"
    echo "$desired_frequency" > "$freq_dev4_path"
    echo "1" > "$gpio_dev3_path"
    echo "1" > "$gpio_dev4_path"
    sleep $delay_small
    echo "0" > "$gpio_dev3_path"
    echo "0" > "$gpio_dev4_path"
    sleep $delay_small
    if [ "$(cat "$freq_dev3_path")" -eq "$desired_frequency" ] && [ "$(cat "$freq_dev4_path")" -eq "$desired_frequency" ]; then
    echo "freq1 $(cat "$freq_dev3_path") for chip1 written"
    echo "freq1 $(cat "$freq_dev4_path") for chip2 written"
    end_time_lo_1=$(date +%s.%6N)
    elapsed_time_lo_1=$(echo "scale=6; ($end_time_lo_1 - $start_time_lo_1) * 1000000" | bc)
    echo "Time taken for LO 1 both chips: $elapsed_time_lo_1 microseconds"
    break
    fi
    done
    sleep $delay
    #both chips LO 2
    while true; do
    start_time_lo_2=$(date +%s.%6N)
    echo "start tuning lo 2"

    echo "$desired_frequency_2" > "$freq_dev3_path"
    echo "$desired_frequency_2" > "$freq_dev4_path"

    echo "1" > "$gpio_dev3_path"
    echo "1" > "$gpio_dev4_path"
    sleep $delay_small
    echo "0" > "$gpio_dev3_path"
    echo "0" > "$gpio_dev4_path"
    sleep $delay_small
    if [ "$(cat "$freq_dev3_path")" -eq "$desired_frequency_2" ] && [ "$(cat "$freq_dev4_path")" -eq "$desired_frequency_2" ]; then
    echo "freq2 $(cat "$freq_dev3_path") for chip1 written"
    echo "freq2 $(cat "$freq_dev4_path") for chip2 written"
    end_time_lo_2=$(date +%s.%6N)
    elapsed_time_lo_2=$(echo "scale=6; ($end_time_lo_2 - $start_time_lo_2) * 1000000" | bc)
    echo "Time taken for LO 2 both chips: $elapsed_time_lo_2 microseconds"
    break
    fi
    done
    sleep $delay

    done



  • We are looking into this issue , will get back to you .

  • Wondering how do you measure the phase offset between the chips?

    Let's assume you use regular LO control (which I understand takes significant longer) but do you see similar offsets compared to FHM?

    -Michael  

  • Hello  , we export Rx data and then run it through a matlab code which measures the phase difference. When we are not using FHM mode, the phase difference varies only by  +/-2.5 degrees. But since we require less LO switching time we are using FHM mode.

  • In FHM mode how long are you waiting after you triggered the frequency update, and before you start capturing samples?

    Are you using IIO to capture the buffer?

    -Michael