How to Configure DPD correctly?

Hello, 

I have a ton of questions to ask about DPD configuration on the ADRV9025 chip. I am trying to configure DPD without the PA connected between the Tx-ORx path solely for testing purposes. I have gone through all the steps required in setting up DPD but there seems to be no signal in the ORx path. The  dpdErrorCode I get shifts between 0x3405 and 0x340E. 

My setup is as follows:

I am running a 2T-2R-2ORx setup utilizing TX3/TX2, RX3/RX2, and ORX3/ORX1. With this setup, I am running DPD on the TX2, connecting the output of the TX2 via a splitter to the ORX1 port and a spectrum analyzer (So as to monitor the TX2-ORX1 path).

For the DPD Tracking Cal Bring up Sequence, I followed the following steps. Note that I have supplied only the necessary snippets to avoid cluttering up this page:

1a. Program the device and run initial calibrations 

1b. Run TxQEC initial calibration after chip initialization (for some reason, the programming failed when I tried to run the tx_qec init cal as part of the POSTMCS during initialization, see code snippet)

recoveryAct = adi_init_tx_cals_run( adrv9025Device, 
                                    ADI_ADRV9025_TX2,  
                                    ( ADI_ADRV9025_TX_QEC_INIT | ADI_ADRV9025_INTERNAL_PATH_DELAY | ADI_ADRV9025_TX_LO_LEAKAGE_INTERNAL )                                        
                                ) ;

2. Disabled ORX channels mapped TX2 to ORX1 and called the adi_adrv9025_RadioCtrlCfgSet function, setting all control modes to SPI (see code snippet)

printf("*** Disabling ORx Channels ***\n") ;
recoveryAct = adi_adrv9025_RxTxEnableSet ( adrv9025Device, 0x06, 0x06 ) ;

rxChannelMaskGet = 0 ;
txChannelMaskGet = 0 ;
usleep(1) ;
recoveryAct = adi_adrv9025_RxTxEnableGet(adrv9025Device, &rxChannelMaskGet, &txChannelMaskGet) ;

recoveryAct = adi_adrv9025_TxToOrxMappingSet(device,ADI_ADRV9025_ORX1, ADI_ADRV9025_TX2);

adrv9025PostMcsInitInst.radioCtrlInit.radioCtrlModeCfg.orxRadioCtrlModeCfg.orxEnableMode = ADI_ADRV9025_ORX_EN_SPI_MODE; //ADI_ADRV9025_ORX_EN_SINGLE_CH_2PIN_MODE;
adrv9025PostMcsInitInst.radioCtrlInit.radioCtrlModeCfg.rxRadioCtrlModeCfg.rxEnableMode = ADI_ADRV9025_RX_EN_SPI_MODE ; //ADI_ADRV9025_RX_EN_PIN_MODE;
adrv9025PostMcsInitInst.radioCtrlInit.radioCtrlModeCfg.txRadioCtrlModeCfg.txEnableMode = ADI_ADRV9025_TX_EN_SPI_MODE ;//ADI_ADRV9025_TX_EN_PIN_MODE;
recoveryAct = adi_adrv9025_RadioCtrlCfgSet(adrv9025Device, &adrv9025PostMcsInitInst.radioCtrlInit.radioCtrlModeCfg);
	

3. Adjusted ORX1 gain, using gain index of 0xFF (see code snippet)

rx_gain.gainIndex       = 0xFF ;
rx_gain.rxChannelMask   = ADI_ADRV9025_ORX1 ;
    
recoveryAct = adi_adrv9025_RxGainSet  (device, &rx_gain, 1);

4. Run the external path delay for TX2 (see code snippet)

adrv9025PostMcsInitInst.initCals.calMask 		= 0x00200000; //external path delay
adrv9025PostMcsInitInst.initCals.channelMask 	= ADI_ADRV9025_TX2 ;
adrv9025PostMcsInitInst.initCals.warmBoot		= 0 ;
recoveryAct = adi_adrv9025_InitCalsRun(device, &adrv9025PostMcsInitInst.initCals);

recoveryAct = adi_adrv9025_InitCalsWait(device, 60000, &initCalsError);

5. Run the Tx external LOL init cals (see code snippet)

