device->Configure resetting register even with config.skipDefaults set?

Hi all,

I’m working with an XTRX device (gateware 16.1) and using the LimeSuiteEng library. I’m running into an issue with register settings being reset after calling device->Configure(config, moduleIndex);.

Problem:

  • Before calling device->Configure(), I set the register LMS7_AGC_BYP_RXTSP (0x040C [6]) using device->WriteRegister.
  • I verify the setting using device->ReadRegister and LimeGUI, and the value appears to be applied correctly.
  • After calling device->Configure(config, moduleIndex), the register value appears to be reset. Both device->ReadRegister and LimeGUI show the register value has reverted to its default.

Things I’ve tried:

  • I’ve set the config.skipDefaults flag, setting it to both undefined and true, with the same result.
  • I see that skipDefaults should avoid calling InitLMS1 (LimeSDR_XTRX.cpp#L277), but this doesn’t seem to prevent the register reset.

Questions:

  • Is there something obvious I’m missing when using skipDefaults or when calling device->Configure?
  • Could something else in the configuration flow be resetting the register?

Any help would be greatly appreciated!

Thanks!

It’s being overwritten here:

The idea of skipDefaults is to not do full reset of all registers, so that it would be possible to first load custom register values file that specify parameters which are chip specific (like voltages, currents, timings…), and then the Configure() would perform the usual SDR parameters configuration on top of that.

Because some of the chip parameters have dependencies in what order they should be changed, the Configure() function was created, so that it would take the entire desired SDR state and write all parameters into chip in expected order.
It modifies a lot of registers, so any custom adjustments should be done after the Configure() call is done.

1 Like

Thanks for the quick and detailed response - very much appreciate it. Would the project be open to a documentation PR relating to this? I may not get to it right away ,but it seems like something that others might hit, and I’ll put it in a backlog queue if that is desirable.

For my use case I expect to be changing frequencies and chip parameters rapidly. I’ll use the Configure code as a reference as to the ordering and avoid calling Configure directly.

Given the use case above, is there anything that comes to mind that I should or shouldn’t do, other than avoid calling Configure?

Yes, contributions are welcome.

Configure() is most useful to get the base configuration done, after that you can change individual parameters using dedicated functions or register writes, the complicated part is the sampling rate/oversampling configuration, because both channels share the same clock. I suspect you’re not going to be modifying sampling rates.

If you intend to do LO frequency rapid changes, then there are couple of possibilities with the LMS7002M chip:

  1. If frequency hopping range is within the sampling rate bandwidth. You can use table of up to 16 values, to offset the LO frequency using NCO mixer. The NCO frequency selection from table requires 1 SPI register write. (each table value modification 2 register writes)
  2. Large frequency changes requires reconfiguring of the LO PLL. That requires 8 register writes (1 of the values needs to be tuned, so it would require variable time to find. These values can be resued, so can be precalculated at the start.
  3. When LO frequency is changed, if the DC/IQ imbalance correctors are used, they should be recalibrated, it’s a relatively long operation, so also should be precalculated at the start for each frequency. Then after LO change just write the precalculated coefficients.

Thanks again. Very much appreciate it.

You are correct in that my use case doesn’t change the sample rate, although it does require decent frequency changes and thus the LO PLL reconfiguration. I’ve confirmed that the NCO configuration might work for a subset of what I’m looking at doing - thanks.

In an effort to precompute and save the LO PLL particulars I’ve been looking through the code in limesuiteng as to how that might be done. I’ve come across pll_sweep.cpp in the utilities. It seems to be using some classes that are fairly deep and internal to the library.

Do you know if there is a higher level API that I could leverage to get/set the pll clocks? I’d prefer to not delve all the way down to something similar to the following in my application unless it can’t be helped.

auto fpgaTxPLL = ...
auto fpgaRxPLL = ...
auto fpga = dynamic_cast<LMS7002M_SDRDevice*>(device)->GetFPGA();
OpStatus status = fpga->SetInterfaceFreq(fpgaTxPLL, fpgaRxPLL, chipInd);

Side note: It seems this utility has been deprecated in favour of other tool? I had to go and modify the CMakeLists.txt and update a header to get it compiled.

Obviously please do let me know if I’m missing something completely - at this point I’m fairly new to the limesuiteng codebase and am trawling through the examples/forum/code to see how this could be done, I very well could have missed something obvious.

Thanks again.

Here’s some code snippet how the PLL state could be stored:

struct PLL_State
{
	static const std::array<uint16_t, 5> addr = {
	//PLL coefficients
	0x011C,
	0x011D,
	0x011E,
	0x011F,
	//PLL VCO tune
	0x0121,
	};
	std::array<uint16_t, 5> values;
}

PLL_State ReadPLLState(LMS7002M* chip, lime::TRXDir dir)
{
	LMS7002M::ChannelScope scope(chip, (dir == TRXDir::Tx ? LMS7002M::Channel::ChSXT : LMS7002M::Channel::ChSXR))

	PLL_State state;
	chip->SPI_read_batch(PLL_State::addr.data(), state.values.data(), state.values.size());
	return state;
}

void WritePLLState(LMS7002M* chip, lime::TRXDir dir, const PLL_State& state)
{
	LMS7002M::ChannelScope scope(chip, (dir == TRXDir::Tx ? LMS7002M::Channel::ChSXT : LMS7002M::Channel::ChSXR))
	
	PLL_State state;
	chip->SPI_write_batch(PLL_State::addr.data(), state.values.data(), state.values.size());
}

In writing the PLL state, the channel changes could be optimized further and put into the same spi write batch (channel change is controlled by 0x0020 register).
There are two PLLs in the LMS7002M chip, one for Rx, and one for Tx.

If your use case is running with different Rx/Tx frequencies (FDD), then you’ll need to reconfigure both PLLs.
If in your use case Rx/Tx are the same frequency (TDD), then you only need to reconfigure Tx PLL, it’s being shared in TDD mode, and RxPLL is disabled.

For those who may read this in the future, I ended up using this comment along with the above to identify the pertinent registers. The parameters were found in LMS7002M_parameters.h, dropping the LMS7_ prefix. The code looks similar to the following:

int modIndex = ...
int chanIndex = ...
SDRDevice* d = ...
std::vector<std::string> params = { "EN_DIV2_DIVPROG", "FRAC_SDM_LSB",
			                        "INT_SDM",         "FRAC_SDM_MSB",
			                        "DIV_LOCH",        "CSW_VCO",
			                        "SEL_VCO" };

// to get/save
for (const std::string& param : params) {
        auto val = d->GetParameter(modIndex, chanIndex, param);
        // save param/val somewhere
}

// to set/load
// iterate through the saved params and vals
// d->SetParameter(...)

These parameters in combination with SetFrequency seem to do the trick, at least for me right now.

I specifically gave only the register addresses, because some of those parameters are in the same address just different bits, so reading/writing individual parameters would be redundant and require more transactions/time.

@ricardas thanks! Appreciate it.

In my case I had a few other parameters being set, so transitioning to the parameters made sense for my application currently. I added a comment in the forum specifically because I couldn’t find anywhere that referenced the parameters to facilitate this ( even if as you do point out the parameters include other bits ).