lib: Add X440/FBX support

Co-authored-by: Lars Amsel <lars.amsel@ni.com>
Co-authored-by: Martin Braun <martin.braun@ettus.com>
This commit is contained in:
Martin Anderseck 2023-05-24 11:09:19 +02:00 committed by Aki Tomita
parent 371c4c139c
commit f5bb3e0275
31 changed files with 3774 additions and 127 deletions

22
host/docs/fbx.dox Normal file
View File

@ -0,0 +1,22 @@
/*! \page page_fbx FBX Daughterboard
\tableofcontents
\section fbx_overview Overview
The FBX daughterboard is a four-channel, balun-coupled transceiver board.
The FBX daughterboard is the daughterboard for the Ettus USRP X440.
Feature list:
- tbw
- Timed tuning is not supported by the X440, because timed tuning of the NCO
(within the RFSoC) is not supported.
\section fbx_too Theory of Operation
The FBX daughterboard has four transceiver chains. The following simplified block
diagram shows their structure:
*/
// vim:ft=doxygen:

View File

@ -15,8 +15,6 @@
- Xilinx Zynq UltraScale+ RFSoC (ZU28DR), includes quad-core ARM Cortex-A53
(1200 MHz), dual-core ARM Cortex-R5F real-time unit, and UltraScale+ FPGA
- 4 GiB DDR4 RAM for Processing System, 2x4 GiB DDR4 RAM for Programmable Logic
- Up to 4x400 MHz of analog bandwidth, center frequency 1 MHz - 7.2 GHz
(tunable to 8 GHz) using \ref page_zbx
- Closed-loop temperature control
- Default airflow: front-to-back
- Field replaceable fan tray
@ -24,7 +22,7 @@
- Full Linux system running on the quad-core ARM Cortex-A53
- Runs MPM (see also \ref page_mpm)
- FPGA Capabilities:
- Timed commands in FPGA
- Timed commands in FPGA (note: only limited timed frequency tuning available)
- Timed sampling in FPGA
- RFNoC capable: Supports various CHDR bus widths from 64-bits (for minimal
footprint) up to 512 bits (for maximum throughput)
@ -33,34 +31,44 @@
- Stackable (with stack mount kit)
- Airflow default direction can be changed to back-to-front with an
accessory, which is sold separately
- RF Capabilities:
- USRP X410: 4x full-duplex RF channels with a maximum analog bandwidth of
400 MHz, tunable from 1 MHz - 7.2 GHz center frequency (tunable to 8 GHz)
using \ref page_zbx
- USRP X440: 8x full-duplex direct-sampling channels, tunable digitally from
1 MHz to 4 GHz, with a maximum sampling rate of 2048 Msps, using \ref page_fbx
\section x4xx_overview Overview and Features
The X4x0 series of USRPs comes in two variants: The USRP X410, and the USRP X440.
Both variants share the same enclosure and motherboard, and are collectively
referred to as X4x0.
\image html x410.png "Ettus USRP X410" width=50%
The Ettus USRP X410 is a fourth-generation Software Defined Radio (SDR) out of the USRP
family of SDRs. It contains two \ref page_zbx "ZBX Daughterboards" for a
total of 4 channels at up to 400 MHz of analog bandwidth each. The analog
features of the \ref page_zbx are described in a separate manual page.
The Ettus USRP X4x0 is a fourth-generation Software Defined Radio (SDR) out of the USRP
family of SDRs. Depending on the variant, it contains either two
\ref page_zbx "ZBX Daughterboards" for a total of 4 channels at up to 400 MHz of
analog bandwidth each, or two \ref page_fbx "FBX Daughterboards" for a total of
8 channels at up to 1.6 GHz of analog bandwidth.
The analog features of the \ref page_zbx and the \ref page_fbx are described in
a separate manual page.
The USRP X410 features a Xilinx RFSoC, running an embedded Linux system. Like
The USRP X4x0 features a Xilinx RFSoC, running an embedded Linux system. Like
other USRPs, it is addressable through a 1 GbE RJ45 connector, which allows full
access to the embedded Linux system, as well as data streaming at low rates. In
addition, it features two QSFP28 connectors, which allow for up to 4x10 GbE or
1x100 GbE connections each.
1x100 GbE connections each. The RFSoC used on the USRP X410 is a ZU28DR speed grade 1,
on the USRP X440 the ZU28DR speed grade 2.
The front panel provides access to the RF connectors (SMA), Tx/Rx status LEDs,
programmable GPIOs, and the power button. The rear panel is where the power and
data connections go (Ethernet, USB) as well as time/clock reference signals and
GPS antenna.
X410's cooling system uses a field replaceable fan assembly and supports two
variants: one that pulls air front-to-back and one that pulls air back-to-front.
By default, the unit comes with the front-to-back fan assembly.
The front panel provides access to the RF connectors (SMA for X410, MMCX for
X440), Tx/Rx status LEDs, programmable GPIOs, and the power button. The rear
panel is where the power and data connections go (Ethernet, USB) as well as
time/clock reference signals and GPS antenna.
\subsection x4xx_overview_rfsoc The RFSoC CPU/FPGA and Host Operating System
The main chip (the SoC) of the X410 is a Xilinx Zynq UltraScale+ RFSoC
The main chip (the SoC) of the X4x0 is a Xilinx Zynq UltraScale+ RFSoC
(ZU28DR). It contains an ARM quad-core Cortex A53 CPU (referred to as
the "APU"), an UltraScale+ FPGA including peripherals such as built-in data
converters and SD-FEC cores, and an ARM Cortex-R5F real-time processor
@ -84,18 +92,29 @@ It is possible to connect to the host OS either via SSH or serial console (see
sections \ref x4xx_getting_started_ssh and \ref x4xx_getting_started_serial,
respectively).
The X410 has a higher maximum analog bandwidth than previous USRPs. It can provide
rates up to 500 Msps, resulting in a usable analog bandwidth of up to 400 MHz.
In order to facilitate the higher bandwidth, UHD
uses a technology called \ref page_dpdk "Data Plane Development Kit (DPDK)".
See the DPDK page for details on how it can improve streaming, and how to use
it.
The X4x0 has a higher maximum analog bandwidth than previous USRPs. The USRP X410
can provide rates up to 500 Msps, resulting in a usable analog bandwidth of up to 400 MHz.
\subsection x4xx_overview_dboards Daughterboard Connectivity
The USRP X440 can, depending on the FPGA image used, provide up to 2048 Msps of
sampling rate, resulting in a usable analog bandwidth of up to 1.6 GHz on two
channels, or 8 channels at 500 Msps.
In order to facilitate the higher bandwidth, UHD may use a technology called
\ref page_dpdk "Data Plane Development Kit (DPDK)".
See the DPDK page for details on how it can improve streaming, and how to use it.
\subsection x4xx_overview_x410_dboards USRP X410 Daughterboard Connectivity
The Ettus USRP X410 contains two ZBX daughterboards. To find out more about the
capabilities of these analog front-end cards, see \ref page_zbx.
\subsection x4xx_overview_x440_dboards USRP X440 Daughterboard Connectivity
The Ettus USRP X440 contains two FBX daughterboards. Unlike the ZBX daughtercards,
they have no analog mixers or filters, but directly connect the converters to the
RF connectors. To find out more about the
capabilities of these analog front-end cards, see \ref page_zbx and \ref page_fbx.
\subsection x4xx_overview_panels Front and Back Panels
\image html x410_front_panel.png "Ettus USRP X410 Front Panel" width=90%
@ -127,6 +146,16 @@ related connections, and some status LEDs:
It is also possible to stream data over this interface, albeit at a slow rate
(approx. 10 Msps).
The back panel is identical between X4x0 variants.
\image html x410_front_panel.png "Ettus USRP X440 Front Panel" width=90%
FIXME insert X440 front panel pic!
The front panel provides access to the RF ports and status LEDs of the \ref page_fbx.
It also provides access to the front-panel GPIO connectors (2x HDMI) and the
power button.
\subsection x4xx_overview_micro The STM32 Microcontroller
The STM32 microcontroller (also referred to as the "SCU") controls various
@ -142,7 +171,9 @@ for whatever reason.
\subsection x4xx_overview_rackmount Rack Mounting and Cooling
Coming soon!
The X4x0's cooling system uses a field replaceable fan assembly and supports two
variants: one that pulls air front-to-back and one that pulls air back-to-front.
By default, the unit comes with the front-to-back fan assembly.
\subsection x4xx_overview_storage eMMC Storage
@ -181,17 +212,22 @@ is currently already mounted.
\section x4xx_getting_started Getting Started
Firstly, download and install UHD on a host computer following \ref page_install
or \ref page_build_guide. The USRP X410 requires UHD version 4.1 or above.
or \ref page_build_guide. The following minimum UHD versions are required:
\subsection x4xx_getting_started_assembling Assembling the X410
- The USRP X410 requires UHD version 4.1 or above.
- The USRP X440 requires UHD version 4.5 or above.
Inside the kit you will find the X410 and an X410 power supply. Plug these in,
It is generally recommended to use the latest UHD version.
\subsection x4xx_getting_started_assembling Assembling the X4x0
Inside the kit you will find the X4x0 and an X4x0 power supply. Plug these in,
connect the 1GbE RJ45 interface to your network, and power on the device by
pressing the power button.
\subsection x4xx_getting_started_network_connectivity Network Connectivity
Once the X410 has booted, determine the IP address and verify network
Once the X4x0 has booted, determine the IP address and verify network
connectivity by running `uhd_find_devices` on the host computer:
$ uhd_find_devices
@ -206,7 +242,10 @@ connectivity by running `uhd_find_devices` on the host computer:
product: x410
type: x4xx
By default, an X410 will use DHCP to attempt to find an address.
By default, an X4x0 will use DHCP to attempt to find an address.
All X4x0 variants will report their `type` as 'x4xx'. The `product` key can be
used to identify the X410/X440 variant.
At this point, you should run:
@ -218,33 +257,29 @@ Note: If you receive the following error:
Error: RuntimeError: Graph edge list is empty for rx channel 0
then you will need to download a UHD-compatible FPGA as described in
then you will need to download a UHD-compatible FPGA image as described in
\ref x4xx_updating_fpga or using the following command (it assumes that FPGA
images have been downloaded previously using uhd_images_downloader, or that the
command is run on the device itself):
uhd_image_loader --args type=x4xx,addr=<ip address>,fpga=X4_200
When running on the device, use 127.0.0.1 as the IP address.
When running on the device, use `127.0.0.1` as the IP address.
You can now use existing UHD examples or applications (such as
rx_sample_to_file, rx_ascii_art_dft, or tx_waveforms) or other UHD-compatible
applications to start receiving and transmitting with the device.
See \ref x4xx_getting_started_network_connectivity_ifcs for further details on
the various network interfaces available on the X410.
the various network interfaces available on the X4x0.
\subsubsection x4xx_getting_started_network_connectivity_ifcs Network Interfaces
The Ettus USRP X410 has various network interfaces:
The Ettus USRP X4x0 has various network interfaces:
- `eth0`: RJ45 port.
- The RJ45 port comes up with a default configuration of DHCP, that will
request a network address from your DHCP server (if available on your
network). This interface is agnostic of FPGA image flavor.
- `int0`: internal interface for network communication between the embedded ARM
processor and FPGA.
- The internal network interface is configured with a static address:
`169.254.0.1/24`. This interface is agnostic of FPGA image flavor.
- `sfpX [, sfpX_1, sfpX_2, sfpX_3]`: QSFP28 network interface(s), up-to four
(one per lane) based on implemented protocol.
- Each QSFP28 port has four high-speed transceiver lanes. Therefore,
@ -253,6 +288,11 @@ The Ettus USRP X410 has various network interfaces:
`sfpX_1-3` for the other three lanes. Each network interface has a default
static IP address. Note that for multi-lane protocols, such as 100 GbE, a
single interface is used (`sfpX`).
- `int0`: internal interface for network communication between the embedded ARM
processor and FPGA. It is generally not recommended or necessary to directly
connect to this interface.
- The internal network interface is configured with a static address:
`169.254.0.1/24`. This interface is agnostic of FPGA image flavor.
The configuration files for these network interfaces are stored in:
`/data/network/<interface>.network`.
@ -299,7 +339,7 @@ replacing the IP address with the IP of your choice.
\subsubsection x4xx_getting_started_network_connectivity_leds Network Status LEDs
The Ettus USRP X410 is equipped with status LEDs for its network-capable ports:
The Ettus USRP X4x0 is equipped with status LEDs for its network-capable ports:
RJ45 and QSFP28s,
see \ref x4xx_getting_started_network_connectivity_leds_rj45 and
\ref x4xx_getting_started_network_connectivity_leds_qsfp28 accordingly.
@ -332,7 +372,7 @@ Link / Activity | Amber (blinking) |
\subsection x4xx_getting_started_security Security-related Settings
The X410 ships without a root password set. It is possible to ssh into the
The X4x0 ships without a root password set. It is possible to ssh into the
device by simply connecting as root, and thus gaining access to all subsystems.
To set a password, run the command
@ -386,7 +426,7 @@ The microcontroller (which controls the power sequencing, among other things)
also has a serial console available. To connect to the microcontroller, use the
other UART device. In the example above:
$ sudo screen /dev/serial/by-id/usb-Digilent_Digilent_USB_Device_2516351DDCC0-if02-port0 115200
$ sudo screen /dev/serial/by-id/usb-Digilent_Digilent_USB_Device_251635234ABC-if02-port0 115200
It provides a very simple prompt. The command 'help' will list all available
commands. A direct connection to the microcontroller can be used to hard-reset
@ -395,6 +435,9 @@ example, running the command `reboot` will emulate a reset button press,
resetting the state of the device, while the command `powerbtn` will emulate a
power button press, turning the device back on again.
Note that the final 6 digits of the serial number match between the device and
the serial connection (in this example, 234ABC).
\subsection x4xx_getting_started_ssh SSH Connection
The USRP X4x0-Series devices have two network connections: The dual QSFP28
@ -457,7 +500,7 @@ these images can be loaded by SSH'ing into the device and running:
Unlike the USRP X310 or other third-generation USRP devices, the FPGA image
flavors do not only encode how the QSFP28 connectors are configured, but also
which master clock rates are available. This is because the data converter
configuration is part of the FPGA image (the ADCs/DACs on the X410 are on the
configuration is part of the FPGA image (the ADCs/DACs on the X4x0 are on the
same die as the FPGA).
The image flavor names consist of two short strings, separated by an
@ -492,7 +535,7 @@ If the QSFP 1 configuration is not specified, then that port is unused. XG and
CG indicate that each QSFP port has a single 10 GbE or 100 GbE, respectively
(same as previous USRPs). The 'G' in this case is short for 'gigabit'.
As of UHD 4.4, the following images flavors are shipped with UHD:
As of UHD 4.4, the following USRP X410 images flavors are shipped with UHD:
| FPGA Image Flavor | Bandwidth | QSFP28 Port 0 Interface | QSFP28 Port 1 Interface | DDC/DUC | DRAM |
|-------------------|-----------|-------------------------|-------------------------|---------|--------------------------|
@ -500,6 +543,15 @@ As of UHD 4.4, the following images flavors are shipped with UHD:
| UC_200 | 200 MHz | Unused | 100 GbE | Yes | Yes (4 GiB, 4-Ch Replay) |
| CG_400 | 400 MHz | 100 GbE | 100 GbE | No | No |
As of UHD 4.5, the following USRP X440 images flavors are shipped with UHD:
| FPGA Image Flavor | Bandwidth | QSFP28 Port 0 Interface | QSFP28 Port 1 Interface | DDC/DUC | DRAM |
|-------------------|-----------|-------------------------|-------------------------|---------|--------------------------|
| X4_400 | 400 MHz | 4x 10 GbE (All Lanes) | Unused | No | No |
| CG_400 | 400 MHz | 100 GbE | 100 GbE | No | No |
| X4_1600 | 1600 MHz | 4x 10 GbE (All Lanes) | Unused | No | Yes (8 GiB, 2-Ch Replay) |
| CG_1600 | 1600 MHz | 100 GbE | 100 GbE | No | No |
The following list shows some potential use-cases for different FPGA images:
- `X4_200` or `UC_200`: 200 MHz analog bandwidth, or below (using RFNoC
@ -513,6 +565,11 @@ The following list shows some potential use-cases for different FPGA images:
DRAM using the RFNoC Record/Replay block. Up to 4 x 10 GbE connections may be
used to access the DRAM from an external host computer. Note that 10 GbE is
not fast enough for continuous full-rate streaming at this rate.
- `X4_1600` (USRP X440 only): Highest bandwidth streaming including DRAM support.
This is a useful image for capture/replay from DRAM. Note that streaming at
full rate (2 Gsps) exceeds the streaming capabilities of 10 GbE, which means
that direct streaming (without using DRAM) is not possible at these rates with
this image.
Run `make help` in the `fpga/usrp3/top/x400` directory of the UHD repository to
see a complete list of FPGA images that can be built, some of which are
@ -528,7 +585,7 @@ should use the 200 MHz images and make use the DDC/DUC for lower rates.
\subsubsection x4xx_pl_dram DDR4 Memory for Programmable Logic
The USRP X410 has two banks of DDR4 memory available for use by the
The USRP X4x0 has two banks of DDR4 memory available for use by the
programmable logic in the FPGA. Each bank has a 4 GiB capacity. The DRAM can be
run at up to 2.4 GT/s but is clocked at 2.0 GT/s by default to ease FPGA timing
closure.
@ -540,13 +597,13 @@ interfaces. For 200 MHz images, each AXI interface is 64-bit at 250 MHz. For
maximum sample rate to be read and written to DRAM for up to 4 channels
simultaneously.
The default use for the DRAM is the Replay RFnoC block, which supports
The default use for the DRAM is the Replay RFNoC block, which supports
recording and playback of data in real time. See section \ref
x4xx_updating_fpga_types for a list of which images have DRAM by default.
\section x4xx_updating_filesystems Updating Filesystems
The easiest way to update the filesystem is directly from the X410 via the
The easiest way to update the filesystem is directly from the X4x0 via the
built-in `usrp_update_fs` utility.
To perform a filesystem "factory-reset", run:
@ -583,7 +640,7 @@ works.
\subsubsection x4xx_updating_filesystems_mender_artifact Downloading a Mender Artifact
To initiate an update directly from the X410 device, download a Mender artifact
To initiate an update directly from the X4x0 device, download a Mender artifact
containing the update itself. These are files with a `.mender` suffix. They can
be downloaded by using the `uhd_images_downloader` utility:
@ -609,7 +666,7 @@ Second, use the `-m` switch to use the new manifest file:
\subsubsection x4xx_updating_filesystems_mender_install Installing the Mender Artifact
Once a Mender artifact is downloaded to the X410 (see
Once a Mender artifact is downloaded to the X4x0 (see
\ref x4xx_updating_filesystems_mender_artifact), install it by running mender
on the command line:
@ -673,9 +730,6 @@ you forget to update the CPLD, MPM will fail to fully initialize and emit a
warning. This is not critical, and the CPLD update can be performed later by
following these same steps, but the device will not be usable until then.
Even though the CPLD may not require updating, it is recommended to always run
these steps for a consistent update procedure.
You can update the motherboard (MB) CPLD by running the following command on
the X410:
@ -737,13 +791,13 @@ reboot.
\subsection x4xx_accessing_emmc_usb USB Access to eMMC
While Mender should be used for routine filesystem updates (see \ref
x4xx_updating_filesystems), it is also possible to access the X410's internal
x4xx_updating_filesystems), it is also possible to access the X4x0's internal
eMMC from an external host over USB. This allows accessing or modifying the
filesystem, as well as the ability to flash the device with an entirely new
filesystem.
In order to do so, you'll need an external computer with two USB ports, and two
USB cables to connect the computer to your X410. The instructions below assume
USB cables to connect the computer to your X4x0. The instructions below assume
a Linux host.
First, connect to the APU serial console at a baud rate of 115200. Boot the
@ -756,8 +810,8 @@ This will start the USB mass storage gadget to expose the eMMC as a USB mass
storage device. You should see a spinning indicator on the console, which
indicates the gadget is active.
Next, connect your external computer to the X410's USB to PS port using an OTG
cable. Your computer should recognize the X410 as a mass storage device, and you
Next, connect your external computer to the X4x0's USB to PS port using an OTG
cable. Your computer should recognize the X4x0 as a mass storage device, and you
should see an entry in your kernel logs (`dmesg`) that looks like this:
usb 3-1: New USB device found, idVendor=3923, idProduct=7a7d, bcdDevice= 2.23
@ -769,11 +823,11 @@ should see an entry in your kernel logs (`dmesg`) that looks like this:
sd 6:0:0:0: [sdc] Attached SCSI removable disk
The exact output will depend on your machine, but from this log you can see that
the X410 was recognized and `/dev/sdc` is the block device representing the
the X4x0 was recognized and `/dev/sdc` is the block device representing the
eMMC, with 4 partitions detected (see \ref x4xx_overview_storage for details on
the partition layout).
It is now possible to treat the X410's eMMC as you would any other USB drive:
It is now possible to treat the X4x0's eMMC as you would any other USB drive:
the individual partitions can be mounted and accessed, or the entire block
device can be read/written.
@ -782,7 +836,7 @@ stopped by hitting Ctrl-C at the APU serial console.
\subsubsection x4xx_flash_emmc Flashing eMMC
Once the X410's eMMC is accessible over USB, it's possible to write the
Once the X4x0's eMMC is accessible over USB, it's possible to write the
filesystem image using `dd`. You can obtain the latest filesystem image by
running:
@ -796,30 +850,30 @@ Run:
sudo dd if=/path/to/usrp_x4xx_fs.sdimg of=/dev/sdX bs=1M
to flash the eMMC with this image (replacing /dev/sdX with the block device
of the X410's eMMC as indicated by your kernel log).
of the X4x0's eMMC as indicated by your kernel log).
When copying has completed, hit Ctrl-C on the U-boot
prompt to terminate the mass storage mode. Then, power-cycle the device to load
the new filesystem.
\subsection x4xx_jtag_boot Booting X410 over JTAG
\subsection x4xx_jtag_boot Booting X4x0 over JTAG
If the X410 is no longer able to boot from eMMC, it is possible to boot the
If the X4x0 is no longer able to boot from eMMC, it is possible to boot the
device into u-boot over JTAG. This will allow the filesystem to be reflashed
using the process described in \ref x4xx_accessing_emmc_usb.
In order to boot the X410 over JTAG, you'll first need to have either the
In order to boot the X4x0 over JTAG, you'll first need to have either the
Xilinx SDK, or the freely available Vivado Lab Edition. The following steps
require that one of these is installed and available in your environment.
For convenience, pre-compiled bootloader binaries are provided, along with a
script to handle downloading these into the X410's memory and booting the
script to handle downloading these into the X4x0's memory and booting the
device. These are included in the sdimg package with the name
`usrp_x4xx_recovery.zip`, which can be downloaded using:
uhd_images_downloader -t sdimg -t x4xx
To boot the device over JTAG, first ensure the X410 is powered off, and that you
To boot the device over JTAG, first ensure the X4x0 is powered off, and that you
have serial consoles open to both the SCU and the APU. Configure the device to
boot over JTAG by running `zynqmp bootmode jtag` on the SCU console, and press
the power button (or run the `powerbtn` command at the SCU console). At this
@ -867,8 +921,8 @@ For a list of which arguments can be passed into make(), see Section
\subsection x4xx_usage_gps GPS
The USRP X410 includes a Jackson Labs LTE-Lite GPS module. Its antenna port is
on the rear panel (see \ref x4xx_overview_panels). When the X410 has access to
The USRP X4x0 includes a Jackson Labs LTE-Lite GPS module. Its antenna port is
on the rear panel (see \ref x4xx_overview_panels). When the X4x0 has access to
GPS satellite signals, it can use this module to read out the current GPS time
and location as well as to discipline an onboard OCXO.
@ -896,7 +950,7 @@ GPS lock is reported.
\subsection x4xx_usage_gpio Front-Panel Programmable GPIOs
The USRP X410 has two HDMI front-panel connectors, which are connected to the
The USRP X4x0 has two HDMI front-panel connectors, which are connected to the
FPGA. For a description of the GPIO control API, see \subpage page_x400_gpio_api.
There are multiple sources that can control the state of the GPIO lines.
@ -939,6 +993,20 @@ DB 0 / RF 1 | A:1
DB 1 / RF 0 | B:0
DB 1 / RF 1 | B:1
The RF ports on the front panel of the X440 + FBX correspond to the following
subdev specifications:
Label | Subdev Spec
------------|------------
DB 0 / RF 0 | A:0
DB 0 / RF 1 | A:1
DB 0 / RF 2 | A:2
DB 0 / RF 3 | A:3
DB 1 / RF 0 | B:0
DB 1 / RF 1 | B:1
DB 1 / RF 2 | B:2
DB 1 / RF 3 | B:3
The subdev spec slot identifiers "A" and "B" are not reflected on the front panel.
They were set to match valid subdev specifications of previous USRPs, maintaining
backward compatibility.
@ -994,7 +1062,7 @@ sensors values. If the GPS is disabled, they will always return false.
\subsection x4xx_usage_rearpanelleds Rear Panel Status LEDs
The USRP X410 is equipped with four LEDs located on the device's rear panel.
The USRP X4x0 is equipped with four LEDs located on the device's rear panel.
Each LED supports four different states: Off, Green, Red, and Amber.
One LED (`PWR`) indicates the device's power state (see
\ref x4xx_usage_rearpanelleds_power below).
@ -1006,7 +1074,7 @@ different behaviors are supported for each of these LEDs (see
\subsubsection x4xx_usage_rearpanelleds_power Power LED
The USRP X410's `PWR` LED is reserved to visually indicate the user the
The USRP X4x0's `PWR` LED is reserved to visually indicate the user the
device's power state. \ref x4xx_usage_rearpanelleds_power_behavior describes
what each LED state represents.
@ -1015,13 +1083,13 @@ what each LED state represents.
| `PWR` LED State | Meaning |
|-----------------|---------------------------------------|
| Off | No power is applied |
| Amber | Power is good but X410 is powered off |
| Green | Power is good and X410 is powered on |
| Amber | Power is good but X4x0 is powered off |
| Green | Power is good and X4x0 is powered on |
| Red | Power error state |
\subsubsection x4xx_usage_rearpanelleds_configurable User-configurable LEDs
The USRP X410's user-configurable rear panel status LEDs (`LED 0`, `LED 1`, and
The USRP X4x0's user-configurable rear panel status LEDs (`LED 0`, `LED 1`, and
`LED 2`) allow the user to have visual indication of various device conditions.
\ref x4xx_usage_rearpanelleds_configurable_suppbeh provides a complete list of
the supported behaviors for each user-configurable LED. By default, these LEDs are
@ -1053,12 +1121,12 @@ persistently, see \ref x4xx_usage_rearpanelleds_configurable_tempbeh or
| LED 1 | `fpga` |
| LED 2 | `emmc` |
A user may change the X410 LEDs' default behavior via running a utility on the
A user may change the X4x0 LEDs' default behavior via running a utility on the
on-board ARM processor (Linux).
\paragraph x4xx_usage_rearpanelleds_configurable_tempbeh Temporarily change the LED Behavior
1. Establish a connection (serial or SSH) to the X410's Linux terminal.
1. Establish a connection (serial or SSH) to the X4x0's Linux terminal.
2. Use the `ledctrl` utility to configure each LED based on desired supported behavior:
ledctrl <led> <command>
@ -1072,7 +1140,7 @@ Example:
root@ni-x4xx-1111111:~# ledctrl led0 user0
Sets the X410's `LED 0` to be controlled via the FPGA application using "User LED 0".
Sets the X4x0's `LED 0` to be controlled via the FPGA application using "User LED 0".
\paragraph x4xx_usage_rearpanelleds_configurable_persistentbeh Persistently change the LED Behavior
@ -1098,7 +1166,7 @@ Below is an excerpt of the FPGA source code, setting the `user0`, `user1`, and
// Rear panel LEDs control
// Each LED is comprised of a green (LSB) and a red (MSB) LED
// which the user can control through a 2-bit vector once fabric
// LED control is configured on the X410's Linux shell.
// LED control is configured on the X4x0's Linux shell.
localparam LED_OFF = 2'b00;
localparam LED_GREEN = 2'b01;
localparam LED_RED = 2'b10;
@ -1114,7 +1182,7 @@ Below is an excerpt of the FPGA source code, setting the `user0`, `user1`, and
\image html x4xx_block_diagram.svg "X4x0 Motherboard Block Diagram" width=60%
The USRP X410 has three processors on the motherboard: The RFSoC, the SCU, and a
The USRP X4x0 has three processors on the motherboard: The RFSoC, the SCU, and a
control CPLD. The RFSoC does the bulk of the work. It houses the programmable
logic (PL), the APU and RPU processors (the former running the embedded Linux
system), connects to the data ports (RJ45, QSFP28) and also includes RF data
@ -1159,26 +1227,30 @@ auxiliary board, which contains an OCXO (either GPS-disciplined or controlled by
a DAC), but also connects an external reference to the motherboard. Furthermore,
it houses a PLL for deriving a clock from the network (eCPRI).
The motherboard itself has two main PLLs for clocking purposes: The Sample PLL
The motherboard itself has two main PLLs for clocking purposes: The sample PLL
and the reference PLL. The Sample PLL
(also SPLL) is used to create all clocks used for RF-related purposes. It creates
the sample clock (a very fast clock, ~3 GHz) and the PLL reference clock (PRC)
which is used as a reference for the LO synthesizers (50-64 MHz).
the sample clock (a very fast clock, 1-4 GHz) and the PLL reference clock (PRC,
50-64 MHz) which is used as a reference for the daughterboard LO synthesizers,
the daughterboard CPLD, and as a reference clock inside the FPGA, where it is
further routed to the motherboard CPLD and daughterboard interfaces.
Its input is called the base reference clock (BRC). It has four possible sources:
The reference input to the SPLL is called the base reference clock (BRC). It has
four possible sources:
- The OCXO, which always produces a 10 MHz reference clock. When the clock source
is set to `internal`, this OCXO is only disciplined by a DAC (control of that
DAC is not exposed in this version of UHD), but there is no further control
loop. By selecting `gpsdo` as a clock source, a GPS module is used to
discipline the OCXO (see also \ref x4xx_usage_gps).
- The external reference input SMA port. When an external reference is used (by
selecting `external` as the clock source), the X410 assumes a 10 MHz reference
selecting `external` as the clock source), the X4x0 assumes a 10 MHz reference
clock (it is possible to drive the device with a different external clock
frequency by providing the `ref_clk_freq` device argument, but this is not a
supported use case for this UHD version). Note the clocking card can also import
a PPS signal (when setting the time source to `external`) as well as export it.
- The eCPRI PLL (when using the `nsync` clock source). It will generate a 10 MHz
BRC. Note the intention is to use this for scenarios where the clock is derived
from the network port (e.g., eCPRI), but this version of UHD does not include
from the network port (e.g., SyncE), but this version of UHD does not include
such a feature.
- The reference PLL, when the clock source is set to `mboard` (this is a 25 MHz
BRC). This is not a common use case, as it is not possible to synchronize the
@ -1259,7 +1331,7 @@ Block diagram:
```
Note that this section does not cover every single clock signal present on the
X410, but mainly those clock signals relevant for the operation of the RF
X4x0, but mainly those clock signals relevant for the operation of the RF
components. Refer to the schematic for more details.
\section x4xx_adc_self_cal ADC self calibration

View File

@ -22,6 +22,9 @@ Feature list:
- Maximum input power: 0 dBm (operational, see
<a href="http://www.ni.com/pdf/manuals/378493a">specifications</a> for damage
levels)
- Timed tuning is not supported by the X410, even though it is possible to tune
the ZBX LOs at a given time, because timed tuning of the NCO (within the RFSoC)
is not supported.
See the RF section in the <a href="http://www.ni.com/pdf/manuals/378493a">Ettus
USRP X410 Specifications</a> for a comprehensive ZBX daughterboard
@ -43,7 +46,7 @@ the IF directly, and downconvert to or from DC digitally.
The TX and RX paths are almost symmetric, with slight variations on the various
frequency bands. The various gain stages are spread out along the TX and RX
paths (see also \ref zbx_gain_control, note that the TX path includes selectable
paths (see also \ref zbx_gain_control; note that the TX path includes selectable
amplifiers as well as DSAs). All LO synthesizers are identical (LMX2572).
\subsection zbx_too_cpld Digital Control
@ -293,7 +296,7 @@ usrp->set_tx_gain_profile("table");
usrp->set_tx_gain(30); // ERROR: Now, we have to specify a name
usrp->get_radio_control().set_tx_gain(5); // This works, though. The radio_control
// object is smart enough to infer that
// this is the only action left.
// this is the only action left.
// The previous line and the following have the same effect:
usrp->set_tx_gain(5, "TABLE");
// The CPLD DSA table entry at index 5 is now loaded and applied to the TX and
@ -309,8 +312,8 @@ std::cout << usrp->get_tx_gain() << std::endl;
// the CPLD, which is not possible if there are timed commands queued up.
std::cout << usrp->get_tx_gain("DSA1") << std::endl; // Should print whatever
// was originally stored
// in TX0_TABLE_DSA1, which
// was copied to TX0_DSA1
// in TX0_TABLE_DSA1, which
// was copied to TX0_DSA1
~~~
\subsection zbx_gain_tablenoatr CPLD-Table Gain Profile without ATR control
@ -331,7 +334,7 @@ usrp->set_tx_gain_profile("table_noatr");
usrp->set_tx_gain(30); // ERROR: Now, we have to specify a name
usrp->get_radio_control().set_tx_gain(5); // This works, though. The radio_control
// object is smart enough to infer that
// this is the only action left.
// this is the only action left.
// The previous line and the following have the same effect (but it's a different
// effect than when using gain profile 'table'):
usrp->set_tx_gain(5, "TABLE");
@ -349,8 +352,8 @@ usrp->set_tx_gain(5, "TABLE");
// This is not possible if there are timed commands queued up.
std::cout << usrp->get_tx_gain("DSA1") << std::endl; // Should print whatever
// is currently in TX0_DSA1[i],
// where i is the value of
// SW_RF0_DSA_CONFIG.
// where i is the value of
// SW_RF0_DSA_CONFIG.
~~~
\section zbx_atr Auto-Transmit-Receive Registers (ATR)

View File

@ -12,6 +12,7 @@ UHD_INSTALL(FILES
ref_clk_calibration_iface.hpp
spi_getter_iface.hpp
trig_io_mode_iface.hpp
internal_sync_iface.hpp
DESTINATION ${INCLUDE_DIR}/uhd/features
COMPONENT headers
)

View File

@ -37,7 +37,8 @@ public:
REF_CLK_CALIBRATION,
TRIG_IO_MODE,
GPIO_POWER,
SPI_GETTER_IFACE
SPI_GETTER_IFACE,
INTERNAL_SYNC
};
virtual ~discoverable_feature() = default;

View File

@ -0,0 +1,44 @@
//
// Copyright 2023 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/features/discoverable_feature.hpp>
namespace uhd { namespace features {
/*!
* This feature provides access to the internal sync feature of the X440.
*/
class UHD_API internal_sync_iface : public discoverable_feature
{
public:
using sptr = std::shared_ptr<internal_sync_iface>;
static discoverable_feature::feature_id_t get_feature_id()
{
return discoverable_feature::INTERNAL_SYNC;
}
std::string get_feature_name() const
{
return "Internal Sync";
}
virtual ~internal_sync_iface() = default;
/*!
* Enables the internal 5 MHz sync clock which can be accessed on antenna name
* SYNC_INT.
*/
virtual void enable_sync_clk() = 0;
/*!
* Disables the internal 5 MHz sync clock
*/
virtual void disable_sync_clk() = 0;
};
}} // namespace uhd::features

View File

@ -0,0 +1,388 @@
# Copyright 2020 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Register map for the FBX. Note that the ground truth for this register map
is in the fpga/usrp3/top/x400/dboards/fbx subdirectory, and can be looked
up in fpga/usrp3/top/x400/doc/x440/X440_FPGA.htm
"""
########################################################################
# Template for raw text data describing registers
# name addr[bit range inclusive] default optional enums
########################################################################
REGS_TMPL = """\
########################################################################
## GLOBAL REGISTERS
########################################################################
SCRATCH 0x000C[0:31] 0
DEVICE_ID 0x0010[15:0] ro ro
########################################################################
## RF0 ATR STATE
########################################################################
RF0_TDDS[256] 0x0000[2] 0 RF2, RF1
RF0_RX_RFS[256] 0x0000[1] 0 RF2, RF1
RF0_TX_RX_RFS[256] 0x0000[0] 0 RF2, RF1
########################################################################
## RF1 ATR STATE
########################################################################
RF1_TDDS[256] 0x0400[2] 0 RF2, RF1
RF1_RX_RFS[256] 0x0400[1] 0 RF2, RF1
RF1_TX_RX_RFS[256] 0x0400[0] 0 RF2, RF1
########################################################################
## RF2 ATR STATE
########################################################################
RF2_TDDS[256] 0x0800[2] 0 RF2, RF1
RF2_RX_RFS[256] 0x0800[1] 0 RF2, RF1
RF2_TX_RX_RFS[256] 0x0800[0] 0 RF2, RF1
########################################################################
## RF3 ATR STATE
########################################################################
RF3_TDDS[256] 0x0C00[2] 0 RF2, RF1
RF3_RX_RFS[256] 0x0C00[1] 0 RF2, RF1
RF3_TX_RX_RFS[256] 0x0C00[0] 0 RF2, RF1
########################################################################
## ATR OPTIONS
########################################################################
RF0_ATR_OPTION 0x1000[0] 1 DB, CLASSIC
RF1_ATR_OPTION 0x1000[1] 1 DB, CLASSIC
RF2_ATR_OPTION 0x1000[2] 1 DB, CLASSIC
RF3_ATR_OPTION 0x1000[3] 1 DB, CLASSIC
LED0_ATR_OPTION 0x5000[0] 1 DB, CLASSIC
LED1_ATR_OPTION 0x5000[1] 1 DB, CLASSIC
LED2_ATR_OPTION 0x5000[2] 1 DB, CLASSIC
LED3_ATR_OPTION 0x5000[3] 1 DB, CLASSIC
########################################################################
## Sync Switches
########################################################################
SYNC0_CTRL 0x2000[2:0] 0 NONE, RF1, RF2, RF3, RF4, OFF
SYNC1_CTRL 0x2004[2:0] 0 NONE, RF1, RF2, RF3, RF4, OFF
SYNC2_CTRL 0x2008[2:0] 0 NONE, RF1, RF2, RF3, RF4, OFF
SYNC3_CTRL 0x200C[2:0] 0 NONE, RF1, RF2, RF3, RF4, OFF
SYNC4_CTRL 0x2010[2:0] 0 NONE, RF1, RF2, RF3, RF4, OFF
IO_EXP_SETUP 0x2018[0] 1 NONE, INIT
IO_EXP_CONFIG 0x201c[0] 0 NONE, CONFIG
########################################################################
## LED CONTROL
########################################################################
CH3_TRX1_LED_GR[256] 0x4C00[2] 0 disable, enable
CH3_TRX1_LED_RED[256] 0x4C00[1] 0 disable, enable
CH3_RX2_LED[256] 0x4C00[0] 0 disable, enable
CH2_TRX1_LED_GR[256] 0x4800[2] 0 disable, enable
CH2_TRX1_LED_RED[256] 0x4800[1] 0 disable, enable
CH2_RX2_LED[256] 0x4800[0] 0 disable, enable
CH1_TRX1_LED_GR[256] 0x4400[2] 0 disable, enable
CH1_TRX1_LED_RED[256] 0x4400[1] 0 disable, enable
CH1_RX2_LED[256] 0x4400[0] 0 disable, enable
CH0_TRX1_LED_GR[256] 0x4000[2] 0 disable, enable
CH0_TRX1_LED_RED[256] 0x4000[1] 0 disable, enable
CH0_RX2_LED[256] 0x4000[0] 0 disable, enable
########################################################################
## Internal Sync Signal
########################################################################
SYNC_CLK 0x6000[0] 0 disable, enable
"""
########################################################################
# Template for python methods in the body of the struct
########################################################################
PY_BODY_TMPL = """\
def get_reg(self, addr):
"Return the value of register at address addr"
reg = 0
# First the regular registers
% for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])):
if addr == ${addr}:
% for reg in filter(lambda r: r.get_addr() == addr, regs):
% if reg.get_enums():
reg |= (self.${reg.get_name()}.value & ${reg.get_mask()}) << ${reg.get_shift()}
% else:
reg |= (self.${reg.get_name()} & ${reg.get_mask()}) << ${reg.get_shift()}
% endif
% endfor
% endfor
# Now the arrays
# We can do this because all arrays have a base address that is a multiple
# of 256 (0x100). In other words, this is a hack that only works if you
# know exactly what you're doing, which is OK in this case, because its in
# this same file as the register map.
array_offset = addr % 256
base_addr = addr - array_offset
index = int(array_offset / 4)
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
if base_addr == ${addr}:
% for reg in filter(lambda r: r.get_addr() == addr, regs):
<%
assert reg.get_array_len() == 256, f"Arrays must be length 256! {reg.get_name()}"
assert reg.get_addr() % 256 == 0, "Arrays must start at a multiple of 0x100!"
%>
% if reg.get_enums():
reg |= (int(self.${reg.get_name()}[index].value) & ${reg.get_mask()}) << ${reg.get_shift()}
% else:
reg |= (int(self.${reg.get_name()}[index]) & ${reg.get_mask()}) << ${reg.get_shift()}
% endif
% endfor
% endfor
return reg
<%
all_addrs = set()
for reg in regs:
for index in range(reg.get_array_len() if reg.is_array else 1):
all_addrs.add(reg.get_addr() + index * reg.get_addr_step_size())
%>
def get_all_regs(self):
addrs = {
% for addr in sorted(all_addrs):
${addr},
% endfor
}
return addrs
def get_addr(self, reg_name):
"returns the address of a register with the given name"
return {
% for reg in regs:
'${reg.get_name()}': ${reg.get_addr()},
% endfor
}[reg_name]
"""
########################################################################
# Template for C++ methods in the body of the struct
########################################################################
BODY_TMPL = """\
enum class fbx_reg_field_t {
% for addr in sorted(set(map(lambda r: r.get_addr(), regs))):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
${reg.get_name()},
% endfor
% endfor
};
fbx_reg_field_t get_field_type(const std::string& field_name) {
% for addr in sorted(set(map(lambda r: r.get_addr(), regs))):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
if (field_name == "${reg.get_name()}") {
return fbx_reg_field_t::${reg.get_name()};
} else
% endfor
% endfor
{
UHD_ASSERT_THROW(false);
}
}
uint32_t get_array_size(fbx_reg_field_t field) {
switch(field) {
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
case fbx_reg_field_t::${reg.get_name()}:
return uint32_t(${reg.get_name()}.size());
% endfor
% endfor
default:
UHD_ASSERT_THROW(false);
return 0;
}
}
uint32_t get_field(fbx_reg_field_t field) {
switch(field) {
% for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
case fbx_reg_field_t::${reg.get_name()}:
return uint32_t(${reg.get_name()});
% endfor
% endfor
default:
UHD_ASSERT_THROW(false);
return 0;
}
}
uint32_t get_field(fbx_reg_field_t field, const size_t idx) {
switch(field) {
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
case fbx_reg_field_t::${reg.get_name()}:
return uint32_t(${reg.get_name()}[idx]);
% endfor
% endfor
default:
UHD_ASSERT_THROW(false);
return 0;
}
}
void set_field(fbx_reg_field_t field, uint32_t value) {
switch(field) {
% for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])):
% for reg in filter(lambda r: r.get_addr() == addr and not r.is_readonly(), regs):
case fbx_reg_field_t::${reg.get_name()}:
${reg.get_name()} = static_cast<${reg.get_type()}>(value);
break;
% endfor
% endfor
default:
UHD_ASSERT_THROW(false);
}
}
void set_field(fbx_reg_field_t field, uint32_t value, const size_t idx)
{
switch(field) {
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
case fbx_reg_field_t::${reg.get_name()}:
${reg.get_name()}[idx] = static_cast<${reg.get_type()}>(value);
break;
% endfor
% endfor
default:
UHD_ASSERT_THROW(false);
}
}
uint32_t get_reg(uint16_t addr)
{
uint32_t reg = 0;
// First the regular registers
switch(addr) {
% for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])):
case ${addr}:
% for reg in filter(lambda r: r.get_addr() == addr, regs):
reg |= (uint32_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()};
% endfor
break;
% endfor
default:
break;
}
// Now the arrays
// We can do this because all arrays have a base address that is a multiple
// of 256 (0x100). In other words, this is a hack that only works if you
// know exactly what you're doing, which is OK in this case, because it's in
// this same file as the register map.
const uint16_t array_offset = addr % 256;
const uint16_t base_addr = addr - array_offset;
const size_t index = array_offset / 4;
switch(base_addr) {
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
case ${addr}:
% for reg in filter(lambda r: r.get_addr() == addr, regs):
<% assert reg.get_array_len() == 256, f"Arrays must be length 256! {reg.get_name()}" %>
<% assert reg.get_addr() % 256 == 0, "Arrays must start at a multiple of 0x100!" %>
reg |= (uint32_t(${reg.get_name()}[index]) & ${reg.get_mask()}) << ${reg.get_shift()};
% endfor
break;
% endfor
default:
break;
}
return reg;
}
void set_reg(uint16_t addr, uint32_t val)
{
switch(addr) {
% for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])):
case ${addr}:
% for reg in filter(lambda r: r.get_addr() == addr, regs):
${reg.get_name()} = static_cast<${reg.get_type()}>((val & (${reg.get_mask()} << ${reg.get_shift()})) >> ${reg.get_shift()});
% endfor
break;
% endfor
default:
break;
}
// Now the arrays
// We can do this because all arrays have a base address that is a multiple
// of 256 (0x100). In other words, this is a hack that only works if you
// know exactly what you're doing, which is OK in this case, because it's in
// this same file as the register map.
const uint16_t array_offset = addr % 256;
const uint16_t base_addr = addr - array_offset;
const size_t index = array_offset / 4;
switch(base_addr) {
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
case ${addr}:
% for reg in filter(lambda r: r.get_addr() == addr, regs):
${reg.get_name()}[index] = static_cast<${reg.get_type()}>((val & (${reg.get_mask()} << ${reg.get_shift()})) >> ${reg.get_shift()});
% endfor
break;
% endfor
default: break;
}
}
<%
ro_regs = set()
for reg in filter(lambda reg: reg.is_readonly(), filter(all_addr_filter, regs)):
for index in range(reg.get_array_len() if reg.is_array else 1):
ro_regs.add(reg.get_addr() + index * reg.get_addr_step_size())
rw_regs = set()
for reg in filter(lambda reg: not reg.is_readonly(), filter(all_addr_filter, regs)):
for index in range(reg.get_array_len() if reg.is_array else 1):
rw_regs.add(reg.get_addr() + index * reg.get_addr_step_size())
%>
template<typename T> std::set<T> get_all_addrs(bool include_ro = false)
{
std::set<T> addrs;
if (include_ro) {
% for reg in sorted(ro_regs):
addrs.insert(${reg});
% endfor
}
% for reg in sorted(rw_regs):
addrs.insert(${reg});
% endfor
return addrs;
}
uint16_t get_addr(fbx_reg_field_t field) {
switch(field) {
% for addr in sorted(set([r.get_addr() for r in regs if r.is_array])):
% for reg in filter(lambda r: r.get_addr() == addr, regs):
case fbx_reg_field_t::${reg.get_name()}:
return ${addr};
% endfor
% endfor
default:
UHD_ASSERT_THROW(false);
return 0;
}
}
uint16_t get_addr(const std::string& reg_name)
{
% for reg in regs:
if ("${reg.get_name()}" == reg_name) {
return ${reg.get_addr()};
}
% endfor
return 0;
}
"""
if __name__ == '__main__':
import sys
import common
outfile=sys.argv[1]
if outfile.endswith(".hpp"):
all_addr_filter = lambda reg: "mpm" not in reg.options.get("scope", "")
else:
all_addr_filter = lambda reg: True
common.generate(
name='fbx_regs',
regs_tmpl=REGS_TMPL,
body_tmpl=BODY_TMPL,
py_body_tmpl=PY_BODY_TMPL,
file=__file__,
all_addr_filter=all_addr_filter
)

View File

@ -117,6 +117,10 @@ IFACES = [
fn_from_string("double get_dboard_sample_rate()"),
fn_from_string("void enable_iq_swap(bool is_band_inverted, const std::string& trx, size_t chan)"),
], has_rpcprefix=True),
Interface("fbx_rpc", [
fn_from_string("double get_dboard_sample_rate()"),
fn_from_string("void enable_iq_swap(bool is_band_inverted, const std::string& trx, size_t chan)"),
], has_rpcprefix=True),
]
COMMON_TMPL = """<% import time %>\

View File

@ -10,8 +10,8 @@
#include <uhd/types/time_spec.hpp>
#include <cstdint>
#include <memory>
#include <vector>
#include <string>
#include <vector>
namespace uhd { namespace rfnoc { namespace x400 {
@ -41,7 +41,7 @@ public:
};
//! Identify the NCOs/ADCs/DACs available to this radio control
enum class rfdc_type { RX0, RX1, TX0, TX1 };
enum class rfdc_type { RX0, RX1, RX2, RX3, TX0, TX1, TX2, TX3 };
rfdc_control(uhd::memmap32_iface_timed&& iface, const std::string& log_id);
@ -82,4 +82,27 @@ private:
const std::string _log_id;
};
constexpr std::initializer_list<rfdc_control::rfdc_type> RX_RFDC = {
rfdc_control::rfdc_type::RX0,
rfdc_control::rfdc_type::RX1,
rfdc_control::rfdc_type::RX2,
rfdc_control::rfdc_type::RX3};
constexpr std::initializer_list<rfdc_control::rfdc_type> TX_RFDC = {
rfdc_control::rfdc_type::TX0,
rfdc_control::rfdc_type::TX1,
rfdc_control::rfdc_type::TX2,
rfdc_control::rfdc_type::TX3};
template <typename T, typename F>
void indexed_for(const T& range, const size_t limit, F&& func)
{
std::size_t i = 0;
for (auto it = std::begin(range); it != std::end(range); ++it) {
if (i < limit) {
func(*it, i++);
} else {
break;
}
}
}
}}} // namespace uhd::rfnoc::x400

View File

@ -0,0 +1,82 @@
//
// Copyright 2019 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/exception.hpp>
#include <uhd/types/ranges.hpp>
#include <unordered_map>
#include <array>
#include <cstddef>
#include <cstring>
#include <list>
#include <map>
#include <string>
#include <vector>
namespace uhd { namespace usrp { namespace fbx {
/******************************************************************************
* Important: When changing values here, check if that also requires updating
* the manual (host/docs/fbx.dox). If it also requires changing the website or
* other sales/marketing material, make sure to let the appropriate people know!
*****************************************************************************/
// The FBX has a non-configurable analog bandwidth of 400 MHz. At lower frequency,
// the usable bandwidth may be smaller though. For those smaller bandwidths, see
// the tune maps.
static constexpr double FBX_DEFAULT_BANDWIDTH = 1.6e9; // Hz
static constexpr double FBX_MIN_FREQ = 1e6; // Hz
static constexpr double FBX_MAX_FREQ = 4e9; // Hz
static constexpr double FBX_DEFAULT_FREQ = 1e9; // Hz
static const uhd::freq_range_t FBX_FREQ_RANGE(FBX_MIN_FREQ, FBX_MAX_FREQ);
static constexpr char FBX_GAIN_PROFILE_DEFAULT[] = "default";
static const std::vector<std::string> FBX_GAIN_PROFILES = {FBX_GAIN_PROFILE_DEFAULT};
/*** Antenna-related constants ***********************************************/
// TX and RX SMA connectors on the front panel
constexpr char ANTENNA_TXRX[] = "TX/RX0";
constexpr char ANTENNA_RX[] = "RX1";
// Internal "antenna" ports
constexpr char ANTENNA_CAL_LOOPBACK[] = "CAL_LOOPBACK";
constexpr char ANTENNA_SYNC_INT[] = "SYNC_INT";
constexpr char ANTENNA_SYNC_EXT[] = "SYNC_EXT";
constexpr char ANTENNA_TERMINATION[] = "TERM"; // Only RX path
constexpr char ANTENNA_OFF[] = "OFF"; // Only RX path
// Default antennas (which are selected at init)
constexpr auto DEFAULT_TX_ANTENNA = ANTENNA_TXRX;
constexpr auto DEFAULT_RX_ANTENNA = ANTENNA_RX;
// Helper lists
static const std::vector<std::string> RX_ANTENNAS = {ANTENNA_TXRX,
ANTENNA_RX,
ANTENNA_SYNC_INT,
ANTENNA_SYNC_EXT,
ANTENNA_CAL_LOOPBACK,
ANTENNA_TERMINATION};
static const std::vector<std::string> TX_ANTENNAS = {ANTENNA_TXRX, ANTENNA_CAL_LOOPBACK};
// For branding purposes, FBX changed the antenna names around. For
// existing software, we still accept the old antenna names, but map them to the new ones
static const std::unordered_map<std::string, std::string> TX_ANTENNA_NAME_COMPAT_MAP{
{"TX/RX", ANTENNA_TXRX}};
static const std::unordered_map<std::string, std::string> RX_ANTENNA_NAME_COMPAT_MAP{
{"TX/RX", ANTENNA_TXRX}, {"RX2", ANTENNA_RX}};
static constexpr char RFDC_NCO[] = "rfdc";
static const std::vector<std::string> FBX_LOS = {RFDC_NCO};
static constexpr size_t FBX_MAX_NUM_CHANS = 4;
// These are addresses for the various table-based registers
static constexpr uint32_t ATR_ADDR_0X = 0;
static constexpr uint32_t ATR_ADDR_RX = 1;
static constexpr uint32_t ATR_ADDR_TX = 2;
static constexpr uint32_t ATR_ADDR_XX = 3; // Full-duplex
// Helper for looping
static constexpr std::array<uint32_t, FBX_MAX_NUM_CHANS> ATR_ADDRS{0, 1, 2, 3};
}}} // namespace uhd::usrp::fbx

View File

@ -0,0 +1,163 @@
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include "fbx_constants.hpp"
#include <uhd/types/direction.hpp>
#include <uhd/types/serial.hpp>
#include <uhd/types/time_spec.hpp>
#include <fbx_regs.hpp>
#include <unordered_map>
#include <array>
#include <chrono>
#include <functional>
#include <mutex>
namespace uhd { namespace usrp { namespace fbx {
/*! FBX Control class
*
* This class is very similar to the ZBX CPLD Control class. The main difference is that
* we do not have a CPLD on the FBX daughterboard. Therefore all control is done through
* the FPGA via GPIO lines towards the daughterboard where switches and LEDs are
* controlled (hence the naming).
*/
class fbx_ctrl
{
public:
using sptr = std::shared_ptr<fbx_ctrl>;
enum chan_t { CHAN0, CHAN1, CHAN2, CHAN3, ALL_CHANS, NO_CHAN };
// Note: The values in this enum must match the values in the regmaps.
using poke_fn_type =
std::function<void(const uint32_t, const uint32_t, const chan_t)>;
using peek_fn_type = std::function<uint32_t(const uint32_t)>;
using sleep_fn_type = std::function<void(const uhd::time_spec_t&)>;
fbx_ctrl(poke_fn_type&& poke_fn,
peek_fn_type&& peek_fn,
sleep_fn_type&& sleep_fn,
const std::string& log_id);
~fbx_ctrl(void) = default;
/*! Setting switches required for antenna mode switching, transmitting side
*
* \param channel daughterboard channel to program
* \param idx Table index
* \param antenna desired antenna mode
*/
void set_tx_antenna_switches(
const size_t channel, const uint8_t idx, const std::string& antenna);
/*! Setting switches required for antenna mode switching, receiving side
*
* \param channel daughterboard channel to program
* \param idx Table index
* \param antenna desired antenna mode
*/
void set_rx_antenna_switches(
const size_t channel, const uint8_t idx, const std::string& antenna);
/*! The last sync switch is a common one among all channels. Therefore to know if
* another channel has changed its setting we can query it here to return a correct
* antenna name (SYNC_INT vs. SYNC_EXT).
*
* \return Antenna name according to sync switch setting
*/
std::string get_rx_sync_switch_state();
/*! Peek a register for the value 1 until we either get that value or timeout.
* \param addr: Register address to peek
* \param timeout_ms: Timeout in milliseconds
* \param check: Callback that get register value and return whether value
* is accepted or not
* \return true on success (check accepted register value), false otherwise
*/
bool poll_reg_with_timeout(
const std::string& reg, uint32_t timeout_ms, std::function<bool(uint32_t)> check);
/**************************************************************************
* LED controls
*************************************************************************/
/*! Turn the LEDs on or off
*
* There are two LEDs on FBX, the TRX LED has a red and green component.
*
* TODO: Check if this statement from ZBX holds true for FBX, too:
* Note that toggling any of the LED settings to 'true' won't necessarily
* turn on the LED. The current config register for this channel must
* also match \p idx in order for this state to be applied.
*
* \param channel The channel for which these settings apply
* \param idx The LED table index that is configured
* \param rx On-state of the green RX2 LED
* \param trx_rx On-state of the green TX/RX LED
* \param trx_tx On-state of the red TX/RX LED
*/
void set_leds(const size_t channel,
const uint8_t idx,
const bool rx,
const bool trx_rx,
const bool trx_tx);
/**************************************************************************
* Internal SYNC signal control
*************************************************************************/
/*!
* Enables or disables the internal sync clock (5 MHz) on this daughterboard.
*
* \param enable
*/
void set_internal_sync_clk(const bool enable);
private:
/*! Dump the state of the registers into the actual registers
*
* \param chan Which channel does this change pertain to? This is forwarded
* to _poke32().
* \param save_all If true, save all registers. If false, only change those
* that changed since last save_state() call.
* Note that if save_all is true, the chan parameter does not
* really apply, because all registers (for all channels) are
* written to. Therefore, only use save_all==true in
* combination with NO_CHAN.
*/
void commit(const chan_t chan = NO_CHAN, const bool save_all = false);
/*! Check if any channel is connected to the last sync switch, so we know if we can
* turn it off for better isolation or leave it switched to where ever it is
* currently.
*
* \return Anyone connected (true) or not (false)
*/
bool any_ch_connected_to_sync();
//! Initially set the correct ATR option
void set_classic_atr();
// Cached register state
fbx_regs_t _regs = fbx_regs_t();
//! Poker object
poke_fn_type _poke32;
//! Peeker object
peek_fn_type _peek32;
//! Hardware-timed sleep, used to throttle pokes
sleep_fn_type _sleep;
// infos about the daughtherboard revision
std::string _db_rev_info;
const std::string _log_id;
};
}}} // namespace uhd::usrp::fbx

View File

@ -0,0 +1,438 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include "fbx_constants.hpp"
#include "fbx_ctrl.hpp"
#include "fbx_expert.hpp"
#include <uhd/exception.hpp>
#include <uhd/experts/expert_factory.hpp>
#include <uhd/property_tree.hpp>
#include <uhd/rfnoc/register_iface.hpp>
#include <uhd/rfnoc/rf_control/antenna_iface.hpp>
#include <uhd/rfnoc/rf_control/nameless_gain_mixin.hpp>
#include <uhd/types/direction.hpp>
#include <uhd/types/eeprom.hpp>
#include <uhd/types/wb_iface.hpp>
#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
#include <uhdlib/usrp/common/mpmd_mb_controller.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
#include <uhdlib/usrp/dboard/x400_dboard_iface.hpp>
#include <uhdlib/utils/rpc.hpp>
#include <string>
using namespace uhd::rfnoc;
namespace uhd { namespace usrp { namespace fbx {
const static uint16_t FBX_PID = 0x4007;
/*! \brief Implementation of dboard_iface for balun coupled daughterboard
*/
class fbx_dboard_impl : public uhd::usrp::x400::x400_dboard_iface,
public uhd::rfnoc::rf_control::antenna_radio_control_mixin,
public uhd::rfnoc::rf_control::nameless_gain_mixin
{
public:
using sptr = std::shared_ptr<fbx_dboard_impl>;
using time_accessor_fn_type = std::function<uhd::time_spec_t(size_t)>;
/***********************************************************************
* Structors
**********************************************************************/
fbx_dboard_impl(register_iface& reg_iface,
const size_t reg_base_address,
time_accessor_fn_type&& time_accessor,
const size_t db_idx,
const std::string& radio_slot,
const size_t num_tx_chans,
const size_t num_rx_chans,
const std::string& rpc_prefix,
const std::string& unique_id,
uhd::usrp::x400_rpc_iface::sptr mb_rpcc,
uhd::usrp::fbx_rpc_iface::sptr rpcc,
uhd::rfnoc::x400::rfdc_control::sptr rfdcc,
uhd::property_tree::sptr tree);
virtual ~fbx_dboard_impl();
/************************************************************************
* node_t && noc_block_base API calls
***********************************************************************/
void deinit();
void set_command_time(uhd::time_spec_t time, const size_t chan) override;
/************************************************************************
* API calls
***********************************************************************/
bool is_adc_self_cal_supported() override
{
return true;
}
uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(const double) override
{
// FBX uses a fixed low freq for both TX and RX
return {100e6, // rx_freq
100e6, // tx_freq
{0x7FFF, 0}, // output full scale dac mux
100, // delay
4000, // under
4192, // over
"calib_mode2"};
}
bool select_adc_self_cal_gain(size_t) override;
double get_converter_rate() const override
{
return _rfdc_rate;
}
size_t get_num_rx_channels() const override
{
return _num_rx_chans;
}
size_t get_num_tx_channels() const override
{
return _num_tx_chans;
}
std::string get_rx_antenna(const size_t chan) const override;
rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override
{
return _tx_gain_profile_api;
}
rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override
{
return _rx_gain_profile_api;
}
double set_tx_frequency(const double freq, const size_t chan) override;
double set_rx_frequency(const double freq, const size_t chan) override;
uhd::freq_range_t get_tx_frequency_range(const size_t /*chan*/) const override
{
return FBX_FREQ_RANGE;
}
uhd::freq_range_t get_rx_frequency_range(const size_t /*chan*/) const override
{
return FBX_FREQ_RANGE;
}
double set_tx_bandwidth(const double bandwidth, const size_t chan) override;
double set_rx_bandwidth(const double bandwidth, const size_t chan) override;
uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const override
{
return _tree
->access<uhd::meta_range_t>(
_get_frontend_path(TX_DIRECTION, chan) / "bandwidth" / "range")
.get();
}
uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const override
{
return _tree
->access<uhd::meta_range_t>(
_get_frontend_path(RX_DIRECTION, chan) / "bandwidth" / "range")
.get();
}
using core_iface::get_rx_gain;
using core_iface::get_rx_gain_range;
using core_iface::get_tx_gain;
using core_iface::get_tx_gain_range;
using core_iface::set_rx_gain;
using core_iface::set_tx_gain;
double set_tx_gain(const double, const std::string&, const size_t) override
{
return 0.0;
}
double set_rx_gain(const double, const std::string&, const size_t) override
{
return 0.0;
}
double get_rx_gain(const std::string&, const size_t) override
{
return 0.0;
}
double get_tx_gain(const std::string&, const size_t) override
{
return 0.0;
}
// LO Property Getters
std::vector<std::string> get_tx_lo_names(const size_t /*chan*/) const override
{
return {RFDC_NCO};
}
std::vector<std::string> get_rx_lo_names(const size_t /*chan*/) const override
{
return {RFDC_NCO};
}
std::vector<std::string> get_tx_lo_sources(
const std::string& /*name*/, const size_t /*chan*/) const override
{
return {"internal"};
}
std::vector<std::string> get_rx_lo_sources(
const std::string& /*name*/, const size_t /*chan*/) const override
{
return {"internal"};
}
// LO Frequency Control
double set_tx_lo_freq(const double, const std::string&, const size_t) override;
double set_rx_lo_freq(const double, const std::string&, const size_t) override;
double get_tx_lo_freq(const std::string&, const size_t) override;
double get_rx_lo_freq(const std::string&, size_t) override;
// LO Source Control
void set_tx_lo_source(
const std::string& /*src*/, const std::string& name, const size_t) override
{
_validate_lo_name(name, "set_tx_lo_source");
}
void set_rx_lo_source(
const std::string& /*src*/, const std::string& name, const size_t) override
{
_validate_lo_name(name, "set_rx_lo_source");
}
const std::string get_tx_lo_source(const std::string&, const size_t) override
{
return "internal";
}
const std::string get_rx_lo_source(const std::string&, const size_t) override
{
return "internal";
}
uhd::freq_range_t get_rx_lo_freq_range(
const std::string& name, const size_t chan) const override
{
return _get_lo_freq_range(name, chan);
}
uhd::freq_range_t get_tx_lo_freq_range(
const std::string& name, const size_t chan) override
{
return _get_lo_freq_range(name, chan);
}
void set_rx_lo_export_enabled(const bool, const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"set_rx_lo_export_enabled is not supported on this radio");
}
bool get_rx_lo_export_enabled(const std::string&, const size_t) override
{
return false;
}
void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override
{
throw uhd::not_implemented_error(
"set_tx_lo_export_enabled is not supported on this radio");
}
bool get_tx_lo_export_enabled(const std::string&, const size_t) override
{
return false;
}
/******************************************************************************
* EEPROM API
*****************************************************************************/
eeprom_map_t get_db_eeprom() override;
/**************************************************************************
* Radio Identification API Calls
*************************************************************************/
double get_tx_frequency(size_t chan) override;
double get_rx_frequency(size_t chan) override;
double get_rx_bandwidth(size_t chan) override;
double get_tx_bandwidth(size_t chan) override;
void set_tx_tune_args(const device_addr_t&, const size_t) override
{
RFNOC_LOG_TRACE("tune_args not supported by this radio.");
}
void set_rx_tune_args(const device_addr_t&, const size_t) override
{
RFNOC_LOG_TRACE("tune_args not supported by this radio.");
}
std::vector<std::string> get_tx_gain_names(const size_t) const override
{
return {};
}
uhd::gain_range_t get_tx_gain_range(
const std::string& /*name*/, const size_t /*chan*/) const override
{
return gain_range_t{0.0, 0.0};
}
uhd::gain_range_t get_rx_gain_range(
const std::string& /*name*/, const size_t /*chan*/) const override
{
return gain_range_t{0.0, 0.0};
}
std::vector<std::string> get_rx_gain_names(const size_t) const override
{
return {};
}
void set_rx_agc(const bool, const size_t) override
{
throw uhd::not_implemented_error("set_rx_agc is not supported on this radio");
}
size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override;
std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override;
std::vector<usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(direction_t) override
{
static std::vector<usrp::pwr_cal_mgr::sptr> empty_vtr;
return empty_vtr;
}
/**************************************************************************
* Helpers
*************************************************************************/
std::shared_ptr<fbx_ctrl> get_fbx_ctrl()
{
return _fbx_ctrl;
}
private:
uhd::property_tree::sptr get_tree()
{
return _tree;
}
//! Get subtree path for a given direction/channel
fs_path _get_frontend_path(const direction_t dir, const size_t chan_idx) const;
// Expert map, keyed by the pair of tx/rx and channel
uhd::experts::expert_container::sptr _expert_container;
/**************************************************************************
* Helpers
*************************************************************************/
//! Initialize FBX control
void _init_fbx_ctrl();
//! Init a subtree for the RF frontends
void _init_frontend_subtree(uhd::property_tree::sptr subtree,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path);
//! Initialize property tree
void _init_prop_tree();
//! Init RPC interaction
void _init_mpm();
//! Initializing the expert properties
void _init_frequency_prop_tree(uhd::property_tree::sptr subtree,
uhd::experts::expert_container::sptr expert,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path);
void _init_gain_prop_tree(uhd::property_tree::sptr subtree,
uhd::experts::expert_container::sptr expert,
const fs_path fe_path);
void _init_antenna_prop_tree(uhd::property_tree::sptr subtree,
uhd::experts::expert_container::sptr expert,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path);
void _init_lo_prop_tree(uhd::property_tree::sptr subtree,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path);
//! Init all experts, bind to properties created above
void _init_experts(uhd::experts::expert_container::sptr expert,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path);
std::string get_unique_id() const;
freq_range_t _get_lo_freq_range(const std::string& name, const size_t chan) const;
void _validate_lo_name(const std::string& namem, const std::string& caller) const;
/**************************************************************************
* Private attributes
*************************************************************************/
const std::string _unique_id;
const size_t _num_tx_chans;
const size_t _num_rx_chans;
//! Interface to the registers
uhd::rfnoc::register_iface& _regs;
const size_t _reg_base_address;
//! Interface to get the command time
time_accessor_fn_type _time_accessor;
//! Letter representation of the radio we're currently running
const std::string _radio_slot;
//! Index of this daughterboard
const size_t _db_idx;
// infos about the daughtherboard
std::vector<std::map<std::string, std::string>> _all_dboard_info;
//! Prepended for all dboard RPC calls
const std::string _rpc_prefix;
//! Reference to the MB controller
uhd::rfnoc::mpmd_mb_controller::sptr _mb_control;
//! Reference to wb_iface adapters
std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces;
//! Reference to the RPC client
uhd::usrp::x400_rpc_iface::sptr _mb_rpcc;
uhd::usrp::fbx_rpc_iface::sptr _rpcc;
//! Reference to the RFDC controller
uhd::rfnoc::x400::rfdc_control::sptr _rfdcc;
//! Reference to FBX CTRL
std::shared_ptr<fbx_ctrl> _fbx_ctrl;
//! Reference to this block's subtree
//
// It is mutable because _tree->access<>(..).get() is not const, but we
// need to do just that in some const contexts
mutable uhd::property_tree::sptr _tree;
rf_control::gain_profile_iface::sptr _tx_gain_profile_api;
rf_control::gain_profile_iface::sptr _rx_gain_profile_api;
//! The sampling rate of the RFdc, in the 1-4 GHz range
const double _rfdc_rate;
};
}}} // namespace uhd::usrp::fbx

View File

@ -0,0 +1,363 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include "fbx_constants.hpp"
#include "fbx_ctrl.hpp"
#include <uhd/experts/expert_nodes.hpp>
#include <uhd/property_tree.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
#include <cmath>
#include <memory>
namespace uhd { namespace usrp { namespace fbx {
/*!---------------------------------------------------------
* fbx_scheduling_expert
*
* This expert is responsible for scheduling time sensitive actions
* in other experts. It responds to changes in the command time and
* selectively causes experts to run in order to ensure a synchronized
* system.
*
* There is one scheduling expert per channel, they are shared between RX and TX.
* So, 2 scheduling experts total per radio block.
* ---------------------------------------------------------
*/
class fbx_scheduling_expert : public experts::worker_node_t
{
public:
fbx_scheduling_expert(const experts::node_retriever_t& db, const uhd::fs_path fe_path)
: experts::worker_node_t(fe_path / "fbx_scheduling_expert")
, _command_time(db, fe_path / "time/cmd")
, _frontend_time(db, fe_path / "time/fe")
{
bind_accessor(_command_time);
bind_accessor(_frontend_time);
}
private:
virtual void resolve() override;
// Inputs:
experts::data_reader_t<time_spec_t> _command_time;
// Outputs:
experts::data_writer_t<time_spec_t> _frontend_time;
};
/*!---------------------------------------------------------
* fbx_tx_programming_expert
*
* This expert is responsible for programming the FBX with parameters determined by user
* input or other experts. This includes antenna and LED setting.
*
* This expert should not trigger any other experts, these are all blind parameters
*
* One instance of this expert is required for each TX Channel (0,1,2,3); four total
* --------------------------------------------------------
*/
class fbx_tx_programming_expert : public uhd::experts::worker_node_t
{
public:
fbx_tx_programming_expert(const uhd::experts::node_retriever_t& db,
const uhd::fs_path tx_fe_path,
const uhd::fs_path rx_fe_path,
const size_t chan,
std::shared_ptr<fbx_ctrl> fbx_ctrl)
: experts::worker_node_t(tx_fe_path / "fbx_tx_programming_expert")
, _antenna(db, tx_fe_path / "antenna" / "value")
, _command_time(db, rx_fe_path / "time" / "cmd")
, _frequency(db, tx_fe_path / "freq" / "coerced")
, _fbx_ctrl(fbx_ctrl)
, _chan(chan)
{
bind_accessor(_antenna);
bind_accessor(_command_time);
bind_accessor(_frequency);
}
private:
void resolve() override;
// Inputs from user/API
uhd::experts::data_reader_t<std::string> _antenna;
// Inputs from the Frequency FE expert
// Note: this is just for node dependencies, we want to be notified if just the tune
// frequency has been changed.
uhd::experts::data_reader_t<time_spec_t> _command_time;
uhd::experts::data_reader_t<double> _frequency;
// Expects constructed SW&LED control objects
std::shared_ptr<fbx_ctrl> _fbx_ctrl;
const size_t _chan;
};
/*!---------------------------------------------------------
* fbx_rx_programming_expert (RX Programming Expert)
*
* This expert is responsible for programming the FBX with parameters determined by user
* input or other experts. This includes antenna and LED setting.
*
* This expert should not trigger any other experts, these are all blind parameters
*
* One instance of this expert is required for each RX Channel (0,1,2,3); four total
* --------------------------------------------------------
*/
class fbx_rx_programming_expert : public uhd::experts::worker_node_t
{
public:
fbx_rx_programming_expert(const uhd::experts::node_retriever_t& db,
const uhd::fs_path fe_path,
const size_t chan,
std::shared_ptr<fbx_ctrl> fbx_ctrl)
: experts::worker_node_t(fe_path / "fbx_rx_programming_expert")
, _antenna(db, fe_path / "antenna" / "value")
, _command_time(db, fe_path / "time" / "cmd")
, _frequency(db, fe_path / "freq" / "coerced")
, _fbx_ctrl(fbx_ctrl)
, _chan(chan)
{
bind_accessor(_antenna);
bind_accessor(_command_time);
bind_accessor(_frequency);
}
private:
void resolve() override;
void _update_leds();
// Inputs from user/API
uhd::experts::data_reader_t<std::string> _antenna;
// Inputs from the Frequency FE expert
// Note: this is just for node dependencies, we want to be notified if just the tune
// frequency has been changed.
uhd::experts::data_reader_t<time_spec_t> _command_time;
uhd::experts::data_reader_t<double> _frequency;
// Expects constructed control objects
std::shared_ptr<fbx_ctrl> _fbx_ctrl;
const size_t _chan;
};
/*!---------------------------------------------------------
* fbx_band_inversion_expert
*
* This expert is responsible for handling the band inversion calls to MPM on the target
* device
*
* This expert should not trigger any others
*
* One instance of this expert is required for each Direction (TX/RX) and Channel (0-3);
* eight total
* --------------------------------------------------------
*/
class fbx_band_inversion_expert : public uhd::experts::worker_node_t
{
public:
fbx_band_inversion_expert(const uhd::experts::node_retriever_t& db,
const uhd::fs_path fe_path,
const uhd::direction_t trx,
const size_t chan,
const double rfdc_rate,
uhd::usrp::fbx_rpc_iface::sptr rpcc)
: uhd::experts::worker_node_t(fe_path / "fbx_band_inversion_expert")
, _frequency(db, fe_path / "freq" / "coerced")
, _rpcc(rpcc)
, _trx(trx)
, _chan(chan)
, _rfdc_rate(rfdc_rate)
{
bind_accessor(_frequency);
}
private:
void resolve() override;
// Inputs from Frequency RFDC Freq expert
uhd::experts::data_reader_t<double> _frequency;
uhd::usrp::fbx_rpc_iface::sptr _rpcc;
const uhd::direction_t _trx;
const size_t _chan;
const double _rfdc_rate;
};
/*!---------------------------------------------------------
* fbx_rfdc_freq_expert
*
* This expert is responsible for handling any rfdc frequency calls to MPM on the target
* device
*
* This expert should not trigger any experts
*
* One instance of this expert is required for each Direction (TX/RX) and Channel (0-3);
* eight total
* --------------------------------------------------------
*/
class fbx_rfdc_freq_expert : public uhd::experts::worker_node_t
{
public:
fbx_rfdc_freq_expert(const uhd::experts::node_retriever_t& db,
const uhd::fs_path fe_path,
const uhd::direction_t trx,
const size_t chan,
const std::string rpc_prefix,
int db_idx,
uhd::usrp::x400_rpc_iface::sptr rpcc)
: uhd::experts::worker_node_t(fe_path / "fbx_rfdc_freq_expert")
, _rfdc_freq_desired(
db, fe_path / "los" / RFDC_NCO / "freq" / "value" / "desired")
, _rfdc_freq_coerced(
db, fe_path / "los" / RFDC_NCO / "freq" / "value" / "coerced")
, _frequency_desired(db, fe_path / "freq" / "desired")
, _if_frequency_coerced(db, fe_path / "if_freq" / "coerced")
, _coerced_frequency(db, fe_path / "freq" / "coerced")
, _rpc_prefix(rpc_prefix)
, _db_idx(db_idx)
, _rpcc(rpcc)
, _trx(trx)
, _chan(chan)
{
bind_accessor(_rfdc_freq_desired);
bind_accessor(_rfdc_freq_coerced);
bind_accessor(_frequency_desired);
bind_accessor(_if_frequency_coerced);
bind_accessor(_coerced_frequency);
}
private:
void resolve() override;
// Inputs from user/API
uhd::experts::data_reader_t<double> _rfdc_freq_desired;
// Outputs to user/API
uhd::experts::data_writer_t<double> _rfdc_freq_coerced;
// Inputs from Frequency FE expert
uhd::experts::data_reader_t<double> _frequency_desired;
// Outputs to sync expert
uhd::experts::data_writer_t<double> _if_frequency_coerced;
// Outputs to generic freq property
uhd::experts::data_writer_t<double> _coerced_frequency;
const std::string _rpc_prefix;
const size_t _db_idx;
uhd::usrp::x400_rpc_iface::sptr _rpcc;
const uhd::direction_t _trx;
const size_t _chan;
};
using namespace uhd::rfnoc::x400;
using uhd::rfnoc::x400::rfdc_control;
/*!---------------------------------------------------------
* fbx_sync_expert
*
* This expert is responsible for handling the phase alignment.
* Per channel, there are up to 2 things whose phase need syncing: The NCO, and the
* ADC/DAC gearboxes. However, the NCOs share a sync register. To minimize writes, we thus
* need a single sync expert at the end of the graph, which combines all NCOs.
* --------------------------------------------------------
*/
class fbx_sync_expert : public uhd::experts::worker_node_t
{
public:
fbx_sync_expert(const uhd::experts::node_retriever_t& db,
const size_t num_channels,
const uhd::fs_path tx_fe_path,
const uhd::fs_path rx_fe_path,
rfdc_control::sptr rfdcc)
: uhd::experts::worker_node_t("fbx_sync_expert")
, _fe_time(get_fe_time(db, num_channels, rx_fe_path))
, _nco_freqs(get_nco_freqs(db, num_channels, rx_fe_path, tx_fe_path))
, _num_chans(num_channels)
, _rfdcc(rfdcc)
{
for (auto& fe_time : _fe_time) {
bind_accessor(fe_time);
}
for (auto& nco_freq : _nco_freqs) {
bind_accessor(nco_freq.second);
}
}
private:
/*
* Helper function to get the right number of _fe_time elements depending on the
* number of channels that are currently active.
*/
std::vector<uhd::experts::data_reader_t<uhd::time_spec_t>> get_fe_time(
const uhd::experts::node_retriever_t& db,
const size_t num_channels,
const uhd::fs_path rx_fe_path)
{
UHD_ASSERT_THROW(num_channels <= FBX_MAX_NUM_CHANS);
std::vector<uhd::experts::data_reader_t<uhd::time_spec_t>> fe_time;
fe_time.reserve(num_channels);
for (size_t i = 0; i < num_channels; i++) {
fe_time.emplace_back(db, rx_fe_path / i / "time/fe");
}
return fe_time;
}
/*
* Helper function to get the right number of _nco_freqs elements depending on the
* number of channels that are currently active.
*/
std::map<rfdc_control::rfdc_type, uhd::experts::data_reader_t<double>> get_nco_freqs(
const uhd::experts::node_retriever_t& db,
const size_t num_channels,
const uhd::fs_path rx_fe_path,
const uhd::fs_path tx_fe_path)
{
UHD_ASSERT_THROW(num_channels <= FBX_MAX_NUM_CHANS);
std::map<rfdc_control::rfdc_type, uhd::experts::data_reader_t<double>> nco_freqs;
indexed_for(RX_RFDC,
num_channels,
[&nco_freqs, &db, &rx_fe_path](const auto& item, size_t idx) {
nco_freqs.insert({item, {db, rx_fe_path / idx / "if_freq" / "coerced"}});
});
indexed_for(TX_RFDC,
num_channels,
[&nco_freqs, &db, &tx_fe_path](const auto& item, size_t idx) {
nco_freqs.insert({item, {db, tx_fe_path / idx / "if_freq" / "coerced"}});
});
return nco_freqs;
}
void resolve() override;
// Inputs from user/API
// Command time: We have up to 4 channels, one time spec per channel
std::vector<uhd::experts::data_reader_t<time_spec_t>> _fe_time;
// We have 8 NCOs
std::map<rfdc_control::rfdc_type, uhd::experts::data_reader_t<double>> _nco_freqs;
// This expert has no outputs.
// Attributes
size_t _num_chans;
rfdc_control::sptr _rfdcc;
//! Store the sync state of the ADC gearboxes. If false, we assume they're
// out of sync. This could also be a vector of booleans if we want to be
// able to sync ADC gearboxes individually.
bool _adcs_synced = false;
//! Store the sync state of the DAC gearboxes. If false, we assume they're
// out of sync. This could also be a vector of booleans if we want to be
// able to sync DAC gearboxes individually.
bool _dacs_synced = false;
};
}}} // namespace uhd::usrp::fbx

View File

@ -127,12 +127,10 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args)
UHD_ASSERT_THROW(default_spp > 0);
_spp_prop.push_back(
property_t<int>(PROP_KEY_SPP, default_spp, {res_source_info::USER, chan}));
_atomic_item_size_in.push_back(
property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE,
_atomic_item_size_in.push_back(property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE,
get_atomic_item_size(),
{res_source_info::INPUT_EDGE, chan}));
_atomic_item_size_out.push_back(
property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE,
_atomic_item_size_out.push_back(property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE,
get_atomic_item_size(),
{res_source_info::OUTPUT_EDGE, chan}));
_samp_rate_in.push_back(property_t<double>(
@ -159,13 +157,13 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args)
register_property(&_type_out.back());
// Add AIS resolvers first, they are used as inputs to other resolvers
add_property_resolver({&_atomic_item_size_in.back(),
get_mtu_prop_ref({res_source_info::INPUT_EDGE, chan})},
get_mtu_prop_ref({res_source_info::INPUT_EDGE, chan})},
{&_atomic_item_size_in.back()},
[this, chan,
&ais_in = _atomic_item_size_in.back()]() {
[this, chan, &ais_in = _atomic_item_size_in.back()]() {
RFNOC_LOG_TRACE("Calling resolver for atomic_item_size in@" << chan);
ais_in = uhd::math::lcm<size_t>(ais_in, get_atomic_item_size());
ais_in = std::min<size_t>(ais_in, get_mtu({res_source_info::INPUT_EDGE, chan}));
ais_in = std::min<size_t>(
ais_in, get_mtu({res_source_info::INPUT_EDGE, chan}));
if ((ais_in % get_atomic_item_size()) > 0) {
ais_in = ais_in - (ais_in % get_atomic_item_size());
}
@ -177,7 +175,8 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args)
[this, chan, &ais_out = _atomic_item_size_out.back()]() {
RFNOC_LOG_TRACE("Calling resolver for atomic_item_size out@" << chan);
ais_out = uhd::math::lcm<size_t>(ais_out, get_atomic_item_size());
ais_out = std::min<size_t>(ais_out, get_mtu({res_source_info::OUTPUT_EDGE, chan}));
ais_out = std::min<size_t>(
ais_out, get_mtu({res_source_info::OUTPUT_EDGE, chan}));
if ((ais_out % get_atomic_item_size()) > 0) {
ais_out = ais_out - (ais_out % get_atomic_item_size());
}
@ -243,10 +242,10 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args)
// Resolvers for type: These are constants
add_property_resolver({&_type_in.back()},
{&_type_in.back()},
[& type_in = _type_in.back()]() { type_in.set(IO_TYPE_SC16); });
[&type_in = _type_in.back()]() { type_in.set(IO_TYPE_SC16); });
add_property_resolver({&_type_out.back()},
{&_type_out.back()},
[& type_out = _type_out.back()]() { type_out.set(IO_TYPE_SC16); });
[&type_out = _type_out.back()]() { type_out.set(IO_TYPE_SC16); });
}
// Enable async messages coming from the radio
const uint32_t xbar_port = 1; // FIXME: Find a better way to figure this out
@ -506,22 +505,26 @@ void radio_control_impl::set_rx_agc(const bool, const size_t)
throw uhd::not_implemented_error("set_rx_agc() is not supported on this radio!");
}
void radio_control_impl::set_tx_gain_profile(const std::string& profile, const size_t chan)
void radio_control_impl::set_tx_gain_profile(
const std::string& profile, const size_t chan)
{
_tx_gain_profile_api->set_gain_profile(profile, chan);
}
void radio_control_impl::set_rx_gain_profile(const std::string& profile, const size_t chan)
void radio_control_impl::set_rx_gain_profile(
const std::string& profile, const size_t chan)
{
_rx_gain_profile_api->set_gain_profile(profile, chan);
}
std::vector<std::string> radio_control_impl::get_tx_gain_profile_names(const size_t chan) const
std::vector<std::string> radio_control_impl::get_tx_gain_profile_names(
const size_t chan) const
{
return _tx_gain_profile_api->get_gain_profile_names(chan);
}
std::vector<std::string> radio_control_impl::get_rx_gain_profile_names(const size_t chan) const
std::vector<std::string> radio_control_impl::get_rx_gain_profile_names(
const size_t chan) const
{
return _rx_gain_profile_api->get_gain_profile_names(chan);
}
@ -955,15 +958,14 @@ void radio_control_impl::issue_stream_cmd(
"zero samples");
return;
}
uint64_t num_words = stream_cmd.num_samps / _spc;
uint64_t num_words = stream_cmd.num_samps / _spc;
constexpr uint64_t max_num_words = 0x00FFFFFFFFFFFF; // 48 bits
if (stream_cmd.num_samps % _spc != 0) {
num_words++;
RFNOC_LOG_WARNING("The requested "
+ std::to_string(stream_cmd.num_samps)
+ " samples is not a multiple of the samples per cycle ("
+ std::to_string(_spc) + "); returning "
+ std::to_string(num_words * _spc) + " samples.");
RFNOC_LOG_WARNING("The requested " + std::to_string(stream_cmd.num_samps)
+ " samples is not a multiple of the samples per cycle ("
+ std::to_string(_spc) + "); returning "
+ std::to_string(num_words * _spc) + " samples.");
}
if (num_words > max_num_words) {
RFNOC_LOG_ERROR("Requesting too many samples in a single burst! "

View File

@ -7,7 +7,10 @@
#pragma once
#include "block_controller_factory_python.hpp"
#include <uhd/features/discoverable_feature.hpp>
#include <uhd/features/internal_sync_iface.hpp>
#include <uhd/rfnoc/radio_control.hpp>
#include <pybind11/stl.h>
using namespace uhd::rfnoc;
@ -16,6 +19,10 @@ void export_radio_control(py::module& m)
// Re-import ALL_CHANS here to avoid linker errors
const static auto ALL_CHANS = radio_control::ALL_CHANS;
py::class_<uhd::features::internal_sync_iface>(m, "internal_sync")
.def("enable_sync_clk", &uhd::features::internal_sync_iface::enable_sync_clk)
.def("disable_sync_clk", &uhd::features::internal_sync_iface::disable_sync_clk);
py::class_<radio_control, noc_block_base, radio_control::sptr>(m, "radio_control")
.def(py::init(&block_controller_factory<radio_control>::make_from))
.def("set_rate", &radio_control::set_rate)
@ -171,5 +178,11 @@ void export_radio_control(py::module& m)
.def("get_dboard_fe_from_chan", &radio_control::get_dboard_fe_from_chan)
.def("get_fe_name", &radio_control::get_fe_name)
.def("set_db_eeprom", &radio_control::set_db_eeprom)
.def("get_db_eeprom", &radio_control::get_db_eeprom);
.def("get_db_eeprom", &radio_control::get_db_eeprom)
.def(
"get_internal_sync",
[](radio_control& self) {
return &self.get_feature<uhd::features::internal_sync_iface>();
},
py::return_value_policy::reference_internal);
}

View File

@ -53,3 +53,7 @@ endif(ENABLE_N320)
if(ENABLE_X400)
INCLUDE_SUBDIRECTORY(zbx)
endif(ENABLE_X400)
if(ENABLE_X400)
INCLUDE_SUBDIRECTORY(fbx)
endif(ENABLE_X400)

View File

@ -0,0 +1,16 @@
#
# Copyright 2019 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
IF(ENABLE_X400)
LIST(APPEND X410_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/fbx_dboard.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fbx_dboard_init.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fbx_ctrl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fbx_expert.cpp
)
LIBUHD_APPEND_SOURCES(${X410_SOURCES})
ENDIF(ENABLE_X400)

View File

@ -0,0 +1,303 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <uhd/utils/log.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_ctrl.hpp>
#include <map>
#include <thread>
namespace uhd { namespace usrp { namespace fbx {
fbx_ctrl::fbx_ctrl(poke_fn_type&& poke_fn,
peek_fn_type&& peek_fn,
sleep_fn_type&& sleep_fn,
const std::string& log_id)
: _poke32(std::move(poke_fn))
, _peek32(std::move(peek_fn))
, _sleep(std::move(sleep_fn))
, _log_id(log_id)
{
UHD_LOG_TRACE(_log_id, "Entering fbx_ctrl ctor...");
// Reset and stash the regs state. We can't assume the defaults in
// gen_fbx_regs.py match whats on the hardware.
commit(NO_CHAN, true);
_regs.save_state();
UHD_LOG_TRACE(_log_id, "FPGA communication successful.");
set_classic_atr();
}
bool fbx_ctrl::poll_reg_with_timeout(
const std::string& reg, uint32_t timeout_ms, std::function<bool(uint32_t)> check)
{
auto addr = _regs.get_addr(reg);
// Doing an initial peek to avoid running into the timeout if timed commands were
// used. Peeks are blocking commands but may be triggered as timed commands (with
// delay thus). So whenever a method calls this poll_reg_with_timeout(), chances are
// high that we'll fail as false negative if we don't check initially and only then
// start the timeout.
if (check(_peek32(addr))) {
return true;
}
using namespace std::chrono;
const auto timeout_time =
std::chrono::steady_clock::now()
+ std::chrono::milliseconds(static_cast<int64_t>(timeout_ms));
while (std::chrono::steady_clock::now() < timeout_time) {
if (check(_peek32(addr))) {
return true;
}
}
return false;
}
void fbx_ctrl::set_classic_atr()
{
_regs.RF0_ATR_OPTION = _regs.RF0_ATR_OPTION_CLASSIC;
_regs.RF1_ATR_OPTION = _regs.RF1_ATR_OPTION_CLASSIC;
_regs.RF2_ATR_OPTION = _regs.RF2_ATR_OPTION_CLASSIC;
_regs.RF3_ATR_OPTION = _regs.RF3_ATR_OPTION_CLASSIC;
_regs.LED0_ATR_OPTION = _regs.LED0_ATR_OPTION_CLASSIC;
_regs.LED1_ATR_OPTION = _regs.LED1_ATR_OPTION_CLASSIC;
_regs.LED2_ATR_OPTION = _regs.LED2_ATR_OPTION_CLASSIC;
_regs.LED3_ATR_OPTION = _regs.LED3_ATR_OPTION_CLASSIC;
commit(NO_CHAN);
}
#define TRX_REG_TABLE(X) \
X(0) \
X(1) \
X(2) \
X(3)
#define TX_REG_ASSIGN(CHANNEL) \
if (channel == CHANNEL) { \
if (antenna == ANTENNA_TXRX) { \
_regs.RF##CHANNEL##_TX_RX_RFS[idx] = _regs.RF##CHANNEL##_TX_RX_RFS_RF1; \
_regs.RF##CHANNEL##_TDDS[idx] = _regs.RF##CHANNEL##_TDDS_RF2; \
} else if (antenna == ANTENNA_CAL_LOOPBACK) { \
_regs.RF##CHANNEL##_TX_RX_RFS[idx] = _regs.RF##CHANNEL##_TX_RX_RFS_RF2; \
} else { \
/* Since we have checked the validity of the antenna name before */ \
/* we should never get here. */ \
UHD_THROW_INVALID_CODE_PATH(); \
} \
/* If we're defining the IDLE state and are not in CAL_LOOPBACK antenna */ \
/* then overwrite previous settings with the switch settings for idle */ \
/* (maximum isolation). This doesn't cause additional register writes */ \
/* because this is buffered and only written when commit() is called. */ \
/* If the antenna is set to CAL_LOOPBACK, we won't configure the IDLE */ \
/* case differently than the TX state because the ADC selc cal doesn't */ \
/* switch ATR states and operates in IDLE state. */ \
if (idx == ATR_ADDR_0X && antenna != ANTENNA_CAL_LOOPBACK) { \
_regs.RF##CHANNEL##_TX_RX_RFS[idx] = _regs.RF##CHANNEL##_TX_RX_RFS_RF2; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
_regs.RF##CHANNEL##_TDDS[idx] = _regs.RF##CHANNEL##_TDDS_RF2; \
} \
} else
void fbx_ctrl::set_tx_antenna_switches(
const size_t channel, const uint8_t idx, const std::string& antenna)
{
UHD_ASSERT_THROW(channel < FBX_MAX_NUM_CHANS);
UHD_ASSERT_THROW(
std::find(TX_ANTENNAS.begin(), TX_ANTENNAS.end(), antenna) != TX_ANTENNAS.end());
// Create code for all 4 channels per DB via macro:
TRX_REG_TABLE(TX_REG_ASSIGN) {}
commit(chan_t(channel));
}
/* Putting this into a macro as otherwise we'd have to write it down for all four channels
* individually due to the indices in the variable names */
#define RX_REG_ASSIGN(CHANNEL) \
if (channel == CHANNEL) { \
if (antenna == ANTENNA_TXRX) { \
_regs.SYNC##CHANNEL##_CTRL = _regs.SYNC##CHANNEL##_CTRL_RF1; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
_regs.RF##CHANNEL##_TDDS[idx] = _regs.RF##CHANNEL##_TDDS_RF1; \
} else if (antenna == ANTENNA_RX) { \
_regs.SYNC##CHANNEL##_CTRL = _regs.SYNC##CHANNEL##_CTRL_RF1; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF2; \
_regs.RF##CHANNEL##_TDDS[idx] = _regs.RF##CHANNEL##_TDDS_RF2; \
} else if (antenna == ANTENNA_CAL_LOOPBACK) { \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
_regs.SYNC##CHANNEL##_CTRL = _regs.SYNC##CHANNEL##_CTRL_RF4; \
} else if (antenna == ANTENNA_SYNC_INT) { \
_regs.SYNC4_CTRL = _regs.SYNC4_CTRL_RF1; \
_regs.SYNC##CHANNEL##_CTRL = _regs.SYNC##CHANNEL##_CTRL_RF3; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
} else if (antenna == ANTENNA_SYNC_EXT) { \
_regs.SYNC4_CTRL = _regs.SYNC4_CTRL_RF4; \
_regs.SYNC##CHANNEL##_CTRL = _regs.SYNC##CHANNEL##_CTRL_RF3; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
} else if (antenna == ANTENNA_TERMINATION) { \
_regs.SYNC##CHANNEL##_CTRL = _regs.SYNC##CHANNEL##_CTRL_RF2; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
} else { \
/* Since we have checked the validity of the antenna name before */ \
/* we should never get here. */ \
UHD_THROW_INVALID_CODE_PATH(); \
} \
/* If we're defining the IDLE state and are not in CAL_LOOPBACK antenna */ \
/* then overwrite previous settings with the switch settings for idle */ \
/* (maximum isolation). This doesn't cause additional register writes */ \
/* because this is buffered and only written when commit() is called. */ \
/* If the antenna is set to CAL_LOOPBACK, we won't configure the IDLE */ \
/* case differently than the RX state because the ADC selc cal doesn't */ \
/* switch ATR states and operates in IDLE state. */ \
if (idx == ATR_ADDR_0X && antenna != ANTENNA_CAL_LOOPBACK) { \
_regs.RF##CHANNEL##_TX_RX_RFS[idx] = _regs.RF##CHANNEL##_TX_RX_RFS_RF2; \
_regs.RF##CHANNEL##_RX_RFS[idx] = _regs.RF##CHANNEL##_RX_RFS_RF1; \
_regs.RF##CHANNEL##_TDDS[idx] = _regs.RF##CHANNEL##_TDDS_RF2; \
} \
/*Turn off SYNC4 for better isolation in case no one is using it. */ \
if (!any_ch_connected_to_sync()) { \
_regs.SYNC4_CTRL = _regs.SYNC4_CTRL_OFF; \
} \
} else
void fbx_ctrl::set_rx_antenna_switches(
const size_t channel, const uint8_t idx, const std::string& antenna)
{
UHD_ASSERT_THROW(channel < FBX_MAX_NUM_CHANS);
UHD_ASSERT_THROW(
std::find(RX_ANTENNAS.begin(), RX_ANTENNAS.end(), antenna) != RX_ANTENNAS.end());
// Antenna settings: RX2, TX/RX, CAL_LOOPBACK, TERMINATION
// Create code for all 4 channels via macro
TRX_REG_TABLE(RX_REG_ASSIGN) {}
commit(chan_t(channel));
}
std::string fbx_ctrl::get_rx_sync_switch_state()
{
if (_regs.SYNC4_CTRL == _regs.SYNC4_CTRL_RF4) {
return ANTENNA_SYNC_EXT;
} else if (_regs.SYNC4_CTRL == _regs.SYNC4_CTRL_RF1) {
return ANTENNA_SYNC_INT;
} else if (_regs.SYNC4_CTRL == _regs.SYNC4_CTRL_RF2
|| _regs.SYNC4_CTRL == _regs.SYNC4_CTRL_RF3) {
return ANTENNA_TERMINATION;
} else if (_regs.SYNC4_CTRL == _regs.SYNC4_CTRL_OFF) {
return ANTENNA_OFF;
} else {
UHD_THROW_INVALID_CODE_PATH();
}
}
#define LED_REG_ASSIGN(CHANNEL) \
if (channel == CHANNEL) { \
_regs.CH##CHANNEL##_RX2_LED[idx] = \
rx ? fbx_regs_t::CH##CHANNEL##_RX2_LED_ENABLE \
: fbx_regs_t::CH##CHANNEL##_RX2_LED_DISABLE; \
_regs.CH##CHANNEL##_TRX1_LED_RED[idx] = \
trx_tx ? fbx_regs_t::CH##CHANNEL##_TRX1_LED_RED_ENABLE \
: fbx_regs_t::CH##CHANNEL##_TRX1_LED_RED_DISABLE; \
_regs.CH##CHANNEL##_TRX1_LED_GR[idx] = \
trx_rx ? fbx_regs_t::CH##CHANNEL##_TRX1_LED_GR_ENABLE \
: fbx_regs_t::CH##CHANNEL##_TRX1_LED_GR_DISABLE; \
} else
/******************************************************************************
* LED control
*****************************************************************************/
void fbx_ctrl::set_leds(const size_t channel,
const uint8_t idx,
const bool rx,
const bool trx_rx,
const bool trx_tx)
{
UHD_ASSERT_THROW(channel < FBX_MAX_NUM_CHANS);
// Create code for all 4 channels via macro
TRX_REG_TABLE(LED_REG_ASSIGN) {}
commit(chan_t(channel));
}
/**************************************************************************
* Internal SYNC signal control
*************************************************************************/
void fbx_ctrl::set_internal_sync_clk(const bool enable)
{
_regs.SYNC_CLK = enable ? _regs.SYNC_CLK_ENABLE : _regs.SYNC_CLK_DISABLE;
commit(NO_CHAN);
}
/******************************************************************************
* Private methods
*****************************************************************************/
void fbx_ctrl::commit(const chan_t chan, const bool save_all)
{
UHD_LOG_TRACE(_log_id,
"Storing register cache " << (save_all ? "completely" : "selectively")
<< " to register...");
const auto changed_addrs = save_all ? _regs.get_all_addrs<size_t>()
: _regs.get_changed_addrs<size_t>();
// If something was configured that is connected to the I/O expander we need to run
// the config sequence.
bool io_exp_cfg_req = false;
const auto sync_range_start = _regs.get_addr("SYNC0_CTRL");
const auto sync_range_stop = _regs.get_addr("SYNC4_CTRL");
const auto io_exp_setup = _regs.get_addr("IO_EXP_SETUP");
const auto io_exp_config = _regs.get_addr("IO_EXP_CONFIG");
auto check_first_bit = [](uint32_t value) { return (0x1 & value) == 1; };
for (const auto addr : changed_addrs) {
// The I/O Expander config register is never called on its own but only if any
// value in it was changed. Therefore we exclude it here but call it later on if
// required.
if (addr != io_exp_config) {
_poke32(addr, _regs.get_reg(addr), save_all ? NO_CHAN : chan);
// We need to poll the I/O Expander setup register after we have set it.
// Usually this is only done once during startup.
if (addr == io_exp_setup) {
if (!poll_reg_with_timeout("IO_EXP_SETUP", 100, check_first_bit)) {
throw uhd::io_error(
"I/O Expander could not be initialized successfully.");
}
} else {
// If any value on the I/O expander was changed set the io expander config
// required flag to true
if ((sync_range_start <= addr) && (addr <= sync_range_stop)) {
io_exp_cfg_req = true;
}
}
}
}
// Apply the new values on the I/O expander by first poking, then polling the I/O
// expander config register. Timeout if we don't get the proper value back.
if (io_exp_cfg_req) {
_poke32(io_exp_config, 0x1, NO_CHAN);
// FIXME: In timed commands this will block the execution of the corresponding
// method (e.g. set_trx_antenna()) because peek is a blocking command.
if (!fbx_ctrl::poll_reg_with_timeout("IO_EXP_CONFIG", 100, check_first_bit)) {
throw uhd::io_error(
"I/O Expander configuration did not return properly within 100 ms.");
}
}
_regs.save_state();
UHD_LOG_TRACE(_log_id,
"Storing cache complete: "
"Updated "
<< changed_addrs.size() << " registers.");
}
bool fbx_ctrl::any_ch_connected_to_sync()
{
// If any of the four channels has set its sync switch to RF3 it is routed towards
// the last sync switch. In that case this last sync switch shouldn't be turned
// off, otherwise we can safely do that.
return (_regs.SYNC0_CTRL == _regs.SYNC0_CTRL_RF3
|| _regs.SYNC1_CTRL == _regs.SYNC1_CTRL_RF3
|| _regs.SYNC2_CTRL == _regs.SYNC2_CTRL_RF3
|| _regs.SYNC3_CTRL == _regs.SYNC3_CTRL_RF3);
}
}}} // namespace uhd::usrp::fbx

View File

@ -0,0 +1,294 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <uhd/types/direction.hpp>
#include <uhd/utils/assert_has.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/math.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_dboard.hpp>
#include <cstdlib>
#include <sstream>
namespace uhd { namespace usrp { namespace fbx {
/******************************************************************************
* Structors
*****************************************************************************/
fbx_dboard_impl::fbx_dboard_impl(register_iface& reg_iface,
const size_t reg_base_address,
time_accessor_fn_type&& time_accessor,
const size_t db_idx,
const std::string& radio_slot,
const size_t num_tx_chans,
const size_t num_rx_chans,
const std::string& rpc_prefix,
const std::string& unique_id,
uhd::usrp::x400_rpc_iface::sptr mb_rpcc,
uhd::usrp::fbx_rpc_iface::sptr rpcc,
uhd::rfnoc::x400::rfdc_control::sptr rfdcc,
uhd::property_tree::sptr tree)
: nameless_gain_mixin([](const uhd::direction_t, size_t) { return ""; })
, _unique_id(unique_id)
, _num_tx_chans(num_tx_chans)
, _num_rx_chans(num_rx_chans)
, _regs(reg_iface)
, _reg_base_address(reg_base_address)
, _time_accessor(time_accessor)
, _radio_slot(radio_slot)
, _db_idx(db_idx)
, _rpc_prefix(rpc_prefix)
, _mb_rpcc(mb_rpcc)
, _rpcc(rpcc)
, _rfdcc(rfdcc)
, _tree(tree)
, _rfdc_rate(_rpcc->get_dboard_sample_rate())
{
RFNOC_LOG_TRACE("Entering fbx_dboard_impl ctor...");
RFNOC_LOG_TRACE("Radio slot: " << _radio_slot);
UHD_ASSERT_THROW(_num_rx_chans <= FBX_MAX_NUM_CHANS);
UHD_ASSERT_THROW(_num_tx_chans <= FBX_MAX_NUM_CHANS);
_rx_antenna = std::make_shared<uhd::rfnoc::rf_control::enumerated_antenna>(
tree,
[this](size_t chan) {
return this->_get_frontend_path(RX_DIRECTION, chan) / "antenna" / "value";
},
RX_ANTENNAS,
RX_ANTENNA_NAME_COMPAT_MAP);
_tx_antenna = std::make_shared<uhd::rfnoc::rf_control::enumerated_antenna>(
tree,
[this](size_t chan) {
return this->_get_frontend_path(TX_DIRECTION, chan) / "antenna" / "value";
},
TX_ANTENNAS,
TX_ANTENNA_NAME_COMPAT_MAP);
_tx_gain_profile_api = std::make_shared<rf_control::enumerated_gain_profile>(
FBX_GAIN_PROFILES, FBX_GAIN_PROFILE_DEFAULT, get_num_tx_channels());
_rx_gain_profile_api = std::make_shared<rf_control::enumerated_gain_profile>(
FBX_GAIN_PROFILES, FBX_GAIN_PROFILE_DEFAULT, get_num_rx_channels());
_expert_container =
uhd::experts::expert_factory::create_container("fbx_radio_" + _radio_slot);
_init_fbx_ctrl();
// Prop tree requires the initialization of certain peripherals
_init_prop_tree();
_expert_container->resolve_all();
}
fbx_dboard_impl::~fbx_dboard_impl()
{
RFNOC_LOG_TRACE("fbx_dboard::dtor()");
}
void fbx_dboard_impl::deinit()
{
_wb_ifaces.clear();
}
void fbx_dboard_impl::set_command_time(uhd::time_spec_t time, const size_t chan)
{
// When the command time gets updated, import it into the expert graph
get_tree()
->access<time_spec_t>(fs_path("dboard") / "rx_frontends" / chan / "time/cmd")
.set(time);
}
std::string fbx_dboard_impl::get_unique_id() const
{
return _unique_id;
}
/******************************************************************************
* API Calls
*****************************************************************************/
std::string fbx_dboard_impl::get_rx_antenna(const size_t chan) const
{
std::string ant = _rx_antenna->get_antenna(chan);
if (ant == ANTENNA_SYNC_INT || ant == ANTENNA_SYNC_EXT) {
return _fbx_ctrl->get_rx_sync_switch_state();
}
return _rx_antenna->get_antenna(chan);
}
double fbx_dboard_impl::set_tx_frequency(const double req_freq, const size_t chan)
{
const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
_tree->access<double>(fe_path / "freq").set(req_freq);
return _tree->access<double>(fe_path / "freq").get();
}
double fbx_dboard_impl::set_rx_frequency(const double req_freq, const size_t chan)
{
const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
_tree->access<double>(fe_path / "freq").set(req_freq);
return _tree->access<double>(fe_path / "freq").get();
}
double fbx_dboard_impl::set_tx_bandwidth(const double bandwidth, const size_t chan)
{
const double bw = get_tx_bandwidth(chan);
if (!uhd::math::frequencies_are_equal(bandwidth, bw)) {
RFNOC_LOG_WARNING("Invalid analog bandwidth: " << (bandwidth / 1e6) << " MHz.");
}
return bw;
}
double fbx_dboard_impl::get_tx_bandwidth(size_t chan)
{
return _tree
->access<double>(_get_frontend_path(TX_DIRECTION, chan) / "bandwidth/value")
.get();
}
double fbx_dboard_impl::set_rx_bandwidth(const double bandwidth, const size_t chan)
{
const double bw = get_rx_bandwidth(chan);
if (!uhd::math::frequencies_are_equal(bandwidth, bw)) {
RFNOC_LOG_WARNING("Invalid analog bandwidth: " << (bandwidth / 1e6) << " MHz.");
}
return bw;
}
double fbx_dboard_impl::get_rx_bandwidth(size_t chan)
{
return _tree
->access<double>(_get_frontend_path(RX_DIRECTION, chan) / "bandwidth/value")
.get();
}
double fbx_dboard_impl::get_tx_frequency(size_t chan)
{
const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
return _tree->access<double>(fe_path / "freq").get();
}
double fbx_dboard_impl::get_rx_frequency(size_t chan)
{
const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
return _tree->access<double>(fe_path / "freq").get();
}
fs_path fbx_dboard_impl::_get_frontend_path(
const direction_t dir, const size_t chan_idx) const
{
UHD_ASSERT_THROW(
chan_idx < (dir == TX_DIRECTION ? get_num_tx_channels() : get_num_rx_channels()));
const std::string frontend = dir == TX_DIRECTION ? "tx_frontends" : "rx_frontends";
return fs_path("dboard") / frontend / chan_idx;
}
double fbx_dboard_impl::set_tx_lo_freq(
double freq, const std::string& name, const size_t chan)
{
_validate_lo_name(name, "set_tx_lo_freq");
RFNOC_LOG_TRACE("set_tx_lo_freq(freq=" << freq << ", name=" << name << ")");
const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
return _tree->access<double>(fe_path / "los" / name / "freq" / "value")
.set(freq)
.get();
}
double fbx_dboard_impl::get_tx_lo_freq(const std::string& name, const size_t chan)
{
_validate_lo_name(name, "get_tx_lo_freq");
RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ")");
const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
return _tree->access<double>(fe_path / "los" / name / "freq" / "value").get();
}
double fbx_dboard_impl::set_rx_lo_freq(
double freq, const std::string& name, const size_t chan)
{
_validate_lo_name(name, "set_rx_lo_freq");
RFNOC_LOG_TRACE("set_rx_lo_freq(freq=" << freq << ", name=" << name << ")");
const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
return _tree->access<double>(fe_path / "los" / name / "freq" / "value")
.set(freq)
.get();
}
double fbx_dboard_impl::get_rx_lo_freq(const std::string& name, size_t chan)
{
_validate_lo_name(name, "get_rx_lo_freq");
RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ")");
const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
return _tree->access<double>(fe_path / "los" / name / "freq" / "value").get();
}
freq_range_t fbx_dboard_impl::_get_lo_freq_range(
const std::string& name, const size_t /*chan*/) const
{
_validate_lo_name(name, "_get_lo_freq_range");
return freq_range_t{0.0, _rfdc_rate};
}
void fbx_dboard_impl::_validate_lo_name(
const std::string& name, const std::string& caller) const
{
if (name != RFDC_NCO) {
throw uhd::value_error("Invalid LO name: " + name + " called from " + caller);
}
}
/******************************************************************************
* EEPROM API
*****************************************************************************/
eeprom_map_t fbx_dboard_impl::get_db_eeprom()
{
return _mb_rpcc->get_db_eeprom(_db_idx);
}
size_t fbx_dboard_impl::get_chan_from_dboard_fe(
const std::string& fe, const uhd::direction_t dir) const
{
char* end;
errno = 0;
uint32_t fe_num = std::strtol(fe.c_str(), &end, 0);
const auto max_num_chans =
(dir == TX_DIRECTION ? get_num_tx_channels() : get_num_rx_channels());
if (errno == 0 && strlen(end) == 0 && fe_num < max_num_chans) {
return fe_num;
} else {
throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe);
}
}
std::string fbx_dboard_impl::get_dboard_fe_from_chan(
const size_t chan, const uhd::direction_t dir) const
{
const auto max_num_chans =
(dir == TX_DIRECTION ? get_num_tx_channels() : get_num_rx_channels());
if (chan < max_num_chans) {
return std::to_string(chan);
} else {
throw uhd::lookup_error(
std::string("[X400] Invalid channel: ") + std::to_string(chan));
}
}
/*********************************************************************
* ADC Self Cal API
**********************************************************************/
bool fbx_dboard_impl::select_adc_self_cal_gain(size_t chan)
{
return _mb_rpcc->get_threshold_status(_db_idx, chan, 0);
}
}}} // namespace uhd::usrp::fbx

View File

@ -0,0 +1,265 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <uhd/cal/database.hpp>
#include <uhd/exception.hpp>
#include <uhd/experts/expert_container.hpp>
#include <uhd/experts/expert_factory.hpp>
#include <uhd/property_tree.hpp>
#include <uhd/property_tree.ipp>
#include <uhd/rfnoc/register_iface.hpp>
#include <uhd/types/direction.hpp>
#include <uhd/types/eeprom.hpp>
#include <uhd/types/ranges.hpp>
#include <uhd/types/sensors.hpp>
#include <uhd/utils/log.hpp>
#include <uhdlib/rfnoc/reg_iface_adapter.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_constants.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_dboard.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_expert.hpp>
#include <boost/algorithm/string.hpp>
#include <sstream>
#include <vector>
using namespace uhd;
using namespace uhd::experts;
using namespace uhd::rfnoc;
// ostream << operator overloads for our enum classes, so that property nodes of that type
// can be added to our expert graph
namespace uhd { namespace usrp { namespace fbx {
void fbx_dboard_impl::_init_fbx_ctrl()
{
RFNOC_LOG_TRACE("Initializing FBX CTRL");
_fbx_ctrl = std::make_shared<fbx_ctrl>(
[this](const uint32_t addr, const uint32_t data, const fbx_ctrl::chan_t chan) {
const auto time_spec = (chan == fbx_ctrl::NO_CHAN) ? time_spec_t::ASAP
: (chan == fbx_ctrl::CHAN1) ? _time_accessor(1)
: _time_accessor(0);
_regs.poke32(_reg_base_address + addr, data, time_spec);
},
[this](const uint32_t addr) {
// We don't do timed peeks, so no chan parameter here.
return _regs.peek32(_reg_base_address + addr);
},
[this](const uhd::time_spec_t& sleep_time) { _regs.sleep(sleep_time); },
get_unique_id() + "::FBX_CTRL");
UHD_ASSERT_THROW(_fbx_ctrl);
}
void fbx_dboard_impl::_init_prop_tree()
{
auto subtree = get_tree()->subtree(fs_path("dboard"));
// Construct RX frontend
for (size_t chan_idx = 0; chan_idx < get_num_rx_channels(); chan_idx++) {
const fs_path fe_path = fs_path("rx_frontends") / chan_idx;
// Command time needs to be shadowed into the property tree so we can use
// it in the expert graph. TX and RX share the command time, so we could
// put it onto its own sub-tree, or copy the property between TX and RX.
// With respect to TwinRX and trying to keep the tree lean and browsable,
// we compromise and put the command time onto the RX frontend path, even
// though it's also valid for TX.
// This data node will be used for scheduling the other experts:
expert_factory::add_data_node<time_spec_t>(
_expert_container, fe_path / "time/fe", time_spec_t(0.0));
// This prop node will be used to import the command time into the
// graph:
expert_factory::add_prop_node<time_spec_t>(
_expert_container, subtree, fe_path / "time/cmd", time_spec_t(0.0));
_init_frontend_subtree(subtree, RX_DIRECTION, chan_idx, fe_path);
// The time nodes get connected with one scheduling expert per channel:
expert_factory::add_worker_node<fbx_scheduling_expert>(
_expert_container, _expert_container->node_retriever(), fe_path);
}
// Construct TX frontend
// Note: the TX frontend uses the RX property tree, this must
// be constructed after the RX frontend
for (size_t chan_idx = 0; chan_idx < get_num_tx_channels(); chan_idx++) {
const fs_path fe_path = fs_path("tx_frontends") / chan_idx;
_init_frontend_subtree(subtree, TX_DIRECTION, chan_idx, fe_path);
}
// Now add the sync worker:
UHD_ASSERT_THROW(get_num_rx_channels() == get_num_tx_channels());
expert_factory::add_worker_node<fbx_sync_expert>(_expert_container,
_expert_container->node_retriever(),
get_num_rx_channels(),
fs_path("tx_frontends"),
fs_path("rx_frontends"),
_rfdcc);
subtree->create<eeprom_map_t>("eeprom")
.add_coerced_subscriber([](const eeprom_map_t&) {
throw uhd::runtime_error("Attempting to update daughterboard eeprom!");
})
.set_publisher([this]() { return get_db_eeprom(); });
}
void fbx_dboard_impl::_init_frontend_subtree(uhd::property_tree::sptr subtree,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path)
{
static constexpr char FBX_FE_NAME[] = "FBX";
RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel "
<< chan_idx << " to prop tree path " << fe_path);
// Standard attributes
subtree->create<std::string>(fe_path / "name").set(FBX_FE_NAME);
subtree->create<std::string>(fe_path / "connection").set("IQ");
_init_frequency_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path);
_init_gain_prop_tree(subtree, _expert_container, fe_path);
_init_antenna_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path);
_init_lo_prop_tree(subtree, trx, chan_idx, fe_path);
_init_experts(_expert_container, trx, chan_idx, fe_path);
}
void fbx_dboard_impl::_init_experts(expert_container::sptr expert,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path)
{
RFNOC_LOG_TRACE(fe_path + ", Creating experts...");
if (trx == TX_DIRECTION) {
expert_factory::add_worker_node<fbx_tx_programming_expert>(expert,
expert->node_retriever(),
fe_path,
fs_path("rx_frontends") / chan_idx,
chan_idx,
_fbx_ctrl);
} else {
expert_factory::add_worker_node<fbx_rx_programming_expert>(
expert, expert->node_retriever(), fe_path, chan_idx, _fbx_ctrl);
}
expert_factory::add_worker_node<fbx_band_inversion_expert>(
expert, expert->node_retriever(), fe_path, trx, chan_idx, _rfdc_rate, _rpcc);
expert_factory::add_worker_node<fbx_rfdc_freq_expert>(expert,
expert->node_retriever(),
fe_path,
trx,
chan_idx,
_rpc_prefix,
_db_idx,
_mb_rpcc);
RFNOC_LOG_TRACE(fe_path + ", Experts created");
}
void fbx_dboard_impl::_init_frequency_prop_tree(uhd::property_tree::sptr subtree,
expert_container::sptr expert,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path)
{
expert_factory::add_dual_prop_node<double>(
expert, subtree, fe_path / "freq", FBX_DEFAULT_FREQ, AUTO_RESOLVE_ON_WRITE);
expert_factory::add_dual_prop_node<double>(
expert, subtree, fe_path / "if_freq", 0.0, AUTO_RESOLVE_ON_WRITE);
expert_factory::add_dual_prop_node<double>(expert,
subtree,
fe_path / "los" / RFDC_NCO / "freq" / "value",
// Initialize with current value
_mb_rpcc->rfdc_get_nco_freq(trx == TX_DIRECTION ? "tx" : "rx", _db_idx, chan_idx),
AUTO_RESOLVE_ON_WRITE);
subtree->create<double>(fe_path / "bandwidth" / "value")
.set(FBX_DEFAULT_BANDWIDTH)
.set_coercer([](const double) { return FBX_DEFAULT_BANDWIDTH; });
subtree->create<meta_range_t>(fe_path / "bandwidth" / "range")
.set({FBX_DEFAULT_BANDWIDTH, FBX_DEFAULT_BANDWIDTH})
.set_coercer([](const meta_range_t&) {
return meta_range_t(FBX_DEFAULT_BANDWIDTH, FBX_DEFAULT_BANDWIDTH);
});
subtree->create<meta_range_t>(fe_path / "freq" / "range")
.set(FBX_FREQ_RANGE)
.add_coerced_subscriber([](const meta_range_t&) {
throw uhd::runtime_error("Attempting to update freq range!");
});
}
void fbx_dboard_impl::_init_gain_prop_tree(uhd::property_tree::sptr subtree,
expert_container::sptr expert,
const fs_path fe_path)
{
// To satisfy RFNoC we need to create the path, although we're not going to use it.
const auto gain_base_path = fe_path / "gains";
expert_factory::add_dual_prop_node<double>(
expert, subtree, gain_base_path / "all" / "value", 0, AUTO_RESOLVE_ON_WRITE);
subtree->create<meta_range_t>(fe_path / "gains" / "all" / "range")
.add_coerced_subscriber([](const meta_range_t&) {
throw uhd::runtime_error("Attempting to update gain range!");
})
.set_publisher([this]() {
const auto empty_range = uhd::meta_range_t(0, 0, 0);
return empty_range;
});
}
void fbx_dboard_impl::_init_antenna_prop_tree(uhd::property_tree::sptr subtree,
expert_container::sptr expert,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path)
{
const std::string default_ant = trx == TX_DIRECTION ? DEFAULT_TX_ANTENNA
: DEFAULT_RX_ANTENNA;
expert_factory::add_prop_node<std::string>(expert,
subtree,
fe_path / "antenna" / "value",
default_ant,
AUTO_RESOLVE_ON_WRITE);
subtree->access<std::string>(fe_path / "antenna" / "value")
.set_coercer([trx](const std::string& ant_name) {
const auto ant_map = trx == TX_DIRECTION ? TX_ANTENNA_NAME_COMPAT_MAP
: RX_ANTENNA_NAME_COMPAT_MAP;
return ant_map.count(ant_name) ? ant_map.at(ant_name) : ant_name;
});
subtree->create<std::vector<std::string>>(fe_path / "antenna" / "options")
.set(trx == TX_DIRECTION ? get_tx_antennas(chan_idx) : get_rx_antennas(chan_idx))
.add_coerced_subscriber([](const std::vector<std::string>&) {
throw uhd::runtime_error("Attempting to update antenna options!");
});
}
void fbx_dboard_impl::_init_lo_prop_tree(uhd::property_tree::sptr subtree,
const uhd::direction_t trx,
const size_t chan_idx,
const fs_path fe_path)
{
// LO lock sensor
// as there are no LOs on the boards we can assume lo_locked to be
// always true
subtree->create<sensor_value_t>(fe_path / "sensors" / "lo_locked")
.set(sensor_value_t("all_los", false, "locked", "unlocked"))
.add_coerced_subscriber([](const sensor_value_t&) {
throw uhd::runtime_error("Attempting to write to sensor!");
})
.set_publisher([this, trx, chan_idx]() {
return sensor_value_t("all_los", true, "locked", "unlocked");
});
subtree->create<sensor_value_t>(fe_path / "sensors" / "nco_locked")
.add_coerced_subscriber([](const sensor_value_t&) {
throw uhd::runtime_error("Attempting to write to sensor!");
})
.set_publisher([this]() {
return sensor_value_t(
RFDC_NCO, this->_rfdcc->get_nco_reset_done(), "locked", "unlocked");
});
}
}}} // namespace uhd::usrp::fbx

View File

@ -0,0 +1,232 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <uhd/types/direction.hpp>
#include <uhd/utils/assert_has.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/math.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_expert.hpp>
#include <uhdlib/utils/interpolation.hpp>
#include <algorithm>
#include <array>
using namespace uhd;
namespace uhd { namespace usrp { namespace fbx {
namespace {
/*********************************************************************
* Misc/calculative helper functions
**********************************************************************/
/*
* I and Q may be swapped depending on the frequency we are operating on. According to
* measurements up to the 8th Nyquist zone we have to switch like this:
* RX: First Nyquist zone IQ swapped, all others are okay as is.
* TX: First Nyquist zone is okay, all others are swapped.
* So the swapping is exactly mirrored for RX and TX.
*/
bool _is_band_inverted(
const uhd::direction_t trx, const double freq, const double rfdc_rate)
{
constexpr std::array<bool, 8> invert_zones = {
true, false, false, false, false, false, false, false};
// Find out in which Nyquist zone we are (counting from 0):
const size_t nyquist_zone = freq / (rfdc_rate / 2);
const bool rx_invert = invert_zones[nyquist_zone];
const bool tx_invert = !invert_zones[nyquist_zone];
if (trx == RX_DIRECTION) {
return rx_invert;
} else {
return tx_invert;
}
}
std::string _get_trx_string(const direction_t dir)
{
if (dir == RX_DIRECTION) {
return "rx";
} else if (dir == TX_DIRECTION) {
return "tx";
} else {
UHD_THROW_INVALID_CODE_PATH();
}
}
} // namespace
/*!---------------------------------------------------------
* EXPERT RESOLVE FUNCTIONS
*
* This sections contains all expert resolve functions.
* These methods are triggered by any of the bound accessors becoming "dirty",
* or changing value
* --------------------------------------------------------
*/
void fbx_scheduling_expert::resolve()
{
// We currently have no fancy scheduling, but here is where we'd add it if
// we need to do that (e.g., plan out SYNC pulse timing vs. NCO timing etc.)
_frontend_time = _command_time;
}
void fbx_tx_programming_expert::resolve()
{
for (const size_t idx : ATR_ADDRS) {
_fbx_ctrl->set_tx_antenna_switches(_chan, idx, _antenna);
}
// We do not update LEDs on switching TX antenna value by definition
}
void fbx_rx_programming_expert::resolve()
{
for (const size_t idx : ATR_ADDRS) {
// If using the TX/RX terminal, only configure the ATR RX state since the state of
// the switch at other times is controlled by TX
if (_antenna != ANTENNA_TXRX || idx == ATR_ADDR_RX) {
_fbx_ctrl->set_rx_antenna_switches(_chan, idx, _antenna);
}
}
_update_leds();
}
void fbx_rx_programming_expert::_update_leds()
{
// We default to the RX1 for all rX antenna values that are not TX/RX0
const bool rx_on_trx = _antenna == ANTENNA_TXRX;
// clang-format off
// G==Green, R==Red RX2 TX/RX-G TX/RX-R
_fbx_ctrl->set_leds(_chan, ATR_ADDR_0X, false, false, false);
_fbx_ctrl->set_leds(_chan, ATR_ADDR_RX, !rx_on_trx, rx_on_trx, false);
_fbx_ctrl->set_leds(_chan, ATR_ADDR_TX, false, false, true );
_fbx_ctrl->set_leds(_chan, ATR_ADDR_XX, !rx_on_trx, rx_on_trx, true );
// clang-format on
}
void fbx_band_inversion_expert::resolve()
{
const bool band_inverted = _is_band_inverted(_trx, _frequency, _rfdc_rate);
_rpcc->enable_iq_swap(band_inverted, _get_trx_string(_trx), _chan);
}
void fbx_rfdc_freq_expert::resolve()
{
const double desired_rfdc_freq = [&]() -> double {
if (_rfdc_freq_desired.is_dirty() && !_frequency_desired.is_dirty()) {
return FBX_FREQ_RANGE.clip(_rfdc_freq_desired);
}
return FBX_FREQ_RANGE.clip(_frequency_desired);
}();
_rfdc_freq_coerced = _rpcc->rfdc_set_nco_freq(
_get_trx_string(_trx), _db_idx, _chan, desired_rfdc_freq);
_if_frequency_coerced = _rfdc_freq_coerced;
_coerced_frequency = _if_frequency_coerced;
}
void fbx_sync_expert::resolve()
{
// Local helper consts
// clang-format off
constexpr std::array<std::array<rfdc_control::rfdc_type, 2>, FBX_MAX_NUM_CHANS> all_ncos{{
{rfdc_control::rfdc_type::RX0, rfdc_control::rfdc_type::TX0},
{rfdc_control::rfdc_type::RX1, rfdc_control::rfdc_type::TX1},
{rfdc_control::rfdc_type::RX2, rfdc_control::rfdc_type::TX2},
{rfdc_control::rfdc_type::RX3, rfdc_control::rfdc_type::TX3}
}};
// clang-format on
// We might have fewer channels than the maximum, so limit our list
std::vector<std::array<rfdc_control::rfdc_type, 2>> ncos = {
all_ncos.begin(), all_ncos.begin() + _num_chans};
// Now do some timing checks
std::vector<bool> chan_needs_sync;
for (size_t i = 0; i < _num_chans; i++) {
chan_needs_sync.push_back(_fe_time.at(i) != uhd::time_spec_t::ASAP);
}
// If there's no command time, no need to synchronize anything
if (std::none_of(
chan_needs_sync.begin(), chan_needs_sync.end(), [](bool v) { return v; })) {
UHD_LOG_TRACE(get_name(), "No command time: Skipping phase sync.");
return;
}
// Create map of time specs with corresponding channels. Example:
// t0 = 0, t1 = 1, t2 = 1, t3 = 3 (with t* being times and numbers being channels)
// => chans_to_sync == {0: (0,), 1: (1,2), 3: (3,)}
std::map<uhd::time_spec_t, std::vector<size_t>> chans_to_sync;
for (size_t i = 0; i < _fe_time.size(); i++) {
chans_to_sync[_fe_time.at(i)].push_back(i);
}
std::set<rfdc_control::rfdc_type> ncos_to_sync;
std::set<rfdc_control::rfdc_type> gearboxes_to_sync;
for (auto times_chans : chans_to_sync) {
ncos_to_sync.clear();
gearboxes_to_sync.clear();
for (auto chan : times_chans.second) {
for (const auto& nco_idx : ncos[chan]) {
if (_nco_freqs.at(nco_idx).is_dirty()) {
ncos_to_sync.insert(nco_idx);
}
}
}
UHD_LOG_TRACE(get_name(),
"Syncing " << times_chans.second.size()
<< " channel(s): " << ncos_to_sync.size() << " NCO(s).");
// Sync the gearboxes:
// Gearboxes are special, because they only need to be synchronized once
// per session, assuming the command time has been set. Unfortunately we
// have no way here to know if the timekeeper time was updated, but it is
// well documented that in order to synchronize devices, one first has to
// make sure the timekeepers are running in sync (by calling
// set_time_next_pps() accordingly).
// The logic we use here is that we will always have to update the NCO when
// doing a synced tune, so we update all the gearboxes for the NCOs -- but
// only if they have not yet been synchronized.
if (!_adcs_synced || !_dacs_synced) {
for (size_t chan = 0; chan < _num_chans; chan++) {
// In ncos, element 0 in each array element is RX
const auto rx = ncos[chan].at(0);
if (ncos_to_sync.count(rx) && !_adcs_synced) {
gearboxes_to_sync.insert(rx);
// Technically, they're not synced yet but this saves us from
// having to look up which RFDCs map to RX again later
_adcs_synced = true;
}
// In ncos, element 0 in each array element is TX
const auto tx = ncos[chan].at(1);
if (ncos_to_sync.count(tx) && !_dacs_synced) {
gearboxes_to_sync.insert(tx);
// Technically, they're not synced yet but this saves us from
// having to look up which RFDCs map to TX again later
_dacs_synced = true;
}
}
if (!gearboxes_to_sync.empty()) {
UHD_LOG_TRACE(get_name(),
"Resetting "
<< gearboxes_to_sync.size() << " gearboxes. ADCs synced: "
<< (_adcs_synced ? "True" : "False")
<< "; DACs synced: " << (_dacs_synced ? "True" : "False"));
_rfdcc->reset_gearboxes(
std::vector<rfdc_control::rfdc_type>(
gearboxes_to_sync.cbegin(), gearboxes_to_sync.cend()),
times_chans.first);
}
}
if (!ncos_to_sync.empty()) {
_rfdcc->reset_ncos(std::vector<rfdc_control::rfdc_type>(
ncos_to_sync.cbegin(), ncos_to_sync.cend()),
times_chans.first);
}
}
}
}}} // namespace uhd::usrp::fbx

View File

@ -17,6 +17,7 @@ if(ENABLE_X400)
${CMAKE_CURRENT_SOURCE_DIR}/x400_gpio_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/x400_radio_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/x400_rfdc_control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/x400_internal_sync.cpp
)
if(ENABLE_DPDK)

View File

@ -0,0 +1,27 @@
//
// Copyright 2023 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "x400_internal_sync.hpp"
#include <uhd/utils/log.hpp>
namespace uhd { namespace features {
internal_sync::internal_sync(uhd::usrp::fbx::fbx_ctrl::sptr fbx_ctrl)
: _fbx_ctrl(fbx_ctrl)
{
}
void internal_sync::enable_sync_clk()
{
_fbx_ctrl->set_internal_sync_clk(true);
}
void internal_sync::disable_sync_clk()
{
_fbx_ctrl->set_internal_sync_clk(false);
}
}} // namespace uhd::features

View File

@ -0,0 +1,40 @@
//
// Copyright 2023 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/features/internal_sync_iface.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_ctrl.hpp>
namespace uhd { namespace features {
/*!
* This class should serve as starting point for using the internal sync. Currently it
* only supports switching on and off the 5 MHz Sync Clock. So it has to be extended once
* more functionality should be required.
*/
class internal_sync : public internal_sync_iface
{
public:
internal_sync(uhd::usrp::fbx::fbx_ctrl::sptr fbx_ctrl);
/*
* Enables the sync clock for this daughterboard.
*/
void enable_sync_clk() override;
/*
* Disables the sync clock for this daughterboard.
*/
void disable_sync_clk() override;
private:
uhd::usrp::fbx::fbx_ctrl::sptr _fbx_ctrl;
};
}} // namespace uhd::features

View File

@ -6,6 +6,7 @@
#include "x400_radio_control.hpp"
#include "x400_gpio_control.hpp"
#include "x400_internal_sync.hpp"
#include <uhd/rfnoc/registry.hpp>
#include <uhd/types/serial.hpp>
#include <uhd/utils/log.hpp>
@ -14,6 +15,7 @@
#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
#include <uhdlib/usrp/cores/spi_core_4000.hpp>
#include <uhdlib/usrp/dboard/debug_dboard.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_dboard.hpp>
#include <uhdlib/usrp/dboard/null_dboard.hpp>
#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
@ -54,10 +56,15 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller());
UHD_ASSERT_THROW(_mb_control)
RFNOC_LOG_TRACE("num timekeepers: " << _mb_control->get_num_timekeepers()
<< "radio slot: " << _radio_slot);
// select time keeper based on block id but restrict to number of time keepers
// available
const size_t tk_idx =
_mb_control->get_num_timekeepers() >= 2 ? get_block_id().get_block_count() : 0;
_x4xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>(
_mb_control->get_timekeeper(0));
_mb_control->get_timekeeper(tk_idx));
UHD_ASSERT_THROW(_x4xx_timekeeper);
_rpcc = _mb_control->dynamic_cast_rpc_as<uhd::usrp::x400_rpc_iface>();
if (!_rpcc) {
_rpcc = std::make_shared<uhd::usrp::x400_rpc>(_mb_control->get_rpc_client());
@ -144,6 +151,35 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
zbx_rpc_sptr,
_rfdcc,
get_tree());
} else if (std::stol(pid) == uhd::usrp::fbx::FBX_PID) {
auto fbx_rpc_sptr = _mb_control->dynamic_cast_rpc_as<uhd::usrp::fbx_rpc_iface>();
if (!fbx_rpc_sptr) {
fbx_rpc_sptr = std::make_shared<uhd::usrp::fbx_rpc>(
_mb_control->get_rpc_client(), _rpc_prefix);
}
_daughterboard = std::make_shared<uhd::usrp::fbx::fbx_dboard_impl>(
regs(),
regmap::PERIPH_BASE,
[this](const size_t instance) { return get_command_time(instance); },
get_block_id().get_block_count(),
_radio_slot,
get_num_input_ports(),
get_num_output_ports(),
_rpc_prefix,
get_unique_id(),
_rpcc,
fbx_rpc_sptr,
_rfdcc,
get_tree());
auto fbx_dboard =
std::dynamic_pointer_cast<uhd::usrp::fbx::fbx_dboard_impl>(_daughterboard);
if (fbx_dboard != NULL) {
RFNOC_LOG_DEBUG("Registering internal sync feature")
auto int_sync = std::make_shared<uhd::features::internal_sync>(
uhd::features::internal_sync(fbx_dboard->get_fbx_ctrl()));
register_feature(int_sync);
}
} else if (std::stol(pid) == uhd::rfnoc::DEBUG_DB_PID) {
_daughterboard = std::make_shared<debug_dboard_impl>();
} else if (std::stol(pid) == uhd::rfnoc::IF_TEST_DBOARD_PID) {
@ -284,7 +320,12 @@ void x400_radio_control_impl::_init_prop_tree()
void x400_radio_control_impl::_validate_master_clock_rate_args()
{
auto block_args = get_block_args();
// auto block_args = get_block_args(); // FIXME use the block args!
// Comparing master_clock_rate with get_rate() further down is utterly
// pointless, because get_rate() gets initialized from the DB RPC call
// that returns us the MCR. What we need to do is to parse the block args,
// check if there was a master_clock_rate key in there, and then compare
// those two. FIXME resolve this.
// Note: MCR gets set during the init() call (prior to this), which takes
// in arguments from the device args. So if the block args contain a
@ -304,8 +345,9 @@ void x400_radio_control_impl::_init_mpm()
{
// Init sensors
for (const auto& dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) {
// TODO: We should pull the number of channels from _daughterboard
for (size_t chan_idx = 0; chan_idx < uhd::usrp::zbx::ZBX_NUM_CHANS; chan_idx++) {
const size_t num_chans = (dir == RX_DIRECTION) ? get_num_input_ports()
: get_num_output_ports();
for (size_t chan_idx = 0; chan_idx < num_chans; chan_idx++) {
_init_mpm_sensors(dir, chan_idx);
}
}
@ -328,8 +370,10 @@ std::string x400_radio_control_impl::_get_trx_string(const direction_t dir) cons
void x400_radio_control_impl::_init_mpm_sensors(
const direction_t dir, const size_t chan_idx)
{
// TODO: We should pull the number of channels from _daughterboard
UHD_ASSERT_THROW(chan_idx < uhd::usrp::zbx::ZBX_NUM_CHANS);
UHD_ASSERT_THROW(dir == RX_DIRECTION || dir == TX_DIRECTION);
const size_t num_chans = (dir == RX_DIRECTION) ? get_num_input_ports()
: get_num_output_ports();
UHD_ASSERT_THROW(chan_idx < num_chans);
const std::string trx = _get_trx_string(dir);
const fs_path fe_path = fs_path("dboard")
/ (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends")

View File

@ -50,8 +50,12 @@ void rfdc_control::reset_gearboxes(
static const std::unordered_map<rfdc_type, const uint32_t> gb_map{
{rfdc_type::TX0, regmap::DAC_RESET_MSB},
{rfdc_type::TX1, regmap::DAC_RESET_MSB},
{rfdc_type::TX2, regmap::DAC_RESET_MSB},
{rfdc_type::TX3, regmap::DAC_RESET_MSB},
{rfdc_type::RX0, regmap::ADC_RESET_MSB},
{rfdc_type::RX1, regmap::ADC_RESET_MSB}};
{rfdc_type::RX1, regmap::ADC_RESET_MSB},
{rfdc_type::RX2, regmap::ADC_RESET_MSB},
{rfdc_type::RX3, regmap::ADC_RESET_MSB}};
uint32_t reset_word = 0;
for (const auto gb : gearboxes) {

View File

@ -388,10 +388,46 @@ IF(ENABLE_X400)
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_expert.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_dboard.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_dboard_init.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_expert.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_ctrl.cpp
${UHD_SOURCE_DIR}/lib/utils/compat_check.cpp
${UHD_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/gpio_atr_3000.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/spi_core_4000.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/spi_core_4000.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_internal_sync.cpp
$<TARGET_OBJECTS:uhd_rpclib>
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/rpclib/include
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/flatbuffers/include
)
# TODO: Fix dependency list/duplicates
UHD_ADD_RFNOC_BLOCK_TEST(
TARGET ferrum_radio_block_test.cpp
EXTRA_SOURCES
${UHD_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
${UHD_SOURCE_DIR}/lib/usrp/common/pwr_cal_mgr.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_gpio_control.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_radio_control.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/adc_self_calibration.cpp
${UHD_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp
${UHD_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp
${UHD_SOURCE_DIR}/lib/usrp/mpmd/mpmd_mb_controller.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_dboard.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_dboard_init.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_expert.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard_init.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_expert.cpp
${UHD_SOURCE_DIR}/lib/utils/compat_check.cpp
${UHD_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/gpio_atr_3000.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/spi_core_4000.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_internal_sync.cpp
$<TARGET_OBJECTS:uhd_rpclib>
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/rpclib/include
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/flatbuffers/include

View File

@ -0,0 +1,199 @@
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "../rfnoc_graph_mock_nodes.hpp"
#include "ferrum_radio_mock.hpp"
#include "x4xx_fbx_mpm_mock.hpp"
#include <uhd/rfnoc/mock_block.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/math.hpp>
#include <uhdlib/rfnoc/graph.hpp>
#include <boost/test/unit_test.hpp>
#include <cstddef>
#include <iostream>
#include <thread>
using namespace uhd;
using namespace uhd::rfnoc;
using namespace std::chrono_literals;
using namespace uhd::usrp::fbx;
using namespace uhd::experts;
BOOST_FIXTURE_TEST_CASE(fbx_api_freq_tx_test, x400_radio_fixture)
{
const std::string log = "FBX_API_TX_FREQUENCY_TEST";
const double ep = 10;
// TODO: consult step size
uhd::freq_range_t fbx_freq(FBX_MIN_FREQ, FBX_MAX_FREQ, 100e6);
for (const size_t chan : {0, 1, 2, 3}) {
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->RETURN)\n");
for (double iter = fbx_freq.start(); iter <= fbx_freq.stop();
iter += fbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
const double freq = test_radio->set_tx_frequency(iter, chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->GET)\n");
for (double iter = fbx_freq.start(); iter <= fbx_freq.stop();
iter += fbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
test_radio->set_tx_frequency(iter, chan);
const double freq = test_radio->get_tx_frequency(chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
}
}
BOOST_FIXTURE_TEST_CASE(fbx_api_freq_rx_test, x400_radio_fixture)
{
const std::string log = "FBX_API_RX_FREQUENCY_TEST";
const double ep = 10;
// TODO: consult step size
uhd::freq_range_t fbx_freq(FBX_MIN_FREQ, FBX_MAX_FREQ, 100e6);
for (const size_t chan : {0, 1, 2, 3}) {
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->RETURN)\n");
for (double iter = fbx_freq.start(); iter <= fbx_freq.stop();
iter += fbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
const double freq = test_radio->set_rx_frequency(iter, chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->GET\n");
for (double iter = fbx_freq.start(); iter <= fbx_freq.stop();
iter += fbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
test_radio->set_rx_frequency(iter, chan);
const double freq = test_radio->get_rx_frequency(chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
}
}
BOOST_FIXTURE_TEST_CASE(fbx_frequency_test, x400_radio_fixture)
{
auto tree = test_radio->get_tree();
const std::string log = "FBX_FREQUENCY_TEST";
const double ep = 10;
// TODO: consult step size
uhd::freq_range_t fbx_freq(FBX_MIN_FREQ, FBX_MAX_FREQ, 100e6);
for (const auto& fe_path : {
fs_path("dboard/tx_frontends/0"),
fs_path("dboard/tx_frontends/1"),
fs_path("dboard/rx_frontends/0"),
fs_path("dboard/rx_frontends/1"),
}) {
UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " FREQ CHANGE\n");
for (double iter = fbx_freq.start(); iter <= fbx_freq.stop();
iter += fbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
tree->access<double>(fe_path / "freq").set(iter);
const double ret_value = tree->access<double>(fe_path / "freq").get();
BOOST_REQUIRE(abs(iter - ret_value) < ep);
}
}
}
/*****************************************************************************************
* In X440, all channels per DB share a sync switch for internal or external sync. So when
* setting e.g. ch0 to sync_ext and then setting ch1 to sync_int, this influences ch0,
* too. At the same time this shouldn't have any influence on other channels' antennas.
* This will be tested here. BOOST_FIXTURE_TEST_CASE(fbx_sync_antenna_test,
* x400_radio_fixture)
*****************************************************************************************/
BOOST_FIXTURE_TEST_CASE(fbx_rx_antenna_test, x400_radio_fixture)
{
// First check that setting and getting the antennas works
for (const std::string& ant : test_radio->get_rx_antennas(0)) {
test_radio->set_rx_antenna(ant, 0);
BOOST_REQUIRE_EQUAL(ant, test_radio->get_rx_antenna(0));
}
// Then check the internal and external sync limitation. First reset everything to RX
// port
for (size_t channel = 0; channel < FBX_MAX_NUM_CHANS; channel++) {
test_radio->set_rx_antenna(ANTENNA_RX, channel);
}
// Then set a single channel to SYNC_INT and check the others, they must remain at RX
test_radio->set_rx_antenna(ANTENNA_SYNC_INT, 0);
BOOST_REQUIRE_EQUAL(ANTENNA_SYNC_INT, test_radio->get_rx_antenna(0));
for (size_t channel = 1; channel < FBX_MAX_NUM_CHANS; channel++) {
BOOST_REQUIRE_EQUAL(ANTENNA_RX, test_radio->get_rx_antenna(channel));
}
// Now we change ch1 to SYNC_EXT. This should change ch0, too, but not the others.
test_radio->set_rx_antenna(ANTENNA_SYNC_EXT, 1);
BOOST_REQUIRE_EQUAL(ANTENNA_SYNC_EXT, test_radio->get_rx_antenna(1));
BOOST_REQUIRE_EQUAL(ANTENNA_SYNC_EXT, test_radio->get_rx_antenna(0));
BOOST_REQUIRE_EQUAL(ANTENNA_RX, test_radio->get_rx_antenna(2));
BOOST_REQUIRE_EQUAL(ANTENNA_RX, test_radio->get_rx_antenna(3));
}
BOOST_FIXTURE_TEST_CASE(fbx_tx_antenna_test, x400_radio_fixture)
{
auto prev_ant = test_radio->get_tx_antenna(0);
for (const std::string& ant : test_radio->get_tx_antennas(0)) {
test_radio->set_tx_antenna(ant, 0);
BOOST_REQUIRE_EQUAL(ant, test_radio->get_tx_antenna(0));
// Check that setting a different channel won't change the original channel
test_radio->set_tx_antenna(prev_ant, 1);
BOOST_REQUIRE_EQUAL(ant, test_radio->get_tx_antenna(0));
prev_ant = ant;
}
}
/******************************************************************************
* RFNoC Graph Test
*
* This test case ensures that the Radio Block can be added to an RFNoC graph.
*****************************************************************************/
BOOST_FIXTURE_TEST_CASE(x400_radio_test_graph, x400_radio_fixture)
{
detail::graph_t graph{};
detail::graph_t::graph_edge_t edge_port_info0;
edge_port_info0.src_port = 0;
edge_port_info0.dst_port = 0;
// edge_port_info0.property_propagation_active = true;
edge_port_info0.edge = detail::graph_t::graph_edge_t::DYNAMIC;
detail::graph_t::graph_edge_t edge_port_info1;
edge_port_info1.src_port = 1;
edge_port_info1.dst_port = 1;
// edge_port_info1.property_propagation_active = true;
edge_port_info1.edge = detail::graph_t::graph_edge_t::DYNAMIC;
mock_radio_node_t mock_radio_block{0};
mock_terminator_t mock_sink_term(2, {}, "MOCK_SINK");
mock_terminator_t mock_source_term(2, {}, "MOCK_SOURCE");
UHD_LOG_INFO("TEST", "Priming mock block properties");
node_accessor.init_props(&mock_radio_block);
mock_source_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::OUTPUT_EDGE, 0});
mock_source_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::OUTPUT_EDGE, 1});
mock_sink_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::INPUT_EDGE, 0});
mock_sink_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::INPUT_EDGE, 1});
UHD_LOG_INFO("TEST", "Creating graph...");
graph.connect(&mock_source_term, test_radio.get(), edge_port_info0);
graph.connect(&mock_source_term, test_radio.get(), edge_port_info1);
graph.connect(test_radio.get(), &mock_sink_term, edge_port_info0);
graph.connect(test_radio.get(), &mock_sink_term, edge_port_info1);
UHD_LOG_INFO("TEST", "Committing graph...");
graph.commit();
UHD_LOG_INFO("TEST", "Commit complete.");
}

View File

@ -0,0 +1,167 @@
//
// Copyright 2022 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include "../../lib/usrp/x400/x400_radio_control.hpp"
#include "x4xx_fbx_mpm_mock.hpp"
#include <uhd/rfnoc/mock_block.hpp>
#include <uhd/utils/log.hpp>
#include <uhdlib/rfnoc/node_accessor.hpp>
#include <iostream>
using namespace uhd;
using namespace uhd::rfnoc;
using namespace std::chrono_literals;
using namespace uhd::usrp::fbx;
using namespace uhd::experts;
// Redeclare this here, since it's only defined outside of UHD_API
noc_block_base::make_args_t::~make_args_t() = default;
namespace {
/* This class extends mock_reg_iface_t by adding a constructor that initializes
* some of the read memory to contain the memory size for the radio block.
*/
class ferrum_radio_mock_reg_iface_t : public mock_reg_iface_t
{
// Start address of RFDC control register space
static constexpr uint32_t rfdc_offset =
radio_control_impl::regmap::PERIPH_BASE + 0x8000;
static constexpr uint32_t spi_offset =
radio_control_impl::regmap::PERIPH_BASE
+ 0xC000 /*DIO Window*/ + 0x2000 /*DIO Regmap*/;
static constexpr uint32_t gpio_offset =
radio_control_impl::regmap::PERIPH_BASE
+ 0xC000 /*DIO Window*/ + 0x1000 /*GPIO Regmap*/;
public:
ferrum_radio_mock_reg_iface_t(size_t num_channels)
{
for (size_t chan = 0; chan < num_channels; chan++) {
const uint32_t reg_compat =
radio_control_impl::regmap::REG_COMPAT_NUM
+ chan * radio_control_impl::regmap::REG_CHAN_OFFSET;
read_memory[reg_compat] = (radio_control_impl::MINOR_COMPAT
| (radio_control_impl::MAJOR_COMPAT << 16));
}
read_memory[radio_control_impl::regmap::REG_RADIO_WIDTH] =
(32 /* bits per sample */ << 16) | 1 /* sample per clock */;
// Ensure that the SPI Status is always SPI_READY
read_memory[spi_offset + 0x18] |= 1 << 24;
// Setup the GPIO addresses
read_memory[gpio_offset + 0x4] = 0;
}
void _poke_cb(uint32_t addr, uint32_t data, uhd::time_spec_t, bool) override
{
// Are we on the peripheral?
if (addr >= radio_control_impl::regmap::PERIPH_BASE) {
// handle all the periphs stuff that is not CPLD here
} else {
return;
}
// This is the register space that used to be CPLD in ZBX but we shortened it here
// to just write R/W registers.
if (addr >= radio_control_impl::regmap::PERIPH_BASE && addr < rfdc_offset) {
read_memory[addr] = data;
} else {
return;
}
// Are we poking the RFDC controls?
if (addr >= rfdc_offset) {
_poke_rfdc_cb(addr, data);
return;
}
}
void _poke_rfdc_cb(const uint32_t addr, const uint32_t data)
{
read_memory[addr] |= data;
}
}; // class ferrum_radio_mock_reg_iface_t
/*
* x400_radio_fixture is a class which is instantiated before each test
* case is run. It sets up the block container, mock register interface,
* and x400_radio_control object, all of which are accessible to the test
* case. The instance of the object is destroyed at the end of each test
* case.
*/
constexpr size_t DEFAULT_MTU = 8000;
//! Helper class to make sure we get the most logging. The logging level can be
// overridden using a special test-specific environment variable,
// `UHD_UNITTEST_LOG_LEVEL`.
// TODO: Put into own header
struct uhd_log_enabler
{
uhd_log_enabler(uhd::log::severity_level level)
{
const char* env_p = std::getenv("UHD_UNITTEST_LOG_LEVEL");
if (env_p) {
auto parsed_level = uhd::log::parse_log_level_from_string(env_p);
if (parsed_level) {
level = *parsed_level;
} else {
std::cout << "Unable to parse UHD_UNITTEST_LOG_LEVEL " << env_p
<< std::endl;
}
}
std::cout << "Setting log level to " << level << "..." << std::endl;
uhd::log::set_log_level(level);
uhd::log::set_console_level(level);
std::this_thread::sleep_for(10ms);
}
};
struct x400_radio_fixture
{
x400_radio_fixture()
: ule(uhd::log::trace) // Note: When debugging this test, either set
// this to a lower level, or create a
// uhd_log_enabler in the test-under-test
, num_channels(uhd::usrp::fbx::FBX_MAX_NUM_CHANS)
, num_input_ports(num_channels)
, num_output_ports(num_channels)
, reg_iface(std::make_shared<ferrum_radio_mock_reg_iface_t>(num_channels))
, rpcs(std::make_shared<uhd::test::ferrum_mock_rpc_server>(device_info))
, mbc(std::make_shared<mpmd_mb_controller>(rpcs, device_info))
, block_container(get_mock_block(RADIO_BLOCK,
num_channels,
num_channels,
device_info,
DEFAULT_MTU,
X400,
reg_iface,
mbc))
, test_radio(block_container.get_block<x400_radio_control_impl>())
{
node_accessor.init_props(test_radio.get());
}
~x400_radio_fixture() {}
// Must remain the first member so we make sure the log level is high
uhd_log_enabler ule;
const size_t num_channels;
const size_t num_input_ports;
const size_t num_output_ports;
uhd::device_addr_t device_info = uhd::device_addr_t("master_clock_rate=4e9");
std::shared_ptr<ferrum_radio_mock_reg_iface_t> reg_iface;
std::shared_ptr<uhd::test::ferrum_mock_rpc_server> rpcs;
mpmd_mb_controller::sptr mbc;
mock_block_container block_container;
std::shared_ptr<x400_radio_control_impl> test_radio;
node_accessor_t node_accessor{};
};
} // namespace

View File

@ -0,0 +1,395 @@
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/types/device_addr.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_constants.hpp>
#include <uhdlib/usrp/dboard/fbx/fbx_dboard.hpp>
#include <stdlib.h>
#include <boost/test/unit_test.hpp>
#include <map>
#include <memory>
#include <string>
#include <vector>
using namespace uhd::usrp;
namespace uhd { namespace test {
namespace {
constexpr double DEFAULT_MCR = 4e9;
}
//! Mock MPM server for X440/FBX
//
// This is a mock server that mimicks an X410 with a FBX daughterboard.
class ferrum_mock_rpc_server : public x400_rpc_iface,
public mpmd_rpc_iface,
public dboard_base_rpc_iface,
public fbx_rpc_iface,
public dio_rpc_iface
{
public:
ferrum_mock_rpc_server(const uhd::device_addr_t& device_info)
: _device_info(device_info)
{
}
uhd::rpc_client::sptr get_raw_rpc_client() override
{
// This function is unimplemented! Perhaps you need to:
// - Add it to the appropriate RPC interface,
// - Retrofit all calls to your desired function to directly use the RPC
// interface, and
// - Add a mock implementation here.
UHD_THROW_INVALID_CODE_PATH();
}
/**************************************************************************
* RPC Call Mockups
*
* The following public methods are replacements of that normally happens in
* the Python-based MPM. Some notes on writing mocks:
* - These are mocks, so don't go fancy and only let them do the bare
* minimum required for tests
* - Remember to add them to _init_rpc() further down
*************************************************************************/
// TODO: Most probably 2, but not yet clear
size_t get_num_timekeepers() override
{
return 1;
}
std::vector<std::string> get_mb_sensors() override
{
return {"ref_locked"};
}
std::vector<std::string> get_gpio_banks() override
{
return {"GPIO0", "GPIO1"};
}
// TODO: ?
bool supports_feature(const std::string& feature) override
{
return feature == "ref_clk_calibration";
}
std::vector<std::map<std::string, std::string>> get_dboard_info() override
{
return {{
// One entry per dboard info
{"pid", std::to_string(uhd::usrp::fbx::FBX_PID)}
// End of entries
}};
}
void set_tick_period(const size_t, const uint64_t) override
{
// nop
}
// TODO: ?
double get_master_clock_rate() override
{
return _device_info.cast<double>("master_clock_rate", DEFAULT_MCR);
}
std::vector<std::string> get_sensors(const std::string&) override
{
return {};
}
std::map<std::string, std::string> get_sensor(
const std::string&, const std::string&, size_t) override
{
return {};
}
void set_cal_frozen(bool, size_t, size_t) override
{
// nop
}
std::vector<int> get_cal_frozen(size_t, size_t) override
{
return {};
}
void set_calibration_mode(size_t, size_t, std::string) override
{
// nop
}
std::map<std::string, std::vector<uint8_t>> get_db_eeprom(const size_t) override
{
return {{
// One line per entry
{"pid", s2u8("mock")}, // Used to specify power cal API
{"serial", s2u8("BADCODE")}
// End of entries
}};
}
double rfdc_set_nco_freq(const std::string& trx,
const size_t /*db_id*/,
const size_t chan,
const double freq) override
{
BOOST_REQUIRE(trx == "rx" || trx == "tx");
BOOST_REQUIRE(chan < uhd::usrp::fbx::FBX_MAX_NUM_CHANS);
nco_freq[trx][chan] = freq;
return freq;
}
double rfdc_get_nco_freq(
const std::string& trx, const size_t /*db_id*/, const size_t chan) override
{
BOOST_REQUIRE(trx == "rx" || trx == "tx");
BOOST_REQUIRE(chan < uhd::usrp::fbx::FBX_MAX_NUM_CHANS);
// On construction, the expert will ask for the current nco frequency, and our
// nco_freq map won't have a value yet.
if (nco_freq.find(trx) == nco_freq.end()
|| nco_freq.at(trx).find(chan) == nco_freq.at(trx).end()) {
return 0;
}
return nco_freq.at(trx).at(chan);
}
double get_dboard_sample_rate() override
{
const double mcr = _device_info.cast<double>("master_clock_rate", DEFAULT_MCR);
static const std::map<double, double> spll_map{
// One line per entry
{122.88e6, 2.94912e9},
{122.88e6 * 4, 2.94912e9},
// TODO: These entries need to be updated for more sample rates.
{4e9, 4.0e9}
// End of entries
};
return spll_map.at(mcr);
}
void enable_iq_swap(const bool, const std::string&, const size_t) override
{
// nop
}
std::vector<std::string> get_gpio_srcs(const std::string& /*bank*/) override
{
return {"DB0_SPI"};
}
uint64_t get_timekeeper_time(size_t /*timekeeper_idx*/, bool /*last_pps*/) override
{
return 0;
}
void set_timekeeper_time(
size_t /*timekeeper_idx*/, uint64_t /*ticks*/, bool /*last_pps*/) override
{
// nop
}
std::string get_time_source() override
{
return "";
}
std::vector<std::string> get_time_sources() override
{
return {};
}
std::string get_clock_source() override
{
return "";
}
std::vector<std::string> get_clock_sources() override
{
return {};
}
std::map<std::string, std::string> get_sync_source() override
{
return {};
}
std::vector<std::map<std::string, std::string>> get_sync_sources() override
{
return {};
}
void set_clock_source_out(bool /*enb*/) override
{
// nop
}
void set_trigger_io(const std::string& /*direction*/) override
{
// nop
}
std::map<std::string, std::string> get_mb_eeprom() override
{
return {};
}
std::vector<std::string> get_gpio_src(const std::string& /*bank*/) override
{
return {};
}
void set_gpio_src(
const std::string& /*bank*/, const std::vector<std::string>& /*src*/) override
{
// nop
}
void set_ref_clk_tuning_word(uint32_t /*tuning_word*/) override
{
// nop
}
uint32_t get_ref_clk_tuning_word() override
{
return 0;
}
void store_ref_clk_tuning_word(uint32_t /*tuning_word*/) override
{
// nop
}
sensor_value_t::sensor_map_t get_mb_sensor(const std::string& /*sensor*/) override
{
return {};
}
void set_time_source(const std::string& /*source*/) override
{
// nop
}
void set_clock_source(const std::string& /*source*/) override
{
// nop
}
void set_sync_source(const std::map<std::string, std::string>& /*source*/) override
{
// nop
}
bool get_threshold_status(
size_t /*db_number*/, size_t /*chan*/, size_t /*threshold_block*/) override
{
return false;
}
void set_dac_mux_enable(
size_t /*db_number*/, size_t /*chan*/, int /*enable*/) override
{
// nop
}
void set_dac_mux_data(size_t /*i*/, size_t /*q*/) override
{
// nop
}
double get_spll_freq() override
{
return 0.0;
}
void setup_threshold(size_t /*db_number*/,
size_t /*chan*/,
size_t /*threshold_block*/,
const std::string& /*mode*/,
size_t /*delay*/,
size_t /*under*/,
size_t /*over*/) override
{
// nop
}
std::vector<std::string> dio_get_supported_voltage_levels(const std::string&) override
{
return {"OFF", "1V8", "2V5", "3V3"};
}
void dio_set_voltage_level(const std::string&, const std::string&) override
{
// nop
}
std::string dio_get_voltage_level(const std::string&) override
{
return "3V3";
}
void dio_set_port_mapping(const std::string&) override
{
// nop
}
bool is_db_gpio_ifc_present(const size_t) override
{
return true;
}
void dio_set_pin_directions(const std::string&, uint32_t) override
{
// nop
}
void dio_set_external_power(const std::string&, bool) override
{
// nop
}
std::string dio_get_external_power_state(const std::string&) override
{
return "OFF";
}
std::map<std::string, std::string> synchronize(
const std::map<std::string, std::string>&, bool) override
{
return {};
}
std::map<std::string, std::string> aggregate_sync_data(
const std::list<std::map<std::string, std::string>>& collated_sync_data) override
{
return collated_sync_data.front();
}
///////////////////////////////////////////////////////////////////////////
// Public attributes for easy inspection
//
// Use this in the mock functions to cache values, or expose values that get
// tested later
// TODO: We'll need other NCO settings
std::map<std::string, std::map<size_t, double>> nco_freq;
//
///////////////////////////////////////////////////////////////////////////
private:
uhd::device_addr_t _device_info;
static std::vector<uint8_t> s2u8(const std::string& s)
{
return std::vector<uint8_t>(s.begin(), s.end());
}
};
}} // namespace uhd::test

View File

@ -224,7 +224,8 @@ struct uhd_log_enabler
if (parsed_level) {
level = *parsed_level;
} else {
std::cout << "Unable to parse UHD_UNITTEST_LOG_LEVEL " << env_p << std::endl;
std::cout << "Unable to parse UHD_UNITTEST_LOG_LEVEL " << env_p
<< std::endl;
}
}