adrv9025PostMcsInitInst.initCals.calMask        = 0x00000800; //external LOL
adrv9025PostMcsInitInst.initCals.channelMask 	= ADI_ADRV9025_TX2 ;
adrv9025PostMcsInitInst.initCals.warmBoot		= 0 ;

recoveryAct = adi_adrv9025_InitCalsRun(device, &adrv9025PostMcsInitInst.initCals);

recoveryAct = adi_adrv9025_InitCalsWait(device, 60000, &initCalsError);

6. Configured the CFR settings (adi_adrv9025_CfrCtrlConfigSet,  adi_adrv9025_CfrCorrectionPulseWrite_v2, adi_adrv9025_CfrEnableSet, adi_adrv9025_CfrHardClipperConfigSet, see code snippet)

7. Run the CFR Init Cals (with 60s wait time passed to the adi_adrv9025_InitCalsWait function)

8. Loaded the PA model (see code snippet)

dpdModelConfigInst.txChannelMask = ADI_ADRV9025_TX2 ; 
dpdModelConfigInst.dpdNumFeatures = 22;

char* dpd_model = "/media/sddata/defaultDpdModel.txt"  ;
FILE *fp = fopen(dpd_model, "r");

for(int n = 0; n <dpdModelConfigInst.dpdNumFeatures ; n++)
{
    fscanf(fp, "%d %d %d %d %f %f",
    	&dpdModelConfigInst.dpdFeatures[n].i,
    	&dpdModelConfigInst.dpdFeatures[n].j,
    	&dpdModelConfigInst.dpdFeatures[n].k,
    	&dpdModelConfigInst.dpdFeatures[n].lut,
    	&dpdModelConfigInst.dpdFeatures[n].coeffReal,
    	&dpdModelConfigInst.dpdFeatures[n].coeffImaginary);
    
    printf("\n%d %d %d %d %f %f\n",
    	dpdModelConfigInst.dpdFeatures[n].i,
    	dpdModelConfigInst.dpdFeatures[n].j,
    	dpdModelConfigInst.dpdFeatures[n].k,
    	dpdModelConfigInst.dpdFeatures[n].lut,
    	dpdModelConfigInst.dpdFeatures[n].coeffReal,
    	dpdModelConfigInst.dpdFeatures[n].coeffImaginary);
}
adi_adrv9025_TxChannels_e tx_channels ;
fclose(fp);
recoveryAct = adi_adrv9025_DpdModelConfigSet(adrv9025Device, &dpdModelConfigInst);

--- My DPD MODEL ---

   0  0  0   0       0            0
   0  0  1   0       0            0
   0  0  2   0       0            0
   0  0  3   0       0            0
   0  0  4   0       0            0
   0  0  5   0       0            0
   1  1  1   2       0            0
   1  1  2   2       0            0
   1  1  3   2       0            0
   1  1  4   2       0            0
   1  1  5   2       0            0
   0  1  0   8       0            0
   0  1  1   8       0            0
   0  1  2   8       0            0
   0  1  3   8       0            0
   0  1  4   8       0            0
   0  1  5   8       0            0
   2  2  1  26       0            0
   2  2  2  26       0            0
   2  2  3  26       0            0
   2  2  4  26       0            0
   2  2  5  26       0            0

9. Asserted DPD Reset  (ADI_ADRV9025_DPD_RESET_FULL, see code snippet)

recoveryAct = adi_adrv9025_DpdReset(adrv9025Device, ADI_ADRV9025_TX2, ADI_ADRV9025_DPD_RESET_FULL);
if (recoveryAct != ADI_COMMON_ACT_NO_ACTION)
{
    printf("ERROR: : %s:%u has failed.\n", __func__, __LINE__);
    return recoveryAct;
}

10. Run DPD Tracking configuration (see code snippet)

int32_t MThreshold_dBFS                 = -21; 
int16_t DpdLowPowerThreshold_dBFS       = -36;
int16_t DpdLowPowerThresholdORx_dBFS    = -36;

int32_t temp                            = pow(10,(MThreshold_dBFS/20.0))*FULL_SCALE_CODE;
int32_t dpdMThreshold_val               = pow(temp,2);
int16_t minAvgSignalLevel_val           = pow(10,(DpdLowPowerThreshold_dBFS/20.0))*FULL_SCALE_CODE;
int16_t minAvgSignalLevelOrx_val        = pow(10,(DpdLowPowerThresholdORx_dBFS/20.0))*FULL_SCALE_CODE;

