I'll try to take a stab at explaining this, and if it helps, I'll add it to some READMEs.
Accessing Registers
Once you've added a register in QSYS and built the FPGA, you'll find that some files will be generated in the
bladeRF_nios_bsp directory. In here you'll see headers that contain the base address of your address space. Try doing a build and then looking for the LMS6 register access space region, described by a
LMS_SPI_BASE definition.
The
devices.c file is generally where we add functions (generally small wrappers with nice names) to register accesses.
Here is an example of where we access the LMS6002 register space (8-bit address and data).
Packet formats
As you found
from this README, there's a number of different packet formats. The formats in the form
pkt_AxD are intended for register access with
A-bit addresses (or masks) and
D bit data. You'll also see a
pkt_retune for "quick retune" and "scheduled retune" functionality, as well as a
pkt_legacy for reverse compatibility with earlier FPGAs.
A "magic byte" is the first byte of a packet and denotes the format of the data the follows. Values of 0x80-0xff will
not be used in upstream bladeRF code and are reserved for users to make customizations without worrying about conflicting with any packet formats we may add in the future. (If you're going to submit a PR to integrate your code upstream, you may choose one of the "official" values.)
In the
pkt_AxD formats, you'll see an ID field that denotes the device/module to communicate with. Again, values of 0x80-0xff are available for user customizations -- official code will not use these values.
Adding a (sub)packet
To either add a new packet format entirely, or add a new ID to an existing packet, you'll want to modify
the header files found here.
These files are located in
fpga_common/include because both libbladeRF and the NIOS II code both use these definitions.
The implementation of a packet should contain descriptions of the request and response formats,
similar to what's shown here for the
pkt_32x32.
You can then add a definition of your custom
pkt_32x32 ID
here.
Handle the packet in the NIOS II
Next, you'll want to add the code to handle there request and response associated with this new ID in the NIOS code. For each packet format, there's an associated C file
here.
For example,
this is where you could place a function call to write your 32-bit data, and
this is where you could place a function call to read it.
The functions to perform those reads/writes are up to you, and depend on the FPGA module you're talking to.
At this point, the FPGA side of thing should be ready to handle reads/writes of your packet changes. Onto the host side!
NIOS II access in libbladeRF
libbladeRF's
nios_access.h and
nios_access.c
provide functions to perform the operations made available by these packets. These are conveniently named wrappers around using these packets, and abstract away the underlying packet handling; the caller does not and should not need to care about the underlying packet format.
Thus, you'd add a read/write pair of functions to these files.
Some simple example are the
nios_lms6_read() and
nios_lms6_write() functions, which utilize the
pkt_8x8 format.
Next, you need to expose this functionality to the libbladeRF code through the "backend" interface. If you take a look
here at usb.c, you'll see that there's a function table
of all the operations that may be performed over a "backend". You can add a function pointer to
this structure and assign in in
usb.c. (This whole "backend" abstraction was made to allow us to add IPC and network backends at some point...still a TODO unfortunately...)
At this point, your functionality has been exposed to the libbladeRF core code.
API access
If you want to extend the libbladeRF API to allow you to use your functionality, you can do so by adding a function prototype to
libbladeRF.h and an implementation to
bladerf.c. Generally, I prefer that new functionaly be added into separate .c/.h files and that bladerf.c remains just a place to acquire the "control lock", if needed, and call into the module's interface.
Going back to our earlier example of the lms6_read/write implementations in
nios_access.c -- we see the calls into that "backend" interface
here for the LMS6 read and
here for the write.
Comments
This certainly seems like a lot, but it's not too bad once you've been through it a time or two. Bear in mind this whole interface is more intended for "slow" control data, as opposed to the tens of MSPS worth of data the sample streaming interface is designed for.
Let me know if anything's unclear!
Cheers,
Jon