mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
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:
parent
371c4c139c
commit
f5bb3e0275
22
host/docs/fbx.dox
Normal file
22
host/docs/fbx.dox
Normal 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:
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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;
|
||||
|
||||
44
host/include/uhd/features/internal_sync_iface.hpp
Normal file
44
host/include/uhd/features/internal_sync_iface.hpp
Normal 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
|
||||
388
host/lib/ic_reg_maps/gen_fbx_regs.py
Executable file
388
host/lib/ic_reg_maps/gen_fbx_regs.py
Executable 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
|
||||
)
|
||||
@ -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 %>\
|
||||
|
||||
@ -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
|
||||
|
||||
82
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_constants.hpp
Normal file
82
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_constants.hpp
Normal 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
|
||||
163
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_ctrl.hpp
Normal file
163
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_ctrl.hpp
Normal 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
|
||||
438
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_dboard.hpp
Normal file
438
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_dboard.hpp
Normal 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
|
||||
363
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_expert.hpp
Normal file
363
host/lib/include/uhdlib/usrp/dboard/fbx/fbx_expert.hpp
Normal 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
|
||||
@ -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! "
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
16
host/lib/usrp/dboard/fbx/CMakeLists.txt
Normal file
16
host/lib/usrp/dboard/fbx/CMakeLists.txt
Normal 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)
|
||||
|
||||
303
host/lib/usrp/dboard/fbx/fbx_ctrl.cpp
Normal file
303
host/lib/usrp/dboard/fbx/fbx_ctrl.cpp
Normal 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
|
||||
294
host/lib/usrp/dboard/fbx/fbx_dboard.cpp
Normal file
294
host/lib/usrp/dboard/fbx/fbx_dboard.cpp
Normal 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
|
||||
265
host/lib/usrp/dboard/fbx/fbx_dboard_init.cpp
Normal file
265
host/lib/usrp/dboard/fbx/fbx_dboard_init.cpp
Normal 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
|
||||
232
host/lib/usrp/dboard/fbx/fbx_expert.cpp
Normal file
232
host/lib/usrp/dboard/fbx/fbx_expert.cpp
Normal 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
|
||||
@ -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)
|
||||
|
||||
27
host/lib/usrp/x400/x400_internal_sync.cpp
Normal file
27
host/lib/usrp/x400/x400_internal_sync.cpp
Normal 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
|
||||
40
host/lib/usrp/x400/x400_internal_sync.hpp
Normal file
40
host/lib/usrp/x400/x400_internal_sync.hpp
Normal 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
|
||||
@ -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")
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
199
host/tests/rfnoc_block_tests/ferrum_radio_block_test.cpp
Normal file
199
host/tests/rfnoc_block_tests/ferrum_radio_block_test.cpp
Normal 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.");
|
||||
}
|
||||
167
host/tests/rfnoc_block_tests/ferrum_radio_mock.hpp
Normal file
167
host/tests/rfnoc_block_tests/ferrum_radio_mock.hpp
Normal 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
|
||||
395
host/tests/rfnoc_block_tests/x4xx_fbx_mpm_mock.hpp
Normal file
395
host/tests/rfnoc_block_tests/x4xx_fbx_mpm_mock.hpp
Normal 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
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user