dpdTrackingConfigInst.txChannelMask                             = ADI_ADRV9025_TX2; 
dpdTrackingConfigInst.minAvgSignalLevel                         = minAvgSignalLevel_val;
dpdTrackingConfigInst.minAvgSignalLevelOrx                      = minAvgSignalLevelOrx_val;
dpdTrackingConfigInst.dpdMThreshold                             = temp ;
dpdTrackingConfigInst.dpdPeakSearchWindowSize                   = 65536;
dpdTrackingConfigInst.dpdIndirectRegularizationValue            = 30;
dpdTrackingConfigInst.dpdDirectRegularizationValue              = 30; 
dpdTrackingConfigInst.dpdSamples                                = 16384;
dpdTrackingConfigInst.dpdUpdateMode                             = ADI_ADRV9025_DPD_TRACKING_UPDATE_MODE_0;
dpdTrackingConfigInst.dpdIndirectRegularizationLowPowerValue    = 20;
dpdTrackingConfigInst.dpdFilterSel                              = 0; 
dpdTrackingConfigInst.enableDirectLearning                      = 0;
dpdTrackingConfigInst.dpdMu                                     = 50;

recoveryAct = adi_adrv9025_DpdTrackingConfigSet(adrv9025Device, &dpdTrackingConfigInst);

11. Setup closed-loop gain control configurations (for monitoring purposes. See code snippet)

adi_adrv9025_ClgcConfig_t clgc_config   ;
clgc_config.clgcEnableGainControl   = 0 ;
clgc_config.txChannelMask           = ADI_ADRV9025_TX2 ;

recoveryAct = adrv9025_ClgcConfigSet( adrv9025Device, &clgc_config, 1) ;

12. Enabled TX QEC and TX LOL tracking Calibrations (See code snippet)

recoveryAct = adi_adrv9025_TrackingCalsEnableSet (  adrv9025Device, 
                                                        ( ADI_ADRV9025_TRACK_TX2_QEC | ADI_ADRV9025_TRACK_TX2_LOL ), 
                                                        ADI_ADRV9025_TRACKING_CAL_ENABLE
                                                    );

13. Enabled DPD Tracking Calibration (See code snippet)

adi_adrv9025_TrackingCalsEnableSet (device, ADI_ADRV9025_TRACK_TX2_DPD, ADI_ADRV9025_TRACKING_CAL_ENABLE);

14. Enabled CLGC Tracking Calibration  (See code snippet)

recoveryAct = adi_adrv9025_TrackingCalsEnableSet (  adrv9025Device, 
                                                        ADI_ADRV9025_TRACK_TX2_CLGC, 
                                                        ADI_ADRV9025_TRACKING_CAL_ENABLE
                                                    );

15.  Started monitoring DPD tracking calibration (See code snippet)

//Wait for some time before getting the status
sleep(20);


adi_adrv9025_DpdStatus_t dpdStatus;
int32_t recoveryAction = 0;
recoveryAction = adi_adrv9025_DpdStatusGet (device, txChannel, &dpdStatus);

printf ("dpdErrorCode		:0x%x\n",dpdStatus.dpdErrorCode);
printf ("dpdPercentComplete	:%d\n",dpdStatus.dpdPercentComplete);
printf ("dpdPerformanceMetric	:%d\n",dpdStatus.dpdPerformanceMetric);
printf ("dpdIterCount		:%d\n",dpdStatus.dpdIterCount);
printf ("dpdUpdateCount		:%d\n",dpdStatus.dpdUpdateCount);

printf ("dpdSyncStatus		:%d\n",dpdStatus.dpdSyncStatus);


float meanTu = dpdStatus.dpdStatistics.dpdMeanTuPower;
float peakTu = dpdStatus.dpdStatistics.dpdPeakTuPower;
float meanTx = dpdStatus.dpdStatistics.dpdMeanTxPower;
float peakTx = dpdStatus.dpdStatistics.dpdPeakTxPower;
float meanOrx = dpdStatus.dpdStatistics.dpdMeanOrxPower;
float peakOrx = dpdStatus.dpdStatistics.dpdPeakOrxPower;

