In our previous two posts, I showed how to setup projects for a Persona Device and Programmable Device. We then ran through a quick systems check of allocating and deallocating personas within a Node. This time we’ll look at setting up a specific type of Persona that implements an FEI interface (i.e., the persona is going to represent a radio).
Project Setup
Reminder: the REDHAWK IDE does not support making Persona devices as of 2.2.x. Similar to both of the previous articles, we’re going to leverage the IDE to build the initial FEI project, generate code, and then run the Persona code generator. I’m going to repeat most of the procedure from part 1 here to contain it and the changes in one spot:
- Right-click in the Project Explorer, New -> Project…
- Choose REDHAWK Front End Device Project.
- On the New Device Project panel:
a. Name the device and press Next.
b. Choose C++ as the programming language.
c. Use Next and Finish through the rest of the wizard, configuring your FEI’s properties. - From the Project’s Implementations tab, select the implementation and scroll down to Code.
a. Change the Type to Shared Library
b. Change the Entry Point and File values tocpp/libPROJECT_NAME.so
(wherePROJECT_NAME
is the name you used on step 3) - Save your changes.
- Press the button for Generate All Implementations.
- Create a copy of your implementation header and cpp files (
PROJECT_NAME.{h,cpp}
). Later, we will need the FEI helper functions from these, which are all declared or defined below theserviceFunction
in each case. - Open a terminal at the project directory and run the following:
gt; make -C cpp distclean
gt; redhawk-codegen -f --persona PROJECT_NAME.spd.xml
Component PROJECT_NAME
build.sh
PROJECT_NAME.spec
Tests PROJECT_NAME
tests/test_PROJECT_NAME.py
Implementation: cpp
cpp/PROJECT_NAME.cpp
cpp/PROJECT_NAME.h
cpp/PROJECT_NAME_base.cpp
cpp/PROJECT_NAME_base.h
cpp/main.cpp
cpp/Makefile.am
cpp/Makefile.am.ide
cpp/configure.ac
cpp/build.sh
cpp/reconf
cpp/PROJECT_NAME_persona_base.cpp (added)
cpp/PROJECT_NAME_persona_base.h (added)
cpp/port_impl.cpp (added)
cpp/port_impl.h (added)
cpp/struct_props.h
cpp/template_impl.cpp (deleted)
- Edit the
cpp/.md5sums
file to removemain.cpp
,PROJECT_NAME_persona_base.h
andPROJECT_NAME_persona_base.cpp
from the list. - Re-run the IDE’s code generator to restore the files that were deleted.
- Open the implementation header and cpp files to copy in the FEI helper function declarations and implementations, in each case.
At this point you have an shared library FEI Persona device that can be maintained via the IDE and integrated with your Programmable Device of choice.
API
The API is the same as in part 1 with the notable difference that your implementation class’s allocateCapacity
and deallocateCapacity
calls should call the FEI implementations to allow all that other glue code to run. The catch here is the current implementation in the FEI base class will log errors if it receives anything other than FRONTEND::...
property IDs. Here is one approach for allocateCapacity
:
CORBA::Boolean PROJECT_NAME_i::allocateCapacity(const CF::Properties& capacities)
throw (CF::Device::InvalidState, CF::Device::InvalidCapacity, CORBA::SystemException)
{
RH_TRACE(__logger, __PRETTY_FUNCTION__);
bool allocationSuccess = true;
if (isBusy() || isLocked()) {
RH_WARN(__logger, __FUNCTION__ <<
": Cannot allocate capacities... Device state is locked and/or busy");
return false;
}
if (capacities.length() == 0) {
if (not attemptToProgramParent()) {
RH_ERROR(__logger, __FUNCTION__ << "Failed to program parent");
return false;
}
} else {
// Review the properties
redhawk::PropertyMap tuner_props;
CORBA::ULong ii;
try {
for (ii = 0; ii < capacities.length(); ++ii) {
const std::string id = (const char*) capacities[ii].id;
PropertyInterface* property = getPropertyFromId(id);
if(not property){
RH_DEBUG(__logger, "UNKNOWN PROPERTY");
throw CF::Device::InvalidCapacity("UNKNOWN PROPERTY", capacities);
}
// Save off any FRONTEND properties for later.
if (id == "FRONTEND::tuner_allocation" || id == "FRONTEND::scanner_allocation" || id == "FRONTEND::listener_allocation") {
tuner_props[id] = capacities[ii].value;
continue;
} else {
try {
property->setValue(capacities[ii].value);
}
catch(const std::logic_error &e){
RH_DEBUG(__logger, "COULD NOT PARSE CAPACITY: " << e.what());
throw CF::Device::InvalidCapacity("COULD NOT PARSE CAPACITY", capacities);
};
}
}
// If any FRONTEND properties were found, forward to the base class for distribution.
if (0 < tuner_props.length()) {
allocationSuccess &= FrontendTunerDevice::allocateCapacity(tuner_props);
}
}
catch(const std::logic_error &e) {
deallocateCapacity(capacities);
return allocationSuccess;
}
catch(frontend::AllocationAlreadyExists &e) {
// Don't call deallocateCapacity if the allocationId already exists
// - Would end up deallocating a valid tuner/listener
throw static_cast<CF::Device::InvalidCapacity>(e);
}
catch(CF::Device::InvalidCapacity &e) {
deallocateCapacity(capacities);
throw e;
}
catch(...){
deallocateCapacity(capacities);
throw;
};
}
return allocationSuccess;
}
Edit 2019/08/02: As Jim Courtney pointed out below, your
allocateCapacity
declaration and definition both need to remove theInsufficientCapacity
throw.
The above is the melding of the original Persona allocateCapacity
and the one from fe_tuner_device.cpp
. The key here is that sending an empty list of capacities attempts to program the persona device (in this example). Sending a list, we build a separate property map of those that won’t offend the FEI base class and then if that list is populated, pass it along (this is to support having our own allocable properties separate from FEI in the future). The deallocateCapacity
is a similar melding. Feel free to explore your own paradigm, however.
Life Cycle
These functions are the same as part 1 as well, however now you should also backfill all of the FEI helper functions (like deviceEnable
) to be what you need in addition to the Persona Device -related life cycle functions (if you need them). In the example in the demo video (below), I needed the various before and after programming methods for unbinding and binding drivers ahead of probing the attached hardware’s properties.
Demo!
The demo system is a Xilinx ZCU102 with an Analog Devices ADRV9371 EBZ evaluation board configured for two independent receive channels using RX1 and the observer interface (the transmit is detected, but not implemented). (Sound familiar? This was the original demo.). The data is currently streamed over the DMA interface from the Analog Devices reference design with the plan to move it out-of-band with a VITA49 framer within the FPGA, which is managing its own ethernet adapter apart from Linux.
As for the REDHAWK side of things, it’s 2.2.3 running on a Yocto Rocko version of Poky, and contains 3 devices in its node: GPP, FpgaManager, and ADRV9371EBZ. Of the latter two devices, the FpgaManager is the Programmable which, as the name suggests, communicates with the 4.14 Linux kernel FPGA Manager interface to manage the UltraScale+ FPGA onboard. The ADRV9371EBZ is the FEI Persona exposing the evaluation board when active.
Oh, and we also installed ESRA, the Extensible Spectrum Reasoning Application. What it provides is a way to task an arbitrary number of front-end devices to meet the spectral needs of an individual analysis task such as a stare or a scan. It does this by managing a set of waveforms which coallate coarse energy detections through an inference engine to produce signal profiles as well as provide RF snapshotting and other features. For the UI, we’re using Angular-REDHAWK and REST-Python.