I'll try to take a stab at explaining this, and if it helps, I'll add it to some READMEs.
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
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).
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
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
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()
functions, which utilize the pkt_8x8
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.
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
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!