printf ("dpdModelTable		:%d\n",dpdStatus.dpdModelTable);


printf ("dpdMeanTuPower		:%f\n",20*log10(sqrt(meanTu) / 32768));
printf ("dpdPeakTuPower		:%f\n",(20*log10(sqrt(peakTu) / 32768)));
printf ("dpdMeanTxPower		:%f\n",(20*log10(sqrt(meanTx) / 32768)));
printf ("dpdPeakTxPower		:%f\n",20*log10(sqrt(peakTx) / 32768));
printf ("dpdMeanOrxPower		:%f\n",20*log10(sqrt(meanOrx) / 32768));
printf ("dpdPeakOrxPower		:%f\n",20*log10(sqrt(peakOrx) / 32768));
printf ("dpdDirectEvm		:%f\n",dpdStatus.dpdStatistics.dpdDirectEvm);
printf ("dpdIndirectEvm		:%f\n",dpdStatus.dpdStatistics.dpdIndirectEvm);
printf ("dpdSelectError		:%f\n",dpdStatus.dpdStatistics.dpdSelectError);
printf ("dpdIndirectError	:%f\n",dpdStatus.dpdStatistics.dpdIndirectError);
printf ("dpdErrorStatus0 (metrics:actions): X:X	:%d:%d\n",	dpdStatus.dpdErrorStatus0.dpdMetricsMask,
						    dpdStatus.dpdErrorStatus0.dpdActionMask);

printf ("dpdErrorStatus1 (metrics:actions)		:%d:%d\n", 	dpdStatus.dpdErrorStatus1.dpdMetricsMask,
							dpdStatus.dpdErrorStatus1.dpdActionMask);
printf ("dpdPersistentErrorStatus0 (metrics:actions)	:%d:%d\n", 	dpdStatus.dpdPersistentErrorStatus0.dpdMetricsMask,
							dpdStatus.dpdPersistentErrorStatus0.dpdActionMask);
printf ("dpdPersistentErrorStatus1 (metrics:actions)	:%d:%d\n", 	dpdStatus.dpdPersistentErrorStatus1.dpdMetricsMask,
							dpdStatus.dpdPersistentErrorStatus1.dpdActionMask);
printf ("reservedPM		:%d\n",dpdStatus.reservedPM);
printf ("reservedTP		:%d\n",dpdStatus.reservedTP);
printf ("reservedPR		:%d\n",dpdStatus.reservedPR);

16. Started monitoring CLGC status (See code snippet)

recoveryAct = adi_adrv9025_ClgcStatusGet(   adrv9025Device  ,
                                                ADI_ADRV9025_TX2,
                                                &clgc_status
                                            );

printf("******** CLGC Monitoring ****************\n")  ;
printf ("clgc_status.clgcLoopGain           : %f\n", clgc_status.clgcLoopGain) ;
printf ("clgc_status.clgcTxRmsPower           : %f\n", clgc_status.clgcTxRmsPower) ;
printf ("clgc_status.clgcOrxRmsPower           : %f\n", clgc_status.clgcOrxRmsPower) ;
printf ("clgc_status.activeTxAttenIndex           : 0x%x\n", clgc_status.activeTxAttenIndex) ;
printf ("clgc_status.activeOrxGainIndex           : 0x%x\n", clgc_status.activeOrxGainIndex) ;

---- And, here's the output of the above code: ----- 

*** Tx2 DPD Status ***
***************************************
Retrieving DPD status
dpdErrorCode :0x3405
dpdPercentComplete :0
dpdPerformanceMetric :0
dpdIterCount :18
dpdUpdateCount :0
dpdSyncStatus :4
dpdModelTable :0
dpdMeanTuPower :-14.280720
dpdPeakTuPower :-3.605944
dpdMeanTxPower :-inf
dpdPeakTxPower :-inf
dpdMeanOrxPower :-58.033522
dpdPeakOrxPower :-49.337814
dpdDirectEvm :4.338248
dpdIndirectEvm :nan
dpdSelectError :0.000000
dpdIndirectError :0.000000
dpdErrorStatus0 (metrics:actions): X:X :0:0
dpdErrorStatus1 (metrics:actions) :0:0
dpdPersistentErrorStatus0 (metrics:actions) :0:0
dpdPersistentErrorStatus1 (metrics:actions) :0:0
reservedPM :0
reservedTP :40249683
reservedPR :40249683
***************************************

*** Running Monitor CLGC tracking Calibration Status ***
******** CLGC Monitoring ****************
clgc_status.clgcLoopGain : 0.000000
clgc_status.clgcTxRmsPower : 0.000000
clgc_status.clgcOrxRmsPower : 0.000000
clgc_status.activeTxAttenIndex : 0x0
clgc_status.activeOrxGainIndex : 0x0
---------------- End of output ----------------------------
 
I am not sure what I am doing wrong here. Could it be because I have not connected a PA yet? 


One thing to add is that after asserting reset, the signal on the TX2-ORX1 path dies and never comes back up. Could it be that I am not performing reset correctly? 
[edited by: AlexAntwi at 2:44 AM (GMT -5) on 12 Nov 2021]
  • Make sure that the TX to ORX mapping is correct. Check the ORX signal at the output by giving a signal from TX. Hope an attenuator is cnnected in between TX to ORX as the maximum useable input that can be given to ORX is -11dBm.

    Refer to the document for DPD:

    https://ez.analog.com/wide-band-rf-transceivers/design-support-adrv9026/w/documents/15624/adrv9029-dpd-and-cfr-user-guide 

  • dpdErrorCode of 3405 refers to 'ORX signal being Too small' which means the ORX signal is not present/ out of threshold.

    You have mentioned ORX disabled in Step2, is it enabled while you are running DPD tracking cal?

    Also, while you are running ext. path delay cal, hope the PA is switched on.

    Have you generated the Initdata.c from the latest GUI with the required ORX selection and used in your code?  

  • Hello

    ORx is disabled while running DPD tracking Cal. Actually, it stays disabled.

    As I mentioned, I have not connected a PA between the Tx2-ORx1 path yet. That is if you mean the external PA.

    I generated the initdata.c some time back and haven't changed it since, so I am not sure if the software has been update since then. And, from the initdata.c file, I can see we are using framer[1] for ORx1_I/Q. Please see the below snippet from the framer[1] setting. 

     {  // framer[1]
                0,  // enableJesd204C
                0,  // bankId
                1,  // deviceId
                0,  // lane0Id
                2,  // jesd204M
                32,  // jesd204K
                4,  // jesd204F
                16,  // jesd204Np
                0,  // jesd204E
                1,  // scramble
                4,  // serializerLanesEnabled
                0,  // lmfcOffset
                1,  // syncbInSelect
                0,  // overSample
                1,  // syncbInLvdsMode
                0,  // syncbInLvdsPnInvert
                { // serializerLaneCrossbar
                    8,  // lane0FramerOutSel
                    8,  // lane1FramerOutSel
                    0,  // lane2FramerOutSel
                    8   // lane3FramerOutSel
                },
                { // adcCrossbar
                    ADI_ADRV9025_ADC_ORX1_I,  // conv0
                    ADI_ADRV9025_ADC_ORX1_Q,  // conv1
                    ADI_ADRV9025_ADC_DISABLE,  // conv2
                    ADI_ADRV9025_ADC_DISABLE,  // conv3
                    ADI_ADRV9025_ADC_DISABLE,  // conv4
                    ADI_ADRV9025_ADC_DISABLE,  // conv5
                    ADI_ADRV9025_ADC_DISABLE,  // conv6
                    ADI_ADRV9025_ADC_DISABLE,  // conv7
                    ADI_ADRV9025_ADC_DISABLE,  // conv8
                    ADI_ADRV9025_ADC_DISABLE,  // conv9
                    ADI_ADRV9025_ADC_DISABLE,  // conv10
                    ADI_ADRV9025_ADC_DISABLE,  // conv11
                    ADI_ADRV9025_ADC_DISABLE,  // conv12
                    ADI_ADRV9025_ADC_DISABLE,  // conv13
                    ADI_ADRV9025_ADC_DISABLE,  // conv14
                    ADI_ADRV9025_ADC_DISABLE,  // conv15
                    ADI_ADRV9025_ADC_DISABLE,  // conv16
                    ADI_ADRV9025_ADC_DISABLE,  // conv17
                    ADI_ADRV9025_ADC_DISABLE,  // conv18
                    ADI_ADRV9025_ADC_DISABLE,  // conv19
                    ADI_ADRV9025_ADC_DISABLE,  // conv20
                    ADI_ADRV9025_ADC_DISABLE,  // conv21
                    ADI_ADRV9025_ADC_DISABLE,  // conv22
                    ADI_ADRV9025_ADC_DISABLE   // conv23
                },

     I may have to edit it so it supports ORx3 (since we are using that too. But, that should not be a problem since I am using the ORx1 right? 

  • Hello

    I am using a splitter at the TX output. The splitter has two outputs. One part is connected to the ORX port and the other, I connected it to a spectrum analyzer for monitoring the TX-ORX signal. After programming the chip and enabling Tx and writing the data, I am able to see the data on this path I just described. After I assert reset, this signal dies. and all I see is a low-power single-tone signal in the spectrum analyzer. The other path where I am not running DPD is constantly showing the data on the spectrum analyzer. So, unless I did not get you well, I am sending a signal from TX. 

    And, I followed all the steps from the document in the link you shared. Could it be that I used the wrong parameters? Kindly check my code snippets for me, please. 

  • ORx is disabled while running DPD tracking Cal. Actually, it stays disabled.

    ORX path must be enabled while DPD tracking cal is running.

    As I mentioned, I have not connected a PA between the Tx2-ORx1 path yet. That is if you mean the external PA.

    Oh yes, I missed that the PA is not connected in your setup.But the ORX path must be enabled for External Path delay cal.

    Can you please generate the required configuration from the TES GUI and then use it?

  • Hello

    Thank you for your response. I am a bit confused when you say I have to enable the ORX path while DPD cal is running because I got the idea of switching it off from another Analog devices employee. Kindly check Pvalavan's post from this link: https://ez.analog.com/wide-band-rf-transceivers/design-support-adrv9026/f/q-a/544898/adrv9029-dpd-configuration. I have quoted the part where I understood as I had to turn off the ORX pasth below:

    "While testing DPD we recommend to disable the ORX Path. Before enabling DPD you can enable ORX path and measure ORXDecpower if required"

    Could you kindly clarify for me? In the mean time, I will go ahead and enable the ORX path and let you know what I get from it. 

    Also, I already generated the required configuration from the TES GUI. That's what I am using. The only thing missing is the ORX3 path which I didn't enable. I hope I can test without that path enabled for now.

  • Correction: As you are using API/ SPI mode for ORX control, the ARM takes care of enabling/ disabling the ORX while DPD cal is running. So, ORX path need not be enabled explicitly.

    Also, I already generated the required configuration from the TES GUI. That's what I am using. The only thing missing is the ORX3 path which I didn't enable. I hope I can test without that path enabled for now.

    Yes, this should not be a problem.

    This issue is something to do with ORX mapping, are you testing in FDD or TDD mode, Which use case are you using?

  • Hello 

    Thanks a lot for the clarification.

    To answer your question, I am using Use Case 50 nonLink Sharing. 

    This issue is something to do with ORX mapping

    I am beginning to think so too. The signal doesn't go off completely as in when the Tx channel is disabled. I see a low-powered single tone when my written data gets taken down. Could it be the system reverts to a different mapping during DPD reset and as it cannot detect any data, it just sends an internally generated carrier signal?

    When I call adi_adrv9025_TxToOrxMappingGet() however, it returns with 0x2 which is TX2 and that is correct. Could it be a problem in my GUI configuration?

    Kindly let me know If you would need other information about my TES GUI configuration. I can send you my files and pictures of the GUI configurations. 

  • Can you please disable CLGC tracking cal and test only with DPD tracking cal enable?

    Kindly let me know If you would need other information about my TES GUI configuration. I can send you my files and pictures of the GUI configurations. 

    Yes, please share your files, pictures of the GUI configurations.

  • Hello 

    Please check the link below for my GUI configuration images and generated files. 

    https://drive.google.com/file/d/1fa2g6JW450Zu_EiTTun6RbaSbRuG-U6e/view?usp=sharing

    About testing with only DPD cal enable, that was what I did the first time I tried running DPD. I thought I probably had to include CLGC in order for the capture timeout and ORx low power error messages to go away. The results then, and now are the same.