aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authorLars Amsel <lars.amsel@ni.com>2021-06-04 08:27:50 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 12:01:53 -0500
commit2a575bf9b5a4942f60e979161764b9e942699e1e (patch)
tree2f0535625c30025559ebd7494a4b9e7122550a73 /host
parente17916220cc955fa219ae37f607626ba88c4afe3 (diff)
downloaduhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'host')
-rw-r--r--host/docs/dboards.dox13
-rw-r--r--host/docs/devices.dox2
-rw-r--r--host/docs/res/ZBX_simplified_blockdiagram.svg14492
-rw-r--r--host/docs/res/x410.pngbin0 -> 905376 bytes
-rw-r--r--host/docs/res/x410_back_panel.pngbin0 -> 735111 bytes
-rw-r--r--host/docs/res/x410_front_panel.pngbin0 -> 437613 bytes
-rw-r--r--host/docs/res/x4xx_block_diagram.svg962
-rw-r--r--host/docs/res/x4xx_rearpanel_status_leds.pngbin0 -> 28704 bytes
-rw-r--r--host/docs/usrp_x4xx.dox953
-rw-r--r--host/docs/zbx.dox469
-rw-r--r--host/include/uhd/cal/CMakeLists.txt25
-rw-r--r--host/include/uhd/cal/dsa_cal.fbs36
-rw-r--r--host/include/uhd/cal/dsa_cal.hpp130
-rw-r--r--host/include/uhd/cal/dsa_cal_generated.h274
-rw-r--r--host/include/uhd/features/CMakeLists.txt1
-rw-r--r--host/include/uhd/features/adc_self_calibration_iface.hpp45
-rw-r--r--host/include/uhd/features/discoverable_feature.hpp3
-rw-r--r--host/include/uhd/features/ref_clk_calibration_iface.hpp44
-rw-r--r--host/include/uhd/rfnoc/blocks/radio.yml8
-rw-r--r--host/include/uhd/rfnoc/core/io_signatures.yml48
-rw-r--r--host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json17
-rw-r--r--host/include/uhd/rfnoc/core/x410_bsp.yml57
-rw-r--r--host/include/uhd/rfnoc/defaults.hpp2
-rw-r--r--host/include/uhd/rfnoc/mb_controller.hpp4
-rw-r--r--host/include/uhd/rfnoc/rf_control/core_iface.hpp1
-rw-r--r--host/lib/CMakeLists.txt1
-rw-r--r--host/lib/cal/CMakeLists.txt1
-rw-r--r--host/lib/cal/cal_python.hpp44
-rw-r--r--host/lib/cal/dsa_cal.cpp253
-rw-r--r--host/lib/ic_reg_maps/CMakeLists.txt10
-rwxr-xr-xhost/lib/ic_reg_maps/common.py6
-rwxr-xr-xhost/lib/ic_reg_maps/gen_lmk04816_regs.py20
-rwxr-xr-xhost/lib/ic_reg_maps/gen_lmx2572_regs.py663
-rwxr-xr-xhost/lib/ic_reg_maps/gen_zbx_cpld_regs.py450
-rw-r--r--host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp40
-rw-r--r--host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp8
-rw-r--r--host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp21
-rw-r--r--host/lib/include/uhdlib/usrp/common/lmx2572.hpp102
-rw-r--r--host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp42
-rw-r--r--host/lib/include/uhdlib/usrp/common/rpc.py51
-rw-r--r--host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp85
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp592
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp361
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp42
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp269
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp487
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp416
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp837
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp86
-rw-r--r--host/lib/rc/CMakeLists.txt12
-rw-r--r--host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.calbin0 -> 4504 bytes
-rw-r--r--host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.calbin0 -> 4504 bytes
-rw-r--r--host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.calbin0 -> 4504 bytes
-rw-r--r--host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.calbin0 -> 4504 bytes
-rw-r--r--host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.calbin0 -> 3832 bytes
-rw-r--r--host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.calbin0 -> 3832 bytes
-rw-r--r--host/lib/rc/cal/zbx_dsa_rx.calbin0 -> 32176 bytes
-rw-r--r--host/lib/rc/cal/zbx_dsa_rx.json1081
-rw-r--r--host/lib/rc/cal/zbx_dsa_tx.calbin0 -> 35256 bytes
-rw-r--r--host/lib/rc/cal/zbx_dsa_tx.json1349
-rw-r--r--host/lib/rfnoc/rf_control/gain_profile.cpp19
-rw-r--r--host/lib/usrp/CMakeLists.txt1
-rw-r--r--host/lib/usrp/common/CMakeLists.txt1
-rw-r--r--host/lib/usrp/common/lmx2572.cpp1030
-rw-r--r--host/lib/usrp/dboard/CMakeLists.txt7
-rw-r--r--host/lib/usrp/dboard/zbx/CMakeLists.txt17
-rw-r--r--host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp931
-rw-r--r--host/lib/usrp/dboard/zbx/zbx_dboard.cpp758
-rw-r--r--host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp685
-rw-r--r--host/lib/usrp/dboard/zbx/zbx_expert.cpp672
-rw-r--r--host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp162
-rw-r--r--host/lib/usrp/mpmd/mpmd_devices.hpp2
-rw-r--r--host/lib/usrp/mpmd/mpmd_image_loader.cpp17
-rw-r--r--host/lib/usrp/mpmd/mpmd_mb_controller.cpp64
-rw-r--r--host/lib/usrp/x300/x300_mb_controller.hpp4
-rw-r--r--host/lib/usrp/x400/CMakeLists.txt20
-rw-r--r--host/lib/usrp/x400/adc_self_calibration.cpp196
-rw-r--r--host/lib/usrp/x400/adc_self_calibration.hpp46
-rw-r--r--host/lib/usrp/x400/x400_radio_control.cpp752
-rw-r--r--host/lib/usrp/x400/x400_radio_control.hpp198
-rw-r--r--host/lib/usrp/x400/x400_rfdc_control.cpp73
-rwxr-xr-xhost/python/uhd/imgbuilder/image_builder.py13
-rw-r--r--host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako26
-rw-r--r--host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako20
-rw-r--r--host/python/uhd/usrp/cal/libtypes.py2
-rw-r--r--host/python/uhd/utils/mpmtools.py12
-rw-r--r--host/tests/CMakeLists.txt58
-rw-r--r--host/tests/cal_data_dsa_test.cpp91
-rw-r--r--host/tests/devtest/CMakeLists.txt3
-rw-r--r--host/tests/devtest/devtest_x4x0.py75
-rwxr-xr-xhost/tests/devtest/multi_usrp_test.py19
-rw-r--r--host/tests/lmx2572_test.cpp150
-rw-r--r--host/tests/mb_controller_test.cpp4
-rw-r--r--host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp1222
-rw-r--r--host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp337
-rw-r--r--host/tests/streaming_performance/run_X4xx_max_rate_tests.py193
-rw-r--r--host/tests/x400_rfdc_control_test.cpp52
-rw-r--r--host/tests/zbx_cpld_test.cpp135
-rw-r--r--host/utils/CMakeLists.txt1
-rwxr-xr-xhost/utils/rfnoc_image_builder.py4
-rw-r--r--host/utils/uhd_adc_self_cal.cpp76
101 files changed, 32996 insertions, 67 deletions
diff --git a/host/docs/dboards.dox b/host/docs/dboards.dox
index 7dc398f46..8898cfd37 100644
--- a/host/docs/dboards.dox
+++ b/host/docs/dboards.dox
@@ -467,6 +467,19 @@ Please refer to \ref e31x_dboards.
Please refer to \ref n3xx_mg.
+\subsection dboards_zbx ZBX XCVR board
+
+Features:
+- Dual channel transceivers
+- TX/RX and RX2 antenna ports per channel
+- Frequency Range: 1 MHz to 8 GHz
+- Relative Gain Range: 0 - 60 dB (RX gain range reduced below 500 MHz)
+
+The ZBX daughterboard only works with the X410 motherboard.
+
+More information:
+\li \subpage page_zbx
+
\subsection dboards_clock_rate Daughterboard reference clock
The USRP motherboard provides a reference clock to the daughterboards, which
diff --git a/host/docs/devices.dox b/host/docs/devices.dox
index dbe860382..c7609400d 100644
--- a/host/docs/devices.dox
+++ b/host/docs/devices.dox
@@ -32,6 +32,7 @@
## USRP X-Series Devices
\li \subpage page_usrp_x3x0
+\li \subpage page_usrp_x4xx
## USRP Legacy Series
@@ -49,6 +50,7 @@ unless stated otherwise, they will still work with this version of UHD.
\li \subpage page_dboards
\li \subpage page_twinrx
+\li \subpage page_zbx
## OctoClock
diff --git a/host/docs/res/ZBX_simplified_blockdiagram.svg b/host/docs/res/ZBX_simplified_blockdiagram.svg
new file mode 100644
index 000000000..53fa51649
--- /dev/null
+++ b/host/docs/res/ZBX_simplified_blockdiagram.svg
@@ -0,0 +1,14492 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generated by Microsoft Visio, SVG Export X410_RF_BlockDiagram.svg RevB_VnV_Update -->
+
+<svg
+ xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16.250069in"
+ height="8.5370684in"
+ viewBox="0 0 1170.0049 614.66892"
+ xml:space="preserve"
+ class="st43"
+ version="1.1"
+ id="svg18142"
+ sodipodi:docname="ZBX_simplified_blockdiagram.svg"
+ inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+ style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"><metadata
+ id="metadata18146"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3840"
+ inkscape:window-height="2063"
+ id="namedview18144"
+ showgrid="false"
+ inkscape:zoom="0.7071068"
+ inkscape:cx="-522.85483"
+ inkscape:cy="723.81531"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg18142"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <v:documentProperties
+ v:langID="1033"
+ v:viewMarkup="false">
+ <v:userDefs>
+ <v:ud
+ v:nameU="msvNoAutoConnect"
+ v:val="VT0(1):26" />
+ </v:userDefs>
+ </v:documentProperties>
+
+ <style
+ type="text/css"
+ id="style9098">
+ <![CDATA[
+ .st1 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st2 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st3 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st4 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st5 {fill:#000000;font-family:Calibri;font-size:0.666664em}
+ .st6 {font-size:1em}
+ .st7 {fill:none;stroke:#000000;stroke-dasharray:3.5,2.5,0,2.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st8 {fill:#000000;font-family:Calibri;font-size:0.75em;font-style:italic;font-weight:bold}
+ .st9 {font-size:1em;text-decoration:underline}
+ .st10 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72}
+ .st11 {fill:#000000;font-family:Arial;font-size:0.666664em}
+ .st12 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.25}
+ .st13 {marker-end:url(#mrkr13-157);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st14 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.25773195876289}
+ .st15 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st16 {fill:#ffffff}
+ .st17 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st18 {fill:#000000;font-family:Calibri;font-size:0.499992em}
+ .st19 {marker-end:url(#mrkr5-252);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st20 {fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st21 {fill:#000000;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st22 {fill:#000000;font-family:Calibri;font-size:0.666664em;font-style:italic}
+ .st23 {fill:#000000;font-family:Arial;font-size:0.499992em}
+ .st24 {fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.25}
+ .st25 {fill:#000000;font-family:Arial;font-size:0.499992em;font-style:italic}
+ .st26 {font-family:Calibri;font-size:1em}
+ .st27 {fill:none;stroke:#000000;stroke-dasharray:0.5,1;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st28 {fill:#000000;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+ .st29 {fill:none;stroke:#000000;stroke-dasharray:0,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st30 {fill:#000000;font-family:Arial;font-size:1.33333em;font-style:italic;font-weight:bold}
+ .st31 {fill:#000000;font-family:Arial;font-size:0.666664em;font-style:italic;font-weight:bold}
+ .st32 {fill:#000000;font-family:Arial;font-size:1.33333em}
+ .st33 {marker-end:url(#mrkr5-2463);marker-start:url(#mrkr5-2461);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st34 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.16556291390728}
+ .st35 {marker-end:url(#mrkr5-2463);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st36 {baseline-shift:32.4939%;font-size:0.649878em}
+ .st37 {stroke:#000000;stroke-dasharray:1.5,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st38 {marker-end:url(#mrkr5-2728);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.25}
+ .st39 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.14792899408284}
+ .st40 {stroke:#000000;stroke-dasharray:3.5,2.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st41 {marker-end:url(#mrkr5-252);marker-start:url(#mrkr5-3283);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
+ .st42 {fill:#000000;font-family:Arial;font-size:0.333344em;font-style:italic}
+ .st43 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+ ]]>
+ </style>
+
+ <defs
+ id="Markers">
+ <g
+ id="lend13">
+ <path
+ d="M 3,1 0,0 3,-1 v 2"
+ style="stroke:none"
+ id="path9100"
+ inkscape:connector-curvature="0" />
+ </g>
+ <marker
+ id="mrkr13-157"
+ class="st14"
+ v:arrowType="13"
+ v:arrowSize="0"
+ v:setback="11.64"
+ refX="-11.64"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1">
+ <use
+ xlink:href="#lend13"
+ transform="scale(-3.88)"
+ id="use9103"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </marker>
+ <g
+ id="lend5">
+ <path
+ d="M 2,1 0,0 1.98117,-0.993387 C 1.67173,-0.364515 1.67301,0.372641 1.98465,1.00043"
+ style="stroke:none"
+ id="path9106"
+ inkscape:connector-curvature="0" />
+ </g>
+ <marker
+ id="mrkr5-252"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1">
+ <use
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use9109"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </marker>
+ <marker
+ id="mrkr5-2461"
+ class="st34"
+ v:arrowType="5"
+ v:arrowSize="2"
+ v:setback="9.85"
+ refX="9.8500004"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.16556291;stroke-opacity:1">
+ <use
+ xlink:href="#lend5"
+ transform="scale(6.04)"
+ id="use9112"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </marker>
+ <marker
+ id="mrkr5-2463"
+ class="st34"
+ v:arrowType="5"
+ v:arrowSize="2"
+ v:setback="10.57"
+ refX="-10.57"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.16556291;stroke-opacity:1">
+ <use
+ xlink:href="#lend5"
+ transform="scale(-6.04)"
+ id="use9115"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </marker>
+ <marker
+ id="mrkr5-2728"
+ class="st39"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="10.39"
+ refX="-10.39"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.147929;stroke-opacity:1">
+ <use
+ xlink:href="#lend5"
+ transform="scale(-6.76)"
+ id="use9118"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </marker>
+ <marker
+ id="mrkr5-3283"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.07"
+ refX="6.0700002"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1">
+ <use
+ xlink:href="#lend5"
+ transform="scale(3.88)"
+ id="use9121"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </marker>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="mrkr5-252-3"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use36817" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="mrkr5-252-6"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use9109-4" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="marker56505"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use56503" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="marker56509"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use56507" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="mrkr5-252-9"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use9109-6" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="marker57020"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use57018" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="marker57024"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use57022" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="mrkr5-252-9-0"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use9109-6-5" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="marker57300"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use57298" /></marker><marker
+ style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"
+ id="marker57304"
+ class="st14"
+ v:arrowType="5"
+ v:arrowSize="0"
+ v:setback="6.79"
+ refX="-6.79"
+ orient="auto"
+ markerUnits="strokeWidth"
+ overflow="visible"><use
+ height="100%"
+ width="100%"
+ y="0"
+ x="0"
+ xlink:href="#lend5"
+ transform="scale(-3.88)"
+ id="use57302" /></marker></defs>
+ <path
+ d="m 479.25,244.04002 h 79.88 v -38.65"
+ class="st1"
+ id="path9129"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 479.25,206.45002 h 72 v -12.03"
+ class="st1"
+ id="path9134"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 757.57191,211.38094 -0.13676,62.13985"
+ class="st1"
+ id="path9139"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 944.52,184.44202 v -20.61 h 10.36"
+ class="st1"
+ id="path9144"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 944.544,178.70802 v 17.46 h -19"
+ class="st1"
+ id="path9149"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 824.676,196.64402 h 93"
+ class="st1"
+ id="path9154"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 955.922,170.03302 v 69.72 h -32.06"
+ class="st1"
+ id="path9159"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 823.17,205.64402 v 33.96 h 95.63"
+ class="st1"
+ id="path9164"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 14.4818,158.78402 h 29.88 l 16.19,-15.06 -16.19,-12.33 h -29.88 z"
+ class="st2"
+ id="path9169"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><circle
+ cx="57.176804"
+ cy="148.66301"
+ class="st3"
+ id="ellipse9174"
+ r="1.125"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="17.070599"
+ y="131.78801"
+ width="24.075001"
+ height="18"
+ class="st4"
+ id="rect9194"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="17.070599"
+ y="138.38799"
+ class="st5"
+ v:langID="1033"
+ id="text9198"
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RFSoC<v:newlineChar /><tspan
+ x="17.070599"
+ dy="14.400001"
+ class="st6"
+ id="tspan9196"
+ style="font-size:7.99996805px">DAC</tspan></text>
+
+<rect
+ x="17.070599"
+ y="149.50601"
+ width="24.075001"
+ height="9.28125"
+ class="st4"
+ id="rect9205"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="20.816799"
+ y="166.382"
+ width="47.970001"
+ height="30.6563"
+ class="st4"
+ id="rect9214"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 6.4353,508.89062 h 69.88 V 86.390917 h -69.88 z"
+ class="st7"
+ id="path9230"
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:0.40170002;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:2.81190007, 2.00850005, 0, 2.00850005" /><rect
+ x="12.1492"
+ y="90.894012"
+ width="51.837898"
+ height="17.7188"
+ class="st4"
+ id="rect9237"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="12.1492"
+ y="97.053017"
+ class="st8"
+ v:langID="1033"
+ id="text9245"
+ style="font-style:italic;font-weight:bold;font-size:9px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList /><tspan
+ class="st9"
+ id="tspan9239"
+ style="font-size:9px;text-decoration: underline;text-decoration-line: underline">X410 Mother</tspan><v:newlineChar /><tspan
+ x="12.1492"
+ dy="14.400001"
+ class="st9"
+ id="tspan9241"
+ style="font-size:9px;text-decoration: underline;text-decoration-line: underline"> </tspan><tspan
+ class="st9"
+ id="tspan9243"
+ style="font-size:9px;text-decoration: underline;text-decoration-line: underline">Board</tspan></text>
+
+<path
+ d="m 17.8191,448.58002 h 29.88 l 16.19,-15.06 -16.19,-12.33 h -29.88 z"
+ class="st2"
+ id="path9250"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="20.407799"
+ y="421.58203"
+ width="24.075001"
+ height="18"
+ class="st4"
+ id="rect9270"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="20.407799"
+ y="428.18201"
+ class="st5"
+ v:langID="1033"
+ id="text9274"
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RFSoC<v:newlineChar /><tspan
+ x="20.407799"
+ dy="14.400001"
+ class="st6"
+ id="tspan9272"
+ style="font-size:7.99996805px">ADC</tspan></text>
+
+<rect
+ x="20.407799"
+ y="439.30103"
+ width="24.075001"
+ height="9.28125"
+ class="st4"
+ id="rect9281"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 129.206,134.45202 a 2.06836,2.15275 0 1 0 0,4.3 2.06836,2.15275 0 1 0 0,4.3 2.0705218,2.155 0 1 0 0,4.31 2.06836,2.15275 0 1 0 0,4.3"
+ class="st1"
+ id="path9310"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 129.206,134.45202 h 1.08"
+ class="st1"
+ id="path9312"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 129.206,151.66202 h 1.08"
+ class="st1"
+ id="path9314"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 128.0839,155.02402 h 4.49 l -2.25,3.37 z"
+ class="st3"
+ id="path9328"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 130.328,155.02702 v -3.37"
+ class="st1"
+ id="path9333"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 121.724,134.45202 a 2.06836,2.15275 0 1 1 0,4.3 2.06836,2.15275 0 1 1 0,4.3 2.0705218,2.155 0 1 1 0,4.31 2.06836,2.15275 0 1 1 0,4.3"
+ class="st1"
+ id="path9341"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 121.724,134.45202 h -1.08"
+ class="st1"
+ id="path9343"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 121.724,151.66202 h -1.08"
+ class="st1"
+ id="path9345"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="111.782"
+ y="158.70999"
+ width="30.7332"
+ height="19.066601"
+ class="st10"
+ id="rect9360"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 116.52538,134.46904 4.11997,-0.0173"
+ class="st1"
+ id="path9369"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 114.992,151.66002 h 5.66"
+ class="st1"
+ id="path9374"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9383"
+ class="st10"
+ height="10.125"
+ width="31.476"
+ y="124.08101"
+ x="110.589" /><path
+ d="m 171.165,115.68202 h -14.48 v 13.5"
+ class="st1"
+ id="path9394"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 129.128,134.45202 h 19.69"
+ class="st1"
+ id="path9399"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="156.69"
+ y="121.269"
+ width="57.375"
+ height="19.066601"
+ class="st10"
+ id="rect9406"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="154.66701"
+ y="166.50003"
+ width="64.125"
+ height="19.4767"
+ class="st10"
+ id="rect9417"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 170.19,151.64402 h -13.21 v -13.5"
+ class="st1"
+ id="path9426"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 173.25,115.63202 h 43.31 v 13.51"
+ class="st1"
+ id="path9431"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="219.937"
+ y="85.499992"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect9438"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><circle
+ cx="60.439301"
+ cy="438.30704"
+ class="st3"
+ id="ellipse9447"
+ r="1.125"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 129.128,134.45202 h 5.99"
+ class="st13"
+ id="path9452"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 224.25,134.41702 h 88.5"
+ class="st1"
+ id="path9457"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="125.918"
+ y="88.701996"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect9493"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="270.58701"
+ y="145.23001"
+ width="25.825701"
+ height="10.125"
+ class="st10"
+ id="rect9504"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path9513"
+ class="st16"
+ d="m 297.785,134.59402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9515"
+ class="st1"
+ d="m 314.225,127.90402 -13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9517"
+ class="st1"
+ d="m 300.835,127.90402 13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9519"
+ class="st1"
+ d="m 317.265,134.59402 h 3.9" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9521"
+ class="st1"
+ d="m 297.785,134.59402 h -4.32" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9523"
+ class="st1"
+ d="m 297.785,134.59402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9525"
+ class="st1"
+ d="m 307.315,144.33402 v 4.28" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9532"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="131.60687"
+ x="299.22333" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text9534"
+ v:langID="1033"
+ class="st18"
+ y="137.00488"
+ x="299.22333"><v:paragraph /><v:tabList />IF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9541"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="131.32562"
+ x="310.4733" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text9543"
+ v:langID="1033"
+ class="st18"
+ y="136.72363"
+ x="310.4733"><v:paragraph /><v:tabList />RF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9550"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="137.23187"
+ x="304.1254" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text9552"
+ v:langID="1033"
+ class="st18"
+ y="142.62988"
+ x="304.1254"><v:paragraph /><v:tabList />LO</text>
+
+<rect
+ x="293.035"
+ y="104.99403"
+ width="40.317299"
+ height="19.4767"
+ class="st10"
+ id="rect9560"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="324"
+ y="146.02402"
+ width="25.825701"
+ height="10.125"
+ class="st10"
+ id="rect9571"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 321.3119,134.59581 90.43974,-0.21364"
+ class="st1"
+ id="path9578"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="347.08701"
+ y="105.73203"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect9619"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9630"
+ class="st15"
+ height="19.125"
+ width="9"
+ y="124.59827"
+ x="380.25" /><path
+ d="m 384.755,126.50402 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21"
+ class="st1"
+ id="path9637"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9646"
+ class="st1"
+ d="m 384.75,141.12902 v 4.11" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9651"
+ class="st1"
+ d="m 384.75,126.50402 v -3.2" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path9656"
+ class="st19"
+ d="m 377.99992,142.42991 12.60034,-16.96197" /><rect
+ x="378.888"
+ y="87.573029"
+ width="40.317299"
+ height="19.4767"
+ class="st10"
+ id="rect9664"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="368.388"
+ y="107.59601"
+ width="33.321201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect9675"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="373.48801"
+ y="115.06104"
+ class="st11"
+ v:langID="1033"
+ id="text9677"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 1</text>
+
+<circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse9684"
+ class="st3"
+ cy="137.86302"
+ cx="-156.69"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse9689"
+ class="st3"
+ cy="130.55052"
+ cx="-156.69"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse9694"
+ class="st3"
+ cy="134.48802"
+ cx="-149.94"
+ transform="scale(-1,1)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9699"
+ class="st1"
+ d="m 150.7853,133.74368 4.95311,-2.59129" /><path
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9704"
+ class="st21"
+ d="m 155.33052,131.36427 -1.17,2.28 -0.49,-1.41 -0.92,-0.91 z" /><path
+ d="m 176.625,151.64402 h 39.94 v -13.5"
+ class="st1"
+ id="path9710"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse9717"
+ class="st3"
+ cy="137.81201"
+ cx="216.563" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse9722"
+ class="st3"
+ cy="130.49951"
+ cx="216.563" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse9727"
+ class="st3"
+ cy="134.43701"
+ cx="223.313" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9732"
+ class="st1"
+ d="m 222.4677,133.69268 -4.95311,-2.59129" /><path
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9737"
+ class="st21"
+ d="m 217.92248,131.31327 1.17,2.28 0.49,-1.41 0.92,-0.91 z" /><path
+ d="m 732.61,125.59002 v -12.74 h 69.19"
+ class="st1"
+ id="path9743"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 746.01,197.39402 H 732.7 v -52.24"
+ class="st1"
+ id="path9748"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="744.422"
+ y="166.65503"
+ width="40.317299"
+ height="19.4767"
+ class="st10"
+ id="rect9755"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path9766"
+ class="st16"
+ d="m 748.046,197.38102 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9768"
+ class="st1"
+ d="m 764.486,190.69102 -13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9770"
+ class="st1"
+ d="m 751.096,190.69102 13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9772"
+ class="st1"
+ d="m 767.526,197.38102 h 3.9" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9774"
+ class="st1"
+ d="m 748.046,197.38102 h -4.32" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9776"
+ class="st1"
+ d="m 748.046,197.38102 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9778"
+ class="st1"
+ d="m 757.576,207.12102 v 4.28" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9785"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="194.18713"
+ x="761.54541" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text9787"
+ v:langID="1033"
+ class="st18"
+ y="199.58514"
+ x="761.54541"><v:paragraph /><v:tabList />IF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9794"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="193.98163"
+ x="749.04993" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text9796"
+ v:langID="1033"
+ class="st18"
+ y="199.37964"
+ x="749.04993"><v:paragraph /><v:tabList />RF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9803"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="200.01888"
+ x="754.38641" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text9805"
+ v:langID="1033"
+ class="st18"
+ y="205.41689"
+ x="754.38641"><v:paragraph /><v:tabList />LO</text>
+
+<path
+ d="m 640.59232,135.10702 78.98,-0.006"
+ class="st1"
+ id="path9811"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 646.981,135.10102 h 5.99"
+ class="st13"
+ id="path9816"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 732.7,154.49002 v 5.99"
+ class="st13"
+ id="path9821"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 735.422,112.85302 h 5.99"
+ class="st13"
+ id="path9826"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect
+ x="850.172"
+ y="127.321"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect9833"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="851.09003"
+ y="159.34599"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect9844"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 771.57259,197.38224 38.15948,-0.19902"
+ class="st1"
+ id="path9851"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 823.28,187.64402 v -37.08 h 91.8"
+ class="st1"
+ id="path9856"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9863"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="140.73601"
+ x="852.95898" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9868"
+ class="st1"
+ d="m 854.98836,144.77544 8.44,-0.04 4.33,8.7" /><rect
+ x="859.62903"
+ y="173.963"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect9876"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="834.85498"
+ y="205.10699"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect9887"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="883.38599"
+ y="206.09399"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect9896"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9905"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="187.194"
+ x="849.159" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9910"
+ class="st1"
+ d="m 863.9614,191.23344 -8.44,-0.04 -4.33,8.7" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9947"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="186.519"
+ x="907.815" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9952"
+ class="st1"
+ d="m 909.84436,190.55844 8.44,-0.04 4.33,8.7" /><rect
+ x="872.72601"
+ y="218.258"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect9960"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="882.53699"
+ y="250.47102"
+ width="26.1562"
+ height="19.4767"
+ class="st4"
+ id="rect9971"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="859.5"
+ y="252.72099"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect9982"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect9991"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="230.125"
+ x="882.53699" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path9996"
+ class="st1"
+ d="m 897.3394,234.16444 -8.44,-0.04 -4.33,8.7" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path10046"
+ class="st19"
+ d="m 969.422,160.22643 h -5.8 l -4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10051"
+ class="st3"
+ cy="169.201"
+ cx="955.79419" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10056"
+ class="st3"
+ cy="163.55043"
+ cx="955.79419" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10061"
+ class="st3"
+ cy="157.23511"
+ cx="955.79419" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10066"
+ class="st3"
+ cy="150.5874"
+ cx="955.79419" /><path
+ d="m 955.359,149.46702 v -36.59 h -160.31"
+ class="st1"
+ id="path10072"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse10079"
+ class="st3"
+ cy="204.56702"
+ cx="-823.41193"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse10084"
+ class="st3"
+ cy="196.51669"
+ cx="-823.41193"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse10089"
+ class="st3"
+ cy="188.04262"
+ cx="-823.41193"
+ transform="scale(-1,1)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path10094"
+ class="st19"
+ d="m 809.736,197.18357 h 5.8 l 4.92,-6.87" /><rect
+ x="775.27399"
+ y="208.16299"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10131"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="903.047"
+ y="205.91299"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10140"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 809.577,197.09902 -11.53,-7.69 v 15.38 z"
+ class="st3"
+ id="path10147"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="823.284"
+ y="156.02899"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect10154"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 944.44,151.65202 v 5.44 h 10.36"
+ class="st1"
+ id="path10163"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 944.437,152.73002 v -2.16 h -30.37"
+ class="st1"
+ id="path10168"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="814.172"
+ y="102.41299"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect10175"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="791.672"
+ y="163.457"
+ width="26.537201"
+ height="21.9375"
+ class="st4"
+ id="rect10186"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1011.34,156.99802 h 75.2"
+ class="st1"
+ id="path10195"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1055.25,157.09302 -11.53,-7.69 v 15.38 z"
+ class="st3"
+ id="path10200"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10207"
+ class="st1"
+ d="m 1138.3944,166.96162 4.3629,4.36285" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10212"
+ class="st1"
+ d="m 1138.3946,166.9616 4.3628,-4.36285" /><path
+ d="m 1097.8,176.97202 v 80.73"
+ class="st1"
+ id="path10218"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1097.8,203.78102 v 5.69"
+ class="st13"
+ id="path10223"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 1073.75,156.99802 h 6.97"
+ class="st13"
+ id="path10228"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 1122.42,167.06202 h 6.97"
+ class="st13"
+ id="path10233"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 1128.08,167.06202 h -9"
+ class="st13"
+ id="path10238"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 1138.76,167.06102 h -28.72"
+ class="st1"
+ id="path10243"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1124.67"
+ y="175.30901"
+ width="39.375"
+ height="32.051601"
+ class="st10"
+ id="rect10250"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1128.67"
+ y="184.13103"
+ class="st11"
+ v:langID="1033"
+ id="text10256"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Tx/Rx<v:newlineChar /><tspan
+ x="1128.67"
+ dy="14.400001"
+ class="st6"
+ id="tspan10252"
+ style="font-size:7.99996805px">1 MHz </tspan>–<v:newlineChar /><tspan
+ x="1128.67"
+ dy="14.400001"
+ class="st6"
+ id="tspan10254"
+ style="font-size:7.99996805px">8 GHz</tspan></text>
+
+<path
+ d="m 1042.55,288.72402 h 63.33 v 126.17"
+ class="st1"
+ id="path10261"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1012.22"
+ y="258.34604"
+ width="52.3125"
+ height="31.872601"
+ class="st10"
+ id="rect10268"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1016.22"
+ y="267.07901"
+ class="st11"
+ v:langID="1033"
+ id="text10274"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Cal <tspan
+ x="1016.22"
+ dy="14.400001"
+ class="st6"
+ id="tspan10270"
+ style="font-size:7.99996805px">Loopback </tspan><tspan
+ x="1016.22"
+ dy="14.400001"
+ class="st6"
+ id="tspan10272"
+ style="font-size:7.99996805px">Path</tspan></text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path10281"
+ class="st19"
+ d="m 1111.305,167.00843 h -5.8 l -4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10286"
+ class="st3"
+ cy="175.983"
+ cx="1097.6771" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10291"
+ class="st3"
+ cy="170.33244"
+ cx="1097.6771" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10296"
+ class="st3"
+ cy="164.01711"
+ cx="1097.6771" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10301"
+ class="st3"
+ cy="157.3694"
+ cx="1097.6771" /><rect
+ x="1101.38"
+ y="130.44403"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect10309"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="704.81299"
+ y="86.221024"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect10320"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1011.38,163.59802 h 9.34 v 13.01"
+ class="st1"
+ id="path10329"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1097.51,156.14402 v -41.39 h -5.13"
+ class="st1"
+ id="path10334"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1089,170.56602 h 7.42"
+ class="st1"
+ id="path10339"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="983.25"
+ y="88.681023"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect10346"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1092.38,114.75102 h -82.12 v 36.72"
+ class="st1"
+ id="path10355"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1042.55,288.72302 h -32.3 v -118.69"
+ class="st1"
+ id="path10360"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1105.88,308.97102 v 5.69"
+ class="st13"
+ id="path10365"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path10372"
+ class="st19"
+ d="m 719.109,135.11143 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10377"
+ class="st3"
+ cy="144.086"
+ cx="-732.73682"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10382"
+ class="st3"
+ cy="138.43544"
+ cx="-732.73682"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10387"
+ class="st3"
+ cy="132.1201"
+ cx="-732.73682"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10392"
+ class="st3"
+ cy="125.47241"
+ cx="-732.73682"
+ transform="scale(-1,1)" /><rect
+ x="738.797"
+ y="127.62103"
+ width="20.25"
+ height="8.6133299"
+ class="st10"
+ id="rect10400"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="742.797"
+ y="133.724"
+ class="st23"
+ v:langID="1033"
+ id="text10402"
+ style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />NC</text>
+
+<path
+ d="m 733.703,132.37002 h 7.42"
+ class="st1"
+ id="path10407"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 733.734,138.41302 h 7.42"
+ class="st1"
+ id="path10412"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="895.789"
+ y="161.32899"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10419"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 536.512,280.51502 h -110.7 v -135.98"
+ class="st1"
+ id="path10455"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="478.633"
+ y="144.98999"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10462"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path10471"
+ class="st19"
+ d="m 411.625,134.32443 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10476"
+ class="st3"
+ cy="143.29901"
+ cx="-425.25284"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10481"
+ class="st3"
+ cy="137.64844"
+ cx="-425.25284"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10486"
+ class="st3"
+ cy="131.3331"
+ cx="-425.25284"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse10491"
+ class="st3"
+ cy="124.68541"
+ cx="-425.25284"
+ transform="scale(-1,1)" /><rect
+ x="391.845"
+ y="137.97102"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect10499"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 425.34,123.84602 v -13.6 h 14.63"
+ class="st1"
+ id="path10508"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="506.84003"
+ y="76.5"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10515"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 528.332,96.096017 h -75.52 v 4.750003"
+ class="st1"
+ id="path10524"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="475.92899"
+ y="105.75"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10531"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="525.375"
+ y="108.72099"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10540"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="474.75"
+ y="117.269"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10549"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="480.42899"
+ y="155.11499"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10560"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="447.75"
+ y="190.52299"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10571"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="456.215"
+ y="226.037"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10582"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 514.831,96.092017 h 91.54 v 3.42"
+ class="st1"
+ id="path10591"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect10598"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="86.874992"
+ x="553.50201" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10603"
+ class="st1"
+ d="m 555.53136,90.914437 8.44,-0.04 4.33,8.7" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect10640"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="87.840004"
+ x="488.78497" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10645"
+ class="st1"
+ d="m 503.5874,91.879437 -8.44,-0.04 -4.33,8.700003" /><path
+ d="m 528.786,136.18602 h -61.35 v -17.34"
+ class="st1"
+ id="path10651"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 527.625,136.18202 h 63 v -23.87"
+ class="st1"
+ id="path10656"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect10663"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="127.28101"
+ x="487.52698" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10668"
+ class="st1"
+ d="m 490.43745,141.83357 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10673"
+ class="st1"
+ d="m 502.0792,141.83357 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><path
+ d="m 426.551,131.44102 h 13.5 v 13.77"
+ class="st1"
+ id="path10679"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="480.42899"
+ y="180.72099"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10686"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="458.12701"
+ y="215.18199"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10695"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="458.12701"
+ y="253.274"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect10704"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 618.75,110.20802 h 10.41 v 14.2"
+ class="st1"
+ id="path10711"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 426.289,137.62902 h 8.55 v 13.77"
+ class="st1"
+ id="path10716"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 480.938,244.02002 h -46.07 v -93.8"
+ class="st1"
+ id="path10721"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 480.375,207.62602 h -40.32 v -65.84"
+ class="st1"
+ id="path10726"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 605.25,113.22102 h -7.88 v 31.5"
+ class="st1"
+ id="path10731"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 628.313,132.17502 h -15.19 v 20.62"
+ class="st1"
+ id="path10736"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 565.875,195.34102 h 53.46 v -37.12"
+ class="st1"
+ id="path10741"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 558.562,186.34502 h 54.56 v -34.3"
+ class="st1"
+ id="path10746"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 519.75,173.26602 h 77.63 v -29.53"
+ class="st1"
+ id="path10751"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect10758"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="198.896"
+ x="466.28699" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10763"
+ class="st1"
+ d="m 469.19745,213.44857 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10768"
+ class="st1"
+ d="m 480.8392,213.44857 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect10776"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="235.28999"
+ x="464.03699" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10781"
+ class="st1"
+ d="m 466.94745,249.84257 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path10786"
+ class="st1"
+ d="m 478.5892,249.84257 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect
+ x="634.96301"
+ y="101.97102"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect10794"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="525.85199"
+ y="148.769"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10805"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="528.75"
+ y="183.23999"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10843"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="506.25"
+ y="215.18199"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10881"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="506.25"
+ y="252.99001"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect10919"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="919.922"
+ y="104.52898"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect10991"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="919.922"
+ y="109.92999"
+ class="st18"
+ v:langID="1033"
+ id="text10993"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 4</text>
+
+<rect
+ x="918.797"
+ y="104.39401"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect10998"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="578.25"
+ y="87.447983"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect11005"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="578.25"
+ y="92.848984"
+ class="st18"
+ v:langID="1033"
+ id="text11007"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 1</text>
+
+<rect
+ x="577.13507"
+ y="87.323074"
+ width="17.123955"
+ height="6.7298865"
+ rx="3.9516819"
+ ry="3.3649433"
+ class="st24"
+ id="rect11012"
+ style="fill:none;stroke:#000000;stroke-width:0.27011338;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="573.75"
+ y="127.94798"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect11019"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="573.75"
+ y="133.34898"
+ class="st18"
+ v:langID="1033"
+ id="text11021"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 2</text>
+
+<rect
+ x="572.633"
+ y="127.82103"
+ width="16.597727"
+ height="6.7339883"
+ rx="3.8302445"
+ ry="3.3669941"
+ class="st24"
+ id="rect11026"
+ style="fill:none;stroke:#000000;stroke-width:0.26601166;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="574.875"
+ y="165.47699"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect11033"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="574.875"
+ y="170.87799"
+ class="st18"
+ v:langID="1033"
+ id="text11035"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 3</text>
+
+<rect
+ x="573.75952"
+ y="165.35156"
+ width="16.992392"
+ height="6.730906"
+ rx="3.9213212"
+ ry="3.365453"
+ class="st24"
+ id="rect11040"
+ style="fill:none;stroke:#000000;stroke-width:0.26909411;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="535.5"
+ y="198.89398"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect11047"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="535.5"
+ y="204.29498"
+ class="st18"
+ v:langID="1033"
+ id="text11049"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 4</text>
+
+<rect
+ x="534.38251"
+ y="198.7285"
+ width="16.466179"
+ height="6.7350245"
+ rx="3.7998877"
+ ry="3.3675122"
+ class="st24"
+ id="rect11054"
+ style="fill:none;stroke:#000000;stroke-width:0.26497579;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="536.625"
+ y="236.38499"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect11061"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="536.625"
+ y="241.786"
+ class="st18"
+ v:langID="1033"
+ id="text11063"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 5</text>
+
+<rect
+ x="535.50854"
+ y="236.25853"
+ width="16.729277"
+ height="6.7329569"
+ rx="3.8606024"
+ ry="3.3664784"
+ class="st24"
+ id="rect11068"
+ style="fill:none;stroke:#000000;stroke-width:0.26704329;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="918.797"
+ y="141.62099"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect11075"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="918.797"
+ y="147.02199"
+ class="st18"
+ v:langID="1033"
+ id="text11077"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 1</text>
+
+<rect
+ x="917.672"
+ y="141.48601"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect11082"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="929.75098"
+ y="188.06798"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect11089"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="929.75098"
+ y="193.46898"
+ class="st18"
+ v:langID="1033"
+ id="text11091"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 2</text>
+
+<rect
+ x="927.797"
+ y="188.17001"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect11096"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="938.21802"
+ y="231.84598"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect11103"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="938.21802"
+ y="237.24699"
+ class="st18"
+ v:langID="1033"
+ id="text11105"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 3</text>
+
+<rect
+ x="936.23401"
+ y="232.08101"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect11110"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="703.27399"
+ y="178.644"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect11146"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 693.936,135.09502 -11.53,-7.69 v 15.38 z"
+ class="st3"
+ id="path11153"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="704.25"
+ y="147.23999"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect11160"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="672.71503"
+ y="106.64403"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect11169"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 114.758,424.67502 a 2.06836,2.15275 0 1 0 0,4.3 2.06836,2.15275 0 1 0 0,4.3 2.0705218,2.155 0 1 0 0,4.31 2.06836,2.15275 0 1 0 0,4.3"
+ class="st1"
+ id="path11182"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 114.758,424.67502 h 1.08"
+ class="st1"
+ id="path11184"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 114.758,441.88502 h 1.08"
+ class="st1"
+ id="path11186"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 113.6359,445.24702 h 4.49 l -2.25,3.37 z"
+ class="st3"
+ id="path11200"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 115.88,445.25002 v -3.37"
+ class="st1"
+ id="path11205"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 107.276,424.67502 a 2.06836,2.15275 0 1 1 0,4.3 2.06836,2.15275 0 1 1 0,4.3 2.0705218,2.155 0 1 1 0,4.31 2.06836,2.15275 0 1 1 0,4.3"
+ class="st1"
+ id="path11213"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 107.276,424.67502 h -1.08"
+ class="st1"
+ id="path11215"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 107.276,441.88502 h -1.08"
+ class="st1"
+ id="path11217"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="100.959"
+ y="448.93301"
+ width="30.7332"
+ height="19.066601"
+ class="st10"
+ id="rect11232"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 102.07739,424.69204 4.11996,-0.0173"
+ class="st1"
+ id="path11241"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 100.543,441.88302 h 5.66"
+ class="st1"
+ id="path11246"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11255"
+ class="st10"
+ height="10.125"
+ width="31.476"
+ y="414.17804"
+ x="94.512001" /><path
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11260"
+ class="st12"
+ d="m 104.8062,414.43315 h 5.06" /><path
+ d="m 200.639,405.90002 h -14.48 v 13.5"
+ class="st1"
+ id="path11266"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 115.83396,424.67489 25.5399,-0.0718"
+ class="st1"
+ id="path11271"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="186.164"
+ y="413.08603"
+ width="57.375"
+ height="17.4627"
+ class="st10"
+ id="rect11278"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="187.875"
+ y="458.59705"
+ width="64.125"
+ height="19.4767"
+ class="st10"
+ id="rect11289"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 210.533,441.85702 h -24.08 v -13.5"
+ class="st1"
+ id="path11298"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 209.077,405.86002 h 33.34 v 13.5"
+ class="st1"
+ id="path11303"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="240.18799"
+ y="382.733"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect11310"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="165.09399"
+ y="375.634"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect11321"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse11332"
+ class="st3"
+ cy="428.07602"
+ cx="-186.164"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse11337"
+ class="st3"
+ cy="420.76352"
+ cx="-186.164"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse11342"
+ class="st3"
+ cy="424.70102"
+ cx="-179.414"
+ transform="scale(-1,1)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11347"
+ class="st1"
+ d="m 180.2593,423.95668 4.95311,-2.59129" /><path
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11352"
+ class="st21"
+ d="m 184.80452,421.57727 -1.17,2.28 -0.49,-1.41 -0.92,-0.91 z" /><path
+ d="m 210.938,441.85702 h 31.48 v -13.5"
+ class="st1"
+ id="path11358"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 248.42106,424.456 120.21999,-0.0312"
+ class="st1"
+ id="path11363"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path11370"
+ class="st19"
+ d="m 1092.375,425.53043 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11375"
+ class="st3"
+ cy="434.50504"
+ cx="-1106.0028"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11380"
+ class="st3"
+ cy="428.85446"
+ cx="-1106.0028"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11385"
+ class="st3"
+ cy="422.53912"
+ cx="-1106.0028"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11390"
+ class="st3"
+ cy="415.89142"
+ cx="-1106.0028"
+ transform="scale(-1,1)" /><path
+ d="m 1114.87,341.59602 v 80.93 H 1107"
+ class="st1"
+ id="path11396"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11403"
+ class="st15"
+ height="13.7462"
+ width="6.4687901"
+ y="438.90686"
+ x="1108.0601" /><path
+ d="m 1111.292,440.27602 2.1,0.88 -4.2,1.75 4.2,1.75 -4.2,1.75 4.2,1.75 -4.2,1.76 2.1,0.87"
+ class="st1"
+ id="path11410"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11419"
+ class="st1"
+ d="m 1111.294,450.78802 v 2.96" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11424"
+ class="st1"
+ d="m 1111.294,440.27602 v -2.3" /><path
+ d="m 1112.9116,456.16602 h -3.23 l 1.61,2.43 z"
+ class="st3"
+ id="path11431"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1111.2942,456.17002 v -2.43"
+ class="st1"
+ id="path11436"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1107,434.40802 h 4.3 v 4.5"
+ class="st1"
+ id="path11443"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 1133.14,428.75002 H 1107"
+ class="st1"
+ id="path11448"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11455"
+ class="st1"
+ d="m 1133.2044,428.65858 4.3629,4.36285" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11460"
+ class="st1"
+ d="m 1133.2046,428.6586 4.3628,-4.36285" /><rect
+ x="1126.13"
+ y="438.15002"
+ width="43.875"
+ height="34.6203"
+ class="st10"
+ id="rect11468"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1130.13"
+ y="448.26001"
+ class="st11"
+ v:langID="1033"
+ id="text11474"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Rx2 Input<v:newlineChar /><tspan
+ x="1130.13"
+ dy="14.400001"
+ class="st6"
+ id="tspan11470"
+ style="font-size:7.99996805px">1 MHz </tspan>–<v:newlineChar /><tspan
+ x="1130.13"
+ dy="14.400001"
+ class="st6"
+ id="tspan11472"
+ style="font-size:7.99996805px">8 GHz</tspan></text>
+
+<path
+ d="m 1130.63,428.75002 h -9"
+ class="st13"
+ id="path11479"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect
+ x="1068.05"
+ y="444.53302"
+ width="33.321201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect11486"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1073.15"
+ y="451.99805"
+ class="st11"
+ v:langID="1033"
+ id="text11488"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 1</text>
+
+<path
+ d="m 1092.38,425.53002 h -23.62"
+ class="st1"
+ id="path11493"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11500"
+ class="st15"
+ height="19.125"
+ width="9"
+ y="416.75027"
+ x="1077.75" /><path
+ d="m 1082.255,418.65602 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21"
+ class="st1"
+ id="path11507"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11516"
+ class="st1"
+ d="m 1082.25,433.28102 v 4.11" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11521"
+ class="st1"
+ d="m 1082.25,418.65602 v -3.2" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path11526"
+ class="st19"
+ d="m 1075.4999,434.58191 12.6004,-16.96197" /><rect
+ x="1069.87"
+ y="439.11905"
+ width="30.375"
+ height="19.4767"
+ class="st10"
+ id="rect11534"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 846.543,485.98202 h 93"
+ class="st1"
+ id="path11543"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 741.45,414.92802 v -22.53 h 75.46"
+ class="st1"
+ id="path11548"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 783.555,486.55002 h -42.01 v -52.24"
+ class="st1"
+ id="path11553"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="780.56201"
+ y="456.18903"
+ width="40.317299"
+ height="19.4767"
+ class="st10"
+ id="rect11560"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path11571"
+ class="st16"
+ d="m 787.18,486.58902 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11573"
+ class="st1"
+ d="m 803.62,479.89902 -13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11575"
+ class="st1"
+ d="m 790.23,479.89902 13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11577"
+ class="st1"
+ d="m 806.66,486.58902 h 3.9" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11579"
+ class="st1"
+ d="m 787.18,486.58902 h -4.32" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11581"
+ class="st1"
+ d="m 787.18,486.58902 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11583"
+ class="st1"
+ d="m 796.71,496.32902 v 4.28" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11590"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="483.39514"
+ x="800.67938" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text11592"
+ v:langID="1033"
+ class="st18"
+ y="488.79315"
+ x="800.67938"><v:paragraph /><v:tabList />IF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11599"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="483.18964"
+ x="788.1839" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text11601"
+ v:langID="1033"
+ class="st18"
+ y="488.58765"
+ x="788.1839"><v:paragraph /><v:tabList />RF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11608"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="489.2269"
+ x="793.52039" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text11610"
+ v:langID="1033"
+ class="st18"
+ y="494.62491"
+ x="793.52039"><v:paragraph /><v:tabList />LO</text>
+
+<path
+ d="m 733.7628,424.45035 -4.06,-0.004"
+ class="st13"
+ id="path11616"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 741.547,448.64402 v -5.43"
+ class="st13"
+ id="path11621"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect
+ x="874.82599"
+ y="416.70203"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect11628"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="896.59399"
+ y="448.42801"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect11639"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 810.70682,486.59088 21.30989,-0.0689"
+ class="st1"
+ id="path11646"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 853.69,472.65802 v -32.76 h 83.08"
+ class="st1"
+ id="path11651"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect11658"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="430.07401"
+ x="899.91498" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path11663"
+ class="st1"
+ d="m 901.94436,434.11344 8.44,-0.04 4.33,8.7" /><rect
+ x="881.49597"
+ y="466.177"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect11671"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1055.25,414.86102 v -22.5 H 816.91"
+ class="st1"
+ id="path11718"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse11725"
+ class="st3"
+ cy="493.90506"
+ cx="-845.27887"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse11730"
+ class="st3"
+ cy="485.85471"
+ cx="-845.27887"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse11735"
+ class="st3"
+ cy="477.38065"
+ cx="-845.27887"
+ transform="scale(-1,1)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path11740"
+ class="st19"
+ d="m 831.603,486.52157 h 5.8 l 4.92,-6.87" /><rect
+ x="797.34399"
+ y="495.72104"
+ width="52.3125"
+ height="21.9375"
+ class="st10"
+ id="rect11777"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="768.84497"
+ y="496.16299"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect11796"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="815.625"
+ y="442.31201"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect11810"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 936.134,439.90002 h 33.32 v 15.43"
+ class="st1"
+ id="path11819"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="838.03998"
+ y="383.39401"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect11826"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="741.453"
+ y="393.92303"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect11837"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path11848"
+ class="st19"
+ d="m 727.953,424.45043 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11853"
+ class="st3"
+ cy="433.42502"
+ cx="-741.58081"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11858"
+ class="st3"
+ cy="427.77444"
+ cx="-741.58081"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11863"
+ class="st3"
+ cy="421.45911"
+ cx="-741.58081"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse11868"
+ class="st3"
+ cy="414.81143"
+ cx="-741.58081"
+ transform="scale(-1,1)" /><rect
+ x="1040.77"
+ y="405.79001"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect11932"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1040.77"
+ y="411.19101"
+ class="st18"
+ v:langID="1033"
+ id="text11934"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 3</text>
+
+<rect
+ x="1038.52"
+ y="405.72104"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect11939"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="941.83002"
+ y="431.793"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect11946"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="941.83002"
+ y="437.194"
+ class="st18"
+ v:langID="1033"
+ id="text11948"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 1</text>
+
+<rect
+ x="940.70502"
+ y="431.65802"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect11953"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="946.39099"
+ y="478.07401"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect11960"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="946.39099"
+ y="483.47501"
+ class="st18"
+ v:langID="1033"
+ id="text11962"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 2</text>
+
+<rect
+ x="944.43701"
+ y="478.17603"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect11967"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1058.25"
+ y="376.85706"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect11974"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 798.665,392.39002 h -5.05"
+ class="st13"
+ id="path11983"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path
+ d="m 731.24918,424.45006 -80.99,-0.0226"
+ class="st1"
+ id="path11988"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="183.375"
+ y="108.06898"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect12217"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="183.375"
+ y="113.46999"
+ class="st18"
+ v:langID="1033"
+ id="text12219"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 1</text>
+
+<rect
+ x="181.13644"
+ y="107.94547"
+ width="17.484533"
+ height="6.7271137"
+ rx="4.0348921"
+ ry="3.3635569"
+ class="st24"
+ id="rect12224"
+ style="fill:none;stroke:#000000;stroke-width:0.27288619;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="198.563"
+ y="144.13498"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect12231"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="198.563"
+ y="149.53598"
+ class="st18"
+ v:langID="1033"
+ id="text12233"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 2</text>
+
+<rect
+ x="197.44254"
+ y="144.00455"
+ width="15.724568"
+ height="6.7409463"
+ rx="3.6287467"
+ ry="3.3704731"
+ class="st24"
+ id="rect12238"
+ style="fill:none;stroke:#000000;stroke-width:0.25905383;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="228.375"
+ y="408.47699"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect12245"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="228.375"
+ y="413.87799"
+ class="st18"
+ v:langID="1033"
+ id="text12247"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 1</text>
+
+<rect
+ x="227.25"
+ y="408.34204"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect12252"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="225.68301"
+ y="434.125"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect12259"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="225.68301"
+ y="439.526"
+ class="st18"
+ v:langID="1033"
+ id="text12261"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 2</text>
+
+<rect
+ x="224.55801"
+ y="433.99002"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect12266"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 607.341,443.83902 h 19.37 v -9.59"
+ class="st1"
+ id="path12271"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 642.216,413.76702 v -25.24 h -55.5"
+ class="st1"
+ id="path12276"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 513.28,413.76902 v -25.24 h 73.44"
+ class="st1"
+ id="path12281"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path12288"
+ class="st19"
+ d="m 499.5,424.27943 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12293"
+ class="st3"
+ cy="433.25403"
+ cx="-513.12787"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12298"
+ class="st3"
+ cy="427.60345"
+ cx="-513.12787"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12303"
+ class="st3"
+ cy="421.28812"
+ cx="-513.12787"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12308"
+ class="st3"
+ cy="414.64041"
+ cx="-513.12787"
+ transform="scale(-1,1)" /><rect
+ x="554.09003"
+ y="369"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect12316"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="622.68701"
+ y="380.15399"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect12365"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="622.68701"
+ y="385.55499"
+ class="st18"
+ v:langID="1033"
+ id="text12367"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 1</text>
+
+<rect
+ x="621.56201"
+ y="380.01904"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect12372"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 600.591,443.85002 h -69.63 v -9.59"
+ class="st1"
+ id="path12377"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="555.80402"
+ y="423"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect12384"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="611.396"
+ y="436.198"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect12395"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="611.396"
+ y="441.599"
+ class="st18"
+ v:langID="1033"
+ id="text12397"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 2</text>
+
+<rect
+ x="610.27557"
+ y="436.06763"
+ width="15.742751"
+ height="6.7407994"
+ rx="3.6329424"
+ ry="3.3703997"
+ class="st24"
+ id="rect12402"
+ style="fill:none;stroke:#000000;stroke-width:0.25920072;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path12409"
+ class="st19"
+ d="m 655.959,424.42843 h -5.8 l -4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12414"
+ class="st3"
+ cy="433.40302"
+ cx="642.33118" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12419"
+ class="st3"
+ cy="427.75244"
+ cx="642.33118" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12424"
+ class="st3"
+ cy="421.43713"
+ cx="642.33118" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse12429"
+ class="st3"
+ cy="414.78943"
+ cx="642.33118" /><rect
+ x="644.021"
+ y="379.49405"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect12437"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path12486"
+ class="st16"
+ d="m 373.118,424.42402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12488"
+ class="st1"
+ d="m 389.558,417.73402 -13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12490"
+ class="st1"
+ d="m 376.168,417.73402 13.39,13.39" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12492"
+ class="st1"
+ d="m 392.598,424.42402 h 3.9" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12494"
+ class="st1"
+ d="m 373.118,424.42402 h -4.32" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12496"
+ class="st1"
+ d="m 373.118,424.42402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect12505"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="421.43689"
+ x="374.5563" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text12507"
+ v:langID="1033"
+ class="st18"
+ y="426.8349"
+ x="374.5563"><v:paragraph /><v:tabList />IF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect12514"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="421.15564"
+ x="385.8063" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text12516"
+ v:langID="1033"
+ class="st18"
+ y="426.55365"
+ x="385.8063"><v:paragraph /><v:tabList />RF</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect12523"
+ class="st17"
+ height="6.18751"
+ width="9.16047"
+ y="427.06189"
+ x="379.4584" /><text
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"
+ id="text12525"
+ v:langID="1033"
+ class="st18"
+ y="432.4599"
+ x="379.4584"><v:paragraph /><v:tabList />LO</text>
+
+<rect
+ x="365.043"
+ y="393.69803"
+ width="40.317299"
+ height="19.4767"
+ class="st10"
+ id="rect12533"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="397.71201"
+ y="402.34604"
+ width="25.825701"
+ height="10.125"
+ class="st10"
+ id="rect12544"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="382.5"
+ y="435.53403"
+ width="52.3125"
+ height="21.9375"
+ class="st10"
+ id="rect12562"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse12582"
+ class="st3"
+ cy="427.51602"
+ cx="242.75601" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse12587"
+ class="st3"
+ cy="420.20352"
+ cx="242.75601" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.125"
+ id="ellipse12592"
+ class="st3"
+ cy="424.14102"
+ cx="249.50601" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12597"
+ class="st1"
+ d="m 248.6607,423.39668 -4.95311,-2.5913" /><path
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12602"
+ class="st21"
+ d="m 244.11548,421.01727 1.17,2.28 0.49,-1.41 0.92,-0.91 z" /><path
+ d="m 396.64527,424.42565 89.35995,-0.0857"
+ class="st1"
+ id="path12608"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 754.54626,361.06691 5.62328,-8.99727"
+ class="st12"
+ id="path12642"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1084.1747,361.06691 -5.6232,-8.99727"
+ class="st12"
+ id="path12647"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 1078.55,352.06702 H 760.17"
+ class="st12"
+ id="path12652"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="845.60498"
+ y="338.39401"
+ width="69.882797"
+ height="9.28125"
+ class="st10"
+ id="rect12659"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 876.676,351.89402 v -3.94"
+ class="st12"
+ id="path12668"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 426.79627,361.1808 5.62327,-8.99727"
+ class="st12"
+ id="path12673"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 751.39474,361.25021 -5.62328,-8.99727"
+ class="st12"
+ id="path12678"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 745.76648,352.25 -312.97,-0.0694"
+ class="st12"
+ id="path12683"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="577.789"
+ y="335.37677"
+ width="69.882797"
+ height="9.28125"
+ class="st10"
+ id="rect12690"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 608.859,351.89402 v -3.94"
+ class="st12"
+ id="path12699"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><g
+ id="g57527"
+ transform="translate(-31.5,-43.499983)"><path
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12704"
+ class="st12"
+ d="m 132.75026,403.47078 5.62328,-8.99727" /><path
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12709"
+ class="st12"
+ d="m 396.63474,403.51989 -5.62328,-8.99727" /><path
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12714"
+ class="st12"
+ d="M 391.024,394.471 H 138.374" /><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect12721"
+ class="st10"
+ height="9.28125"
+ width="69.882797"
+ y="380.97101"
+ x="230.625" /><path
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path12730"
+ class="st12"
+ d="m 261.695,394.471 v -3.94" /></g><g
+ id="shape2201-1696"
+ v:mID="2201"
+ v:groupContext="shape"
+ v:layerMember="1"
+ transform="rotate(45,683.9744,1168.4508)">
+ <title
+ id="title12749">Sheet.2201</title>
+ <v:userDefs>
+ <v:ud
+ v:nameU="SubType"
+ v:val="VT0(0):26" />
+ </v:userDefs>
+ </g><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect12768"
+ class="st4"
+ height="9.28125"
+ width="38.8377"
+ y="435.79703"
+ x="456.16299" /><rect
+ x="274.79401"
+ y="396.05804"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect12821"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="755.50098"
+ y="446.32401"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect12861"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1119.3"
+ y="388.58304"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect12870"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="963"
+ y="127.49402"
+ width="40.317299"
+ height="19.4767"
+ class="st10"
+ id="rect13021"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="962.08899"
+ y="178.85802"
+ width="33.321201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13032"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="967.18903"
+ y="186.32304"
+ class="st11"
+ v:langID="1033"
+ id="text13034"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 2</text>
+
+<rect
+ x="939.41602"
+ y="77.994026"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect13041"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 965.167,160.23802 h 32.71"
+ class="st1"
+ id="path13050"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="715.96301"
+ y="468.737"
+ width="26.537201"
+ height="19.066601"
+ class="st4"
+ id="rect13120"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="684.34802"
+ y="166.909"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect13131"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="971.44"
+ y="359.24405"
+ width="30.375"
+ height="19.4767"
+ class="st10"
+ id="rect13142"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="928.17902"
+ y="378.72104"
+ width="46.0709"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13153"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="932.98901"
+ y="386.18607"
+ class="st11"
+ v:langID="1033"
+ id="text13155"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 2 HB</text>
+
+<rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13162"
+ class="st15"
+ height="19.125"
+ width="9"
+ y="382.82727"
+ x="975.38" /><path
+ d="m 979.885,384.73302 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21"
+ class="st1"
+ id="path13169"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13178"
+ class="st1"
+ d="m 979.88,399.35802 v 4.11" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13183"
+ class="st1"
+ d="m 979.88,384.73302 v -3.2" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path13188"
+ class="st19"
+ d="m 973.1299,400.6589 12.6004,-16.96197" /><rect
+ x="419.08701"
+ y="396.72104"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect13201"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="666.58698"
+ y="397.72104"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect13217"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="741.547"
+ y="460.21005"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect13228"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13239"
+ class="st15"
+ height="19.125"
+ width="9"
+ y="415.85629"
+ x="490.422" /><path
+ d="m 494.927,417.76202 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21"
+ class="st1"
+ id="path13246"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13255"
+ class="st1"
+ d="m 494.922,432.38702 v 4.11" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13260"
+ class="st1"
+ d="m 494.922,417.76202 v -3.2" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path13265"
+ class="st19"
+ d="m 488.17192,433.68791 12.60033,-16.96197" /><rect
+ x="471.61899"
+ y="400.50803"
+ width="38.035599"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13273"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="476.849"
+ y="407.97305"
+ class="st11"
+ v:langID="1033"
+ id="text13275"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 3b</text>
+
+<rect
+ x="471.345"
+ y="380.82004"
+ width="30.375"
+ height="19.4767"
+ class="st10"
+ id="rect13282"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13293"
+ class="st15"
+ height="19.125"
+ width="9"
+ y="415.67029"
+ x="706.474" /><path
+ d="m 710.979,417.57602 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21"
+ class="st1"
+ id="path13300"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13309"
+ class="st1"
+ d="m 710.974,432.20102 v 4.11" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13314"
+ class="st1"
+ d="m 710.974,417.57602 v -3.2" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path13319"
+ class="st19"
+ d="m 704.22392,433.50191 12.60033,-16.96197" /><rect
+ x="693.19"
+ y="399.81802"
+ width="38.035599"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13327"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="698.41998"
+ y="407.28305"
+ class="st11"
+ v:langID="1033"
+ id="text13329"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 3a</text>
+
+<rect
+ x="697.021"
+ y="379.49405"
+ width="30.375"
+ height="19.4767"
+ class="st10"
+ id="rect13336"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 178.52575,424.60002 H 149.35743"
+ class="st13"
+ id="path13345"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.60871387;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect
+ x="758.59601"
+ y="268.47104"
+ width="42.932701"
+ height="10.125"
+ class="st10"
+ id="rect13385"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 382.65,434.22102 v 102.24"
+ class="st1"
+ id="path13392"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 796.68,498.73502 v 95.27"
+ class="st1"
+ id="path13397"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="412.24399"
+ y="77.994026"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect13514"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13525"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="271.77902"
+ x="453.57401" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13530"
+ class="st1"
+ d="m 456.48445,286.33157 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13535"
+ class="st1"
+ d="m 468.1262,286.33157 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect
+ x="445.5"
+ y="262.52701"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect13543"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="444.965"
+ y="288.72101"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect13554"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path13601"
+ class="st19"
+ d="m 439.047,110.24343 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13606"
+ class="st3"
+ cy="119.218"
+ cx="-452.67484"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13611"
+ class="st3"
+ cy="113.56743"
+ cx="-452.67484"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13616"
+ class="st3"
+ cy="107.25211"
+ cx="-452.67484"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13621"
+ class="st3"
+ cy="100.60441"
+ cx="-452.67484"
+ transform="scale(-1,1)" /><path
+ d="m 453.938,107.32602 h 13.5 v 13.77"
+ class="st1"
+ id="path13627"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13634"
+ class="st15"
+ height="13.7462"
+ width="6.4687901"
+ y="125.52184"
+ x="449.578" /><path
+ d="m 452.81,126.89102 2.1,0.88 -4.2,1.75 4.2,1.75 -4.2,1.75 4.2,1.75 -4.2,1.76 2.1,0.87"
+ class="st1"
+ id="path13641"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13650"
+ class="st1"
+ d="m 452.812,137.40302 v 2.96" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13655"
+ class="st1"
+ d="m 452.812,126.89102 v -2.3" /><path
+ d="m 454.4296,142.78102 h -3.23 l 1.61,2.43 z"
+ class="st3"
+ id="path13662"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 452.8122,142.78502 v -2.43"
+ class="st1"
+ id="path13667"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 452.813,124.59202 v -4.39"
+ class="st1"
+ id="path13674"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 453.938,113.54302 h 7.31 v 13.77"
+ class="st1"
+ id="path13679"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 522.599,173.26302 h -61.35 v -47.03"
+ class="st1"
+ id="path13684"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13691"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="164.114"
+ x="488.25" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13696"
+ class="st1"
+ d="m 491.16045,178.66657 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13701"
+ class="st1"
+ d="m 502.8022,178.66657 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect
+ x="538.875"
+ y="272.38501"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect13709"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="538.875"
+ y="277.78601"
+ class="st18"
+ v:langID="1033"
+ id="text13711"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 6</text>
+
+<rect
+ x="537.75854"
+ y="272.25854"
+ width="16.729277"
+ height="6.7329569"
+ rx="3.8606024"
+ ry="3.3664784"
+ class="st24"
+ id="rect13716"
+ style="fill:none;stroke:#000000;stroke-width:0.26704329;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="547.92902"
+ y="104.22101"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect13723"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path13732"
+ class="st19"
+ d="m 619.879,110.17843 h -5.8 l -4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13737"
+ class="st3"
+ cy="119.153"
+ cx="606.25116" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13742"
+ class="st3"
+ cy="113.50244"
+ cx="606.25116" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13747"
+ class="st3"
+ cy="107.1871"
+ cx="606.25116" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13752"
+ class="st3"
+ cy="100.53941"
+ cx="606.25116" /><path
+ d="m 590.62,112.94002 v -5.91 h 14.63"
+ class="st1"
+ id="path13758"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13765"
+ class="st15"
+ height="13.7462"
+ width="6.4687901"
+ y="125.72183"
+ x="603" /><path
+ d="m 606.232,127.09102 2.1,0.88 -4.2,1.75 4.2,1.75 -4.2,1.75 4.2,1.75 -4.2,1.76 2.1,0.87"
+ class="st1"
+ id="path13772"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13781"
+ class="st1"
+ d="m 606.234,137.60302 v 2.96" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path13786"
+ class="st1"
+ d="m 606.234,127.09102 v -2.3" /><path
+ d="m 607.8516,142.98102 h -3.23 l 1.61,2.43 z"
+ class="st3"
+ id="path13793"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 606.2342,142.98502 v -2.43"
+ class="st1"
+ id="path13798"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 606.234,124.79202 v -4.39"
+ class="st1"
+ id="path13805"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 628.643,138.72602 h -9.28 v 20.62"
+ class="st1"
+ id="path13810"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path13817"
+ class="st19"
+ d="m 642.938,135.10743 h -5.8 l -4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13822"
+ class="st3"
+ cy="144.082"
+ cx="629.31018" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13827"
+ class="st3"
+ cy="138.43144"
+ cx="629.31018" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13832"
+ class="st3"
+ cy="132.1161"
+ cx="629.31018" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse13837"
+ class="st3"
+ cy="125.4684"
+ cx="629.31018" /><rect
+ x="610.21301"
+ y="74.971024"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect13845"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 579.375,203.22102 h 49.96 v -57.81"
+ class="st1"
+ id="path13854"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 536.08,280.44002 h 34.19 v -68.22"
+ class="st1"
+ id="path13859"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 562.388,186.35602 h -11.14 v 10.34"
+ class="st1"
+ id="path13864"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 570.263,195.34602 h -11.14 v 10.34"
+ class="st1"
+ id="path13869"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 581.513,203.21002 h -11.25 v 10.34"
+ class="st1"
+ id="path13874"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="0"
+ y="0"
+ width="400.5"
+ height="23.941799"
+ class="st10"
+ id="rect13881"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="817.92902"
+ y="250.42201"
+ width="46.0709"
+ height="15.375"
+ class="st4"
+ id="rect13890"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="129.653"
+ y="106.66301"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13923"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="133.843"
+ y="114.12804"
+ class="st11"
+ v:langID="1033"
+ id="text13925"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 1</text>
+
+<rect
+ x="221.91299"
+ y="105.34602"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13932"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="226.103"
+ y="112.81104"
+ class="st11"
+ v:langID="1033"
+ id="text13934"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 2</text>
+
+<rect
+ x="394.8349"
+ y="142.24677"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13941"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="399.0249"
+ y="149.71179"
+ class="st11"
+ v:langID="1033"
+ id="text13943"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 3</text>
+
+<rect
+ x="421.64062"
+ y="91.472122"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13950"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="425.83063"
+ y="98.937149"
+ class="st11"
+ v:langID="1033"
+ id="text13952"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 4</text>
+
+<rect
+ x="610.172"
+ y="95.221016"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13959"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="614.362"
+ y="102.68604"
+ class="st11"
+ v:langID="1033"
+ id="text13961"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 5</text>
+
+<rect
+ x="634.922"
+ y="121.09601"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13968"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="639.112"
+ y="128.56104"
+ class="st11"
+ v:langID="1033"
+ id="text13970"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 6</text>
+
+<rect
+ x="705.00403"
+ y="106.51001"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13977"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="709.19397"
+ y="113.97504"
+ class="st11"
+ v:langID="1033"
+ id="text13979"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 7</text>
+
+<rect
+ x="825.14203"
+ y="175.91501"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13986"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="829.33197"
+ y="183.38004"
+ class="st11"
+ v:langID="1033"
+ id="text13988"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 8</text>
+
+<rect
+ x="939.797"
+ y="98.033012"
+ width="26.1562"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect13995"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="943.987"
+ y="105.49803"
+ class="st11"
+ v:langID="1033"
+ id="text13997"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 9</text>
+
+<rect
+ x="976.21997"
+ y="108.72101"
+ width="32.0625"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14004"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="981.13"
+ y="116.18604"
+ class="st11"
+ v:langID="1033"
+ id="text14006"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 10</text>
+
+<rect
+ x="1104.47"
+ y="150.73302"
+ width="32.0625"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14013"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1109.38"
+ y="158.19804"
+ class="st11"
+ v:langID="1033"
+ id="text14015"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 11</text>
+
+<rect
+ x="1119.3"
+ y="408.23303"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14031"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1123.6801"
+ y="415.69806"
+ class="st11"
+ v:langID="1033"
+ id="text14033"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 1</text>
+
+<rect
+ x="1058.25"
+ y="397.84604"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14040"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1062.63"
+ y="405.31107"
+ class="st11"
+ v:langID="1033"
+ id="text14042"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 2</text>
+
+<rect
+ x="817.21301"
+ y="462.42603"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14049"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="821.59302"
+ y="469.89105"
+ class="st11"
+ v:langID="1033"
+ id="text14051"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 3</text>
+
+<rect
+ x="644.021"
+ y="398.97104"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14058"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="648.401"
+ y="406.43607"
+ class="st11"
+ v:langID="1033"
+ id="text14060"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 5</text>
+
+<rect
+ x="489.46301"
+ y="442.87503"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14067"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="493.84302"
+ y="450.34006"
+ class="st11"
+ v:langID="1033"
+ id="text14069"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 6</text>
+
+<rect
+ x="490.73102"
+ y="438.24805"
+ width="26.537201"
+ height="19.4767"
+ class="st10"
+ id="rect14076"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="244.323"
+ y="402.39102"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14087"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="248.703"
+ y="409.85605"
+ class="st11"
+ v:langID="1033"
+ id="text14089"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 7</text>
+
+<rect
+ x="154.59399"
+ y="405.57904"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14096"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="158.974"
+ y="413.04407"
+ class="st11"
+ v:langID="1033"
+ id="text14098"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 8</text>
+
+<rect
+ x="765.91498"
+ y="398.59903"
+ width="26.537201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14114"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="770.29498"
+ y="406.06406"
+ class="st11"
+ v:langID="1033"
+ id="text14116"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 4</text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect14130"
+ class="st10"
+ height="9.28125"
+ width="13.7812"
+ y="141.45201"
+ x="1122.75" /><rect
+ x="744.60901"
+ y="120.51901"
+ width="13.7812"
+ height="9.28125"
+ class="st10"
+ id="rect14145"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect14161"
+ class="st10"
+ height="9.28125"
+ width="13.7812"
+ y="397.42603"
+ x="1141.66" /><path
+ d="m 749.41127,56.250203 5.62328,-8.99727"
+ class="st12"
+ id="path14256"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1124.8347,56.423203 -5.6232,-8.99727"
+ class="st12"
+ id="path14261"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 1119.2037,47.42293 755.03374,47.249903"
+ class="st12"
+ id="path14266"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="879.51599"
+ y="33.750008"
+ width="69.882797"
+ height="9.28125"
+ class="st10"
+ id="rect14273"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 910.587,47.250017 v -3.94"
+ class="st12"
+ id="path14282"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 336.53627,56.509206 5.62327,-8.99727"
+ class="st12"
+ id="path14287"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 739.28474,56.509203 -5.62328,-8.99727"
+ class="st12"
+ id="path14292"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 733.661,47.510017 h -391.5"
+ class="st12"
+ id="path14297"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="500.39099"
+ y="33.750008"
+ width="69.882797"
+ height="9.28125"
+ class="st10"
+ id="rect14304"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 531.462,47.250017 v -3.94"
+ class="st12"
+ id="path14313"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 148.50027,58.500199 5.62327,-8.99727"
+ class="st12"
+ id="path14318"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 303.91474,58.500203 -5.62328,-8.99727"
+ class="st12"
+ id="path14323"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 298.286,49.500017 H 154.126"
+ class="st12"
+ id="path14328"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="173.25"
+ y="36.000008"
+ width="69.882797"
+ height="9.28125"
+ class="st10"
+ id="rect14335"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 204.32,49.499917 v -3.94"
+ class="st12"
+ id="path14344"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="285"
+ y="108.511"
+ width="46.0709"
+ height="13.6007"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14562"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="292.03"
+ y="117.71199"
+ class="st11"
+ v:langID="1033"
+ id="text14568"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />2<tspan
+ dy="-2.8199999"
+ class="st36"
+ v:baseFontSize="8"
+ id="tspan14564"
+ style="font-size:5.19900322px;baseline-shift:32.49390125%">nd</tspan><tspan
+ dy="1.836"
+ class="st6"
+ id="tspan14566"
+ style="font-size:7.99996805px"> </tspan>Mixer</text>
+
+<rect
+ x="740.25"
+ y="151.37"
+ width="46.0709"
+ height="13.6007"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14575"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="747.06"
+ y="160.57098"
+ class="st11"
+ v:langID="1033"
+ id="text14577"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />1st Mixer</text>
+
+<rect
+ x="367.92899"
+ y="374.12003"
+ width="46.0709"
+ height="13.6007"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect14584"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="374.95901"
+ y="383.32101"
+ class="st11"
+ v:langID="1033"
+ id="text14590"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />2<tspan
+ dy="-2.8199999"
+ class="st36"
+ v:baseFontSize="8"
+ id="tspan14586"
+ style="font-size:5.19900322px;baseline-shift:32.49390125%">nd</tspan><tspan
+ dy="1.836"
+ class="st6"
+ id="tspan14588"
+ style="font-size:7.99996805px"> </tspan>Mixer</text>
+
+<g
+ id="g57621"
+ transform="translate(-33,1.5000172)"><rect
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect14597"
+ class="st20"
+ ry="4.5"
+ rx="4.5"
+ height="13.6007"
+ width="46.0709"
+ y="457.37"
+ x="802.71503" /><text
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"
+ id="text14599"
+ v:langID="1033"
+ class="st11"
+ y="466.57098"
+ x="809.52502"><v:paragraph
+ v:horizAlign="1" /><v:tabList />1st Mixer</text>
+
+</g><rect
+ x="337.5"
+ y="139.5"
+ width="46.0709"
+ height="17.4627"
+ class="st10"
+ id="rect14606"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="667.07501"
+ y="141.75002"
+ width="38.8377"
+ height="13.5"
+ class="st10"
+ id="rect14615"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="732.79602"
+ y="493.74002"
+ width="38.035599"
+ height="8.7313404"
+ class="st10"
+ id="rect14624"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 846.81,477.32602 h 6.94 v -7.48"
+ class="st1"
+ id="path14631"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="670.21899"
+ y="433.073"
+ width="36.5625"
+ height="8.7313404"
+ class="st10"
+ id="rect14638"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="674.21899"
+ y="439.23401"
+ class="st23"
+ v:langID="1033"
+ id="text14640"
+ style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />6.3V Bias</text>
+
+<rect
+ x="422.43799"
+ y="436.34702"
+ width="36.5625"
+ height="8.7313404"
+ class="st10"
+ id="rect14647"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="267.96399"
+ y="432.211"
+ width="38.035599"
+ height="8.7313404"
+ class="st10"
+ id="rect14656"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="717.04901"
+ y="226.49402"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect14814"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="763.33698"
+ y="237.24001"
+ width="38.8377"
+ height="8.7313404"
+ class="st10"
+ id="rect14825"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="342.58701"
+ y="486.77304"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect14834"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="759.66901"
+ y="534.02301"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect14854"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="771.10199"
+ y="216.72099"
+ width="19.295601"
+ height="8.7313404"
+ class="st4"
+ id="rect14874"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path14911"
+ class="st19"
+ d="m 996.745,160.23843 h 5.8 l 4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse14916"
+ class="st3"
+ cy="169.21301"
+ cx="-1010.3728"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse14921"
+ class="st3"
+ cy="163.56244"
+ cx="-1010.3728"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse14926"
+ class="st3"
+ cy="157.2471"
+ cx="-1010.3728"
+ transform="scale(-1,1)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse14931"
+ class="st3"
+ cy="150.59941"
+ cx="-1010.3728"
+ transform="scale(-1,1)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path14939"
+ class="st1"
+ d="m 743.49,138.48202 0.88,-2.1 1.75,4.2 1.75,-4.2 1.75,4.2 1.75,-4.2 1.76,4.2 0.87,-2.1" /><path
+ d="m 754.002,138.48002 h 2.96"
+ class="st1"
+ id="path14948"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 743.49,138.48002 h -2.3"
+ class="st1"
+ id="path14953"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path14960"
+ class="st3"
+ d="m 759.37966,136.86262 v 3.23 l 2.43,-1.61 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path14965"
+ class="st1"
+ d="m 759.38366,138.48002 h -2.43" /><rect
+ x="1053"
+ y="137.97102"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect14973"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1043.72"
+ y="162"
+ width="38.652802"
+ height="8.7313404"
+ class="st10"
+ id="rect14984"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1077.295,179.56602 v -9 h 13.82"
+ class="st1"
+ id="path14991"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1030.03,195.45802 h -9.34 v -19.48"
+ class="st1"
+ id="path14996"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1077.29,177.90802 v 17.59 h -50.89"
+ class="st1"
+ id="path15001"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1056.37,195.53102 -11.53,-7.69 v 15.38 z"
+ class="st3"
+ id="path15006"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1051.04"
+ y="199.49402"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect15013"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 961.97692,108.12869 c 0.82176,4.05931 1.96281,7.91626 2.58035,12.23843 0.87824,6.12254 0.72031,13.16646 -1.20816,18.00394 -0.96877,2.41331 -2.37022,4.27591 -3.46678,5.98504 l -0.16883,0.31622"
+ class="st38"
+ id="path15022"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><path
+ d="m 999.6205,118.84507 c -0.409,3.36712 -1.1516,6.62885 -1.3345,10.16814 -0.2638,5.00828 0.5892,10.60318 2.9676,14.14765 1.0795,1.5928 2.4617,2.77404 3.6461,3.85859 l 0.2396,0.27176"
+ class="st38"
+ id="path15027"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><rect
+ x="1032.98"
+ y="122.22101"
+ width="36"
+ height="13.3594"
+ class="st10"
+ id="rect15034"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1036.98"
+ y="127.10004"
+ class="st23"
+ v:langID="1033"
+ id="text15038"
+ style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />600 MHz -<v:newlineChar /><tspan
+ x="1036.98"
+ dy="14.400001"
+ class="st6"
+ id="tspan15036"
+ style="font-size:5.99990416px">8 GHz</tspan></text>
+
+<rect
+ x="1010.25"
+ y="202.5"
+ width="42.932701"
+ height="13.3594"
+ class="st10"
+ id="rect15045"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1014.25"
+ y="210.979"
+ class="st23"
+ v:langID="1033"
+ id="text15047"
+ style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />1 - 600 MHz</text>
+
+<path
+ d="m 1010.25,223.47102 v 5.69"
+ class="st13"
+ id="path15052"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse15059"
+ class="st3"
+ cy="473.01602"
+ cx="969.79413" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse15064"
+ class="st3"
+ cy="464.96573"
+ cx="969.79413" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.2711"
+ id="ellipse15069"
+ class="st3"
+ cy="456.49164"
+ cx="969.79413" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path15074"
+ class="st19"
+ d="m 983.47,465.63257 h -5.8 l -4.92,-6.87" /><path
+ d="m 1109.05,265.51002 h -11.25 v -14.34"
+ class="st1"
+ id="path15080"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1105.88,265.51602 h 9 v 76.08"
+ class="st1"
+ id="path15085"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1114.87,330.08302 v 5.69"
+ class="st13"
+ id="path15090"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect
+ x="1014.8"
+ y="370.84604"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect15102"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1008.21"
+ y="398.51901"
+ width="38.035599"
+ height="8.7313404"
+ class="st10"
+ id="rect15113"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="969.19"
+ y="424.98203"
+ width="34.3125"
+ height="19.066601"
+ class="st10"
+ id="rect15122"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="970.78003"
+ y="445.09604"
+ width="33.321201"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect15133"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="976.32001"
+ y="452.56107"
+ class="st11"
+ v:langID="1033"
+ id="text15135"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Sw 11</text>
+
+<rect
+ x="1040.77"
+ y="419.22101"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect15142"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1040.77"
+ y="424.62201"
+ class="st18"
+ v:langID="1033"
+ id="text15144"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text>
+
+<rect
+ x="1040.77"
+ y="424.77701"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect15151"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1040.77"
+ y="430.17801"
+ class="st18"
+ v:langID="1033"
+ id="text15153"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text>
+
+<rect
+ x="749.16498"
+ y="418.09601"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect15160"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="749.16498"
+ y="423.49701"
+ class="st18"
+ v:langID="1033"
+ id="text15162"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text>
+
+<rect
+ x="749.16498"
+ y="423.79001"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect15169"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="749.16498"
+ y="429.19101"
+ class="st18"
+ v:langID="1033"
+ id="text15171"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text>
+
+<path
+ d="m 1055.812,434.88602 v 30.79 h -73.69"
+ class="st1"
+ id="path15176"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path15183"
+ class="st19"
+ d="m 1069.315,425.37643 h -5.8 l -4.92,-6.87" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse15188"
+ class="st3"
+ cy="434.35101"
+ cx="1055.6871" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse15193"
+ class="st3"
+ cy="428.70044"
+ cx="1055.6871" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse15198"
+ class="st3"
+ cy="422.38513"
+ cx="1055.6871" /><circle
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="0.997159"
+ id="ellipse15203"
+ class="st3"
+ cy="415.73743"
+ cx="1055.6871" /><rect
+ x="1020.9399"
+ y="474.34601"
+ width="36.5625"
+ height="13.6007"
+ class="st10"
+ id="rect15216"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 960.75,474.34602 v 11.65 h -21.38"
+ class="st1"
+ id="path15223"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1071"
+ y="479.93704"
+ width="36.5625"
+ height="22.5345"
+ class="st4"
+ id="rect15230"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="1071"
+ y="487.14102"
+ class="st22"
+ v:langID="1033"
+ id="text15234"
+ style="font-style:italic;font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />1 MHz –<v:newlineChar /><tspan
+ x="1071"
+ dy="14.400001"
+ class="st6"
+ id="tspan15232"
+ style="font-size:7.99996805px">3.0 GHz</tspan></text>
+
+<path
+ d="m 1068.7493,486.71635 c -2.7297,-1.37605 -5.5834,-2.4355 -8.3063,-4.08053 -3.8481,-2.31436 -7.4552,-5.80857 -8.6304,-9.47485 -0.4886,-1.5559 -0.5488,-3.14932 -0.6297,-4.56705 l -0.051,-0.35148"
+ class="st38"
+ id="path15239"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><path
+ d="m 1035.0012,133.47829 c -6.2623,1.95231 -7.0483,5.89398 -9.3927,10.14048 -1.5343,2.77441 -3.7435,5.68033 -4.1142,10.42525 v 0.35846"
+ class="st38"
+ id="path15244"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><path
+ d="m 742.577,421.51202 h 4.06"
+ class="st1"
+ id="path15249"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 742.577,427.82202 h 4.06"
+ class="st1"
+ id="path15254"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1050.21,428.91102 h 4.06"
+ class="st1"
+ id="path15259"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1050.21,422.55202 h 4.06"
+ class="st1"
+ id="path15264"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 307.31,148.38102 v 78.66"
+ class="st1"
+ id="path15269"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 254.2687,227.14565 53.07453,-0.26144"
+ class="st1"
+ id="path15323"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none" /><rect
+ x="339.75"
+ y="171.72101"
+ width="52.3125"
+ height="21.9375"
+ class="st10"
+ id="rect15439"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><g
+ id="g56342"
+ transform="translate(-31.5,-22.499983)"><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path15279"
+ class="st16"
+ d="m 272.26875,249.54775 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15281"
+ class="st1"
+ d="m 279.01875,249.54775 a 2.25,2.25 0 0 0 4.5,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15283"
+ class="st1"
+ d="m 274.51875,249.54775 a 2.25,2.25 0 1 1 4.5,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15285"
+ class="st1"
+ d="m 272.26875,249.54775 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15290"
+ class="st3"
+ height="15.1875"
+ width="19.620001"
+ y="241.72276"
+ x="239.14874" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15297"
+ class="st17"
+ height="9.28125"
+ width="13.5"
+ y="244.67676"
+ x="244.14375" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15299"
+ v:langID="1033"
+ class="st5"
+ y="251.71777"
+ x="244.14375"><v:paragraph /><v:tabList />PLL</text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path15304"
+ class="st19"
+ d="m 258.76875,249.54775 h 10.11" /><path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1"
+ inkscape:connector-curvature="0"
+ id="path15309"
+ class="st27"
+ d="m 312.06575,269.73175 h -89.86 v -47.25 h 89.86 z" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15316"
+ class="st17"
+ height="9.28125"
+ width="58.682701"
+ y="222.48175"
+ x="223.71075" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15318"
+ v:langID="1033"
+ class="st5"
+ y="229.52277"
+ x="223.71075"><v:paragraph /><v:tabList />LMX2572</text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15328"
+ class="st1"
+ d="m 292.51875,250.02075 v 15.75 h -13.5" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path15333"
+ class="st19"
+ d="m 280.70375,265.75575 h -36.56 v -5.38" /><circle
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.40625"
+ id="ellipse15338"
+ class="st21"
+ cy="249.54773"
+ cx="292.51901" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path15343"
+ class="st19"
+ d="m 218.26875,248.80075 h 17.49" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15350"
+ class="st17"
+ height="18.0126"
+ width="11.8125"
+ y="248.91876"
+ x="223.33176" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15354"
+ v:langID="1033"
+ class="st5"
+ y="255.52179"
+ x="223.33176"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan
+ style="font-size:7.99996805px"
+ id="tspan15352"
+ class="st6"
+ dy="14.400001"
+ x="223.33176">In</tspan></text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15381"
+ class="st17"
+ height="9.28125"
+ width="66.375"
+ y="231.34074"
+ x="223.89375" /><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15512"
+ class="st10"
+ height="12.3166"
+ width="46.0709"
+ y="208.15073"
+ x="236.54575" /><text
+ style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000"
+ id="text15516"
+ v:langID="1033"
+ class="st31"
+ y="216.70773"
+ x="240.54575"><v:paragraph /><v:tabList /><tspan
+ style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline"
+ id="tspan15514"
+ class="st9">Tx LO2</tspan></text>
+
+</g><rect
+ x="313.58099"
+ y="188.59001"
+ width="38.8377"
+ height="15.75"
+ class="st10"
+ id="rect15543"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="253.181"
+ y="509.24203"
+ width="46.0709"
+ height="12.3166"
+ class="st10"
+ id="rect15755"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="283.43234"
+ y="504.01047"
+ class="st31"
+ v:langID="1033"
+ id="text15759"
+ style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList /><tspan
+ class="st9"
+ id="tspan15757"
+ style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline">Rx LO2</tspan></text>
+
+<rect
+ x="1005.75"
+ y="435.74405"
+ width="30.375"
+ height="19.4767"
+ class="st10"
+ id="rect15841"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="974.84003"
+ y="481.09604"
+ width="46.0709"
+ height="10.125"
+ rx="4.5"
+ ry="4.5"
+ class="st20"
+ id="rect15852"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="980.31"
+ y="488.56107"
+ class="st11"
+ v:langID="1033"
+ id="text15854"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />DSA 2 LB</text>
+
+<rect
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15861"
+ class="st15"
+ height="19.125"
+ width="9"
+ y="457.57727"
+ x="1003.63" /><path
+ d="m 1008.135,459.48302 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21"
+ class="st1"
+ id="path15868"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15877"
+ class="st1"
+ d="m 1008.13,474.10802 v 4.11" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15882"
+ class="st1"
+ d="m 1008.13,459.48302 v -3.2" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)"
+ inkscape:connector-curvature="0"
+ id="path15887"
+ class="st19"
+ d="m 1001.3799,475.40891 12.6004,-16.96198" /><rect
+ x="981.67999"
+ y="405.15802"
+ width="39.819099"
+ height="10.125"
+ class="st10"
+ id="rect15895"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="985.67999"
+ y="412.62305"
+ class="st11"
+ v:langID="1033"
+ id="text15897"
+ style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Ganged</text>
+
+<path
+ d="M 995.62,414.72002 H 1008 v 38.25"
+ class="st40"
+ id="path15902"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.5, 2.5" /><path
+ d="m 994.34,414.72102 h -14.62 v -10.05"
+ class="st40"
+ id="path15907"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.5, 2.5" /><rect
+ x="83.836998"
+ y="157.09601"
+ width="25.825701"
+ height="10.125"
+ class="st10"
+ id="rect15914"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15923"
+ class="st1"
+ d="m 87.75,139.30222 h 22.88 v -2.85" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15928"
+ class="st1"
+ d="m 116.4375,134.46102 h -5.8 v 3.82" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15933"
+ class="st1"
+ d="m 87.4144,148.60102 h 22.88 v 2.85" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15938"
+ class="st1"
+ d="m 114.795,151.67877 h -4.49 v -1.91" /><path
+ d="m 82.97196,137.41102 v 3.82"
+ class="st1"
+ id="path15948"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 84.88196,141.23102 a 2.39003,2.39003 0 0 1 0,-3.82"
+ class="st1"
+ id="path15950"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 87.75,139.32304 H 83.93"
+ class="st1"
+ id="path15955"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 82.97,139.32304 H 80.1"
+ class="st1"
+ id="path15957"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 82.97196,146.68102 v 3.82"
+ class="st1"
+ id="path15968"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 84.88196,150.50102 a 2.39003,2.39003 0 0 1 0,-3.82"
+ class="st1"
+ id="path15970"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 87.75,148.59304 H 83.93"
+ class="st1"
+ id="path15975"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 82.97,148.59304 H 80.1"
+ class="st1"
+ id="path15977"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15983"
+ class="st1"
+ d="m 55.125,139.32102 h 25.58" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15988"
+ class="st1"
+ d="m 58.30105,148.62154 h 22.14" /><g
+ id="shape2816-3202"
+ v:mID="2816"
+ v:groupContext="shape"
+ v:layerMember="1"
+ transform="rotate(45,833.77972,574.24788)">
+ <title
+ id="title16005">Sheet.2816</title>
+ <v:userDefs>
+ <v:ud
+ v:nameU="SubType"
+ v:val="VT0(0):26" />
+ </v:userDefs>
+ </g><path
+ d="m 73.356,429.43902 h 25.83 v -2.85"
+ class="st1"
+ id="path16023"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 102.375,424.67002 h -3.37 v 3.82"
+ class="st1"
+ id="path16028"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="M 73.02,438.73002 H 97.1 v 2.85"
+ class="st1"
+ id="path16033"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 102.375,441.87002 h -5.28 v -1.91"
+ class="st1"
+ id="path16038"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16048"
+ class="st1"
+ d="m 69.47196,427.54002 v 3.82" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16050"
+ class="st1"
+ d="m 71.38196,431.36002 a 2.39003,2.39003 0 0 1 0,-3.82" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16055"
+ class="st1"
+ d="M 74.25,429.45204 H 70.43" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16057"
+ class="st1"
+ d="M 69.47,429.45204 H 66.6" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16068"
+ class="st1"
+ d="m 68.79596,436.81002 v 3.82" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16070"
+ class="st1"
+ d="m 70.70596,440.63002 a 2.39003,2.39003 0 0 1 0,-3.82" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16075"
+ class="st1"
+ d="m 73.574,438.72204 h -3.82" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16077"
+ class="st1"
+ d="m 68.794,438.72204 h -2.87" /><path
+ d="m 58.5606,429.45702 h 8.04"
+ class="st1"
+ id="path16083"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 61.4701,438.75802 h 4.68"
+ class="st1"
+ id="path16088"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="74.836998"
+ y="445.20605"
+ width="25.825701"
+ height="12.7949"
+ class="st10"
+ id="rect16124"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16187"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="230.125"
+ x="914.03699" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16192"
+ class="st1"
+ d="m 916.06636,234.16444 8.44,-0.04 4.33,8.7" /><rect
+ x="914.03699"
+ y="250.47102"
+ width="26.1562"
+ height="19.4767"
+ class="st4"
+ id="rect16200"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 969.75,474.48702 v 61.29 H 949.5"
+ class="st1"
+ id="path16209"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 960.75,476.34602 v -11.25 h 7.87"
+ class="st1"
+ id="path16214"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 854.123,513.43502 v -19.48 h -7.48"
+ class="st1"
+ id="path16219"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 854.09,509.98002 v 25.86 h 95.41"
+ class="st1"
+ id="path16224"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="949.20398"
+ y="527.57397"
+ width="11.25"
+ height="6.6812701"
+ class="st4"
+ id="rect16269"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="949.20398"
+ y="532.97498"
+ class="st18"
+ v:langID="1033"
+ id="text16271"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 3</text>
+
+<rect
+ x="947.25"
+ y="527.67603"
+ width="14.625"
+ height="6.75"
+ rx="3.375"
+ ry="3.375"
+ class="st24"
+ id="rect16276"
+ style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="884.25"
+ y="513.98999"
+ width="63"
+ height="8.7313404"
+ class="st4"
+ id="rect16283"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="711"
+ y="196.37601"
+ width="52.3125"
+ height="21.9375"
+ class="st10"
+ id="rect16294"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 530.959,435.79302 v -14.48 h -16.51"
+ class="st1"
+ id="path16311"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 641.334,421.60002 h -14.63 v 12.79"
+ class="st1"
+ id="path16316"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 619.104,495.53302 h 11.98 v -56.22"
+ class="st1"
+ id="path16321"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 622.453,495.54102 h -96.42 v -36"
+ class="st1"
+ id="path16326"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="616.26599"
+ y="487.03601"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect16333"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="616.26599"
+ y="492.43698"
+ class="st18"
+ v:langID="1033"
+ id="text16335"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 3</text>
+
+<rect
+ x="557.45001"
+ y="475.01901"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect16380"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="247.6875"
+ y="201.34676"
+ width="58.682701"
+ height="21.9375"
+ class="st4"
+ id="rect16409"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="729.77197"
+ y="572.06299"
+ width="58.682701"
+ height="21.9375"
+ class="st4"
+ id="rect16467"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="326.159"
+ y="513.263"
+ width="58.682701"
+ height="21.9375"
+ class="st4"
+ id="rect16612"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="194.674"
+ y="95.908012"
+ width="25.825701"
+ height="10.125"
+ class="st10"
+ id="rect16652"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="614.77478"
+ y="487.12885"
+ width="15.545476"
+ height="6.7423978"
+ rx="3.5874174"
+ ry="3.3711989"
+ class="st24"
+ id="rect16659"
+ style="fill:none;stroke:#000000;stroke-width:0.2576021;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 525.985,461.25002 v -33.75 h -11.53"
+ class="st1"
+ id="path16664"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 641.209,427.85002 h -10.13 v 12.79"
+ class="st1"
+ id="path16669"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="583.271"
+ y="553.08698"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect16676"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="619.271"
+ y="535.63495"
+ width="13.5"
+ height="6.6812701"
+ class="st4"
+ id="rect16694"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="619.271"
+ y="541.03595"
+ class="st18"
+ v:langID="1033"
+ id="text16696"
+ style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 4</text>
+
+<rect
+ x="618.15271"
+ y="535.50671"
+ width="16.268867"
+ height="6.7365861"
+ rx="3.754354"
+ ry="3.368293"
+ class="st24"
+ id="rect16701"
+ style="fill:none;stroke:#000000;stroke-width:0.26341397;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="553.48602"
+ y="526.5"
+ width="46.0709"
+ height="8.7313404"
+ class="st4"
+ id="rect16713"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 520.219,469.33302 v -36 h -5.77"
+ class="st1"
+ id="path16722"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 614.771,544.50002 h -94.5 v -75.09"
+ class="st1"
+ id="path16727"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16734"
+ class="st3"
+ height="17.4627"
+ width="17.4627"
+ y="536.14496"
+ x="591.88" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16739"
+ class="st1"
+ d="m 594.79045,550.69757 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16744"
+ class="st1"
+ d="m 606.4322,550.69757 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><path
+ d="m 615.007,544.50002 h 20.62 v -88.03"
+ class="st1"
+ id="path16779"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 641.209,433.48002 h -5.58 v 23.27"
+ class="st1"
+ id="path16784"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="305.81699"
+ y="375.75705"
+ width="58.682701"
+ height="38.243401"
+ class="st4"
+ id="rect16818"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="135"
+ y="395.07904"
+ width="30.9128"
+ height="19.4767"
+ class="st10"
+ id="rect16838"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="137.69"
+ y="432.26901"
+ width="36.5625"
+ height="8.7313404"
+ class="st10"
+ id="rect16849"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16858"
+ class="st3"
+ height="11.5313"
+ width="11.5313"
+ y="435.23502"
+ x="210.938" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16863"
+ class="st1"
+ d="m 212.85672,438.71863 4.98,-0.02 2.55,5.13" /><rect
+ x="162"
+ y="445.5"
+ width="47.970001"
+ height="12.3166"
+ class="st4"
+ id="rect16871"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="208.5"
+ y="445.50003"
+ width="46.0709"
+ height="13.5"
+ class="st4"
+ id="rect16880"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16889"
+ class="st3"
+ height="11.5313"
+ width="11.5313"
+ y="435.23502"
+ x="192.73" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16894"
+ class="st1"
+ d="m 201.96296,438.61183 -4.98,-0.02 -2.55,5.13" /><rect
+ x="191.924"
+ y="389.25"
+ width="47.970001"
+ height="12.3166"
+ class="st4"
+ id="rect16902"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16911"
+ class="st3"
+ height="11.5313"
+ width="11.5313"
+ y="400.21902"
+ x="-210.51601"
+ transform="scale(-1,1)" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16916"
+ class="st1"
+ d="m 201.28304,403.59583 4.98,-0.02 2.55,5.13" /><rect
+ x="226.211"
+ y="146.25703"
+ width="58.682701"
+ height="38.243401"
+ class="st4"
+ id="rect16951"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16966"
+ class="st3"
+ height="11.5313"
+ width="11.5313"
+ y="145.617"
+ x="183.938" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path16971"
+ class="st1"
+ d="m 185.85672,149.10063 4.98,-0.02 2.55,5.13" /><rect
+ x="135"
+ y="155.882"
+ width="47.970001"
+ height="12.3166"
+ class="st4"
+ id="rect16979"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="181.5"
+ y="155.88301"
+ width="46.0709"
+ height="13.5"
+ class="st4"
+ id="rect16988"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16997"
+ class="st3"
+ height="11.5313"
+ width="11.5313"
+ y="145.617"
+ x="165.73" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path17002"
+ class="st1"
+ d="m 174.96296,148.99383 -4.98,-0.02 -2.55,5.13" /><rect
+ x="153.467"
+ y="97.340996"
+ width="47.970001"
+ height="12.3166"
+ class="st4"
+ id="rect17010"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17019"
+ class="st3"
+ height="11.5313"
+ width="11.5313"
+ y="107.563"
+ x="166.556" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path17024"
+ class="st1"
+ d="m 175.78896,110.93983 -4.98,-0.02 -2.55,5.13" /><rect
+ x="423"
+ y="132.18701"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17032"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="423"
+ y="141.75002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17041"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="423"
+ y="126.00002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17050"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="423"
+ y="118.12502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17059"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="452.25"
+ y="95.625015"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17068"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="452.25"
+ y="101.25002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17077"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="451.04001"
+ y="108.00002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17086"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="450"
+ y="117.05501"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17095"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="594"
+ y="95.625015"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17104"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="594"
+ y="101.25002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17113"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="594"
+ y="107.43701"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17122"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="603.47699"
+ y="119.06302"
+ width="19.295601"
+ height="4.87501"
+ class="st10"
+ id="rect17131"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="618.10199"
+ y="119.25002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17140"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="618.10199"
+ y="126.56201"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17149"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="618.10199"
+ y="133.31201"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17158"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="627.10199"
+ y="141.75002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17167"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="730.60199"
+ y="121.50002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17176"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="731.25"
+ y="132.75002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17185"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="731.25"
+ y="126.56201"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17194"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="730.60199"
+ y="140.62502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17203"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="812.25"
+ y="181.12502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17212"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="812.16498"
+ y="204.75002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17221"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="822.20398"
+ y="191.25002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17230"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="944.43701"
+ y="158.17502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17239"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="944.35199"
+ y="170.76302"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17248"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="944.43701"
+ y="150.99901"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17257"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="943.70398"
+ y="142.67502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17266"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1008"
+ y="144.00002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17275"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1008"
+ y="171.00002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17284"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1010.25"
+ y="150.27802"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17293"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1010.25"
+ y="157.50002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17302"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1084.5"
+ y="164.25002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17311"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1047.17"
+ y="218.25"
+ width="38.652802"
+ height="8.7313404"
+ class="st10"
+ id="rect17320"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1086.16"
+ y="156.93701"
+ width="19.295601"
+ height="9"
+ class="st10"
+ id="rect17329"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1095.75"
+ y="176.62502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17338"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1086.16"
+ y="149.39401"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17347"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1103.45"
+ y="416.25003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17356"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1092.38"
+ y="409.50003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17365"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1104.75"
+ y="423.00003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17374"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1104.75"
+ y="428.62503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17383"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1044"
+ y="416.25003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17392"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1044"
+ y="423.00003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17401"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1053"
+ y="435.37503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17410"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1021.71"
+ y="484.01901"
+ width="38.035599"
+ height="8.7313404"
+ class="st10"
+ id="rect17419"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="957.85199"
+ y="450.00003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17428"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="957.85199"
+ y="459.00003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17437"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="958.5"
+ y="473.62503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17446"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="1053"
+ y="408.37503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17455"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="729.95398"
+ y="407.25003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17464"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="738"
+ y="415.09702"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17473"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="738"
+ y="421.87503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17482"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="729"
+ y="433.12503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17491"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="843.85498"
+ y="470.57104"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17500"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="843.75"
+ y="479.25003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17509"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="843.75"
+ y="488.25003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17518"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="631.56097"
+ y="428.06204"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17545"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="630.521"
+ y="422.43704"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17554"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="630.521"
+ y="416.25003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17563"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="631.56097"
+ y="408.37503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17572"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="511.271"
+ y="427.50003"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17581"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="512.22498"
+ y="421.87503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17590"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="511.271"
+ y="408.37503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17599"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="512.22498"
+ y="415.12503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17608"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="540.521"
+ y="380.24933"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17619"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 542.55036,384.28874 8.44,-0.04 4.33,8.7"
+ class="st1"
+ id="path17624"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17632"
+ class="st4"
+ height="17.4627"
+ width="23.0355"
+ y="398.259"
+ x="542.67249" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17646"
+ class="st4"
+ height="17.4627"
+ width="26.537201"
+ y="398.78702"
+ x="591.73602" /><rect
+ x="592.11426"
+ y="379.88803"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17657"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 606.91664,383.92744 -8.44,-0.04 -4.33,8.7"
+ class="st1"
+ id="path17662"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17673"
+ class="st4"
+ height="19.4767"
+ width="23.0355"
+ y="453.02304"
+ x="587.50299" /><rect
+ x="590.28937"
+ y="434.16071"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17684"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 605.09179,438.20014 -8.44,-0.04 -4.33,8.7"
+ class="st1"
+ id="path17689"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="542.5282"
+ y="433.55542"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17700"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 544.55755,437.59484 8.44,-0.04 4.33,8.7"
+ class="st1"
+ id="path17705"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17713"
+ class="st4"
+ height="19.4767"
+ width="20.619301"
+ y="451.82404"
+ x="542.48999" /><rect
+ x="542.47241"
+ y="484.39441"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17727"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 544.50179,488.43384 8.44,-0.04 4.33,8.7"
+ class="st1"
+ id="path17732"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17740"
+ class="st4"
+ height="19.4767"
+ width="20.619301"
+ y="502.66306"
+ x="540.521" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17754"
+ class="st4"
+ height="19.4767"
+ width="23.0355"
+ y="503.69205"
+ x="587.50299" /><rect
+ x="590.28937"
+ y="484.82971"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17765"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 605.09179,488.86914 -8.44,-0.04 -4.33,8.7"
+ class="st1"
+ id="path17770"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="240.10199"
+ y="413.43802"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17797"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="239.54001"
+ y="428.06204"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17806"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="174.85201"
+ y="429.46902"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17815"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="174.20399"
+ y="414.56302"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17824"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="145.60201"
+ y="123.75002"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17833"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="145.60201"
+ y="138.14401"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17842"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="214.22701"
+ y="136.68802"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17851"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="214.70399"
+ y="124.36502"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17860"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="758.25"
+ y="282.53302"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17887"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="744.75"
+ y="282.37503"
+ width="19.295601"
+ height="6.75"
+ class="st10"
+ id="rect17896"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="873"
+ y="476.02032"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17907"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 875.02936,480.05974 8.44,-0.04 4.33,8.7"
+ class="st1"
+ id="path17912"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17920"
+ class="st4"
+ height="17.4627"
+ width="21.375"
+ y="493.73904"
+ x="873" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17934"
+ class="st4"
+ height="17.4627"
+ width="21.375"
+ y="494.02103"
+ x="918" /><rect
+ x="919.95618"
+ y="476.02103"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17945"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 934.75856,480.06044 -8.44,-0.04 -4.33,8.7"
+ class="st1"
+ id="path17950"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="873"
+ y="527.3183"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17961"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 875.02936,531.35774 8.44,-0.04 4.33,8.7"
+ class="st1"
+ id="path17966"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17974"
+ class="st4"
+ height="17.4627"
+ width="21.375"
+ y="545.03699"
+ x="873" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect17988"
+ class="st4"
+ height="17.4627"
+ width="21.375"
+ y="542.87097"
+ x="919.125" /><rect
+ x="921.08118"
+ y="524.87097"
+ width="17.4627"
+ height="17.4627"
+ class="st3"
+ id="rect17999"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 935.88356,528.91044 -8.44,-0.04 -4.33,8.7"
+ class="st1"
+ id="path18004"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1086.16,156.94002 h 1.71 v 7.31"
+ class="st1"
+ id="path18020"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 1087.9856,164.25002 8.83,2e-5"
+ class="st1"
+ id="path18025"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path18090"
+ class="st1"
+ d="m 134.859,444.79802 v -15.19" /><path
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path18095"
+ class="st21"
+ d="m 134.85925,440.29752 2.25,-4.5 h -4.5 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path18100"
+ class="st1"
+ d="m 137.953,440.29752 h -5.91" /><rect
+ x="131.519"
+ y="452.25003"
+ width="29.3561"
+ height="13.5"
+ class="st10"
+ id="rect18108"
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="141.02901"
+ y="457.20004"
+ class="st23"
+ v:langID="1033"
+ id="text18112"
+ style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph
+ v:horizAlign="1" /><v:tabList />Pwr <tspan
+ x="136.35899"
+ dy="14.400001"
+ class="st6"
+ id="tspan18110"
+ style="font-size:5.99990416px">Limiter</tspan></text>
+
+<path
+ d="m 134.859,429.60902 v -4.78"
+ class="st1"
+ id="path18117"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle
+ cx="-424.75833"
+ cy="135"
+ class="st3"
+ id="ellipse18122"
+ r="1.05469"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="rotate(-90)" /><path
+ d="m 134.999,458.32002 -2.04,-3.82 h 4.08 z"
+ class="st3"
+ id="path18127"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 134.859,454.50002 v -9.7"
+ class="st1"
+ id="path18132"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 130.872,420.75002 v 22.5 h 9 v -22.5 z"
+ class="st27"
+ id="path18137"
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" />
+<g
+ id="shape2253-1834"
+ v:mID="2253"
+ v:groupContext="shape"
+ transform="translate(975.4035,-621.19558)"><title
+ id="title40763">Sheet.2253</title><rect
+ x="0"
+ y="772.875"
+ width="9"
+ height="19.125"
+ class="st15"
+ id="rect40765"
+ style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g
+ id="group2254-1836"
+ transform="rotate(90,807.69667,961.28187)"
+ v:mID="2254"
+ v:groupContext="group"
+ v:layerMember="1"><v:custProps><v:cp
+ v:nameU="Label"
+ v:lbl="Label"
+ v:type="0"
+ v:format="@"
+ v:sortKey=""
+ v:invis="false"
+ v:ask="false"
+ v:langID="1033"
+ v:val="VT4()" /><v:cp
+ v:nameU="Slider_Position"
+ v:lbl="Slider position (0-1)"
+ v:type="2"
+ v:langID="1033"
+ v:val="VT0(0.5):26" /></v:custProps><v:userDefs><v:ud
+ v:nameU="Form"
+ v:val="VT0(1):5" /><v:ud
+ v:nameU="SubType"
+ v:val="VT0(0):26" /><v:ud
+ v:nameU="visDescription"
+ v:val="VT4(Represents a fixed or variable resistor.)" /><v:ud
+ v:nameU="visVersion"
+ v:val="VT0(11):26" /></v:userDefs><title
+ id="title40768">Resistor</title><g
+ id="shape2255-1837"
+ v:mID="2255"
+ v:groupContext="shape"
+ v:layerMember="1"><title
+ id="title40770">Sheet.2255</title><v:userDefs><v:ud
+ v:nameU="SubType"
+ v:val="VT0(0):26" /><v:ud
+ v:nameU="Form"
+ v:val="VT0(1):5" /></v:userDefs><path
+ d="m 0,789.07 1.22,-2.92 2.44,5.85 2.43,-5.85 2.44,5.85 2.44,-5.85 2.44,5.85 1.21,-2.93"
+ class="st1"
+ id="path40772"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g
+ id="shape2256-1840"
+ v:mID="2256"
+ v:groupContext="shape"
+ v:layerMember="1"
+ transform="rotate(-45,5.0429876,792.08413)"><title
+ id="title40775">Sheet.2256</title><v:userDefs><v:ud
+ v:nameU="SubType"
+ v:val="VT0(0):26" /></v:userDefs></g></g><g
+ id="shape2257-1842"
+ v:mID="2257"
+ v:groupContext="shape"
+ transform="rotate(90,801.84667,970.05689)"><title
+ id="title40779">Sheet.2257</title><path
+ d="M 0,792 H 4.11"
+ class="st1"
+ id="path40781"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g
+ id="shape2258-1845"
+ v:mID="2258"
+ v:groupContext="shape"
+ transform="rotate(-90,170.74439,-17.159199)"><title
+ id="title40784">Sheet.2258</title><path
+ d="M 0,792 H 3.2"
+ class="st1"
+ id="path40786"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g
+ id="shape2259-1848"
+ v:mID="2259"
+ v:groupContext="shape"
+ transform="rotate(-53.3929,-132.35949,-486.84399)"><title
+ id="title40789">Sheet.2259</title><path
+ d="M 0,792 H 21.13"
+ class="st19"
+ id="path40791"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-3)" /></g><text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="180.49945"
+ y="39.647911"
+ id="text55352"><tspan
+ sodipodi:role="line"
+ id="tspan55350"
+ x="180.49945"
+ y="39.647911"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF2: 860-2250 MHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="508.771"
+ y="39.641914"
+ id="text55352-8"><tspan
+ sodipodi:role="line"
+ id="tspan55350-0"
+ x="508.771"
+ y="39.641914"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF1: 2.8 - 8.2 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="884.37524"
+ y="39.641914"
+ id="text55352-8-0"><tspan
+ sodipodi:role="line"
+ id="tspan55350-0-5"
+ x="884.37524"
+ y="39.641914"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">RF: 1 MHz - 8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="165.375"
+ y="103.40627"
+ id="text55411"><tspan
+ sodipodi:role="line"
+ id="tspan55409"
+ x="165.375"
+ y="103.40627"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1060 MHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="183.10202"
+ y="166.58252"
+ id="text55411-0"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8"
+ x="183.10202"
+ y="166.58252"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2050 MHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="505.6438"
+ y="80.485016"
+ id="text55411-0-6"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4"
+ x="505.6438"
+ y="80.485016"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 4.3 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="480.14105"
+ y="122.00026"
+ id="text55411-0-6-2"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-5"
+ x="480.14105"
+ y="122.00026"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">4.3 - 5.1 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="473.4848"
+ y="159.87526"
+ id="text55411-0-6-8"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-6"
+ x="473.4848"
+ y="159.87526"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">5.1 - 5.7 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="452.31906"
+ y="195.02751"
+ id="text55411-0-6-8-2"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-6-8"
+ x="452.31906"
+ y="195.02751"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">5.7 - 6.4 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="451.38159"
+ y="231.96501"
+ id="text55411-0-6-8-2-4"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-6-8-7"
+ x="451.38159"
+ y="231.96501"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">6.4 - 7.0 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="444.10107"
+ y="269.09402"
+ id="text55411-0-6-8-2-4-2"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-6-8-7-4"
+ x="444.10107"
+ y="269.09402"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">7.0 - 8.0 GHz</tspan></text>
+
+<g
+ id="g56872"
+ transform="translate(-29.005631,-41.867401)"><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13901"
+ class="st10"
+ height="12.3166"
+ width="46.0709"
+ y="269.59497"
+ x="667.151" /><text
+ style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000"
+ id="text13905"
+ v:langID="1033"
+ class="st31"
+ y="283.25641"
+ x="680.89581"><v:paragraph /><v:tabList /><tspan
+ style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline"
+ id="tspan13903"
+ class="st9">Tx LO1</tspan></text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16438"
+ class="st4"
+ height="21.9375"
+ width="58.682701"
+ y="272.97098"
+ x="717.86298" /><path
+ style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none"
+ inkscape:connector-curvature="0"
+ id="path15323-0"
+ class="st1"
+ d="m 733.2,315.73635 53.07453,-0.26144" /><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path15279-4"
+ class="st16"
+ d="m 719.70006,315.63848 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15281-6"
+ class="st1"
+ d="m 726.45006,315.63848 a 2.25,2.25 0 0 0 4.5,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15283-2"
+ class="st1"
+ d="m 721.95006,315.63848 a 2.25,2.25 0 1 1 4.5,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15285-6"
+ class="st1"
+ d="m 719.70006,315.63848 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15290-7"
+ class="st3"
+ height="15.1875"
+ width="19.620001"
+ y="307.81351"
+ x="686.58008" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15297-5"
+ class="st17"
+ height="9.28125"
+ width="13.5"
+ y="310.76749"
+ x="691.57507" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15299-6"
+ v:langID="1033"
+ class="st5"
+ y="317.8085"
+ x="691.57507"><v:paragraph /><v:tabList />PLL</text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-6)"
+ inkscape:connector-curvature="0"
+ id="path15304-9"
+ class="st19"
+ d="m 706.20006,315.63848 h 10.11" /><path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1"
+ inkscape:connector-curvature="0"
+ id="path15309-8"
+ class="st27"
+ d="m 759.49706,335.82248 h -89.86 v -47.25 h 89.86 z" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15316-7"
+ class="st17"
+ height="9.28125"
+ width="58.682701"
+ y="288.57248"
+ x="671.14209" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15318-2"
+ v:langID="1033"
+ class="st5"
+ y="295.61349"
+ x="671.14209"><v:paragraph /><v:tabList />LMX2572</text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15328-8"
+ class="st1"
+ d="m 739.95006,316.11148 v 15.75 h -13.5" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-6)"
+ inkscape:connector-curvature="0"
+ id="path15333-2"
+ class="st19"
+ d="m 728.13506,331.84648 h -36.56 v -5.38" /><circle
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.40625"
+ id="ellipse15338-9"
+ class="st21"
+ cy="315.63846"
+ cx="739.95032" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-6)"
+ inkscape:connector-curvature="0"
+ id="path15343-9"
+ class="st19"
+ d="m 665.70006,314.89148 h 17.49" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15350-6"
+ class="st17"
+ height="18.0126"
+ width="11.8125"
+ y="315.00949"
+ x="670.76306" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15354-0"
+ v:langID="1033"
+ class="st5"
+ y="321.61252"
+ x="670.76306"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan
+ style="font-size:7.99996805px"
+ id="tspan15352-2"
+ class="st6"
+ dy="14.400001"
+ x="670.76306">In</tspan></text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15381-7"
+ class="st17"
+ height="9.28125"
+ width="66.375"
+ y="297.43149"
+ x="671.32507" /><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15512-6"
+ class="st10"
+ height="12.3166"
+ width="46.0709"
+ y="274.24146"
+ x="683.97705" /></g><text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="829.07629"
+ y="109.69658"
+ id="text55411-0-6-21"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-59"
+ x="829.07629"
+ y="109.69658"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="842.33459"
+ y="134.35692"
+ id="text55411-0-6-9"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-1"
+ x="842.33459"
+ y="134.35692"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1 MHz - 1.8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="860.93207"
+ y="179.69615"
+ id="text55411-0-6-9-4"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-1-9"
+ x="860.93207"
+ y="179.69615"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1.8 - 2.3 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="889.09277"
+ y="222.91805"
+ id="text55411-0-6-9-4-1"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-1-9-0"
+ x="889.09277"
+ y="222.91805"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2.3 - 3 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="685.80005"
+ y="183.14729"
+ id="text55411-0-6-21-7"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-59-5"
+ x="685.80005"
+ y="183.14729"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.2 - 8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="186.95876"
+ y="393.17401"
+ id="text55411-8"><tspan
+ sodipodi:role="line"
+ id="tspan55409-7"
+ x="186.95876"
+ y="393.17401"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1060 MHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="197.19902"
+ y="460.26077"
+ id="text55411-0-0"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-48"
+ x="197.19902"
+ y="460.26077"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2050 MHz</tspan></text>
+
+<g
+ id="g57241"
+ transform="translate(124.42157,-133.27438)"><path
+ style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none"
+ inkscape:connector-curvature="0"
+ id="path15323-1"
+ class="st1"
+ d="m 205.1539,670.52718 53.07453,-0.26144" /><path
+ style="fill:#ffffff"
+ inkscape:connector-curvature="0"
+ id="path15279-42"
+ class="st16"
+ d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15281-2"
+ class="st1"
+ d="m 198.40396,670.42931 a 2.25,2.25 0 0 0 4.5,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15283-20"
+ class="st1"
+ d="m 193.90396,670.42931 a 2.25,2.25 0 1 1 4.5,0" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15285-5"
+ class="st1"
+ d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" /><rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15290-5"
+ class="st3"
+ height="15.1875"
+ width="19.620001"
+ y="662.60431"
+ x="158.53395" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15297-2"
+ class="st17"
+ height="9.28125"
+ width="13.5"
+ y="665.55829"
+ x="163.52896" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15299-9"
+ v:langID="1033"
+ class="st5"
+ y="672.5993"
+ x="163.52896"><v:paragraph /><v:tabList />PLL</text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9)"
+ inkscape:connector-curvature="0"
+ id="path15304-0"
+ class="st19"
+ d="m 178.15396,670.42931 h 10.11" /><path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1"
+ inkscape:connector-curvature="0"
+ id="path15309-2"
+ class="st27"
+ d="m 231.45096,690.61331 h -89.86 v -47.25 h 89.86 z" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15316-8"
+ class="st17"
+ height="9.28125"
+ width="58.682701"
+ y="643.36328"
+ x="143.09596" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15318-3"
+ v:langID="1033"
+ class="st5"
+ y="650.4043"
+ x="143.09596"><v:paragraph /><v:tabList />LMX2572</text>
+
+<path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ inkscape:connector-curvature="0"
+ id="path15328-80"
+ class="st1"
+ d="m 211.90396,670.90231 v 15.75 h -13.5" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9)"
+ inkscape:connector-curvature="0"
+ id="path15333-4"
+ class="st19"
+ d="m 200.08896,686.63731 h -36.56 v -5.38" /><circle
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ r="1.40625"
+ id="ellipse15338-0"
+ class="st21"
+ cy="670.42926"
+ cx="211.90422" /><path
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9)"
+ inkscape:connector-curvature="0"
+ id="path15343-91"
+ class="st19"
+ d="m 137.65396,669.68231 h 17.49" /><rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15350-9"
+ class="st17"
+ height="18.0126"
+ width="11.8125"
+ y="669.80029"
+ x="142.71696" /><text
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"
+ id="text15354-6"
+ v:langID="1033"
+ class="st5"
+ y="676.40332"
+ x="142.71696"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan
+ style="font-size:7.99996805px"
+ id="tspan15352-25"
+ class="st6"
+ dy="14.400001"
+ x="142.71696">In</tspan></text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15381-4"
+ class="st17"
+ height="9.28125"
+ width="66.375"
+ y="652.22229"
+ x="143.27896" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16409-6"
+ class="st4"
+ height="21.9375"
+ width="58.682701"
+ y="644.72827"
+ x="198.57271" /></g><g
+ id="g57520"
+ transform="translate(-7.445517,-82.808191)"><rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect13912"
+ class="st10"
+ height="12.3166"
+ width="46.0709"
+ y="634.94897"
+ x="702.36401" /><text
+ style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000"
+ id="text13916"
+ v:langID="1033"
+ class="st31"
+ y="643.50598"
+ x="706.36401"><v:paragraph /><v:tabList /><tspan
+ style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline"
+ id="tspan13914"
+ class="st9">Rx LO1</tspan></text>
+
+<rect
+ style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round"
+ id="rect15755-2"
+ class="st10"
+ height="12.316599"
+ width="46.0709"
+ y="649.13013"
+ x="674.51044" /><rect
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round"
+ id="rect16612-9"
+ class="st4"
+ height="21.9375"
+ width="58.682701"
+ y="653.15112"
+ x="747.48846" /><g
+ transform="translate(545.75103,6.6137609)"
+ id="g57241-4"><path
+ d="m 205.1539,670.52718 53.07453,-0.26144"
+ class="st1"
+ id="path15323-1-3"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none" /><path
+ d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z"
+ class="st16"
+ id="path15279-42-5"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" /><path
+ d="m 198.40396,670.42931 a 2.25,2.25 0 0 0 4.5,0"
+ class="st1"
+ id="path15281-2-1"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 193.90396,670.42931 a 2.25,2.25 0 1 1 4.5,0"
+ class="st1"
+ id="path15283-20-7"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0"
+ class="st1"
+ id="path15285-5-4"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="158.53395"
+ y="662.60431"
+ width="19.620001"
+ height="15.1875"
+ class="st3"
+ id="rect15290-5-3"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="163.52896"
+ y="665.55829"
+ width="13.5"
+ height="9.28125"
+ class="st17"
+ id="rect15297-2-1"
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="163.52896"
+ y="672.5993"
+ class="st5"
+ v:langID="1033"
+ id="text15299-9-4"
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />PLL</text>
+
+<path
+ d="m 178.15396,670.42931 h 10.11"
+ class="st19"
+ id="path15304-0-6"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9-0)" /><path
+ d="m 231.45096,690.61331 h -89.86 v -47.25 h 89.86 z"
+ class="st27"
+ id="path15309-2-9"
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" /><rect
+ x="143.09596"
+ y="643.36328"
+ width="58.682701"
+ height="9.28125"
+ class="st17"
+ id="rect15316-8-4"
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="143.09596"
+ y="650.4043"
+ class="st5"
+ v:langID="1033"
+ id="text15318-3-2"
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />LMX2572</text>
+
+<path
+ d="m 211.90396,670.90231 v 15.75 h -13.5"
+ class="st1"
+ id="path15328-80-2"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 200.08896,686.63731 h -36.56 v -5.38"
+ class="st19"
+ id="path15333-4-6"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9-0)" /><circle
+ cx="211.90422"
+ cy="670.42926"
+ class="st21"
+ id="ellipse15338-0-4"
+ r="1.40625"
+ style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path
+ d="m 137.65396,669.68231 h 17.49"
+ class="st19"
+ id="path15343-91-1"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9-0)" /><rect
+ x="142.71696"
+ y="669.80029"
+ width="11.8125"
+ height="18.0126"
+ class="st17"
+ id="rect15350-9-2"
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text
+ x="142.71696"
+ y="676.40332"
+ class="st5"
+ v:langID="1033"
+ id="text15354-6-8"
+ style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan
+ x="142.71696"
+ dy="14.400001"
+ class="st6"
+ id="tspan15352-25-8"
+ style="font-size:7.99996805px">In</tspan></text>
+
+<rect
+ x="143.27896"
+ y="652.22229"
+ width="66.375"
+ height="9.28125"
+ class="st17"
+ id="rect15381-4-9"
+ style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect
+ x="198.57271"
+ y="644.72827"
+ width="58.682701"
+ height="21.9375"
+ class="st4"
+ id="rect16409-6-2"
+ style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /></g></g><text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="200.69856"
+ y="344.57404"
+ id="text55352-88"><tspan
+ sodipodi:role="line"
+ id="tspan55350-8"
+ x="200.69856"
+ y="344.57404"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF2: 860-2250 MHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="552.70355"
+ y="374.84402"
+ id="text55411-0-6-6"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8"
+ x="552.70355"
+ y="374.84402"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 4.2 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="552.89105"
+ y="425.84402"
+ id="text55411-0-6-6-3"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-8"
+ x="552.89105"
+ y="425.84402"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">4.2 - 5.6 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="554.48706"
+ y="480.59003"
+ id="text55411-0-6-6-3-3"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-8-3"
+ x="554.48706"
+ y="480.59003"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">5.6 - 6.8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="560.31909"
+ y="529.34003"
+ id="text55411-0-6-6-3-3-3"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-8-3-8"
+ x="560.31909"
+ y="529.34003"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">6.8 - 8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="834.90991"
+ y="382.55142"
+ id="text55411-0-6-6-0"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-4"
+ x="834.90991"
+ y="382.55142"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="696.40662"
+ y="468.99521"
+ id="text55411-0-6-6-0-7"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-4-6"
+ x="696.40662"
+ y="468.99521"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.2 - 8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="888.38617"
+ y="424.97382"
+ id="text55411-0-6-6-0-7-8"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-4-6-9"
+ x="888.38617"
+ y="424.97382"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1 MHz - 1.8 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="884.93896"
+ y="470.31705"
+ id="text55411-0-6-6-0-7-0"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-4-6-6"
+ x="884.93896"
+ y="470.31705"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1.8 - 2.3 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="884.85016"
+ y="518.31189"
+ id="text55411-0-6-6-0-7-0-8"><tspan
+ sodipodi:role="line"
+ id="tspan55409-8-4-8-4-6-6-7"
+ x="884.85016"
+ y="518.31189"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2.3 - 3 GHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="579.70264"
+ y="344.57404"
+ id="text55352-88-0"><tspan
+ sodipodi:role="line"
+ id="tspan55350-8-3"
+ x="579.70264"
+ y="344.57404"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF1: 2.8-8.2 MHz</tspan></text>
+
+<text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
+ x="854.24133"
+ y="344.57404"
+ id="text55352-88-0-3"><tspan
+ sodipodi:role="line"
+ id="tspan55350-8-3-3"
+ x="854.24133"
+ y="344.57404"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">RF: 1 MHz - 8 GHz</tspan></text>
+
+</svg> \ No newline at end of file
diff --git a/host/docs/res/x410.png b/host/docs/res/x410.png
new file mode 100644
index 000000000..215527d22
--- /dev/null
+++ b/host/docs/res/x410.png
Binary files differ
diff --git a/host/docs/res/x410_back_panel.png b/host/docs/res/x410_back_panel.png
new file mode 100644
index 000000000..782305c04
--- /dev/null
+++ b/host/docs/res/x410_back_panel.png
Binary files differ
diff --git a/host/docs/res/x410_front_panel.png b/host/docs/res/x410_front_panel.png
new file mode 100644
index 000000000..9df5ce9b4
--- /dev/null
+++ b/host/docs/res/x410_front_panel.png
Binary files differ
diff --git a/host/docs/res/x4xx_block_diagram.svg b/host/docs/res/x4xx_block_diagram.svg
new file mode 100644
index 000000000..760b2f442
--- /dev/null
+++ b/host/docs/res/x4xx_block_diagram.svg
@@ -0,0 +1,962 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="390.99509mm"
+ height="266.53741mm"
+ viewBox="0 0 390.99509 266.53741"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+ sodipodi:docname="x4xx_block_diagram.svg">
+ <defs
+ id="defs2">
+ <marker
+ inkscape:stockid="TriangleInM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleInM"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path1582"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(-0.4)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleOutM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleOutM"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path1591"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(0.4)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path1464"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(1.1,0,0,1.1,1.1,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Lend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path1467"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleInM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleInM-4"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path1582-6"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(-0.4)" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleOutM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleOutM-2"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path1591-6"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(0.4)" />
+ </marker>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4388">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect4390"
+ width="3.9191406"
+ height="7.8382812"
+ x="-92.306511"
+ y="185.79233" />
+ </clipPath>
+ <marker
+ inkscape:stockid="TriangleInM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleInM-42"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path1582-2"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(-0.4)" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleOutM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleOutM-20"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path1591-5"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(0.4)" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleInM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleInM-4-5"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path1582-6-2"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(-0.4)" />
+ </marker>
+ <marker
+ inkscape:stockid="TriangleOutM"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="TriangleOutM-2-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path1591-6-0"
+ d="M 5.77,0 -2.88,5 V -5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="scale(0.4)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2"
+ inkscape:cx="619.2311"
+ inkscape:cy="298.84026"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="3840"
+ inkscape:window-height="2063"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(153.93257,-181.94356)">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75"
+ width="222.2912"
+ height="94.039085"
+ x="-79.911568"
+ y="279.0575" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6"
+ width="49.65358"
+ height="17.939219"
+ x="-153.16962"
+ y="236.56277" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7"
+ width="49.65358"
+ height="17.939219"
+ x="-153.16962"
+ y="254.50182" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-19.579166"
+ y="317.2406"
+ id="text1076"><tspan
+ sodipodi:role="line"
+ id="tspan1074"
+ x="-19.579166"
+ y="317.2406"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332">Xilinx RFSoC</tspan><tspan
+ sodipodi:role="line"
+ x="-19.579166"
+ y="322.52618"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1082" /><tspan
+ sodipodi:role="line"
+ x="-19.579166"
+ y="332.23364"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1084">- Embedded Linux</tspan><tspan
+ sodipodi:role="line"
+ x="-19.579166"
+ y="344.58087"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1086">- Programmable Logic</tspan><tspan
+ sodipodi:role="line"
+ x="-19.579166"
+ y="349.86646"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1080" /></text>
+ <g
+ id="g4037">
+ <rect
+ y="204.5883"
+ x="-153.43257"
+ height="25.20031"
+ width="48.495682"
+ id="rect75-3"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text1076-1"
+ y="214.31781"
+ x="-146.09488"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan1080-5"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="214.31781"
+ x="-146.09488"
+ sodipodi:role="line">Control</tspan><tspan
+ id="tspan1153"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="226.66504"
+ x="-146.09488"
+ sodipodi:role="line">CPLD</tspan></text>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-148.03329"
+ y="243.46608"
+ id="text1076-1-6"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="-148.03329"
+ y="243.46608"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1153-6">DB0 Digital</tspan><tspan
+ sodipodi:role="line"
+ x="-148.03329"
+ y="251.40358"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180">Interface</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-148.03329"
+ y="261.54572"
+ id="text1076-1-6-2"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="-148.03329"
+ y="261.54572"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1153-6-5">DB1 Digital</tspan><tspan
+ sodipodi:role="line"
+ x="-148.03329"
+ y="269.48322"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-8">Interface</tspan></text>
+ <g
+ id="g3809"
+ transform="translate(0,-2.7797713)">
+ <rect
+ y="304.30597"
+ x="-143.25504"
+ height="22.244499"
+ width="39.178856"
+ id="rect75-3-6-7-5"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="326.55048"
+ x="-143.25504"
+ height="22.244499"
+ width="39.178856"
+ id="rect75-3-6-7-5-3"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ inkscape:transform-center-y="-34.660417"
+ inkscape:transform-center-x="-8.2020833"
+ id="text1076-1-6-2-6"
+ y="335.61588"
+ x="-136.62737"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan1153-6-5-2"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="335.61588"
+ x="-136.62737"
+ sodipodi:role="line">DB1 RF</tspan><tspan
+ id="tspan1180-8-8"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="343.55338"
+ x="-136.62737"
+ sodipodi:role="line">Interface</tspan></text>
+ <text
+ inkscape:transform-center-y="-34.660417"
+ inkscape:transform-center-x="-8.2020833"
+ id="text1076-1-6-2-6-4"
+ y="313.99454"
+ x="-136.62737"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan1153-6-5-2-7"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="313.99454"
+ x="-136.62737"
+ sodipodi:role="line">DB0 RF</tspan><tspan
+ id="tspan1180-8-8-2"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="321.93204"
+ x="-136.62737"
+ sodipodi:role="line">Interface</tspan></text>
+ <text
+ inkscape:transform-center-y="-34.660417"
+ inkscape:transform-center-x="-8.2020833"
+ id="text1076-1-6-2-6-4-6"
+ y="324.84866"
+ x="-67.659241"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan1180-8-8-2-8"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="324.84866"
+ x="-67.659241"
+ sodipodi:role="line">ADCs</tspan><tspan
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="332.78616"
+ x="-67.659241"
+ sodipodi:role="line"
+ id="tspan4026">DACs</tspan></text>
+ </g>
+ <g
+ id="g4046"
+ transform="translate(2.9104167,-6.35)">
+ <g
+ id="g4314">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2-1"
+ width="45.697273"
+ height="25.021141"
+ x="-47.73143"
+ y="188.79356" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-41.966232"
+ y="198.75139"
+ id="text1076-1-9"><tspan
+ sodipodi:role="line"
+ x="-41.966232"
+ y="198.75139"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1293">DIO</tspan><tspan
+ sodipodi:role="line"
+ x="-41.966232"
+ y="211.09862"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan4039">Board</tspan></text>
+ </g>
+ </g>
+ <g
+ id="g4320"
+ transform="translate(2.9104167,-7.7495056)">
+ <rect
+ y="190.19307"
+ x="101.19689"
+ height="24.835871"
+ width="45.324921"
+ id="rect75-3-6-7-5-5-2-1-2-0"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text1076-1-9-0"
+ y="199.76915"
+ x="103.24946"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan1293-9"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="199.76915"
+ x="103.24946"
+ sodipodi:role="line">Clocking</tspan><tspan
+ id="tspan4306"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="212.11638"
+ x="103.24946"
+ sodipodi:role="line">Board</tspan></text>
+ </g>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-6"
+ width="47.570648"
+ height="23.52692"
+ x="62.11293"
+ y="394.11664" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="76.652664"
+ y="409.40646"
+ id="text1076-1-9-1-0"><tspan
+ sodipodi:role="line"
+ x="76.652664"
+ y="409.40646"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1369-6">SCU</tspan></text>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1.00479412;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-3-6"
+ width="39.553703"
+ height="44.484207"
+ x="-79.704971"
+ y="301.52859" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -102.98906,263.46406 h 39.356769"
+ id="path1409"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -63.632291,279.07448 V 263.46406"
+ id="path1411"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -103.62525,244.93937 h 43.540931"
+ id="path1409-0"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -60.084319,278.53227 v -33.5929"
+ id="path1411-0"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.13276374;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM);marker-end:url(#TriangleOutM)"
+ d="m -100.54544,312.33209 h 17.182729"
+ id="path1442"
+ inkscape:connector-curvature="0" />
+ <circle
+ id="path1444"
+ style="fill:#000000;stroke:none;stroke-width:0.26458332"
+ cx="-79.933632"
+ cy="316.27014"
+ r="0.39687499" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.13276374;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM-4);marker-end:url(#TriangleOutM-2)"
+ d="m -100.54544,335.65398 h 17.182725"
+ id="path1442-7"
+ inkscape:connector-curvature="0" />
+ <g
+ transform="translate(52.796333,90.404199)"
+ id="g4046-7">
+ <g
+ id="g4104"
+ transform="translate(0,0.52916667)">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2-1-28"
+ width="45.96735"
+ height="13.252675"
+ x="-47.866467"
+ y="188.12936" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-45.903725"
+ y="196.96545"
+ id="text1076-1-9-2"><tspan
+ sodipodi:role="line"
+ x="-45.903725"
+ y="196.96545"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.76111126px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan4039-0">DDR Bank 1</tspan></text>
+ </g>
+ </g>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2-1-28-6"
+ width="45.96735"
+ height="13.252675"
+ x="50.897217"
+ y="279.06274" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="52.859959"
+ y="287.89883"
+ id="text1076-1-9-2-1"><tspan
+ sodipodi:role="line"
+ x="52.859959"
+ y="287.89883"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.76111126px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan4039-0-3">DDR Bank 0</tspan></text>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2-1-2"
+ width="29.467161"
+ height="24.88065"
+ x="12.300733"
+ y="232.11459" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="13.338802"
+ y="241.87804"
+ id="text1076-1-4-8"><tspan
+ sodipodi:role="line"
+ x="13.338802"
+ y="241.87804"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1153-62-1">DDR4</tspan><tspan
+ sodipodi:role="line"
+ x="13.338802"
+ y="254.22527"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1268-3">4 GB</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 27.034313,279.1742 V 257.14028"
+ id="path4135"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2-1-2-7"
+ width="29.467161"
+ height="24.88065"
+ x="60.625546"
+ y="232.11459" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="61.663616"
+ y="241.87804"
+ id="text1076-1-4-1"><tspan
+ sodipodi:role="line"
+ x="61.663616"
+ y="241.87804"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1153-62-10">DDR4</tspan><tspan
+ sodipodi:role="line"
+ x="61.663616"
+ y="254.22527"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1268-34">4 GB</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 75.359127,279.1292 V 257.09528"
+ id="path4135-1"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g4252">
+ <g
+ transform="translate(0.65024451,19.645313)"
+ id="g4239">
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5"
+ width="29.282759"
+ height="24.322077"
+ x="-40.956207"
+ y="404.01358" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-40.048008"
+ y="412.79932"
+ id="text1076-1-4"><tspan
+ sodipodi:role="line"
+ x="-40.048008"
+ y="412.79932"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1153-62">DDR4</tspan><tspan
+ sodipodi:role="line"
+ x="-40.048008"
+ y="425.14655"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1268">4 GB</tspan></text>
+ </g>
+ <g
+ id="g4046-7-5"
+ transform="translate(-0.78179189,171.02789)">
+ <g
+ transform="translate(0,0.52916667)"
+ id="g4104-9">
+ <rect
+ y="188.12936"
+ x="-47.866467"
+ height="13.252675"
+ width="45.96735"
+ id="rect75-3-6-7-5-5-2-1-28-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text1076-1-9-2-14"
+ y="196.96545"
+ x="-45.969872"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan4039-0-9"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.76111126px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="196.96545"
+ x="-45.969872"
+ sodipodi:role="line">DDR Bank 2</tspan></text>
+ </g>
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path4135-10"
+ d="M -25.664583,423.7609 V 372.89476"
+ style="fill:none;stroke:#000000;stroke-width:1.76895344;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -104.57491,224.30994 h 77.800501"
+ id="path1409-0-7"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.44413379;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -26.774409,278.99934 v -54.6894"
+ id="path1411-0-5"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-73.949959"
+ y="229.86198"
+ id="text1076-1-6-8"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="-73.949959"
+ y="229.86198"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-0">SPI</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -105.17838,213.25937 h 77.800506"
+ id="path1409-0-7-4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -105.04608,218.41875 H 114.83567"
+ id="path1409-0-7-8"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 114.83567,218.41875 V 207.70312"
+ id="path4343"
+ inkscape:connector-curvature="0" />
+ <circle
+ id="path4345"
+ style="fill:#000000;stroke:none;stroke-width:0.26458332"
+ cx="114.83566"
+ cy="207.70311"
+ r="0.39687499" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="12.822769"
+ y="216.6508"
+ id="text1076-1-6-8-0"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="12.822769"
+ y="216.6508"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-0-4">SPI</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -27.377874,213.25937 v -5.35781"
+ id="path4365"
+ inkscape:connector-curvature="0" />
+ <circle
+ id="path4367"
+ style="fill:#000000;stroke:none;stroke-width:0.26458332"
+ cx="-27.377874"
+ cy="207.90155"
+ r="0.39687499" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.45002869;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -13.229167,279.10095 V 220.97196"
+ id="path1411-0-5-2"
+ inkscape:connector-curvature="0" />
+ <circle
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4384"
+ cx="-88.403908"
+ cy="189.14923"
+ r="2.4143229"
+ clip-path="url(#clipPath4388)"
+ transform="translate(75.204667,29.644417)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.47673923;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m -13.278776,216.6262 v -9.09838"
+ id="path1411-0-5-2-9"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2"
+ width="40.137661"
+ height="17.029377"
+ x="195.92505"
+ y="347.04404" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect75-3-6-7-5-5-2-9"
+ width="40.137661"
+ height="17.029377"
+ x="195.92505"
+ y="325.99661" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="203.26215"
+ y="332.62393"
+ id="text1076-1-9-1"><tspan
+ sodipodi:role="line"
+ x="203.26215"
+ y="332.62393"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1293-6">QSFP28</tspan><tspan
+ sodipodi:role="line"
+ x="203.26215"
+ y="341.44339"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1369">Port 0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="203.26215"
+ y="353.67136"
+ id="text1076-1-9-1-6"><tspan
+ sodipodi:role="line"
+ x="203.26215"
+ y="353.67136"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1293-6-1">QSFP28</tspan><tspan
+ sodipodi:role="line"
+ x="203.26215"
+ y="362.49081"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1369-0">Port 1</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM-42);marker-end:url(#TriangleOutM-20)"
+ d="m 147.22644,334.5113 h 42.99011"
+ id="path1442-2"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM-4-5);marker-end:url(#TriangleOutM-2-9)"
+ d="M 147.82743,355.55873 H 190.5169"
+ id="path1442-7-8"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1.29576683;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 124.61875,278.94219 V 206.97552"
+ id="path4343-3"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="126.59361"
+ y="241.72008"
+ id="text1076-1-6-8-0-8"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="126.59361"
+ y="241.72008"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-0-4-0">Clock / Time</tspan><tspan
+ sodipodi:role="line"
+ x="126.59361"
+ y="249.65758"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan6173">Reference</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="154.06625"
+ y="329.57532"
+ id="text1076-1-6-8-0-9"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="154.06625"
+ y="329.57532"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-0-4-1">4x 25 Gbps</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="154.55893"
+ y="364.13202"
+ id="text1076-1-6-8-0-9-9"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="154.55893"
+ y="364.13202"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-0-4-1-6">4x 25 Gbps</tspan></text>
+ <g
+ id="g6266"
+ transform="translate(-0.52916667)">
+ <rect
+ y="380.9071"
+ x="196.95403"
+ height="17.029377"
+ width="40.137661"
+ id="rect75-3-6-7-5-5-2-2"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text1076-1-9-1-6-5"
+ y="391.28445"
+ x="209.68861"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ xml:space="preserve"><tspan
+ id="tspan1369-0-4"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ y="391.28445"
+ x="209.68861"
+ sodipodi:role="line">RJ45</tspan></text>
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.70746088;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 82.844459,394.38052 V 372.92779"
+ id="path4343-3-9"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="86.865738"
+ y="385.7019"
+ id="text1076-1-6-8-0-93"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="86.865738"
+ y="385.7019"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-0-4-6">I2C</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 196.21616,389.28709 H 121.30007"
+ id="path1409-0-7-0"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 121.30007,373.01834 v 16.26875"
+ id="path1411-0-5-29"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:0.45336637;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1080"
+ width="95.75473"
+ height="43.819485"
+ x="3.7990155"
+ y="221.71759" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="6.273725"
+ y="228.47993"
+ id="text1076-1-6-9"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="6.273725"
+ y="228.47993"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-6">PL DRAM</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
+ x="-57.121681"
+ y="418.91379"
+ id="text1076-1-6-9-0"
+ inkscape:transform-center-x="-8.2020833"
+ inkscape:transform-center-y="-34.660417"><tspan
+ sodipodi:role="line"
+ x="-57.121681"
+ y="418.91379"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332"
+ id="tspan1180-6-2">PS DRAM</tspan></text>
+ </g>
+</svg>
diff --git a/host/docs/res/x4xx_rearpanel_status_leds.png b/host/docs/res/x4xx_rearpanel_status_leds.png
new file mode 100644
index 000000000..e89563b3f
--- /dev/null
+++ b/host/docs/res/x4xx_rearpanel_status_leds.png
Binary files differ
diff --git a/host/docs/usrp_x4xx.dox b/host/docs/usrp_x4xx.dox
new file mode 100644
index 000000000..f809e191e
--- /dev/null
+++ b/host/docs/usrp_x4xx.dox
@@ -0,0 +1,953 @@
+/*! \page page_usrp_x4xx USRP X4x0 Series
+
+\tableofcontents
+
+\section x4xx_feature_list Comparative features list
+
+- Hardware Capabilities:
+ - Dual QSFP Ports (can be used with 10 GigE)
+ - External PPS input & output
+ - External 10 MHz input & output (other input reference frequencies also supported)
+ - Internal GPSDO for timing, location, and time/frequency reference
+ - External GPIO Connector (2xHDMI)
+ - USB-C debug port, providing JTAG and console access
+ - USB-C OTG port
+ - Xilinx RFSoC (XCZU28DR), 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 fixed logic
+ - Up to 4x400 MHz of analog bandwidth, center frequency 1 MHz - 7.2 GHz using \ref page_zbx
+ - Configurable front-to-back or back-to-front airflow
+- Software Capabilities:
+ - Full Linux system running on the ARM core
+ - Runs MPM (see also \ref page_mpm)
+- FPGA Capabilities:
+ - Timed commands/sampling in FPGA
+ - RFNoC capable: Supports various CHDR bus widths from 64-bits (for minimal
+ footprint) up to 512 bits (for maximum throughput)
+- Rack-mountable with additional rack mount kit (2 USRPs side-by-side, or 1 USRP per 1U)
+- Stackable (with stack mount kit)
+- Front-to-back or back-to-front airflow (switchable)
+
+\section x4xx_overview Overview and Features
+
+\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 USRP X410 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.
+
+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.
+
+\subsection x4xx_overview_rfsoc The RFSoC CPU/FPGA and host operating system
+
+The main chip (the SoC) of the X410 is a Xilinx RFSoC XCZU28DR. 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 an SD-FEC core, and
+an ARM Cortex-R5F real-time processor (the "RPU").
+
+The programmable logic (PL, or FPGA) section of the SoC is responsible for
+handling all sampling data, the high-speed network connections, and any other
+high-speed utilities such as custom RFNoC logic. The processing system (PS, or CPU)
+is running a custom-built OpenEmbedded-based Linux operating system. The OS is
+responsible for all the device and peripheral management, such as running MPM,
+configuring the network interfaces, running local UHD sessions, etc.
+
+The programmable logic bitfile contains certain hard-coded configurations of the
+hardware, such as what type of connectivity the QSFP ports use, and how the RF
+data converters are configured. That means to change the QSFP from a 10 GbE to a
+100 GbE connection requires changing out the bitfile, as well as when
+reconfiguring the data converters for different master clock rates. See
+\ref x4xx_updating_fpga_types for more information.
+
+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.
+
+\subsection x4xx_overview_dboards Daughterboard Connectivity
+
+The USRP X410 contains two ZBX daughterboards. They come pre-assembled.
+To find out more about the capabilities of these analog front-end cards, see
+\ref page_zbx.
+
+\subsection x4xx_overview_panels Front and Back Panels
+
+\image html x410_front_panel.png "X410 Front Panel" width=90%
+
+The X410 front panel provides access to the RF ports of the \ref page_zbx.
+It also provides access to the front-panel GPIO connectors (2x HDMI) and the
+power button.
+
+\image html x410_back_panel.png "X410 Back Panel" width=90%
+
+The back panel provides access to power, data connections, clocking and timing
+related connections, and some status LEDs:
+
+- The QSFP28 connectors have different configurations dependent on the FPGA
+ image type (see also \ref x4xx_updating_fpga_types)
+- The zHD/iPass connectors are unsupported
+- GPS ANT, REF IN, and PPS IN allow connecting a GPS antenna, a reference clock
+ (e.g., 10 MHz) and a 1 PPS signal for timing purposes
+- The TRIG IN/OUT port is not supported in default FPGA images
+- The serial number is embedded in a QR code
+- There are four user-configurable status LEDs (see also \ref x4xx_usage_rearpanelleds)
+- The CONSOLE JTAG USB-C port is a debug port that allows serial access to the
+ SCU or the OS (see also \ref x4xx_getting_started_serial)
+- The USB to PS USB-C port is accessible by the operating system, e.g., to
+ connect mass storage devices. It can also be used to expose the eMMC storage
+ as a mass storage device to an external computer, e.g., for updating the
+ filesystem
+- The RJ45 Ethernet port allows accessing the operation system, e.g., via SSH.
+ It is also possible to stream data over this interface, albeit at a slow rate
+ (approx. 10 Msps).
+
+\subsection x4xx_overview_micro The STM32 microcontroller
+
+The STM32 microcontroller (also referred to as the "SCU") controls various
+low-level features of the X4X0 series motherboard: It controls the power
+sequencing, reads out fan speeds and some of the temperature sensors.
+It is connected to the RFSoC via an I2C bus. It is running software based on
+Chromium EC.
+
+It is possible to log into the STM32 using the serial interface
+(see \ref x4xx_getting_started_serial_micro). This will allow certain low-level
+controls, such as remote power cycling should the CPU have become unresponsive
+for whatever reason.
+
+\subsection x4xx_overview_rackmount Rack Mounting and Cooling
+
+TODO fill out
+- explain how to flip the fan direction
+- maybe explain how to rack-mount
+
+\subsection x4xx_overview_storage eMMC Storage
+
+The main non-volatile storage of the USRP is a 16 GB eMMC storage. This storage
+can be made accessible as a USB Mass Storage device through the USB-OTG connector
+on the back panel.
+
+The entire root file system (Linux kernel, libraries) and any user data are
+stored on the eMMC. It is partitioned into four partitions:
+
+1. Boot partition (contains the bootloader). This partition usually does not
+ require modification.
+2. A data partition, mounted in /data. This is the only partition that is not
+ erased during file system updates.
+3. Two identical system partitions (root file systems). These contain the
+ operating system and the home directory (anything mounted under / that is not
+ the data or boot partition). The reason there are two of these is to enable
+ remote updates: An update running on one partition can update the other one
+ without any effect to the currently running system. Note that the system
+ partitions are erased during updates and are thus unsuitable for permanently
+ storing information.
+
+Note: It is possible to access the currently inactive root file system by
+mounting it. After logging into the device using serial console or SSH (see the
+following two sections), run the following commands:
+
+ $ mkdir temp
+ $ mount /dev/mmcblk0p3 temp # This assumes mmcblk0p3 is currently not mounted
+ $ ls temp # You are now accessing the idle partition:
+ bin data etc lib media proc sbin tmp usr
+ boot dev home lost+found mnt run sys uboot var
+
+The device node in the mount command might differ, depending on which partition
+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.
+
+\subsection x4xx_getting_started_assembling Assembling the X410
+
+Inside the kit you will find the X410 and an X410 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_network_connectivity Network Connectivity
+
+Once the X410 has booted, determine the IP address and verify network
+connectivity by running `uhd_find_devices` on the host computer:
+
+ $ uhd_find_devices
+ --------------------------------------------------
+ -- UHD Device 0
+ --------------------------------------------------
+ Device Address:
+ serial: 1234ABC
+ addr: 10.2.161.10
+ claimed: False
+ mgmt_addr: 10.2.161.10
+ product: x410
+ type: x4xx
+
+By default, an X410 will use DHCP to attempt to find an address.
+
+At this point, you should run:
+
+ uhd_usrp_probe --args addr=<IP address>
+
+to ensure functionality of the device.
+
+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
+\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.
+
+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.
+
+\subsection x4xx_getting_started_security Security-related settings
+
+The X410 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
+
+ $ passwd
+
+on the device.
+
+\subsection x4xx_getting_started_serial Serial connection
+
+It is possible to gain access to the device using a serial terminal
+emulator. To do so, the USB debug port needs to be connected to a separate
+computer to gain access.
+Most Linux, OSX, or other Unix flavours have a tool called 'screen'
+which can be used for this purpose, by running the following command:
+
+ $ sudo screen /dev/ttyUSB2 115200
+
+In this command, we prepend 'sudo' to elevate user privileges (by default,
+accessing serial ports is not available to regular users), we specify the
+device node (in this case, `/dev/ttyUSB2`), and the baud rate (115200).
+
+The exact device node depends on your operating system's driver and other USB
+devices that might be already connected. Modern Linux systems offer alternatives
+to simply trying device nodes; instead, the OS might have a directory of
+symlinks under `/dev/serial/by-id`:
+
+ $ ls /dev/serial/by-id
+ usb-Digilent_Digilent_USB_Device_2516351DDCC0-if02-port0
+ usb-Digilent_Digilent_USB_Device_2516351DDCC0-if03-port0
+
+Note: Exact names depend on the host operating system version and may differ.
+
+The first (with the `if02` suffix) connects to the STM32 microcontroller (SCU), whereas
+the second (with the `if03` suffix) connects to Linux running on the RFSoC APU.
+
+ $ sudo screen /dev/serial/by-id/usb-Digilent_Digilent_USB_Device_2516351DDCC0-if03-port0 115200
+
+After entering the username `root` (no password is set by default), you should be presented with a shell prompt similar to the following:
+
+ root@ni-x4xx-1234ABC:~#
+
+On this prompt, you can enter any Linux command available. Using the default
+configuration, the serial console will also show all kernel log messages (unlike
+when using SSH, for example), and give access to the boot loader (U-boot
+prompt). This can be used to debug kernel or bootloader issues more efficiently
+than when logged in via SSH.
+
+\subsubsection x4xx_getting_started_serial_micro Connecting to the microcontroller
+
+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
+
+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
+the device without physically accessing it (i.e., emulating a power button press)
+and other low-level diagnostics. For example, running the command `reboot` will
+reset the state of the device, and the command `powerbtn` will emulate a button
+press, turning the device back on again.
+
+\subsection x4xx_getting_started_ssh SSH connection
+
+The USRP X4xx-Series devices have two network connections: The dual QSFP28
+ports, and an RJ-45 connector. The latter is by default configured by DHCP; by
+plugging it into into 1 Gigabit switch on a DHCP-capable network, it will get
+assigned an IP address and thus be accessible via ssh.
+
+In case your network setup does not include a DHCP server, refer to the section
+\ref x4xx_getting_started_serial. A serial login can be used to assign an IP address manually.
+
+After the device obtained an IP address you can log in from a Linux or OSX
+machine by typing:
+
+ $ ssh root@ni-x4xx-1234ABC # Replace with your actual device name!
+
+Depending on your network setup, using a `.local` domain may work:
+
+ $ ssh root@ni-x4xx-1234ABC.local
+
+Of course, you can also connect to the IP address directly if you know it (or
+set it manually using the serial console).
+
+Note: The device's hostname is derived from its serial number by default
+(`ni-x4xx-$SERIAL`). You can change the hostname by modifying the `/etc/hostname`
+file and rebooting.
+
+On Microsoft Windows, the connection can be established using a tool such as
+PuTTY, by selecting a username of root without password.
+
+Like with the serial console, you should be presented with a prompt like the
+following:
+
+ root@ni-x4xx-1234ABC:~#
+
+\subsection x4xx_updating_fpga Updating the FPGA
+
+The FPGA can be updated simply using `uhd_image_loader`:
+
+ uhd_image_loader --args type=x4xx,addr=<IP address of device> --fpga-path <path to .bit>
+
+or
+
+ uhd_image_loader --args type=x4xx,addr=<IP address of device>,fpga=FPGA_TYPE
+
+A UHD install will likely have pre-built images in `/usr/share/uhd/images/`.
+Up-to-date images can be downloaded using the `uhd_images_downloader` script:
+
+ uhd_images_downloader
+
+will download images into `/usr/share/uhd/images/` (the path may differ,
+depending on how UHD was installed).
+
+Also note that the USRP already ships with compatible FPGA images on the device -
+these images can be loaded by SSH'ing into the device and running:
+
+ uhd_image_loader --args type=x4xx,mgmt_addr=127.0.0.1,fpga=X4_200
+
+\subsubsection x4xx_updating_fpga_types FPGA Image Flavors
+
+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
+same die as the FPGA).
+The image flavors consist of two short strings, separated by an underscore, e.g.
+`X4_200` is an image flavor which contains 4x10GbE, and can handle an analog
+bandwidth of 200 MHz. The first two characters describe the configuration of
+the QSFP ports: 'X' stands for 10 GbE, 'C' stands for 100 GbE. See the following
+table for more details.
+
+|  FPGA Image Flavor  | QSFP28 Port 0 Interface | QSFP28 Port 1 Interface |
+|---------------------|-------------------------|-------------------------|
+| X1_100 | 1x 10GbE (Lane 0) | N/C |
+| X4_{100, 200} | 4x 10 GbE | N/C |
+| XG_{100, 200} | 1x 10GbE (Lane 0) | 1x 10GbE (Lane 0) |
+| X4_{100, 200} | 4x 10GbE (All Lanes) | N/C |
+| X4C_{100, 200} | 4x 10GbE (All Lanes) | 100GbE |
+| C1_400 | 100GbE | N/C |
+| CG_{100, 400} | 100GbE | 100GbE |
+
+The analog bandwidth determines the available master clock rates. As of UHD 4.1,
+only the X4_200 image is shipped with UHD, which allows a a 245.76 MHz or
+250 MHz master clock rate. The other images are considered experimental (unsupported).
+
+\section x4xx_updating_filesystems Updating Filesystems
+
+Mender is a third-party software that enables remote updating of the root file
+system without physically accessing the device (see also the [Mender
+website](https://mender.io)). Mender can be executed locally on the device, or a
+Mender server can be set up which can be used to remotely update an arbitrary
+number of USRP devices. Mender servers can be self-hosted, or hosted by Mender
+(see [mender.io](https://mender.io) for pricing and availability).
+
+When updating the file system using Mender, the tool will overwrite the root
+file system partition that is not currently mounted (note: the onboard flash
+storage contains two separate root file system partitions, only one is ever used
+at a single time). Any data stored on that partition will be permanently lost,
+including the currently loaded FPGA image. After updating that partition, it
+will reboot into the newly updated partition. Only if the update is confirmed by
+the user, the update will be made permanent. This means that if an update fails,
+the device will be always able to reboot into the partition from which the
+update was originally launched (which presumably is in a working state). Another
+update can be launched now to correct the previous, failed update, until it
+works.
+
+To initiate an update from the device itself, 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:
+
+ $ uhd_images_downloader -t mender -t x4xx
+
+Append the `-l` switch to print out the URLs only:
+
+ $ uhd_images_downloader -t mender -t x4xx -l
+
+Then run mender on the command line:
+
+ $ mender install /path/to/latest.mender
+
+The artifact can also be stored on a remote server:
+
+ $ mender install http://server.name/path/to/latest.mender
+
+This procedure will take a while. If the new filesystem requires an update to
+the MB CPLD, see \ref x4xx_updating_cpld before proceeding. After mender has
+logged a successful update, reboot the device:
+
+ $ reboot
+
+If the reboot worked, and the device seems functional, commit the changes so
+the boot loader knows to permanently boot into this partition:
+
+ $ mender commit
+
+To identify the currently installed Mender artifact from the command line, the
+following file can be queried:
+
+ $ cat /etc/mender/artifact_info
+
+If you are running a hosted server, the updates can be initiated from a web
+dashboard. From there, you can start the updates without having to log into the
+device, and can update groups of USRPs with a few clicks in a web GUI. The
+dashboard can also be used to inspect the state of USRPs. This is a simple way
+to update groups of rack-mounted USRPs with custom file systems.
+
+
+\subsection x4xx_resetting_boot_environment Resetting Boot Environment
+
+In the event that the new system has problems booting, you can attempt to reset
+the boot environment using the following instructions.
+
+First, connect to the USB serial console at a baud rate of 115200. Boot the
+device, and stop the boot sequence by typing `noautoboot` at the prompt. Then,
+run the following commands in the U-boot command prompt:
+
+ env default -a
+ env save
+ reset
+
+The last command will reboot the USRP. If the `/` filesystem was mounted to `mmcblk0p2` as
+described in \ref x4xx_updating_filesystems, then stop the boot again and run:
+
+ run altbootcmd
+
+Otherwise, let the boot continue as normal.
+
+\subsection x4xx_updating_cpld Updating MB CPLD
+
+Caution! Updating the MB CPLD has the potential to brick your device if done
+improperly.
+
+You can update the MB CPLD by running the following command on the X410:
+
+ python3 /usr/lib/python3.7/site-packages/usrp_mpm/periph_manager/x4xx_update_cpld.py --file=<path to cpld-x410.rpd>
+
+Filesystems will usually contain a compatible `cpld-x410.rpd` file at
+`/lib/firmware/ni/cpld-x410.rpd`. If you're installing a new filesystem via
+mender, you may have to mount the new filesystem (before you boot into it) in
+order to access the new firmware:
+
+ mkdir /mnt/other
+ mount /dev/mmcblk0p3 /mnt/other
+ cp /mnt/other/lib/firmware/ni/cpld-x410.rpd ~
+ umount /mnt/other
+
+Note that the other filesystem may be either `/dev/mmcblk0p2` or `/dev/mmcblk0p3`.
+
+If the `x4xx_update_cpld.py` script returns an error, diagnose the error before
+proceeding.
+
+After updating the MB CPLD, a power cycle is required for the changes to take
+effect. Shut down the device using:
+
+ shutdown -h now
+
+and then un-plug, wait several seconds, then re-plug the power to the USRP.
+
+Alternatively, in lieu of physical access, the microcontroller can be accessed
+using the USB serial port as described in \ref x4xx_getting_started_serial, and
+can be used to reboot the device:
+
+ reboot
+ powerbtn
+
+
+\subsection x4xx_updating_scu Updating the SCU
+
+The writable SCU image file is stored on the filesystem under
+`/lib/firmware/ni/ec-titanium-revX.RW.bin` (where X is a revision compatibility
+number). To update, simply replace the `.bin` file with the updated version and
+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
+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
+a Linux host.
+
+First, connect to the APU serial console at a baud rate of 115200. Boot the
+device, and stop the boot sequence by typing `noautoboot` at the prompt. Then,
+run the following command in the U-boot command prompt:
+
+ ums 0 mmc 0
+
+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
+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
+ usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
+ usb 3-1: Product: USB download gadget
+ usb 3-1: Manufacturer: National Instruments
+ sd 6:0:0:0: [sdc] 30932992 512-byte logical blocks: (15.8 GB/14.8 GiB)
+ sdc: sdc1 sdc2 sdc3 sdc4
+ 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
+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:
+the individual partitions can be mounted and accessed, or the entire block
+device can be read/written.
+
+Once you're finished accessing the device over USB, the u-boot gadget may be
+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
+filesystem image using `bmaptool`. You can obtain the latest filesystem image by
+running:
+
+ uhd_images_downloader -t sdimg -t x4xx
+
+The output of this command will indicate where the downloaded image can be
+found.
+
+Run:
+
+ sudo bmaptool /path/to/usrp_x4xx_fs.sdimg.bz2 /dev/sdX
+
+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).
+
+\subsection x4xx_jtag_boot Booting X410 over JTAG
+
+If the X410 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
+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
+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
+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
+point, the device is powered on and the APU is held in reset.
+
+Run `xsdb boot_u-boot.tcl` in the directory where you've extracted the
+bootloader binaries. This will download the various binaries needed to boot the
+device into memory, and bring the APU out of reset. Once this script completes,
+you should see u-boot loading on the APU serial console. From here, you can
+follow the steps in \ref x4xx_accessing_emmc_usb to reflash the eMMC.
+
+After the eMMC has been flashed, run `reboot` at the SCU console to reset the
+device and return back to the default boot mode. A subsequent press of the power
+button will boot the device from the eMMC.
+
+\section x4xx_usage Using a USRP X4x0 from UHD
+
+Like any other USRP, all X4x0 USRPs are controlled by the UHD software. To
+integrate a USRP X4x0 into your C++ application, you would generate a UHD
+device in the same way you would for any other USRP:
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
+auto usrp = uhd::usrp::multi_usrp::make("type=x4xx");
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For a list of which arguments can be passed into make(), see Section
+\ref x4xx_usage_args.
+
+\subsection x4xx_usage_args Device Arguments
+
+ Key | Description | Example Value
+-----------------------|------------------------------------------------------------------------------|---------------------
+ addr | IPv4 address of primary SFP+ port to connect to. | addr=192.168.30.2
+ second_addr | IPv4 address of secondary SFP+ port to connect to. | second_addr=192.168.40.2
+ mgmt_addr | IPv4 address or hostname to which to connect the RPC client. Defaults to `addr'.| mgmt_addr=ni-sulfur-311FE00
+ find_all | When using broadcast, find all devices, even if unreachable via CHDR. | find_all=1
+ master_clock_rate | Master Clock Rate in Hz. | master_clock_rate=250e6
+ serialize_init | Force serial initialization of daughterboards. | serialize_init=1
+ skip_init | Skip the initialization process for the device. | skip_init=1
+ time_source | Specify the time (PPS) source. | time_source=internal
+ clock_source | Specify the reference clock source. | clock_source=internal
+ ref_clk_freq | Specify the external reference clock frequency, default is 10 MHz. | ref_clk_freq=20e6
+ discovery_port | Override default value for MPM discovery port. | discovery_port=49700
+ rpc_port | Override default value for MPM RPC port. | rpc_port=49701
+
+\subsection x4xx_usage_gps GPS
+
+The 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 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.
+
+To use the GPS as a clock and time reference, simply use `gpsdo` as a clock or
+time source. Alternatively, set `gpsdo` as a synchronisation source:
+
+~~~{.cpp}
+// Set clock/time individually:
+usrp->set_clock_source("gpsdo");
+usrp->set_time_source("gpsdo");
+// This is equivalent to the previous commands, but faster, as it sets
+// both settings simultaneously and avoids duplicating settings that are shared
+// between these calls.
+usrp->set_sync_source("clock_source=gpsdo,time_source=gpsdo");
+~~~
+
+Note the GPS module is not always enabled. Its power-on status can be queried
+using the `gps_enabled` GPS sensor (see also \ref x4xx_usage_sensors). When
+disabled, none of the sensors will return useful (if any) values.
+
+When selecting `gpsdo` as a clock source, the GPS will always be enabled. Note
+that acquiring a GPS lock can take some time after enabling the GPS, so if a UHD
+application is enabling the GPS dynamically, it might take some time before a
+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
+FPGA.
+
+Support for using these with UHD is not yet available.
+
+
+\subsection x4xx_usage_subdevspec Subdev Specifications
+
+The RF ports on the front panel of the X410 + ZBX correspond to the following
+subdev specifications:
+
+Label | Subdev Spec
+------------|------------
+DB 0 / RF 0 | A:0
+DB 0 / RF 1 | A:1
+DB 1 / RF 0 | B:0
+DB 1 / RF 1 | B:1
+
+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.
+
+These values can be used for uhd::usrp::multi_usrp::set_rx_subdev_spec() and
+uhd::usrp::multi_usrp::set_tx_subdev_spec() as with other USRPs.
+
+\subsection x4xx_usage_sensors The Sensor API
+
+Like other USRPs, the X4x0 series have daughterboard and motherboard sensors.
+For daughterboard sensors, cf. \ref zbx_sensors.
+
+When using uhd::usrp::multi_usrp, the following API calls are relevant to
+interact with the motherboard sensor API:
+
+- uhd::usrp::multi_usrp::get_mboard_sensor_names()
+- uhd::usrp::multi_usrp::get_mboard_sensor()
+
+The following motherboard sensors are always available:
+
+- `ref_locked`: This will check that all the daughterboards have locked to the
+ external reference.
+- `temp_fpga`: The temperature of the RFSoC die itself.
+- `temp_main_power`: The temperature of the PM-BUS devices which supply 0.85V
+ to the RFSoC.
+- `temp_scu_internal`: The internal temperature reading of the STM32 microcontroller.
+- `fan0`: Fan 0 speed (RPM)
+- `fan1`: Fan 1 speed (RPM)
+
+The GPS sensors will return empty values if the GPS is inactive (note it may be
+inactive when using a different clock than `gpsdo`, see also \ref x4xx_usage_gps).
+There are two types of GPS sensors. The first set requires an active GPS module
+and is acquired by calling into gpsd on the embedded device, which in turn
+communicates with the GPS via a serial interface. For this reason, these sensors
+can take a few seconds before returning a valid value:
+
+- `gps_time`: GPS time in seconds since the epoch
+- `gps_tpv`: A TPV report from GPSd serialized as JSON
+- `gps_sky`: A SKY report from GPSd serialized as JSON
+- `gps_gpgga`: GPGGA string
+
+The seconds set of GPS sensors probes pins on the GPS module. They are all boolean
+sensors values. If the GPS is disabled, they will always return false.
+
+- `gps_enabled`: Returns true if the GPS module is powered on.
+- `gps_locked`: Returns the state of the 'LOCK_OK' pin.
+- `gps_alarm`: Returns the state of the 'ALARM' pin.
+- `gps_warmed_up`: Returns the state of the 'WARMUP_TRAINING' pin. Indicates
+ warmup phase, can be high for minutes after enabling GPS.
+- `gps_survey`: Returns the state of the 'SURVEY_ACTIVE' pin. Indicates state of
+ auto survey process. Indicates that module is locked to GPS, and
+ that there are no events on the GPS module pending.
+
+\subsection x4xx_usage_rearpanelleds Rear Panel Status LEDs
+
+The USRP X410 is equipped with three user-configurable LEDs located on the
+device's rear panel: `LED 0`, `LED 1`, and `LED 2`.
+Each LED supports four different states: Off, Green, Red, and Amber.
+Different behaviors are supported for each LED (see \ref x4xx_usage_rearpanelleds_suppbeh
+below) which are user-configurable. Refer to \ref x4xx_usage_rearpanelleds_defaults
+for default functionality.
+
+<img src="x4xx_rearpanel_status_leds.png" align="right"/>
+
+\subsubsection x4xx_usage_rearpanelleds_suppbeh Supported LED Behaviors
+
+- `activity`: flash green LED for CPU activity
+- `emmc`: flash green LED for eMMC activity
+- `heartbeat`: flash green LED with a heartbeat
+- `fpga`: change LED to green when FPGA is loaded
+- `netdev <interface>`: green LED indicates interface link, amber indicates activity
+ - Where `<interface>` is the name of any network interface (e.g. `eth0`)
+- `none`: LED is constantly off
+- `panic`: red LED turns on when kernel panics
+- `user0`: off, green, red or amber LED state is controlled by FPGA application, User LED 0
+- `user1`: off, green, red or amber LED state is controlled by FPGA application, User LED 1
+- `user2`: off, green, red or amber LED state is controlled by FPGA application, User LED 2
+
+\subsubsection x4xx_usage_rearpanelleds_defaults Behavior
+
+| LED Number | Default Behavior |
+|------------|------------------|
+| LED 0 | `heartbeat` |
+| LED 1 | `fpga` |
+| LED 2 | `emmc` |
+
+A user may change the X410 LEDs' default behavior via running a utility on the
+on-board ARM processor (Linux).
+
+### Temporarily change the LED Behavior
+
+1. Establish a connection (serial or SSH) to the X410's Linux terminal.
+2. Use the `ledctrl` utility to configure each LED based on desired supported behavior
+
+ ledctrl <led> <command>
+
+Where `<led>` valid options are: `led0`, `led1`, and `led2`. These options
+correspond to the rear panel labels.
+The `<command>` valid options are listed in the \ref x4xx_usage_rearpanelleds_suppbeh
+section above, with their corresponding description.
+
+Examples:
+
+ root@ni-x4xx-1111111:~# ledctrl led0 user0
+
+Sets the X410's LED 0 to be controlled via the FPGA application using "User LED 0".
+
+### Persistently change the LED
+
+The above method will not persist across reboots. In order to persist the
+changes, modify the ledctrl service unit files which are run by the init
+system at boot. These files can be found on a running filesystem at, e.g.,
+`/lib/systemd/system/ledctrl-led0.service`.
+
+
+### Using FPGA LED Control
+
+When selecting `user0`, `user1`, and/or `user2` as LED behavior (see
+\ref x4xx_usage_rearpanelleds_suppbeh above), the FPGA application gains control
+of that given LED. The following paragraph describes how the FPGA application can
+control the state for each setting.
+
+FPGA application access to User LED 0-2 requires modification of the FPGA source
+code and is achieved directly via Verilog, using a 2-bit vector to control the state.
+
+Below is an excerpt of the FPGA source code, setting the `user0`, `user1`, and
+`user2` values to green, red, and amber respectively.
+~~~{.v}
+ // 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.
+ localparam LED_OFF = 2'b00;
+ localparam LED_GREEN = 2'b01;
+ localparam LED_RED = 2'b10;
+ localparam LED_AMBER = 2'b11;
+
+ wire [1:0] user_led_ctrl [0:2];
+ assign user_led_ctrl[0] = LED_GREEN;
+ assign user_led_ctrl[1] = LED_RED;
+ assign user_led_ctrl[2] = LED_AMBER;
+~~~
+
+\section x4xx_too Theory of Operation
+
+\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
+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
+converters (ADC/DAC) which are exposed to the daughterboards through a connector.
+The FPGA configuration for the RFSoC can be found in the source code repository
+under `fpga/usrp3/top/x400`. The OpenEmbedded Linux configuration can be found
+on a [separate repository](https://github.com/EttusResearch/meta-ettus).
+
+The SCU is a microcontroller running a baremetal control stack. It controls the
+power sequencing, the fan speeds, connects various peripherals and sensors to
+the Linux kernel, and performs other low-level tasks. It can be accessed through
+a serial console directly from the back-panel. This can be a useful debugging
+tool if the device is not responding to other inputs, and can be used to
+power-cycle and reboot the USRP. It is connected to the RFSoC using an I2C
+interface.
+
+The motherboard control CPLD performs various control tasks, such as controlling
+the clocking card and the DIO connector (note that the DIO pins are also available
+without using the CPLD, which is the normal case when programming the pins for
+an application with higher rates and precise timing).
+The motherboard CPLD is accessible from the RFSoC through a SPI interface, and
+also acts as a SPI mux for accessing peripherals such as the clocking card.
+Access to the motherboard CPLD from within a UHD session always goes through MPM,
+meaning it is not used for high-speed or high-precision control.
+Its source code can be found in the UHD source code repository under
+`fpga/usrp3/top/x400/cpld`.
+
+The RJ45 Ethernet connector is connected directly to the PS and is made available
+in Linux as a regular Ethernet interface. It is possible to stream data to and
+from the FPGA, but the data is tunneled through the operating system, which
+makes it a relatively slow interface.
+The QSFP28 connectors are directly connected to the RFSoC transceivers. Different
+FPGA images configure these either as 10 GbE or 100 GbE interfaces. It
+is possible to access the PS through these interfaces (when configured as Ethernet
+interfaces), but their main purpose is to stream data from and to the FPGA at
+high rates.
+
+\subsection x4xx_too_clocking Clocking
+
+The clocking architecture of the motherboard is spread out between a clocking
+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
+(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).
+
+Its input 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
+ 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
+ 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
+ on-board clock. This is the only clock that does not come from the auxiliary
+ clocking board.
+
+The reference PLL (RPLL) produces clocks that are consumed by the GTY banks (for
+Ethernet), as well as the on-board BRC. By default, its reference is a fixed
+100 MHz clock, but it can also be driven by the eCPRI PLL.
+
+The eCPRI PLL is typically driven by a clock derived from the GTY banks, which
+is the assumption if the clock source is set to 'nsync'. The eCPRI PLL can also be
+driven from the RFSoC ("fabric clock") for testing purposes.
+
+The master clock rate (MCR) depends on the sample clock rate. It also depends on
+the RFDC settings, which are different for different flavours of FPGA images (see
+also \ref x4xx_updating_fpga_types). The actual clock running at this frequency
+is generated digitally, within the RFSoC, and is derived from the SPLL clock.
+
+Block diagram:
+```
+ ┌────────────────────────────────────────────────────────┐
+ │ Clocking Aux Board │
+ │ ┌──────┐ ┌───────┐ ┌────────┐ │
+ │ │GPSDO │ │ DAC │ │External│ │
+ │ └─────┬┘ └─┬─────┘ └───┬────┘ │
+ │ ┌v────v┐ │ ┌──────┐ │
+ │ │ OCXO │ │ │ <───────┼──┐
+ │ └──┬───┘ │ ┌───┐ │ MUX <───────┼─┐│
+ │ │ │ │ │ └──┬───┘ │ ││
+ │ ┌────v──────────────v───v─┐ │ ┌───────v───┐ │ ││
+ │ │ │ └─┤eCPRI PLL │ │ ││
+ │ └┐ MUX ┌┘ │LMK05318 │ │ ││
+ │ └─┐ ┌─┘ │ │ │ ││
+ │ └─┬─────────────────┘ └──┬────────┘ │ ││
+ │ │ │ │ ││
+ └───────────┼───────────────────────────┼────────────────┘ ││
+ │ │ ││
+ │ ┌─────────────┐ │ ││
+ ┌──v──v┐ │ │ ││
+ │ MUX │ │ │ ┌───── 100 MHz ││
+ └──┬───┘ │ │ │ ││
+ │Base Ref. Clock │ │ │ ││
+ ┌───────v───────┐ │ ┌───────v──v──┐ ││
+ │ Sample PLL │ └──┤Reference PLL│ ││
+ │ LMK04832 │ │LMK03328 │ ││
+ └──┬─────────┬──┘ └────┬────────┘ │└─ PL/Fabric Clock
+ │ │ │ │
+ v v v │
+ Sample PLL Reference GTY Banks GTY Recovered
+ Clock Clock Clock
+```
+
+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
+components. Refer to the schematic for more details.
+
+*/
+// vim:ft=doxygen:
diff --git a/host/docs/zbx.dox b/host/docs/zbx.dox
new file mode 100644
index 000000000..76c5585f6
--- /dev/null
+++ b/host/docs/zbx.dox
@@ -0,0 +1,469 @@
+/*! \page page_zbx ZBX Daughterboard
+
+\tableofcontents
+
+\section zbx_overview Overview
+
+The ZBX daughterboard is a two-channel superheterodyne transceiver with a focus
+of telecommunication applications in the frequency range below 8 GHz. It supports
+analog bandwidths of up to 400 MHz.
+
+The ZBX daughterboard is designed for use with the Ettus USPR X410.
+
+Feature list:
+- Frequency range (Tx and Rx): 1 MHz - 7.2 GHz (Note: Tune range extends to 8 GHz)
+- Maximum analog bandwidth: 400 MHz
+- Gain range: 0-60 dB.
+ - Note: Rx gain range is reduced to 0-38 dB for frequencies below 500 MHz.
+- On-board CPLD for high flexibility
+- Maximum output power: 5-20 dBm (depending on frequency)
+- Maximum input power limit: +15 dBm
+
+\section zbx_too Theory of Operations
+
+The ZBX daughterboard has two transceiver chains. The following simplified block
+diagram shows their structure:
+
+\image html ZBX_simplified_blockdiagram.svg "ZBX Block Diagram"
+
+It is a superheterodyne transceiver with up to two IF stages. The second IF
+stage is only used for center frequencies below 3 GHz. Above that frequency, the
+desired center frequency becomes the first intermediate frequency (IF1). The
+second LO stage is always enabled, and moves the IF to a value between 1 and 2
+GHz. The USRP ADC/DAC (running at a sampling rate of approx. 3 GHz) will sample
+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
+amplifiers as well as DSAs). All LO synthesizers are identical (LMX2572).
+
+\subsection zbx_too_cpld Digital Control
+
+For digital controls, the ZBX includes a CPLD (its source code is part of the
+UHD repository, and can be found under `fpga/usrp3/top/x400/dboards/zr/cpld/`).
+The CPLD is controlled via registers. Its register space is exposed as a subset
+of the Radio RFNoC block register space (starting at address 0x80000). The CPLD
+is used to control all switches, DSAs, amplifiers, LEDs, LO synthesizers and
+power rails. The CPLD also controls state-dependent behaviour of the ZBX (i.e.,
+behaviour depending on the RX/TX state). For this purpose, ATR signals from the
+FPGA are routed to the CPLD. Parts of the CPLD feature set are also described in
+\ref zbx_gain_control.
+
+
+\subsection zbx_too_lo_control LO Control
+
+The normal operation of the ZBX daughterboard is to simply tune it to a desired
+center frequency, and UHD will internally calculate frequencies for the
+individual LOs as well as the NCO. UHD uses a few rules when calculating LO and
+NCO frequencies:
+
+- To simplify the algorithms, LO frequencies are quantized to multiples
+ of the LO reference frequency (which itself depends on the master clock rate).
+- To reduce LO spurs, the LO synthesizer outputs are filtered with an analog
+ bandpass filter with a minimum frequency of 3.2 GHz.
+- To avoid <em>injection locking</em> (an effect where nearby synthesizers
+ influence each other), UHD is programmed to choose different frequencies for
+ the various synthesizers. When programming the two channels to the same
+ frequency, the LOs will thus intentionally run at different frequencies. The
+ combination of the various LOs and the NCO frequency still results in the same
+ center frequency.
+
+The state of individual LOs can be queried and configured independently. Use the
+following API calls to do so:
+- multi_usrp API:
+ - uhd::usrp::multi_usrp::get_rx_lo_freq()
+ - uhd::usrp::multi_usrp::get_rx_lo_freq_range()
+ - uhd::usrp::multi_usrp::set_rx_lo_freq()
+ - uhd::usrp::multi_usrp::get_tx_lo_freq()
+ - uhd::usrp::multi_usrp::get_tx_lo_freq_range()
+ - uhd::usrp::multi_usrp::set_tx_lo_freq()
+- RFNoC API:
+ - uhd::rfnoc::radio_control::get_rx_lo_freq()
+ - uhd::rfnoc::radio_control::get_rx_lo_freq_range()
+ - uhd::rfnoc::radio_control::set_rx_lo_freq()
+ - uhd::rfnoc::radio_control::get_tx_lo_freq()
+ - uhd::rfnoc::radio_control::get_tx_lo_freq_range()
+ - uhd::rfnoc::radio_control::set_tx_lo_freq()
+
+Note that manually modifying LOs is considered advanced behaviour, and may result
+in a bad state of the device. To undo manual changes, use the regular API calls
+to set a center frequency.
+
+\section zbx_ant_ports Antenna Ports
+
+The ZBX has two SMA ports per channel, called "TX/RX0" and "RX1".
+In addition, the antenna values can be set to "CAL_LOOPBACK"
+to loop back the Tx path into the Rx path (this is sometimes required for
+calibration purposes). The Rx antenna value can also be set to "TERMINATION" to
+terminate the Rx path.
+
+Use the uhd::usrp::multi_usrp::get_rx_antennas() or uhd::usrp::multi_usrp::get_tx_antennas()
+API calls to enumerate the valid antenna names. When using RFNoC API, use the
+uhd::rfnoc::radio_control::get_rx_antennas() and
+uhd::rfnoc::radio_control::get_tx_antennas() calls, respectively.
+
+\section zbx_phase_alignment Phase Alignment
+
+Like the UBX and SBX daughterboards, the ZBX allows to be phase-aligned. This is
+done by setting a command time before tuning the individual channels:
+~~~{.cpp}
+// Assume that `usrp` is a multi_usrp object
+// 1) Set a command time in the future, e.g. 500ms from now:
+usrp->set_command_time(usrp->get_time_now() + .5);
+// 2) Tune to a new frequency on all channels, e.g., 2 GHz:
+usrp->set_rx_freq(2e9); // The ALL_CHANS argument is implied here
+// 3) Wait until we're past the command time:
+std::this_thread::sleep_for(500ms);
+// Channel phases are now at a deterministic offset. Repeating this procedure
+// will lead to the same offset.
+~~~
+
+\section zbx_pwr_cal Power Calibration
+
+The ZBX supports the UHD power API (see also \ref page_power). UHD ships with
+nominal calibration data which will allow setting the reference power levels
+without previously manually calibrating the device.
+
+\section zbx_sensors Sensors
+
+Every channel has three "locked" sensors for the LO stages (`lo1_locked`,
+`lo2_locked`, and `nco_locked`). A "virtual" sensor called `lo_locked` confirms
+that all LOs that are currently engaged are locked. The "NCO lock" sensor is a
+special case: The NCO is not on the daughterboard (it is part of the
+RFSoC FPGA), but to simplify the API it was placed together with the LO lock
+sensors. Unlike the (analog) synthesizers on the daughterboard, the NCO "unlock"
+is not used to signify a loss of reference lock, but to signal that the NCO is
+still in reset.
+
+Additionally, the ZBX has a temperature sensors `temperature`. While the UHD
+API allows addressing a sensors based on direction (RX/TX) and channel (0/1),
+there is only one physical temperature sensor, and it will return the same value
+regardless of which channel or direction is selected.
+
+The following API calls can be used to enumerate available sensors, and query
+their values:
+- multi_usrp API:
+ - uhd::usrp::multi_usrp::get_rx_sensor_names()
+ - uhd::usrp::multi_usrp::get_rx_sensor()
+ - uhd::usrp::multi_usrp::get_tx_sensor_names()
+ - uhd::usrp::multi_usrp::get_tx_sensor()
+- RFNoC API:
+ - uhd::rfnoc::radio_control::get_rx_sensor_names()
+ - uhd::rfnoc::radio_control::get_rx_sensor()
+ - uhd::rfnoc::radio_control::get_tx_sensor_names()
+ - uhd::rfnoc::radio_control::get_tx_sensor()
+
+\section zbx_gain_control Gain Control
+
+The ZBX has a sophisticated gain control, capable of controlling either an
+overall gain, or manually controlling its individual gain stages. Furthermore,
+the onboard CPLD can store gain tables as well, which can be accessed from other
+RFNoC blocks via RFNoC commands.
+
+The TX path has three gain-related components: Two DSAs, as well an amplifier path.
+The former have an individual gain range of 31 dB. The amplifier path allows
+selecting one of two amplifiers, one with a nominal gain of 14 dB for lower
+frequencies, and one with a nominal gain of 21 dB for higher frequencies. Their
+actual amplification values depend on the specific frequency, and may also vary
+from device to device. The amplifiers can be bypassed.
+
+The RX path consists of four DSAs, each with a gain range of 15 dB.
+
+Different gain behaviours of the ZBX daughterboard are controlled by <em>gain
+profiles</em>, which may be set independently for TX and RX. Different gain
+profiles have different API behaviours as explained in the rest of this section.
+
+To switch between gain profiles, use the uhd::usrp::multi_usrp::set_tx_gain_profile()
+or uhd::usrp::multi_usrp::set_rx_gain_profile() API calls. When using the RFNoC
+API, use the uhd::rfnoc::radio_control::set_tx_gain_profile() or
+uhd::rfnoc::radio_control::set_rx_gain_profile() API calls. The main difference
+between these APIs is that the multi_usrp API calls allow a default channel
+value, which the RFNoC API calls do not.
+
+As with all other devices, the following API calls set or query gain values:
+- Multi USRP:
+ - uhd::usrp::multi_usrp::set_tx_gain()
+ - uhd::usrp::multi_usrp::get_tx_gain()
+ - uhd::usrp::multi_usrp::set_rx_gain()
+ - uhd::usrp::multi_usrp::get_rx_gain()
+- RFNoC
+ - uhd::rfnoc::radio_control::set_tx_gain()
+ - uhd::rfnoc::radio_control::get_tx_gain()
+ - uhd::rfnoc::radio_control::set_rx_gain()
+ - uhd::rfnoc::radio_control::get_rx_gain()
+
+These API calls come in different flavours, with an optional 'gain name' argument.
+Note the multi_usrp API calls default to setting the overall gain value, which
+is not allowed in all gain profiles. All API calls have a corresponding API call
+to query the allowable gain range.
+
+\b Note: The RX gain range is not consistent on ZBX. Below 500 MHz, the gain
+range is reduced to 0-38 dB. It is recommended to use get_rx_gain_range() to
+query the currently valid gain range.
+
+\b Note: Some gain profiles require changing on both TX and RX. For example,
+changing from `default` to `table_noatr` must happen on TX and RX at the same
+time. UHD will automatically change the gain profile accordingly. It is therefore
+recommended to call get_rx_gain_profile() or get_tx_gain_profile() to verify the
+correct gain profile when in doubt.
+
+\subsection zbx_gain_default Default Gain Profile
+
+This gain profile is active by default. It allows setting a single, scalar gain
+value. UHD will internally use a gain table to linearize the overall gain (meaning
+that a 1 dB gain increase will also increase the transmit or receive power by 1 dB).
+However, the UHD-internal gain table is not calibrated per-device, nor does it
+take into account temperature or other changes.
+
+In this gain mode, it is not possible to set the individual gain stages directly,
+but it is possible to read them back. This may be helpful when trying to fine-tune
+gain settings in software.
+
+~~~{.cpp}
+// Assumption: 'usrp' is a multi_usrp object
+usrp->set_tx_gain_profile("default"); // Only necessary if the gain profile was set to something else before
+usrp->set_tx_gain(30); // Will set the gain to 30 dB on all associated daughterboards
+std::cout << usrp->get_tx_gain() << std::endl; // Should print "30"
+usrp->set_tx_gain(0, "DSA1"); // Will cause an exception
+// Individual DSAs may still be queried. Note that even though this is an
+// attenuator, the return value is a gain (higher values mean more TX power):
+auto dsa1_gain = usrp->get_tx_gain("DSA1");
+~~~
+
+<b>ATR Behaviour:</b> The gains will apply to their respective ATR state, i.e.,
+RX gains will be applied to both the RX and full-duplex state, and TX gains will
+be applied to the TX and full-duplex state. In the idle state, gains are set to
+minimum gain.
+
+<b>RFNoC Commands:</b> All DSA values are on one register for a given ATR state,
+and the TX amplifier shares a register with the antenna controls.
+That means that changing the DSA values
+in this gain profile will cause two register writes (one for RX/TX, and one for
+full-duplex). Changing the TX amplifier gain value will incur another two writes.
+
+\subsection zbx_gain_manual Manual Gain Profile
+
+When more control is desired, the manual gain profile can be applied. Here, it
+is no longer possible to request an overall gain value. However, it is now
+possible to set the DSA and amplifier values directly.
+
+~~~{.cpp}
+usrp->set_tx_gain_profile("manual");
+usrp->set_tx_gain(30); // ERROR: Now, we have to specify a name
+usrp->set_tx_gain(5, "DSA1"); // Set DSA1 to 5 dB gain (equals 26 dB attenuation)
+// The following line will return an undefined value and print a warning, but
+// will not throw. That's because calling the overall gain is a common API call
+// done by many utilities, and this behaviour is considered most backward compatible.
+std::cout << usrp->get_tx_gain() << std::endl;
+std::cout << usrp->get_tx_gain("DSA1") << std::endl; // Should print '5'
+~~~
+
+\subsection zbx_gain_table CPLD-Table Gain Profile with ATR control
+
+In this profile, UHD exposes access to the gain table stored on the CPLD. By default,
+the CPLD is initialized with the same gain table as UHD uses internally, i.e.,
+there is no difference in behaviour when using this profile, unless the gain
+table is modified.
+
+Setting DSA values directly from UHD is not possible in this profile, nor is setting
+an overall gain value. However, it is now possible to load an entry from the
+CPLD gain table and apply it to the current DSA settings.
+In this profile, the ATR behaviour for the DSAs is the same as in the 'default'
+or 'manual' profiles. That means loading a DSA table entry requires two writes
+to the CPLD, one for TX/RX, and one for full-duplex.
+
+The gain "values" are no longer interpreted as dB values, but refer to DSA table
+indices.
+
+An important use case of this gain profile is when testing CPLD gain tables, but
+not changing other aspects of the software control. Often, this is an intermediate
+debugging step while developing applications that use RFNoC commands to control
+the gain.
+
+Another use case of this profile is when running RFNoC applications, where the
+gain is controlled from another RFNoC block, but the ATR behaviour is left in
+its default state.
+
+~~~{.cpp}
+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.
+// 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
+// full-duplex ATR modes. In other words, the register values TX0_TABLE_DSA*
+// are copied to TX0_DSA*.
+// The following line will return an undefined value and print a warning, but
+// will not throw. That's because calling the overall gain is a common API call
+// done by many utilities, and this behaviour is considered most backward compatible.
+std::cout << usrp->get_tx_gain() << std::endl;
+// The following line will print the actual value of DSA1. Note, however, that
+// UHD needs to read back the value from the CPLD, since it can't know what's
+// stored on the CPLD. That means the following call will require a read from
+// 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
+~~~
+
+\subsection zbx_gain_tablenoatr CPLD-Table Gain Profile without ATR control
+
+When running applications where the FPGA has full control over the gain, and the
+ATR behaviour should also be replaced by register writes, this profile may be
+used.
+
+The main difference to the previous profile is that when the radio switches
+between RX, TX, full duplex, and idle states, there is no automatic update of
+the gain values. Instead, the current gain values are selected by writing to the
+`SW_RF0_DSA_CONFIG` and `SW_RF1_DSA_CONFIG` registers. Changing these registers
+will select an entry from the `RX/TX DSA` tables. Unlike the gain tables, these
+are not prepopulated.
+
+~~~{.cpp}
+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.
+// 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");
+// The CPLD SW_RF0_DSA_CONFIG register is now set to 5. That means the DSA values
+// stored in TX0_DSA1[5] and TX0_DSA2[5] are now being used, assuming no other
+// entity is sending commands to the CPLD via RFNoC.
+// Copying CPLD gain table entries into the TX0_DSA* registers is not possible
+// from software in this profile. Rather, we are hands-off and let the FPGA take
+// control.
+//
+// Let's assume that an RFNoC block has sent register writes to the radio block
+// in order to load new gain table entries, and apply them. We can still read
+// back the current DSA values (the ones being currently used on the RF chain)
+// by reading back from the CPLD.
+// 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.
+~~~
+
+\section zbx_atr Auto-Transmit-Receive Registers (ATR)
+
+Like other USRPs, the X410 provides GPIOs to the daughterboards that communicate
+the RX/TX state. The ZBX is, by default, configured to switch settings based on
+the current state (RX, TX, full duplex, idle). For example, the TX/RX antenna
+is switched between the TX and RX channels depending on the state. A total of 4
+GPIOs between the motherboard FPGA and the daughterboard CPLD are used for this
+purpose, two pins per channel.
+
+ZBX uses these pins to select between different RF control values
+(e.g., the aforementioned TX/RX antenna switch position; this also includes the
+front-panel LEDs) as well as DSA controls (e.g., when doing RX only, the TX gain
+stages can be set to zero to minimize leakage). Internally, this works by
+converting the ATR pins into a control word. This control word is used to index
+tables that contain different values for RF control/LEDs as well as DSAs.
+
+Example: Assume the device is transmitting, but not receiving, on channel 0. The
+FPGA will set the ATR pins for channel 0 to a binary value of 0b10, which equals
+a decimal value of 2. The transmit gain is controlled by DSA values that are
+stored in tables called TX0_DSA1 and TX0_DSA2, respectively. For the duration
+of the transmission, the DSA values at table position TX0_DSA1[2] and TX0_DSA2[2]
+will thus be used. Similarly, tables for RF path control and LEDs are used to
+configure those. This mode of using the ATR pins is called the "classic ATR" mode
+and is the default behaviour.
+
+The ZBX daughterboard provides two additional modes of utilizing those pins:
+- "Software Defined": In this mode, the ATR pins are ignored. The control word
+ is derived from another register. In this case, changing the table index
+ requires another register write as opposed to the almost instantaneous tracking
+ of the ATR state in the other modes.
+- "FPGA controlled": This is similar to the classic ATR mode, but it combines
+ the pins from channel 0 and 1. The downside is that channel 0 and 1 are no
+ longer independent, but it allows using 16 entries from the tables instead of
+ four as in the classic ATR mode.
+
+The ATR pin mode can be set independently for channel 0 and 1, and also for DSA
+tables vs. RF control/LED tables. Note that combining the "FPGA controlled" mode
+on one channel with the "classic" mode on the other channel would yield a possibly
+conflicting configuration.
+
+Usage of these modes is considered highly advanced usage of ZBX. The "FPGA
+controlled" mode is not supported by UHD without custom modifications (it is
+possible, however, to manually write to the appropriate registers to use this
+mode). Using this mode would also require modifications of the FPGA image to
+add custom controls to the ATR GPIO pins.
+
+The "software defined" mode can be enabled for the DSA tables by using the
+`table_noatr` gain profile (see the previous section).
+
+\section zbx_updating_cpld Updating the ZBX CPLD
+
+If you need to update the ZBX CPLD, you can do so by running the following command
+on the device:
+
+ python3 /usr/lib/python3.?/site-packages/usrp_mpm/dboard_manager/zbx_update_cpld.py
+
+By default, the script will attempt to install an image from the default path to
+both daughterboards. To specify which file or dboards to program, use the
+following options when running the updater script:
+
+- `--dboards=0,1`
+- `--file=<path_to_cpld_image>`
+
+By default, the `cpld-zbx.rpd` file will be provided at
+`/lib/firmware/ni/cpld-zbx.rpd`. Note that after downloading the ZBX CPLD, you
+will need to completely shut down and power-cycle the device.
+
+\subsection zbx_updating_cpld_details CPLD Programming: Details
+
+Read this section if you want to create your own ZBX CPLD image, or require more
+information on how the CPLD image is updated.
+
+The source code for the ZBX CPLD is provided within the UHD code repository, at
+`fpga/usrp3/top/x400/dboards/zbx/cpld`. Read the Makefile for more instructions
+on how to build images.
+
+The build process will produce two CPLD bitfiles: A `.rpd` file and a `.svf` file.
+Both are required for different programming methods. There are two programming
+methods:
+
+- Flash mode: This requires the `.rpd` file. It uses the motherboard CPLD as a
+ programming device. This is the default mode.
+- Legacy mode: This directly calls into openocd to flash the ZBX CPLD. It requires
+ the `.svf` file.
+
+Before running the updater, ensure that MPM is installed on your device as some
+resources from MPM code are used.
+
+You should also ensure that the power rails to the daughterboard are up and that
+MPM can successfully communicate with the daughterboard when running (running
+`uhd_usrp_probe` successfully is sufficient).
+
+MPM does not need to be running when the updater script is executed, but you
+should start it once to ensure a valid FPGA image is loaded and communication to
+the daughterboard is working before attempting this.
+
+To specify a programming mode, use the `--updater` argument. For example, to
+force the legacy mode, use the following command:
+
+ python3 /usr/lib/python3.?/site-packages/usrp_mpm/dboard_manager/zbx_update_cpld.py \
+ --updater=legacy \
+ --file=/lib/firmware/ni/cpld-zbx.svf
+
+This will program the image from the default location onto the CPLD using the
+'legacy' method.
+
+\section zbx_flash Flash Memory and EEPROM
+
+Every ZBX daughterboard has 2 MB of non-volatile flash memory which can be used
+to store data such as daughterboard-specific information. When
+logged into the X410 Linux system, the flash memory is mounted into the
+filesystem under `/mnt/db0_flash` and `/mnt/db1_flash`, respectively. The
+daughterboard also uses a separate EEPROM to store revision, serial, and product
+ID of the daughterboard.
+
+*/
+// vim:ft=doxygen:
diff --git a/host/include/uhd/cal/CMakeLists.txt b/host/include/uhd/cal/CMakeLists.txt
index 38b4885ef..482502c89 100644
--- a/host/include/uhd/cal/CMakeLists.txt
+++ b/host/include/uhd/cal/CMakeLists.txt
@@ -4,21 +4,38 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
+set(FLATBUFFER_SCHEMA_FILES
+ "cal_metadata.fbs"
+ "iq_cal.fbs"
+ "pwr_cal.fbs"
+ "dsa_cal.fbs"
+)
+
+set(FLATBUFFER_GEN_HEADER_FILES
+ "cal_metadata_generated.h"
+ "iq_cal_generated.h"
+ "pwr_cal_generated.h"
+ "dsa_cal_generated.h"
+)
+
UHD_INSTALL(FILES
container.hpp
database.hpp
iq_cal.hpp
pwr_cal.hpp
- iq_cal_generated.h
- pwr_cal_generated.h
+ dsa_cal.hpp
DESTINATION ${INCLUDE_DIR}/uhd/cal
COMPONENT headers
)
UHD_INSTALL(FILES
- iq_cal.fbs
- pwr_cal.fbs
+ ${FLATBUFFER_SCHEMA_FILES}
DESTINATION ${PKG_DATA_DIR}/cal
COMPONENT headers
)
+UHD_INSTALL(FILES
+ ${FLATBUFFER_GEN_HEADER_FILES}
+ DESTINATION ${INCLUDE_DIR}/uhd/cal
+ COMPONENT headers
+ )
diff --git a/host/include/uhd/cal/dsa_cal.fbs b/host/include/uhd/cal/dsa_cal.fbs
new file mode 100644
index 000000000..dc33da497
--- /dev/null
+++ b/host/include/uhd/cal/dsa_cal.fbs
@@ -0,0 +1,36 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+// DSA step setting table
+
+include "cal_metadata.fbs";
+
+namespace uhd.usrp.cal;
+
+// DSA step settings for a given gain in a band
+table DsaStep
+{
+ steps: [uint];
+}
+
+// DSA settings for all gains of a band
+table BandDsaMap
+{
+ max_freq: ulong (key); // max frequency for the band
+ gains: [DsaStep]; // DSA step setting for all gain values
+ name: string; // human readable frequency band name
+}
+
+// Band settings
+table DsaCal
+{
+ metadata: Metadata; // useful additional information
+ band_dsa_map: [BandDsaMap]; // settings for all bands
+}
+
+root_type DsaCal;
+file_identifier "dsas";
+file_extension "cal";
diff --git a/host/include/uhd/cal/dsa_cal.hpp b/host/include/uhd/cal/dsa_cal.hpp
new file mode 100644
index 000000000..e8ead87ee
--- /dev/null
+++ b/host/include/uhd/cal/dsa_cal.hpp
@@ -0,0 +1,130 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_CAL_GAIN_HPP
+#define INCLUDED_LIBUHD_CAL_GAIN_HPP
+
+#include <uhd/cal/container.hpp>
+#include <uhd/config.hpp>
+#include <boost/optional.hpp>
+#include <array>
+#include <map>
+
+namespace uhd { namespace usrp { namespace cal {
+
+/*! Class that stores DSA indices for all ZBX TX bands.
+ */
+class UHD_API zbx_tx_dsa_cal : public container
+{
+public:
+ static constexpr uint32_t NUM_AMP = 1;
+ static constexpr uint32_t NUM_DSA = 2 + NUM_AMP;
+ static constexpr uint32_t NUM_GAIN_STAGES = 61;
+
+ using sptr = std::shared_ptr<zbx_tx_dsa_cal>;
+ using step_settings = std::array<uint32_t, NUM_DSA>;
+
+ /*! Add a new band description
+ *
+ * max_freq is the (inclusive) upper limit
+ * for the band (lower limit derives from the other bands). Name is an
+ * text representation (human readable) for the band. dsa_steps is an
+ * array of DSA settings for all gains in the band.
+ */
+ virtual void add_frequency_band(const double max_freq,
+ const std::string& name,
+ std::array<step_settings, NUM_GAIN_STAGES> dsa_steps) = 0;
+
+ /*! Retrieves DSA settings for frequency and gain_index.
+ *
+ * The settings are
+ * retrieved from the band with the biggest max_freq that is smaller or
+ * equal to freq. DSA settings are the settings at gain_index in that band.
+ * Value errors are thrown if freq is larger that the largest freq_max of
+ * all bands or gain_index is not within range.
+ */
+ virtual const step_settings get_dsa_setting(
+ const double freq, const size_t gain_index) const = 0;
+
+ /* Check whether two frequencies map to the same band.
+ */
+ virtual bool is_same_band(double freq1, double freq2) const = 0;
+
+ /*! Retrieves DSA settings as flat list.
+ * The values are flattened by frequency band, gain and values in that order.
+ * Use NUM_DSA and NUM_GAIN_STAGES to find values in the list.
+ */
+ virtual std::vector<uint32_t> get_band_settings(double freq, uint8_t dsa) const = 0;
+
+ /*!
+ * Clear all stored values
+ */
+ virtual void clear() = 0;
+
+ //! Factory for new cal data sets
+ static sptr make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp);
+
+ //! Default factory
+ static sptr make();
+};
+
+/*! Class that stores DSA indices for all ZBX TX bands.
+ */
+class UHD_API zbx_rx_dsa_cal : public container
+{
+public:
+ static constexpr uint32_t NUM_DSA = 4;
+ static constexpr uint32_t NUM_GAIN_STAGES = 61;
+
+ using sptr = std::shared_ptr<zbx_rx_dsa_cal>;
+ using step_settings = std::array<uint32_t, NUM_DSA>;
+
+ /*! Add a new band description.
+ *
+ * max_freq is the (inclusive) upper limit
+ * for the band (lower limit derives from the other bands). Name is an
+ * text representation (human readable) for the band. dsa_steps is an
+ * array of DSA settings for all gains in the band.
+ */
+ virtual void add_frequency_band(const double max_freq,
+ const std::string& name,
+ std::array<step_settings, NUM_GAIN_STAGES> dsa_steps) = 0;
+
+ virtual bool is_same_band(double freq1, double freq2) const = 0;
+ /*! Retrieves DSA settings for frequency and gain_index.
+ *
+ * The settings are
+ * retrieved from the band with the biggest max_freq that is smaller or
+ * equal to freq. DSA settings are the settings at gain_index in that band.
+ * Value errors are thrown if freq is larger that the largest freq_max of
+ * all bands or gain_index is not within range.
+ */
+ virtual const step_settings get_dsa_setting(
+ const double freq, const size_t gain_index) const = 0;
+
+ /*! Retrieves DSA settings as flat list.
+ * The values are flattend by frequency band, gain and values in that order.
+ * Use NUM_DSA and NUM_GAIN_STAGES to find values in the list.
+ */
+ virtual std::vector<uint32_t> get_band_settings(double freq, uint8_t dsa) const = 0;
+
+ /*!
+ * Clear all stored values
+ */
+ virtual void clear() = 0;
+
+ //! Factory for new cal data sets
+ static sptr make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp);
+
+ //! Default factory
+ static sptr make();
+};
+
+}}} // namespace uhd::usrp::cal
+
+#endif /* INCLUDED_LIBUHD_CAL_GAIN_HPP */
diff --git a/host/include/uhd/cal/dsa_cal_generated.h b/host/include/uhd/cal/dsa_cal_generated.h
new file mode 100644
index 000000000..18e8a2713
--- /dev/null
+++ b/host/include/uhd/cal/dsa_cal_generated.h
@@ -0,0 +1,274 @@
+// automatically generated by the FlatBuffers compiler, do not modify
+
+
+#ifndef FLATBUFFERS_GENERATED_DSACAL_UHD_USRP_CAL_H_
+#define FLATBUFFERS_GENERATED_DSACAL_UHD_USRP_CAL_H_
+
+#include "flatbuffers/flatbuffers.h"
+
+#include "cal_metadata_generated.h"
+
+namespace uhd {
+namespace usrp {
+namespace cal {
+
+struct DsaStep;
+struct DsaStepBuilder;
+
+struct BandDsaMap;
+struct BandDsaMapBuilder;
+
+struct DsaCal;
+struct DsaCalBuilder;
+
+struct DsaStep FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef DsaStepBuilder Builder;
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_STEPS = 4
+ };
+ const flatbuffers::Vector<uint32_t> *steps() const {
+ return GetPointer<const flatbuffers::Vector<uint32_t> *>(VT_STEPS);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyOffset(verifier, VT_STEPS) &&
+ verifier.VerifyVector(steps()) &&
+ verifier.EndTable();
+ }
+};
+
+struct DsaStepBuilder {
+ typedef DsaStep Table;
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_steps(flatbuffers::Offset<flatbuffers::Vector<uint32_t>> steps) {
+ fbb_.AddOffset(DsaStep::VT_STEPS, steps);
+ }
+ explicit DsaStepBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ DsaStepBuilder &operator=(const DsaStepBuilder &);
+ flatbuffers::Offset<DsaStep> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<DsaStep>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<DsaStep> CreateDsaStep(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<flatbuffers::Vector<uint32_t>> steps = 0) {
+ DsaStepBuilder builder_(_fbb);
+ builder_.add_steps(steps);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<DsaStep> CreateDsaStepDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ const std::vector<uint32_t> *steps = nullptr) {
+ auto steps__ = steps ? _fbb.CreateVector<uint32_t>(*steps) : 0;
+ return uhd::usrp::cal::CreateDsaStep(
+ _fbb,
+ steps__);
+}
+
+struct BandDsaMap FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef BandDsaMapBuilder Builder;
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_MAX_FREQ = 4,
+ VT_GAINS = 6,
+ VT_NAME = 8
+ };
+ uint64_t max_freq() const {
+ return GetField<uint64_t>(VT_MAX_FREQ, 0);
+ }
+ bool KeyCompareLessThan(const BandDsaMap *o) const {
+ return max_freq() < o->max_freq();
+ }
+ int KeyCompareWithValue(uint64_t val) const {
+ return static_cast<int>(max_freq() > val) - static_cast<int>(max_freq() < val);
+ }
+ const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>> *gains() const {
+ return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>> *>(VT_GAINS);
+ }
+ const flatbuffers::String *name() const {
+ return GetPointer<const flatbuffers::String *>(VT_NAME);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyField<uint64_t>(verifier, VT_MAX_FREQ) &&
+ VerifyOffset(verifier, VT_GAINS) &&
+ verifier.VerifyVector(gains()) &&
+ verifier.VerifyVectorOfTables(gains()) &&
+ VerifyOffset(verifier, VT_NAME) &&
+ verifier.VerifyString(name()) &&
+ verifier.EndTable();
+ }
+};
+
+struct BandDsaMapBuilder {
+ typedef BandDsaMap Table;
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_max_freq(uint64_t max_freq) {
+ fbb_.AddElement<uint64_t>(BandDsaMap::VT_MAX_FREQ, max_freq, 0);
+ }
+ void add_gains(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>>> gains) {
+ fbb_.AddOffset(BandDsaMap::VT_GAINS, gains);
+ }
+ void add_name(flatbuffers::Offset<flatbuffers::String> name) {
+ fbb_.AddOffset(BandDsaMap::VT_NAME, name);
+ }
+ explicit BandDsaMapBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ BandDsaMapBuilder &operator=(const BandDsaMapBuilder &);
+ flatbuffers::Offset<BandDsaMap> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<BandDsaMap>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<BandDsaMap> CreateBandDsaMap(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ uint64_t max_freq = 0,
+ flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>>> gains = 0,
+ flatbuffers::Offset<flatbuffers::String> name = 0) {
+ BandDsaMapBuilder builder_(_fbb);
+ builder_.add_max_freq(max_freq);
+ builder_.add_name(name);
+ builder_.add_gains(gains);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<BandDsaMap> CreateBandDsaMapDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ uint64_t max_freq = 0,
+ const std::vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>> *gains = nullptr,
+ const char *name = nullptr) {
+ auto gains__ = gains ? _fbb.CreateVector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>>(*gains) : 0;
+ auto name__ = name ? _fbb.CreateString(name) : 0;
+ return uhd::usrp::cal::CreateBandDsaMap(
+ _fbb,
+ max_freq,
+ gains__,
+ name__);
+}
+
+struct DsaCal FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef DsaCalBuilder Builder;
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_METADATA = 4,
+ VT_BAND_DSA_MAP = 6
+ };
+ const Metadata *metadata() const {
+ return GetPointer<const Metadata *>(VT_METADATA);
+ }
+ const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>> *band_dsa_map() const {
+ return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>> *>(VT_BAND_DSA_MAP);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyOffset(verifier, VT_METADATA) &&
+ verifier.VerifyTable(metadata()) &&
+ VerifyOffset(verifier, VT_BAND_DSA_MAP) &&
+ verifier.VerifyVector(band_dsa_map()) &&
+ verifier.VerifyVectorOfTables(band_dsa_map()) &&
+ verifier.EndTable();
+ }
+};
+
+struct DsaCalBuilder {
+ typedef DsaCal Table;
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_metadata(flatbuffers::Offset<Metadata> metadata) {
+ fbb_.AddOffset(DsaCal::VT_METADATA, metadata);
+ }
+ void add_band_dsa_map(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>>> band_dsa_map) {
+ fbb_.AddOffset(DsaCal::VT_BAND_DSA_MAP, band_dsa_map);
+ }
+ explicit DsaCalBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ DsaCalBuilder &operator=(const DsaCalBuilder &);
+ flatbuffers::Offset<DsaCal> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<DsaCal>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<DsaCal> CreateDsaCal(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<Metadata> metadata = 0,
+ flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>>> band_dsa_map = 0) {
+ DsaCalBuilder builder_(_fbb);
+ builder_.add_band_dsa_map(band_dsa_map);
+ builder_.add_metadata(metadata);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<DsaCal> CreateDsaCalDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<Metadata> metadata = 0,
+ std::vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>> *band_dsa_map = nullptr) {
+ auto band_dsa_map__ = band_dsa_map ? _fbb.CreateVectorOfSortedTables<uhd::usrp::cal::BandDsaMap>(band_dsa_map) : 0;
+ return uhd::usrp::cal::CreateDsaCal(
+ _fbb,
+ metadata,
+ band_dsa_map__);
+}
+
+inline const uhd::usrp::cal::DsaCal *GetDsaCal(const void *buf) {
+ return flatbuffers::GetRoot<uhd::usrp::cal::DsaCal>(buf);
+}
+
+inline const uhd::usrp::cal::DsaCal *GetSizePrefixedDsaCal(const void *buf) {
+ return flatbuffers::GetSizePrefixedRoot<uhd::usrp::cal::DsaCal>(buf);
+}
+
+inline const char *DsaCalIdentifier() {
+ return "dsas";
+}
+
+inline bool DsaCalBufferHasIdentifier(const void *buf) {
+ return flatbuffers::BufferHasIdentifier(
+ buf, DsaCalIdentifier());
+}
+
+inline bool VerifyDsaCalBuffer(
+ flatbuffers::Verifier &verifier) {
+ return verifier.VerifyBuffer<uhd::usrp::cal::DsaCal>(DsaCalIdentifier());
+}
+
+inline bool VerifySizePrefixedDsaCalBuffer(
+ flatbuffers::Verifier &verifier) {
+ return verifier.VerifySizePrefixedBuffer<uhd::usrp::cal::DsaCal>(DsaCalIdentifier());
+}
+
+inline const char *DsaCalExtension() {
+ return "cal";
+}
+
+inline void FinishDsaCalBuffer(
+ flatbuffers::FlatBufferBuilder &fbb,
+ flatbuffers::Offset<uhd::usrp::cal::DsaCal> root) {
+ fbb.Finish(root, DsaCalIdentifier());
+}
+
+inline void FinishSizePrefixedDsaCalBuffer(
+ flatbuffers::FlatBufferBuilder &fbb,
+ flatbuffers::Offset<uhd::usrp::cal::DsaCal> root) {
+ fbb.FinishSizePrefixed(root, DsaCalIdentifier());
+}
+
+} // namespace cal
+} // namespace usrp
+} // namespace uhd
+
+#endif // FLATBUFFERS_GENERATED_DSACAL_UHD_USRP_CAL_H_
diff --git a/host/include/uhd/features/CMakeLists.txt b/host/include/uhd/features/CMakeLists.txt
index 798844077..64e50bb12 100644
--- a/host/include/uhd/features/CMakeLists.txt
+++ b/host/include/uhd/features/CMakeLists.txt
@@ -7,6 +7,7 @@
UHD_INSTALL(FILES
discoverable_feature.hpp
discoverable_feature_getter_iface.hpp
+ ref_clk_calibration_iface.hpp
DESTINATION ${INCLUDE_DIR}/uhd/features
COMPONENT headers
)
diff --git a/host/include/uhd/features/adc_self_calibration_iface.hpp b/host/include/uhd/features/adc_self_calibration_iface.hpp
new file mode 100644
index 000000000..fbacccf77
--- /dev/null
+++ b/host/include/uhd/features/adc_self_calibration_iface.hpp
@@ -0,0 +1,45 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/features/discoverable_feature.hpp>
+#include <memory>
+
+namespace uhd { namespace features {
+
+/*! Interface for running ADC self-calibration on supported devices.
+ * Currently, only the X4xx series of devices supports calibrating the
+ * internal ADCs.
+ */
+class adc_self_calibration_iface : public discoverable_feature
+{
+public:
+ using sptr = std::shared_ptr<adc_self_calibration_iface>;
+
+ static discoverable_feature::feature_id_t get_feature_id()
+ {
+ return discoverable_feature::ADC_SELF_CALIBRATION;
+ }
+
+ std::string get_feature_name() const
+ {
+ return "ADC Self Calibration";
+ }
+
+ virtual ~adc_self_calibration_iface() = default;
+
+ //! Runs calibration on the specified channel. This will momentarily
+ // reconfigure both the specified RX channel as well as the matching
+ // TX channel for the operation.
+ //
+ // If you would like to calibrate the ADCs without interrupting the
+ // signal chain, use the rx_codec/<n>/calibration_frozen property on the
+ // motherboard's property tree.
+ virtual void run(const size_t chan) = 0;
+};
+
+}} // namespace uhd::features
diff --git a/host/include/uhd/features/discoverable_feature.hpp b/host/include/uhd/features/discoverable_feature.hpp
index afe25cb63..8cbe8e13f 100644
--- a/host/include/uhd/features/discoverable_feature.hpp
+++ b/host/include/uhd/features/discoverable_feature.hpp
@@ -31,6 +31,9 @@ public:
enum feature_id_t {
RESERVED0,
RESERVED1,
+ FPGA_LOAD_NOTIFICATION,
+ ADC_SELF_CALIBRATION,
+ REF_CLK_CALIBRATION,
};
virtual ~discoverable_feature() = default;
diff --git a/host/include/uhd/features/ref_clk_calibration_iface.hpp b/host/include/uhd/features/ref_clk_calibration_iface.hpp
new file mode 100644
index 000000000..83082da68
--- /dev/null
+++ b/host/include/uhd/features/ref_clk_calibration_iface.hpp
@@ -0,0 +1,44 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/features/discoverable_feature.hpp>
+#include <memory>
+
+namespace uhd { namespace features {
+
+/*! Interface to provide access to functions (set, get and store the tuning
+ * word) to perform calibration of the DAC for the internal reference clock
+ * source on supported devices. Currently, only the X4xx series of devices
+ * supports this.
+ */
+class ref_clk_calibration_iface : public discoverable_feature
+{
+public:
+ using sptr = std::shared_ptr<ref_clk_calibration_iface>;
+
+ static discoverable_feature::feature_id_t get_feature_id()
+ {
+ return discoverable_feature::REF_CLK_CALIBRATION;
+ }
+
+ std::string get_feature_name() const
+ {
+ return "Ref Clk Calibration";
+ }
+
+ virtual ~ref_clk_calibration_iface() = default;
+
+ //! Set the tuning word to be configured on the internal reference clock DAC.
+ virtual void set_ref_clk_tuning_word(uint32_t tuning_word) = 0;
+ //! Returns the tuning word configured on the internal reference clock DAC.
+ virtual uint32_t get_ref_clk_tuning_word() = 0;
+ //! Writes the reference clock tuning word to the clocking board EEPROM.
+ virtual void store_ref_clk_tuning_word(uint32_t tuning_word) = 0;
+};
+
+}} // namespace uhd::features
diff --git a/host/include/uhd/rfnoc/blocks/radio.yml b/host/include/uhd/rfnoc/blocks/radio.yml
index 81b96bf07..bae9fe128 100644
--- a/host/include/uhd/rfnoc/blocks/radio.yml
+++ b/host/include/uhd/rfnoc/blocks/radio.yml
@@ -52,17 +52,17 @@ data:
mdata_sig: ~
io_ports:
- ctrl_port:
+ ctrlport:
type: ctrl_port
drive: master
rename:
pattern: (.*)
repl: m_\1
- time_keeper:
+ time:
type: time_keeper
drive: listener
- x300_radio:
- type: radio_1x32
+ radio:
+ type: radio_8x32
drive: slave
registers:
diff --git a/host/include/uhd/rfnoc/core/io_signatures.yml b/host/include/uhd/rfnoc/core/io_signatures.yml
index 9b1d729f6..de8ab0050 100644
--- a/host/include/uhd/rfnoc/core/io_signatures.yml
+++ b/host/include/uhd/rfnoc/core/io_signatures.yml
@@ -34,6 +34,7 @@ time_keeper:
- name: radio_time
width: 64
+# Single channel radio interface
radio_1x32:
type: master-slave
ports:
@@ -56,6 +57,30 @@ radio_1x32:
type: to-master
width: 1
+# Two channel radio interface, or single channel with 2 SPC
+radio_2x32:
+ type: master-slave
+ ports:
+ - name: radio_rx_data
+ type: from-master
+ width: 64
+ - name: radio_rx_stb
+ type: from-master
+ width: 2
+ - name: radio_rx_running
+ type: to-master
+ width: 2
+ - name: radio_tx_data
+ type: to-master
+ width: 64
+ - name: radio_tx_stb
+ type: from-master
+ width: 2
+ - name: radio_tx_running
+ type: to-master
+ width: 2
+
+# Same as radio_2x32, but kept for backwards compatibility with the old name
x300_radio:
type: master-slave
ports:
@@ -78,6 +103,29 @@ x300_radio:
type: to-master
width: 2
+# Eight channel radio interface, or two channels with 4 SPC
+radio_8x32:
+ type: master-slave
+ ports:
+ - name: radio_rx_data
+ type: from-master
+ width: 256
+ - name: radio_rx_stb
+ type: from-master
+ width: 8
+ - name: radio_rx_running
+ type: to-master
+ width: 8
+ - name: radio_tx_data
+ type: to-master
+ width: 256
+ - name: radio_tx_stb
+ type: from-master
+ width: 8
+ - name: radio_tx_running
+ type: to-master
+ width: 8
+
# A 4-port AXI4 memory-mapped interface with 64-bit data, and 4 GiB address
# space (32-bit).
axi4_mm_4x64_4g:
diff --git a/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json b/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json
index 2d76643be..120ecd5dd 100644
--- a/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json
+++ b/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json
@@ -3,12 +3,13 @@
"properties": {
"schema": { "const": "rfnoc_imagebuilder_args" },
"copyright": { "type": "string" },
- "version": { "type": "number" },
+ "version": { "type": "string" },
"license": { "type": "string" },
"device": { "type": "string" },
+ "image_core_name": { "type": "string" },
"default_target": { "type": "string" },
- "rfnoc_version": { "type": "number" },
- "chdr_width": { "enum": [64, 256] },
+ "rfnoc_version": { "type": "string" },
+ "chdr_width": { "enum": [64, 128, 256, 512] },
"stream_endpoints": { "$ref": "#/definitions/stream_endpoints" },
"noc_blocks": { "$ref": "#/definitions/noc_blocks" },
@@ -45,9 +46,15 @@
"data": { "type": "boolean" },
"num_data_i":{ "type": "integer", "minimum": 1 },
"num_data_o":{ "type": "integer", "minimum": 1 },
- "buff_size": { "type": "integer", "minimum": 0 }
+ "buff_size": { "type": "integer", "minimum": 0 },
+ "buff_size_bytes": { "type": "integer", "minimum": 0 }
},
- "additionalProperties": false
+ "additionalProperties": false,
+ "not": {
+ "anyOf": [
+ {"required": ["buff_size", "buff_size_bytes"]}
+ ]
+ }
},
"noc_block": {
diff --git a/host/include/uhd/rfnoc/core/x410_bsp.yml b/host/include/uhd/rfnoc/core/x410_bsp.yml
new file mode 100644
index 000000000..66b6d77b0
--- /dev/null
+++ b/host/include/uhd/rfnoc/core/x410_bsp.yml
@@ -0,0 +1,57 @@
+type: x410
+type_id: A400
+family: ULTRASCALE
+transports:
+# QSFP Ethernet Ports:
+- name: eth0
+ type: 10G
+ width: 64
+- name: eth1
+ type: 10G
+ width: 64
+- name: eth2
+ type: 10G
+ width: 64
+- name: eth3
+ type: 10G
+ width: 64
+- name: eth4
+ type: 10G
+ width: 64
+# ARM CPU:
+- name: dma
+ type: dma
+ width: 64
+
+clocks:
+- name: radio
+- name: radio_2x
+
+io_ports:
+ ctrlport_radio0:
+ type: ctrl_port
+ drive: slave
+ rename:
+ pattern: (ctrlport_)(.*)
+ repl: m_\1radio0_\2
+ ctrlport_radio1:
+ type: ctrl_port
+ drive: slave
+ rename:
+ pattern: (ctrlport_)(.*)
+ repl: m_\1radio1_\2
+ time:
+ type: time_keeper
+ drive: broadcaster
+ radio0:
+ type: radio_8x32
+ drive: master
+ rename:
+ pattern: (.*)
+ repl: \1_radio0
+ radio1:
+ type: radio_8x32
+ drive: master
+ rename:
+ pattern: (.*)
+ repl: \1_radio1
diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp
index 1b9cb5016..d0f6c2311 100644
--- a/host/include/uhd/rfnoc/defaults.hpp
+++ b/host/include/uhd/rfnoc/defaults.hpp
@@ -72,6 +72,8 @@ static const device_type_t N300 = 0x1300;
static const device_type_t N320 = 0x1320;
//! X300 device family (X300, X310)
static const device_type_t X300 = 0xA300;
+//! X400 device family
+static const device_type_t X400 = 0xA400;
// block identifiers
static const noc_id_t ADDSUB_BLOCK = 0xADD00000;
diff --git a/host/include/uhd/rfnoc/mb_controller.hpp b/host/include/uhd/rfnoc/mb_controller.hpp
index e1268d923..5ca0ce52a 100644
--- a/host/include/uhd/rfnoc/mb_controller.hpp
+++ b/host/include/uhd/rfnoc/mb_controller.hpp
@@ -7,6 +7,7 @@
#pragma once
#include <uhd/config.hpp>
+#include <uhd/features/discoverable_feature_getter_iface.hpp>
#include <uhd/types/device_addr.hpp>
#include <uhd/types/sensors.hpp>
#include <uhd/types/time_spec.hpp>
@@ -21,7 +22,8 @@ namespace uhd { namespace rfnoc {
/*! A default block controller for blocks that can't be found in the registry
*/
-class UHD_API mb_controller : public uhd::noncopyable
+class UHD_API mb_controller : public uhd::noncopyable,
+ public virtual ::uhd::features::discoverable_feature_getter_iface
{
public:
using sptr = std::shared_ptr<mb_controller>;
diff --git a/host/include/uhd/rfnoc/rf_control/core_iface.hpp b/host/include/uhd/rfnoc/rf_control/core_iface.hpp
index ff7ec3550..a96ed2f60 100644
--- a/host/include/uhd/rfnoc/rf_control/core_iface.hpp
+++ b/host/include/uhd/rfnoc/rf_control/core_iface.hpp
@@ -6,6 +6,7 @@
#pragma once
+#include <uhd/types/device_addr.hpp>
#include <uhd/types/ranges.hpp>
#include <stdint.h>
#include <string>
diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt
index 4fff30eaf..910fe3c70 100644
--- a/host/lib/CMakeLists.txt
+++ b/host/lib/CMakeLists.txt
@@ -74,6 +74,7 @@ LIBUHD_REGISTER_COMPONENT("N300" ENABLE_N300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF
LIBUHD_REGISTER_COMPONENT("N320" ENABLE_N320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("X400" ENABLE_X400 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("DPDK" ENABLE_DPDK ON "ENABLE_MPMD;DPDK_FOUND" OFF OFF)
diff --git a/host/lib/cal/CMakeLists.txt b/host/lib/cal/CMakeLists.txt
index a50d654ff..da6bdf1a2 100644
--- a/host/lib/cal/CMakeLists.txt
+++ b/host/lib/cal/CMakeLists.txt
@@ -12,5 +12,6 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/database.cpp
${CMAKE_CURRENT_SOURCE_DIR}/iq_cal.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pwr_cal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/dsa_cal.cpp
)
diff --git a/host/lib/cal/cal_python.hpp b/host/lib/cal/cal_python.hpp
index fa38c568b..919f86e30 100644
--- a/host/lib/cal/cal_python.hpp
+++ b/host/lib/cal/cal_python.hpp
@@ -10,6 +10,7 @@
#include <uhd/cal/database.hpp>
#include <uhd/cal/iq_cal.hpp>
#include <uhd/cal/pwr_cal.hpp>
+#include <uhd/cal/dsa_cal.hpp>
#include <uhd/utils/interpolation.hpp>
#include <uhd/utils/pybind_adaptors.hpp>
#include <pybind11/stl.h>
@@ -124,6 +125,49 @@ void export_cal(py::module& m)
py::arg("power_dbm"),
py::arg("freq"),
py::arg("temperature") = boost::optional<int>());
+
+ py::class_<zbx_tx_dsa_cal, container, zbx_tx_dsa_cal::sptr>(m, "zbx_tx_dsa_cal")
+ .def(py::init([](const std::string& name,
+ const std::string& serial,
+ const uint64_t timestamp) {
+ return zbx_tx_dsa_cal::make(name, serial, timestamp);
+ }))
+ .def(py::init([]() { return zbx_tx_dsa_cal::make(); }))
+ .def(py::init([](const py::bytes data) {
+ return container::make<zbx_tx_dsa_cal>(pybytes_to_vector(data));
+ }))
+ .def("add_frequency_band",
+ &zbx_tx_dsa_cal::add_frequency_band,
+ py::arg("max_freq"),
+ py::arg("name"),
+ py::arg("steps"))
+ .def("clear", &zbx_tx_dsa_cal::clear)
+ .def("get_dsa_setting",
+ &zbx_tx_dsa_cal::get_dsa_setting,
+ py::arg("freq"),
+ py::arg("gain_index"));
+
+ py::class_<zbx_rx_dsa_cal, container, zbx_rx_dsa_cal::sptr>(m, "zbx_rx_dsa_cal")
+ .def(py::init([](const std::string& name,
+ const std::string& serial,
+ const uint64_t timestamp) {
+ return zbx_rx_dsa_cal::make(name, serial, timestamp);
+ }))
+ .def(py::init([]() { return zbx_rx_dsa_cal::make(); }))
+ .def(py::init([](const py::bytes data) {
+ return container::make<zbx_rx_dsa_cal>(pybytes_to_vector(data));
+ }))
+ .def("add_frequency_band",
+ &zbx_rx_dsa_cal::add_frequency_band,
+ py::arg("max_freq"),
+ py::arg("name"),
+ py::arg("steps"))
+ .def("clear", &zbx_rx_dsa_cal::clear)
+ .def("get_dsa_setting",
+ &zbx_rx_dsa_cal::get_dsa_setting,
+ py::arg("freq"),
+ py::arg("gain_index"));
+
}
#endif /* INCLUDED_UHD_CAL_PYTHON_HPP */
diff --git a/host/lib/cal/dsa_cal.cpp b/host/lib/cal/dsa_cal.cpp
new file mode 100644
index 000000000..c0890005c
--- /dev/null
+++ b/host/lib/cal/dsa_cal.cpp
@@ -0,0 +1,253 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/cal/dsa_cal_generated.h>
+#include <uhd/utils/log.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/utils/math.hpp>
+#include <map>
+#include <string>
+#include <array>
+#include <boost/range/adaptor/indexed.hpp>
+
+using namespace uhd::usrp::cal;
+
+namespace {
+constexpr size_t VERSION_MAJOR = 2;
+constexpr size_t VERSION_MINOR = 0;
+
+/***********************************************************************
+ * Helper routines
+ **********************************************************************/
+template<typename base, size_t num_gain_stages, int num_dsa>
+class dsa_cal_impl : public base
+{
+public:
+ dsa_cal_impl(const std::string& name = "",
+ const std::string& serial = "",
+ const uint64_t timestamp = 0)
+ : _name(name)
+ , _serial(serial)
+ , _timestamp(timestamp)
+ {
+ }
+
+ /**************************************************************************
+ * Container API (Basics)
+ *************************************************************************/
+ std::string get_name() const
+ {
+ return _name;
+ }
+
+ std::string get_serial() const
+ {
+ return _serial;
+ }
+
+ uint64_t get_timestamp() const
+ {
+ return _timestamp;
+ }
+
+ /**************************************************************************
+ * Specific APIs
+ *************************************************************************/
+ void add_frequency_band(
+ const double max_freq,
+ const std::string& name,
+ const std::array<std::array<uint32_t, num_dsa>, num_gain_stages> dsa_values)
+ {
+ _data[max_freq] = name_dsas_pair(name, dsa_values);
+ }
+
+ const std::array<uint32_t, num_dsa> get_dsa_setting(double freq, size_t gain_index) const
+ {
+ if (_data.empty()) {
+ throw uhd::runtime_error("Cannot get DSA settings from an empty container.");
+ }
+ // find the lowest band with band_freq_max <= freq
+ const uint64_t freqi = static_cast<uint64_t>(freq);
+ const auto freq_it = _data.lower_bound(freqi);
+ if (freq_it == _data.end()) {
+ throw uhd::value_error("No DSA band found for freq " + std::to_string(freq));
+ }
+ // select DSA setting using gain_index
+ if (gain_index >= num_gain_stages) {
+ throw uhd::value_error(
+ "gain index " + std::to_string(gain_index) + " out of bounds.");
+ }
+ return freq_it->second.second[gain_index];
+ }
+
+ bool is_same_band(double freq1, double freq2) const
+ {
+ const uint64_t freqi1 = static_cast<uint64_t>(freq1);
+ const auto freq_it1 = _data.lower_bound(freqi1);
+ const uint64_t freqi2 = static_cast<uint64_t>(freq2);
+ const auto freq_it2 = _data.lower_bound(freqi2);
+ return freq_it1 == freq_it2;
+ }
+
+ std::vector<uint32_t> get_band_settings(double freq, uint8_t dsa) const
+ {
+ std::vector<uint32_t> result;
+ // find the lowest band with band_freq_max <= freq
+ const uint64_t freqi = static_cast<uint64_t>(freq);
+ const auto freq_it = _data.lower_bound(freqi);
+ if (freq_it == _data.end()) {
+ throw uhd::value_error("No DSA band found for freq " + std::to_string(freq));
+ }
+ // select DSA setting using gain_index
+ for (auto item : freq_it->second.second) {
+ result.push_back(item[dsa]);
+ }
+ return result;
+ }
+
+ void clear()
+ {
+ _data.clear();
+ }
+
+
+ /**************************************************************************
+ * Container API (Serialization/Deserialization)
+ *************************************************************************/
+ std::vector<uint8_t> serialize()
+ {
+ const size_t initial_size_bytes = 2 * num_gain_stages * num_dsa * sizeof(uint32_t); // 2 band of DSA values
+ flatbuffers::FlatBufferBuilder builder(initial_size_bytes);
+
+ std::vector<flatbuffers::Offset<BandDsaMap>> band_dsas;
+ band_dsas.reserve(_data.size());
+ for (auto& band_dsas_pair : _data) {
+ const uint64_t freq = band_dsas_pair.first;
+ std::vector<flatbuffers::Offset<DsaStep>> dsa_steps;
+ auto name_dsas = band_dsas_pair.second;
+ for (auto const& values: name_dsas.second) // iterate over gain indizes
+ {
+ std::vector<uint32_t> steps(values.begin(), values.end());
+ dsa_steps.push_back(CreateDsaStepDirect(builder, &steps));
+ }
+
+ band_dsas.push_back(
+ CreateBandDsaMapDirect(builder, freq, &dsa_steps, name_dsas.first.c_str()));
+
+ }
+
+ // Now load it all into the FlatBuffer
+ auto metadata = CreateMetadataDirect(builder,
+ _name.c_str(),
+ _serial.c_str(),
+ _timestamp,
+ VERSION_MAJOR,
+ VERSION_MINOR);
+ auto gain_dsa_cal =
+ CreateDsaCalDirect(builder, metadata, &band_dsas);
+ FinishDsaCalBuffer(builder, gain_dsa_cal);
+ const size_t table_size = builder.GetSize();
+ const uint8_t* table = builder.GetBufferPointer();
+ return std::vector<uint8_t>(table, table + table_size);
+ }
+
+ // This will amend the existing table. If that's not desired, then it is
+ // necessary to call clear() ahead of time.
+ void deserialize(const std::vector<uint8_t>& data)
+ {
+ clear();
+ auto verifier = flatbuffers::Verifier(data.data(), data.size());
+ if (!VerifyDsaCalBuffer(verifier)) {
+ throw uhd::runtime_error("dsa_cal: Invalid data provided! ");
+ }
+ auto cal_table = GetDsaCal(static_cast<const void*>(data.data()));
+ if (cal_table->metadata()->version_major() != VERSION_MAJOR) {
+ throw uhd::runtime_error("dsa_cal: Compat number mismatch!");
+ }
+ if (cal_table->metadata()->version_minor() != VERSION_MINOR) {
+ UHD_LOG_WARNING("CAL",
+ "gain_cal: Expected compat number "
+ << VERSION_MAJOR << "." << VERSION_MINOR << ", got "
+ << cal_table->metadata()->version_major() << "."
+ << cal_table->metadata()->version_minor());
+ }
+ _name = std::string(cal_table->metadata()->name()->c_str());
+ _serial = std::string(cal_table->metadata()->serial()->c_str());
+ _timestamp = cal_table->metadata()->timestamp();
+
+ auto band_dsa_map = cal_table->band_dsa_map();
+ for (auto it = band_dsa_map->begin(); it != band_dsa_map->end(); ++it) {
+ const uint64_t max_freq = it->max_freq();
+ const std::string name(it->name()->c_str());
+ auto gains = it->gains();
+ if (gains->size() != num_gain_stages) {
+ UHD_LOG_ERROR("CAL", "Invalid number of gain indizes. Got: "
+ << gains->size() << " expected: " << num_gain_stages);
+ throw uhd::runtime_error("Invalid number of gain indizes");
+ }
+ std::array<std::array<uint32_t, num_dsa>, num_gain_stages> indizes;
+ int i = 0;
+ for (auto gain_it = gains->begin(); gain_it != gains->end(); ++gain_it) {
+ if (gain_it->steps()->size() != num_dsa) {
+ UHD_LOG_ERROR("CAL", "Invalid number of attenuator indizes. Got: "
+ << gain_it->steps()->size() << " expected: " << num_dsa);
+ throw uhd::runtime_error("Invalid number of attenuator indizes");
+ }
+ std::copy(gain_it->steps()->begin(),
+ gain_it->steps()->end(),
+ indizes[i++].begin());
+ }
+ add_frequency_band(max_freq, name, indizes);
+ }
+ }
+
+
+private:
+ std::string _name;
+ std::string _serial;
+ uint64_t _timestamp;
+
+ using dsa_steps = std::array<std::array<uint32_t, num_dsa>, num_gain_stages>;
+ using name_dsas_pair = std::pair<std::string, dsa_steps>;
+
+ std::map<uint64_t /* freq */, name_dsas_pair> _data;
+
+};
+
+} //namespace
+std::shared_ptr<zbx_tx_dsa_cal> zbx_tx_dsa_cal::make()
+{
+ return std::make_shared<dsa_cal_impl<
+ zbx_tx_dsa_cal,
+ zbx_tx_dsa_cal::NUM_GAIN_STAGES,
+ zbx_tx_dsa_cal::NUM_DSA>>();
+}
+
+std::shared_ptr<zbx_tx_dsa_cal> zbx_tx_dsa_cal::make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp)
+{
+ return std::make_shared<dsa_cal_impl<
+ zbx_tx_dsa_cal,
+ zbx_tx_dsa_cal::NUM_GAIN_STAGES,
+ zbx_tx_dsa_cal::NUM_DSA>>(name, serial, timestamp);
+}
+
+std::shared_ptr<zbx_rx_dsa_cal> zbx_rx_dsa_cal::make()
+{
+ return std::make_shared<dsa_cal_impl<
+ zbx_rx_dsa_cal,
+ zbx_rx_dsa_cal::NUM_GAIN_STAGES,
+ zbx_rx_dsa_cal::NUM_DSA>>();
+}
+
+std::shared_ptr<zbx_rx_dsa_cal> zbx_rx_dsa_cal::make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp)
+{
+ return std::make_shared<dsa_cal_impl<
+ zbx_rx_dsa_cal,
+ zbx_rx_dsa_cal::NUM_GAIN_STAGES,
+ zbx_rx_dsa_cal::NUM_DSA>>(name, serial, timestamp);
+}
diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt
index 42ac4d051..039614c03 100644
--- a/host/lib/ic_reg_maps/CMakeLists.txt
+++ b/host/lib/ic_reg_maps/CMakeLists.txt
@@ -128,6 +128,11 @@ LIBUHD_PYTHON_GEN_SOURCE(
)
LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_lmx2572_regs.py
+ ${CMAKE_CURRENT_BINARY_DIR}/lmx2572_regs.hpp
+)
+
+LIBUHD_PYTHON_GEN_SOURCE(
${CMAKE_CURRENT_SOURCE_DIR}/gen_lmk04828_regs.py
${CMAKE_CURRENT_BINARY_DIR}/lmk04828_regs.hpp
)
@@ -142,4 +147,9 @@ LIBUHD_PYTHON_GEN_SOURCE(
${CMAKE_CURRENT_BINARY_DIR}/rhodium_cpld_regs.hpp
)
+LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_zbx_cpld_regs.py
+ ${CMAKE_CURRENT_BINARY_DIR}/zbx_cpld_regs.hpp
+ )
+
set(LIBUHD_PYTHON_GEN_SOURCE_DEPS)
diff --git a/host/lib/ic_reg_maps/common.py b/host/lib/ic_reg_maps/common.py
index 96b63410f..59a24d298 100755
--- a/host/lib/ic_reg_maps/common.py
+++ b/host/lib/ic_reg_maps/common.py
@@ -136,7 +136,7 @@ class ${name}_t:
% if reg.is_array:
self.${reg.get_name()} = [self.${reg.get_name()}_t.${reg.get_default()},] * ${reg.get_array_len()}
% else:
- self.${reg.get_name()} = ${reg.get_default()}
+ self.${reg.get_name()} = self.${reg.get_name()}_t.${reg.get_default()}
% endif
% else:
% if reg.is_array:
@@ -156,7 +156,11 @@ class ${name}_t:
if self._state is None:
self._state = ${name}_t()
% for reg in regs:
+ % if reg.is_array:
+ self._state.${reg.get_name()} = self.${reg.get_name()}.copy()
+ % else:
self._state.${reg.get_name()} = self.${reg.get_name()}
+ % endif
% endfor
def get_changed_addrs(self):
diff --git a/host/lib/ic_reg_maps/gen_lmk04816_regs.py b/host/lib/ic_reg_maps/gen_lmk04816_regs.py
index ccc82804f..1f6db9c5b 100755
--- a/host/lib/ic_reg_maps/gen_lmk04816_regs.py
+++ b/host/lib/ic_reg_maps/gen_lmk04816_regs.py
@@ -380,11 +380,29 @@ uint32_t get_reg(int addr){
}
"""
+PY_BODY_TMPL = """\
+def get_reg(self, addr):
+ reg = 0
+ % for addr in sorted(set(map(lambda r: r.get_addr(), regs))):
+ <% if_state = 'if' if loop.index == 0 else 'elif' %>
+ ${if_state} 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
+ return reg
+
+"""
+
if __name__ == '__main__':
import common; common.generate(
name='lmk04816_regs',
regs_tmpl=REGS_TMPL,
body_tmpl=BODY_TMPL,
+ py_body_tmpl=PY_BODY_TMPL,
file=__file__,
)
-
diff --git a/host/lib/ic_reg_maps/gen_lmx2572_regs.py b/host/lib/ic_reg_maps/gen_lmx2572_regs.py
new file mode 100755
index 000000000..0995f57e2
--- /dev/null
+++ b/host/lib/ic_reg_maps/gen_lmx2572_regs.py
@@ -0,0 +1,663 @@
+#!/usr/bin/env python
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Register map for LMX2572
+"""
+
+########################################################################
+# Template for raw text data describing registers
+# name addr[bit range inclusive] default optional enums
+########################################################################
+
+REGS_TMPL = """\
+########################################################################
+## address 0
+########################################################################
+powerdown 0[0] 0 normal_operation, power_down
+reset 0[1] 0 normal_operation, reset
+muxout_ld_sel 0[2] 1 register_readback, lock_detect
+fcal_en 0[3] 1 invalid, enable
+reg0_reserved0 0[4] 1
+fcal_lpfd_adj 0[5:6] 0
+fcal_hpfd_adj 0[7:8] 0
+out_mute 0[9] 1 disabled, muted
+reg0_reserved1 0[10] 0
+add_hold 0[11] 0
+reg0_reserved2 0[12:13] 0x2
+vco_phase_sync_en 0[14] 0 normal_operation, phase_sync_mode
+ramp_en 0[15] 0 normal_operation, frequency_ramping
+########################################################################
+## address 1
+########################################################################
+cal_clk_div 1[0:2] 0
+reg1_reserved0 1[3:15] 0x101
+########################################################################
+## address 2
+########################################################################
+reg2_reserved0 2[0:15] 0x500
+########################################################################
+## address 3
+########################################################################
+reg3_reserved0 3[0:15] 0x782
+########################################################################
+## address 4
+########################################################################
+reg4_reserved0 4[0:15] 0xA43
+########################################################################
+## address 5
+########################################################################
+reg5_reserved0 5[0:10] 0xC8
+ipbuf_term 5[11] 0 normal_operation, internally_terminated
+ipbuf_type 5[12] 1 differential, single_ended
+reg5_reserved1 5[13:15] 1
+########################################################################
+## address 6
+########################################################################
+reg6_reserved0 6[0:10] 0x2
+ldo_dly 6[11:15] 0x19
+########################################################################
+## address 7
+########################################################################
+reg7_reserved0 7[0:13] 0xB2
+out_force 7[14] 0 use_out_mute, no_mute
+reg7_reserved1 7[15] 0
+########################################################################
+## address 8
+########################################################################
+reg8_reserved0 8[0:10] 0
+vco_capctrl_force 8[11] 0 normal_operation, use_vco_capctrl
+reg8_reserved1 8[12:13] 0x2
+vco_daciset_force 8[14] 0 normal_operation, use_vco_daciset
+reg8_reserved2 8[15] 0
+########################################################################
+## address 9
+########################################################################
+reg9_reserved0 9[0:11] 0x4
+osc_2x 9[12] 0 disabled, enabled
+reg9_reserved1 9[13] 0
+mult_hi 9[14] 0 less_than_equal_to_100M, greater_than_100M
+reg9_reserved2 9[15] 0
+########################################################################
+## address 10
+########################################################################
+reg10_reserved0 10[0:6] 0x78
+mult 10[7:11] 1
+reg10_reserved1 10[12:15] 1
+########################################################################
+## address 11
+########################################################################
+reg11_reserved0 11[0:3] 0x8
+pll_r 11[4:11] 1
+reg11_reserved1 11[12:15] 0xB
+########################################################################
+## address 12
+########################################################################
+pll_r_pre 12[0:11] 1
+reg12_reserved0 12[12:15] 0x5
+########################################################################
+## address 13
+########################################################################
+reg13_reserved0 13[0:15] 0x4000
+########################################################################
+## address 14
+########################################################################
+reg14_reserved0 14[0:2] 0x0
+cpg 14[3:6] 0x8
+reg14_reserved1 14[7:15] 0x30
+########################################################################
+## address 15
+########################################################################
+reg15_reserved0 15[0:15] 0x60E
+########################################################################
+## address 16
+########################################################################
+vco_daciset 16[0:8] 0x80
+reg16_reserved0 16[9:15] 0
+########################################################################
+## address 17
+########################################################################
+vco_daciset_strt 17[0:8] 0x96
+reg17_reserved0 17[9:15] 0
+########################################################################
+## address 18
+########################################################################
+reg18_reserved0 18[0:15] 0x64
+########################################################################
+## address 19
+########################################################################
+vco_capctrl 19[0:7] 0xB7
+reg19_reserved0 19[8:15] 0x27
+########################################################################
+## address 20
+########################################################################
+reg20_reserved0 20[0:9] 0x48
+vco_sel_force 20[10] 0 disabled, enabled
+vco_sel 20[11:13] 0x6
+reg20_reserved1 20[14:15] 1
+########################################################################
+## address 21
+########################################################################
+reg21_reserved0 21[0:15] 0x409
+########################################################################
+## address 22
+########################################################################
+reg22_reserved0 22[0:15] 1
+########################################################################
+## address 23
+########################################################################
+reg23_reserved0 23[0:15] 0x7C
+########################################################################
+## address 24
+########################################################################
+reg24_reserved0 24[0:15] 0x71A
+########################################################################
+## address 25
+########################################################################
+reg25_reserved0 25[0:15] 0x624
+########################################################################
+## address 26
+########################################################################
+reg26_reserved0 26[0:15] 0x808
+########################################################################
+## address 27
+########################################################################
+reg27_reserved0 27[0:15] 0x2
+########################################################################
+## address 28
+########################################################################
+reg28_reserved0 28[0:15] 0x488
+########################################################################
+## address 29
+########################################################################
+reg29_reserved0 29[0:15] 0x18C6
+########################################################################
+## address 30
+########################################################################
+reg30_reserved0 30[0:15] 0x18C6
+########################################################################
+## address 31
+########################################################################
+reg31_reserved0 31[0:15] 0xC3E6
+########################################################################
+## address 32
+########################################################################
+reg32_reserved0 32[0:15] 0x5BF
+########################################################################
+## address 33
+########################################################################
+reg33_reserved0 33[0:15] 0x1E01
+########################################################################
+## address 34
+########################################################################
+pll_n_upper_3_bits 34[0:2] 0
+reg34_reserved0 34[3:15] 0x2
+########################################################################
+## address 35
+########################################################################
+reg35_reserved0 35[0:15] 0x4
+########################################################################
+## address 36
+########################################################################
+pll_n_lower_16_bits 36[0:15] 0x28
+########################################################################
+## address 37
+########################################################################
+reg37_reserved0 37[0:7] 0x5
+pfd_dly_sel 37[8:13] 0x2
+reg37_reserved1 37[14] 0
+mash_seed_en 37[15] 0 disabled, enabled
+########################################################################
+## address 38
+########################################################################
+pll_den_upper 38[0:15] 0xFFFF
+########################################################################
+## address 39
+########################################################################
+pll_den_lower 39[0:15] 0xFFFF
+########################################################################
+## address 40
+########################################################################
+mash_seed_upper 40[0:15] 0
+########################################################################
+## address 41
+########################################################################
+mash_seed_lower 41[0:15] 0
+########################################################################
+## address 42
+########################################################################
+pll_num_upper 42[0:15] 0
+########################################################################
+## address 43
+########################################################################
+pll_num_lower 43[0:15] 0
+########################################################################
+## address 44
+########################################################################
+mash_order 44[0:2] 0x2 interger_mode, first_order, second_order, third_order, fourth_order
+reg44_reserved0 44[3:4] 0
+mash_reset_n 44[5] 1 reset, normal_operation
+outa_pd 44[6] 0 normal_operation, power_down
+outb_pd 44[7] 1 normal_operation, power_down
+outa_pwr 44[8:13] 0x22
+reg44_reserved1 44[14:15] 0
+########################################################################
+## address 45
+########################################################################
+outb_pwr 45[0:5] 0x22
+register45_reserved0 45[6:10] 0x18
+outa_mux 45[11:12] 0 channel_divider, vco, not_used, high_impedance
+register45_reserved1 45[13:15] 0x6
+########################################################################
+## address 46
+########################################################################
+outb_mux 46[0:1] 0 channel_divider, vco, sysref, high_impedance
+register46_reserved0 46[2:15] 0x1FC
+########################################################################
+## address 47
+########################################################################
+reg47_reserved0 47[0:15] 0x300
+########################################################################
+## address 48
+########################################################################
+reg48_reserved0 48[0:15] 0x3E0
+########################################################################
+## address 49
+########################################################################
+reg49_reserved0 49[0:15] 0x4180
+########################################################################
+## address 50
+########################################################################
+reg50_reserved0 50[0:15] 0x80
+########################################################################
+## address 51
+########################################################################
+reg51_reserved0 51[0:15] 0x80
+########################################################################
+## address 52
+########################################################################
+reg52_reserved0 52[0:15] 0x420
+########################################################################
+## address 53
+########################################################################
+reg53_reserved0 53[0:15] 0
+########################################################################
+## address 54
+########################################################################
+reg54_reserved0 54[0:15] 0
+########################################################################
+## address 55
+########################################################################
+reg55_reserved0 55[0:15] 0
+########################################################################
+## address 56
+########################################################################
+reg56_reserved0 56[0:15] 0
+########################################################################
+## address 57
+########################################################################
+reg57_reserved0 57[0:15] 0
+########################################################################
+## address 58
+########################################################################
+reg58_reserved0 58[0:8] 1
+inpin_fmt 58[9:11] 0 SYNC_EQUALS_SYSREFREQ_EQUALS_CMOS, SYNC_EQUALS_LVDS_SYSREFREQ_EQUAL_CMOS, SYNC_EQUALS_CMOS_SYSREFREQ_EQUAL_LVDS, SYNC_EQUALS_SYSREFREQ_EQUALS_LVDS, SYNC_EQUALS_SYSREFREQ_EQUALS_CMOS2, SYNC_EQUALS_LVDSFILTERED_SYSREFREQ_EQUALS_CMOS, SYNC_EQUALS_CMOS_SYSREFREQ_EQUALS_LVDSFILTERED, SYNC_EQUALS_SYSREFREQ_EQUALS_LVDSFILTERED
+inpin_lvl 58[12:13] 0 vin_divide_by_4, vin, vin_divide_by_2, invalid
+inpin_hyst 58[14] 0 disabled, enabled
+inpin_ignore 58[15] 1
+########################################################################
+## address 59
+########################################################################
+ld_type 59[0] 1 vcocal, vtune_and_vcocal
+reg59_reserved0 59[1:15] 0
+########################################################################
+## address 60
+########################################################################
+ld_dly 60[0:15] 0x3E8
+########################################################################
+## address 61
+########################################################################
+reg61_reserved0 61[0:15] 0xA8
+########################################################################
+## address 62
+########################################################################
+reg62_reserved0 62[0:9] 0xAF
+dblbuf_en_0 62[10] 0 disabled, enabled
+dblbuf_en_1 62[11] 0 disabled, enabled
+dblbuf_en_2 62[12] 0 disabled, enabled
+dblbuf_en_3 62[13] 0 disabled, enabled
+dblbuf_en_4 62[14] 0 disabled, enabled
+dblbuf_en_5 62[15] 0 disabled, enabled
+########################################################################
+## address 63
+########################################################################
+reg63_reserved0 63[0:15] 0
+########################################################################
+## address 64
+########################################################################
+reg64_reserved0 64[0:15] 0x1388
+########################################################################
+## address 65
+########################################################################
+reg65_reserved0 65[0:15] 0
+########################################################################
+## address 66
+########################################################################
+reg66_reserved0 6[0:15] 0x1F4
+########################################################################
+## address 67
+########################################################################
+reg67_reserved0 67[0:15] 0
+########################################################################
+## address 68
+########################################################################
+reg68_reserved0 68[0:15] 0x3E8
+########################################################################
+## address 69
+########################################################################
+mash_rst_count_upper 69[0:15] 0
+########################################################################
+## address 70
+########################################################################
+mash_rst_count_lower 70[0:15] 0xC350
+########################################################################
+## address 71
+########################################################################
+reg71_reserved0 71[0:1] 1
+sysref_repeat 71[2] 0 master_mode, repeater_mode
+sysref_en 71[3] 0 disabled, enabled
+sysref_pulse 71[4] 0 disabled, enabled
+sysref_div_pre 71[5:7] 0x4 divide_by_2=2, divide_by_4=4
+reg71_reserved1 71[8:15] 0
+########################################################################
+## address 72
+########################################################################
+sysref_div 72[0:10] 1
+reg72_reserved0 72[11:15] 0
+########################################################################
+## address 73
+########################################################################
+jesd_dac1_ctrl 73[0:5] 0x3F
+jesd_dac2_ctrl 73[6:11] 0
+reg73_reserved0 73[12:15] 0
+########################################################################
+## address 74
+########################################################################
+jesd_dac3_ctrl 74[0:5] 0
+jesd_dac4_ctrl 74[6:11] 0
+sysref_pulse_cnt 74[12:15] 0
+########################################################################
+## address 75
+########################################################################
+reg75_reserved0 75[0:5] 0
+chdiv 75[6:10] 0 divide_by_2=0, divide_by_4=1, divide_by_8=3, divide_by_16=5, divide_by_32=7, divide_by_64=9, divide_by_128=12, divide_by_256=14
+reg75_reserved1 75[11:15] 1
+########################################################################
+## address 76
+########################################################################
+reg76_reserved0 76[0:15] 0xC
+########################################################################
+## address 77
+########################################################################
+reg77_reserved0 77[0:15] 0
+########################################################################
+## address 78
+########################################################################
+reg78_reserved0 78[0] 0
+vco_capctrl_strt 78[1:8] 0x32
+quick_recal_en 78[9] 0
+reg78_reserved1 78[10] 0
+ramp_thresh_33rd 78[11] 0
+reg78_reserved2 78[12:15] 0
+########################################################################
+## address 79
+########################################################################
+ramp_thresh_upper 79[0:15] 0
+########################################################################
+## address 80
+########################################################################
+ramp_thresh_lower 80[0:15] 0
+########################################################################
+## address 81
+########################################################################
+ramp_limit_high_33rd 81[0] 0
+reg81_reserved0 81[1:15] 0
+########################################################################
+## address 82
+########################################################################
+ramp_limit_high_upper 82[0:15] 0
+########################################################################
+## address 83
+########################################################################
+ramp_limit_high_lower 83[0:15] 0
+########################################################################
+## address 84
+########################################################################
+ramp_limit_low_33rd 84[0] 0
+reg84_reserved0 84[1:15] 0
+########################################################################
+## address 85
+########################################################################
+ramp_limit_low_upper 85[0:15] 0
+########################################################################
+## address 86
+########################################################################
+ramp_limit_low_lower 86[0:15] 0
+########################################################################
+## address 87
+########################################################################
+reg87_reserved0 87[0:15] 0
+########################################################################
+## address 88
+########################################################################
+reg88_reserved0 88[0:15] 0
+########################################################################
+## address 89
+########################################################################
+reg89_reserved0 89[0:15] 0
+########################################################################
+## address 90
+########################################################################
+reg90_reserved0 90[0:15] 0
+########################################################################
+## address 91
+########################################################################
+reg91_reserved0 91[0:15] 0
+########################################################################
+## address 92
+########################################################################
+reg92_reserved0 92[0:15] 0
+########################################################################
+## address 93
+########################################################################
+reg93_reserved0 93[0:15] 0
+########################################################################
+## address 94
+########################################################################
+reg94_reserved0 94[0:15] 0
+########################################################################
+## address 95
+########################################################################
+reg95_reserved0 95[0:15] 0
+########################################################################
+## address 96
+########################################################################
+reg96_reserved0 96[0:1] 0
+ramp_burst_count 96[2:14] 0
+ramp_burst_en 96[15] 0 disabled, enabled
+########################################################################
+## address 97
+########################################################################
+ramp_burst_trig 97[0:1] 0 ramp_transistion=0, trigger_a=1, trigger_b=2
+reg97_reserved0 97[2] 0
+ramp_triga 97[3:6] 0 disabled=0, rampclk_rising_edge=1, rampdir_rising_edge=2, always_triggered=4, rampclk_falling_edge=9, rampdir_falling_edge=10
+ramp_trgb 97[7:10] 0 disabled=0, rampclk_rising_edge=1, rampdir_rising_edge=2, always_triggered=4, rampclk_falling_edge=9, rampdir_falling_edge=10
+reg97_reserved1 97[11:14] 0
+ramp0_rst 97[15] 0 disabled, reset
+########################################################################
+## address 98
+########################################################################
+ramp0_dly 98[0] 0
+reg98_reserved0 98[1] 0
+ramp0_inc_upper_14 98[15:2] 0
+########################################################################
+## address 99
+########################################################################
+ramp0_inc_lower 99[0:15] 0
+########################################################################
+## address 100
+########################################################################
+ramp0_len 100[0:15] 0
+########################################################################
+## address 101
+########################################################################
+ramp0_next_trig 101[0:1] 0 timeout_counter, trigger_a, trigger_b
+reg101_reserved0 101[2:3] 0
+ramp0_next 101[4] 0 ramp0, ramp1
+ramp1_rst 101[5] 0 disabled, reset
+ramp1_dly 101[6] 0
+reg101_reserved1 101[7:15] 0
+########################################################################
+## address 102
+########################################################################
+ramp1_inc_upper_14 102[13:0] 0
+reg102_reserved0 102[14:15] 0
+########################################################################
+## address 103
+########################################################################
+ramp1_inc_lower 103[0:15] 0
+########################################################################
+## address 104
+########################################################################
+ramp1_len 104[0:15] 0
+########################################################################
+## address 105
+########################################################################
+ramp1_next_trig 105[0:1] 0 timeout_counter, trigger_a, trigger_b
+reg105_reserved0 105[2:3] 0
+ramp1_next 105[4] 0 ramp0, ramp1
+ramp_manual 105[5] 0 automatic_ramping, manual_ramping
+ramp_dly_cnt 105[6:15] 0x111
+########################################################################
+## address 106
+########################################################################
+ramp_scale_count 106[0:2] 0x7
+reg106_reserved0 106[3] 0
+ramp_trig_cal 106[4] 0 disabled, enabled
+reg106_reserved1 106[5:15] 0
+########################################################################
+## address 114
+########################################################################
+fsk_mode_sel 114[0:1] 0 unused=0,fsk_spi=2, fsk_spi_fast=3
+fsk_spi_dev_sel 114[2:4] 0
+fsk_spi_level 114[5:6] 0 disabled, 2fsk, 4fsk, 8fsk
+reg114_reserved0 114[7:9] 0
+fsk_en 114[10] 0 disabled, enabled
+reg114_reserved1 114[11:15] 0xF
+########################################################################
+## address 115
+########################################################################
+reg115_reserved0 115[0:2] 0
+fsk_dev_scale 115[3:7] 0
+reg115_reserved1 115[8:15] 0
+########################################################################
+## address 116
+########################################################################
+fsk_dev0 116[0:15] 0
+########################################################################
+## address 117
+########################################################################
+fsk_dev1 117[0:15] 0
+########################################################################
+## address 118
+########################################################################
+fsk_dev2 118[0:15] 0
+########################################################################
+## address 119
+########################################################################
+fsk_dev3 119[0:15] 0
+########################################################################
+## address 120
+########################################################################
+fsk_dev4 120[0:15] 0
+########################################################################
+## address 121
+########################################################################
+fsk_dev5 121[0:15] 0
+########################################################################
+## address 122
+########################################################################
+fsk_dev6 122[0:15] 0
+########################################################################
+## address 123
+########################################################################
+fsk_dev7 123[0:15] 0
+########################################################################
+## address 124
+########################################################################
+fsk_spi_dev 124[0:15] 0
+########################################################################
+## address 125
+########################################################################
+reg125 125[0:16] 0x2288
+"""
+########################################################################
+# Template for methods in the body of the struct
+########################################################################
+
+BODY_TMPL = """\
+uint16_t get_reg(int addr){
+ uint16_t reg = 0;
+ switch(addr){
+ % for addr in sorted(set(map(lambda r: r.get_addr(), regs))):
+ case ${addr}:
+ % for reg in filter(lambda r: r.get_addr() == addr, regs):
+ reg |= (uint16_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()};
+ % endfor
+ break;
+ % endfor
+ }
+ return reg;
+}
+
+std::set<uint8_t> get_ro_regs()
+{
+ return {107, 108, 109, 110, 111, 112, 113};
+}
+
+int get_num_regs()
+{
+ return 126;
+}
+"""
+
+PY_BODY_TMPL = """\
+def get_reg(self, addr):
+ reg = 0
+ % for addr in sorted(set(map(lambda r: r.get_addr(), regs))):
+ <% if_state = 'if' if loop.index == 0 else 'elif' %>
+ ${if_state} 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
+ return reg
+
+"""
+
+if __name__ == '__main__':
+ import common
+ common.generate(
+ name='lmx2572_regs',
+ regs_tmpl=REGS_TMPL,
+ body_tmpl=BODY_TMPL,
+ py_body_tmpl=PY_BODY_TMPL,
+ file=__file__,
+ )
diff --git a/host/lib/ic_reg_maps/gen_zbx_cpld_regs.py b/host/lib/ic_reg_maps/gen_zbx_cpld_regs.py
new file mode 100755
index 000000000..0efc8a4d7
--- /dev/null
+++ b/host/lib/ic_reg_maps/gen_zbx_cpld_regs.py
@@ -0,0 +1,450 @@
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Register map for the ZBX CPLD. Note that the ground truth for this register map
+is in the fpga/usrp3/top/x400/dboards/zbx/cpld subdirectory, and can be looked
+up in fpga/usrp3/top/x400/dboards/zbx/cpld/doc/ZBX_CPLD.htm
+
+This CPLD regmap works with RevB daughterboards only.
+"""
+
+########################################################################
+# Template for raw text data describing registers
+# name addr[bit range inclusive] default optional enums
+########################################################################
+REGS_TMPL = """\
+########################################################################
+## SLAVE SETUP
+########################################################################
+BOARD_ID 0x0000[0:15] 0
+REVISION 0x0004[0:31] 0
+OLDEST_COMPAT_REVISION 0x0008[0:31] 0
+SCRATCH 0x000C[0:31] 0
+GIT_HASH 0x0010[0:31] 0
+ENABLE_TX_POS_7V0 0x0040[0] 0 disable, enable
+ENABLE_RX_POS_7V0 0x0040[1] 0 disable, enable
+ENABLE_POS_3V3 0x0040[2] 0 disable, enable
+P7V_B_STATUS 0x0044[0] 0
+P7V_A_STATUS 0x0044[1] 0
+PLL_REF_CLOCK_ENABLE 0x0048[0] 0 disable, enable
+########################################################################
+## ATR
+########################################################################
+CURRENT_RF0_CONFIG 0x1000[0:7] 1
+CURRENT_RF1_CONFIG 0x1000[8:15] 1
+CURRENT_RF0_DSA_CONFIG 0x1000[23:16] 1
+CURRENT_RF1_DSA_CONFIG 0x1000[31:24] 1
+RF0_OPTION 0x1004[0:1] 0 sw_defined, classic_atr, fpga_state
+RF1_OPTION 0x1004[8:9] 0 sw_defined, classic_atr, fpga_state
+RF0_DSA_OPTION 0x1004[17:16] 0 sw_defined, classic_atr, fpga_state
+RF1_DSA_OPTION 0x1004[25:24] 0 sw_defined, classic_atr, fpga_state
+SW_RF0_CONFIG 0x1008[0:7] 0
+SW_RF1_CONFIG 0x1008[8:15] 0
+SW_RF0_DSA_CONFIG 0x1008[23:16] 0
+SW_RF1_DSA_CONFIG 0x1008[31:24] 0
+########################################################################
+## LO SPI
+########################################################################
+DATA 0x1020[0:15] 0
+ADDRESS 0x1020[16:22] 0
+READ_FLAG 0x1020[23] 1 write, read
+LO_SELECT 0x1020[24:26] 0 TX0_LO1, TX0_LO2, TX1_LO1, TX1_LO2, RX0_LO1, RX0_LO2, RX1_LO1, RX1_LO2
+START_TRANSACTION 0x1020[28] 0 disable, enable
+SPI_READY 0x1020[30] 0
+DATA_VALID 0x1020[31] 0
+########################################################################
+## LO SYNC
+########################################################################
+PULSE_TX0_LO1_SYNC 0x1024[0] 0
+PULSE_TX0_LO2_SYNC 0x1024[1] 0
+PULSE_TX1_LO1_SYNC 0x1024[2] 0
+PULSE_TX1_LO2_SYNC 0x1024[3] 0
+PULSE_RX0_LO1_SYNC 0x1024[4] 0
+PULSE_RX0_LO2_SYNC 0x1024[5] 0
+PULSE_RX1_LO1_SYNC 0x1024[6] 0
+PULSE_RX1_LO2_SYNC 0x1024[7] 0
+BYPASS_SYNC_REGISTER 0x1024[8] 0 disable, enable
+########################################################################
+## LED CONTROL
+########################################################################
+RX0_RX_LED[256] 0x1400[0] 0 disable, enable
+RX0_TRX_LED[256] 0x1400[1] 0 disable, enable
+TX0_TRX_LED[256] 0x1400[2] 0 disable, enable
+RX1_RX_LED[256] 0x1400[16] 0 disable, enable
+RX1_TRX_LED[256] 0x1400[17] 0 disable, enable
+TX1_TRX_LED[256] 0x1400[18] 0 disable, enable
+########################################################################
+## PATH CONTROL - TX 0
+########################################################################
+TX0_IF2_1_2[256] 0x2000[0] 0 filter_2, filter_1
+TX0_IF1_3[256] 0x2000[2:3] 0 filter_0_3, filter_4, filter_6, filter_5
+TX0_IF1_4[256] 0x2000[4:5] 0 termination, filter_1, filter_2, filter_3
+TX0_IF1_5[256] 0x2000[6:7] 0 filter_3, filter_2, filter_1, termination
+TX0_IF1_6[256] 0x2000[8:9] 0 filter_5, filter_6, filter_4, filter_0_3
+TX0_7[256] 0x2000[10:11] 0 termination, no_connect, highband, lowband
+TX0_RF_8[256] 0x2000[12:14] 0 invalid_0, rf_3, rf_1, invalid_1, rf_2
+TX0_RF_9[256] 0x2000[16:17] 0 rf_3, rf_1, rf_2, highband
+TX0_ANT_10[256] 0x2000[18:19] 0 bypass_amp, cal_loopback, lowband_amp, highband_amp
+TX0_ANT_11[256] 0x2000[20:21] 0 tx_rx, highband_amp, lowband_amp, bypass_amp
+TX0_LO_13[256] 0x2000[24] 0 internal, external
+TX0_LO_14[256] 0x2000[26] 0 external, internal
+########################################################################
+## PATH CONTROL - TX 1
+########################################################################
+TX1_IF2_1_2[256] 0x2400[0] 0 filter_1, filter_2
+TX1_IF1_3[256] 0x2400[2:3] 0 filter_5, filter_6, filter_4, filter_0_3
+TX1_IF1_4[256] 0x2400[4:5] 0 filter_3, filter_2, filter_1, termination
+TX1_IF1_5[256] 0x2400[6:7] 0 termination, filter_1, filter_2, filter_3
+TX1_IF1_6[256] 0x2400[8:9] 0 filter_0_3, filter_4, filter_6, filter_5
+TX1_7[256] 0x2400[10:11] 0 lowband, highband, no_connect, termination
+TX1_RF_8[256] 0x2400[12:14] 0 invalid_0, rf_2, rf_1, invalid_1, rf_3
+TX1_RF_9[256] 0x2400[16:17] 0 highband, rf_2, rf_1, rf_3
+TX1_ANT_10[256] 0x2400[18:19] 0 highband_amp, lowband_amp, bypass_amp, cal_loopback
+TX1_ANT_11[256] 0x2400[20:21] 0 tx_rx, bypass_amp, lowband_amp, highband_amp
+TX1_LO_13[256] 0x2400[24] 0 external, internal
+TX1_LO_14[256] 0x2400[26] 0 internal, external
+########################################################################
+## PATH CONTROL - RX 0
+########################################################################
+RX0_ANT_1[256] 0x2800[0:1] 0 cal_loopback, termination, tx_rx, rx2
+RX0_2[256] 0x2800[2] 0 highband, lowband
+RX0_RF_3[256] 0x2800[4:6] 0 invalid_0, rf_2, rf_1, invalid_1, rf_3
+RX0_4[256] 0x2800[8] 0 lowband, highband
+RX0_IF1_5[256] 0x2800[10:11] 0 filter_4, filter_3, filter_2, filter_1
+RX0_IF1_6[256] 0x2800[12:13] 0 filter_1, filter_2, filter_3, filter_4
+RX0_IF2_7_8[256] 0x2800[14] 0 filter_2, filter_1
+RX0_LO_9[256] 0x2800[16] 0 internal, external
+RX0_LO_10[256] 0x2800[18] 0 internal, external
+RX0_RF_11[256] 0x2800[20:22] 0 invalid_0, rf_3, rf_1, invalid_1, rf_2, invalid_2, invalid_3, invalid_4
+########################################################################
+## PATH CONTROL - RX 1
+########################################################################
+RX1_ANT_1[256] 0x2C00[0:1] 0 cal_loopback, tx_rx, rx2, termination
+RX1_2[256] 0x2C00[2] 0 lowband, highband
+RX1_RF_3[256] 0x2C00[4:6] 0 invalid_0, rf_3, rf_1, invalid_1, rf_2
+RX1_4[256] 0x2C00[8] 0 highband, lowband
+RX1_IF1_5[256] 0x2C00[10:11] 0 filter_1, filter_2, filter_3, filter_4
+RX1_IF1_6[256] 0x2C00[12:13] 0 filter_4, filter_3, filter_2, filter_1
+RX1_IF2_7_8[256] 0x2C00[14] 0 filter_1, filter_2
+RX1_LO_9[256] 0x2C00[16] 0 external, internal
+RX1_LO_10[256] 0x2C00[18] 0 external, internal
+RX1_RF_11[256] 0x2C00[20:22] 0 invalid_0, rf_2, rf_1, invalid_1, rf_3, invalid_2, invalid_3, invalid_4
+########################################################################
+## DSA CONTROL
+########################################################################
+TX0_DSA1[256] 0x3000[0:4] 31
+TX0_DSA2[256] 0x3000[8:12] 31
+TX1_DSA1[256] 0x3400[0:4] 31
+TX1_DSA2[256] 0x3400[8:12] 31
+RX0_DSA1[256] 0x3800[0:3] 15
+RX0_DSA2[256] 0x3800[4:7] 15
+RX0_DSA3_A[256] 0x3800[8:11] 15
+RX0_DSA3_B[256] 0x3800[12:15] 15
+RX1_DSA1[256] 0x3C00[0:3] 15
+RX1_DSA2[256] 0x3C00[4:7] 15
+RX1_DSA3_A[256] 0x3C00[8:11] 15
+RX1_DSA3_B[256] 0x3C00[12:15] 15
+TX0_TABLE_SELECT[256] 0x4000[0:7] 0
+TX1_TABLE_SELECT[256] 0x4400[0:7] 0
+RX0_TABLE_SELECT[256] 0x4800[0:7] 0
+RX1_TABLE_SELECT[256] 0x4C00[0:7] 0
+TX0_TABLE_DSA1[256] 0x5000[0:4] 31
+TX0_TABLE_DSA2[256] 0x5000[8:12] 31
+TX1_TABLE_DSA1[256] 0x5400[0:4] 31
+TX1_TABLE_DSA2[256] 0x5400[8:12] 31
+RX0_TABLE_DSA1[256] 0x5800[0:3] 15
+RX0_TABLE_DSA2[256] 0x5800[4:7] 15
+RX0_TABLE_DSA3_A[256] 0x5800[8:11] 15
+RX0_TABLE_DSA3_B[256] 0x5800[12:15] 15
+RX1_TABLE_DSA1[256] 0x5C00[0:3] 15
+RX1_TABLE_DSA2[256] 0x5C00[4:7] 15
+RX1_TABLE_DSA3_A[256] 0x5C00[8:11] 15
+RX1_TABLE_DSA3_B[256] 0x5C00[12:15] 15
+"""
+
+########################################################################
+# 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, "Arrays must be length 256!"
+ 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 zbx_cpld_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
+};
+
+zbx_cpld_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 zbx_cpld_field_t::${reg.get_name()};
+ } else
+ % endfor
+ % endfor
+ {
+ UHD_ASSERT_THROW(false);
+ }
+}
+
+uint32_t get_array_size(zbx_cpld_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 zbx_cpld_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(zbx_cpld_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 zbx_cpld_field_t::${reg.get_name()}:
+ return uint32_t(${reg.get_name()});
+ % endfor
+ % endfor
+ default:
+ UHD_ASSERT_THROW(false);
+ return 0;
+ }
+}
+
+uint32_t get_field(zbx_cpld_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 zbx_cpld_field_t::${reg.get_name()}:
+ return uint32_t(${reg.get_name()}[idx]);
+ % endfor
+ % endfor
+ default:
+ UHD_ASSERT_THROW(false);
+ return 0;
+ }
+}
+
+void set_field(zbx_cpld_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, regs):
+ case zbx_cpld_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(zbx_cpld_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 zbx_cpld_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, "Arrays must be length 256!" %>
+ <% 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;
+ }
+}
+
+<%
+ 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())
+%>
+std::set<size_t> get_all_addrs()
+{
+ std::set<size_t> addrs;
+ % for addr in sorted(all_addrs):
+ addrs.insert(${addr});
+ % endfor
+ return addrs;
+}
+
+uint16_t get_addr(zbx_cpld_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 zbx_cpld_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 common
+ common.generate(
+ name='zbx_cpld_regs',
+ regs_tmpl=REGS_TMPL,
+ body_tmpl=BODY_TMPL,
+ py_body_tmpl=PY_BODY_TMPL,
+ file=__file__,
+ )
diff --git a/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp b/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp
new file mode 100644
index 000000000..bd88db222
--- /dev/null
+++ b/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp
@@ -0,0 +1,40 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/features/discoverable_feature.hpp>
+#include <memory>
+#include <string>
+
+namespace uhd { namespace features {
+
+/*! This is the mechanism by which USRPs can perform actions whenever the FPGA
+ * is loaded.
+ */
+class fpga_load_notification_iface : public discoverable_feature
+{
+public:
+ using sptr = std::shared_ptr<fpga_load_notification_iface>;
+
+ static discoverable_feature::feature_id_t get_feature_id()
+ {
+ return discoverable_feature::FPGA_LOAD_NOTIFICATION;
+ }
+
+ std::string get_feature_name() const
+ {
+ return "FPGA Load Notification";
+ }
+
+ virtual ~fpga_load_notification_iface() = default;
+
+ /*! Called after the FPGA has finished loading.
+ */
+ virtual void onload() = 0;
+};
+
+}} // namespace uhd::features
diff --git a/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp b/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp
index 1651a1580..b7759098a 100644
--- a/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp
+++ b/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp
@@ -7,6 +7,9 @@
#pragma once
#include <uhd/rfnoc/rf_control/core_iface.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <uhd/types/eeprom.hpp>
#include <uhdlib/rfnoc/rf_control/gain_profile_iface.hpp>
#include <uhdlib/usrp/common/pwr_cal_mgr.hpp>
#include <memory>
@@ -36,6 +39,11 @@ public:
virtual std::vector<uhd::usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(
uhd::direction_t trx) = 0;
+
+ virtual uhd::eeprom_map_t get_db_eeprom() = 0;
+
+ //! See radio_control::set_command_time()
+ virtual void set_command_time(uhd::time_spec_t time, const size_t chan) = 0;
};
}}} // namespace uhd::rfnoc::rf_control
diff --git a/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp b/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp
index f93a42936..c65b6bd25 100644
--- a/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp
+++ b/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp
@@ -6,6 +6,7 @@
#pragma once
+#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -22,6 +23,8 @@ class gain_profile_iface
{
public:
using sptr = std::shared_ptr<gain_profile_iface>;
+ using subscriber_type =
+ std::function<void(const std::string& profile, const size_t chan)>;
virtual ~gain_profile_iface() = default;
@@ -36,6 +39,13 @@ public:
/*! Return the gain profile
*/
virtual std::string get_gain_profile(const size_t chan) const = 0;
+
+ /*! Register a subscriber to a property tree node
+ *
+ * This is useful for all those cases where the gain profile is also a
+ * property in the tree, and setting it here requires also updating the tree.
+ */
+ virtual void add_subscriber(subscriber_type&& sub) = 0;
};
/*! "Default" implementation for gain_profile_iface
@@ -52,9 +62,11 @@ public:
void set_gain_profile(const std::string& profile, const size_t chan) override;
std::string get_gain_profile(const size_t chan) const override;
+ void add_subscriber(subscriber_type&& sub) override;
private:
static const std::string DEFAULT_GAIN_PROFILE;
+ subscriber_type _sub = nullptr;
};
/*! "Enumerated" implementation for gain_profile_iface
@@ -77,10 +89,19 @@ public:
std::vector<std::string> get_gain_profile_names(const size_t) const override;
+ /*! Register a subscriber to a property tree node
+ *
+ * This is useful for all those cases where the gain profile is also a
+ * property in the tree, and setting it here requires also updating the tree.
+ */
+ void add_subscriber(subscriber_type&& sub) override;
+
private:
std::vector<std::string> _possible_profiles;
std::vector<std::string> _gain_profile;
+
+ subscriber_type _sub = nullptr;
};
}}} // namespace uhd::rfnoc::rf_control
diff --git a/host/lib/include/uhdlib/usrp/common/lmx2572.hpp b/host/lib/include/uhdlib/usrp/common/lmx2572.hpp
new file mode 100644
index 000000000..600153f1a
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/common/lmx2572.hpp
@@ -0,0 +1,102 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/types/time_spec.hpp>
+#include <functional>
+#include <memory>
+
+//! Control interface for an LMX2572 synthesizer
+class lmx2572_iface
+{
+public:
+ using sptr = std::shared_ptr<lmx2572_iface>;
+
+ virtual ~lmx2572_iface() = default;
+
+ enum output_t { RF_OUTPUT_A, RF_OUTPUT_B };
+
+ enum mux_in_t { DIVIDER, VCO, HIGH_IMPEDANCE, SYSREF };
+
+ //! Category of phase sync procedure. See Section 8.1.6 ("Application for
+ // SYNC") in the datasheet. Category NONE applies when no phase
+ // synchronization is required.
+ enum sync_cat { CAT1A, CAT1B, CAT2, CAT3, CAT4, NONE };
+
+ //! Write functor: Take address / data pair, craft SPI transaction
+ using write_fn_t = std::function<void(uint8_t, uint16_t)>;
+
+ //! Read functor: Return value given address
+ using read_fn_t = std::function<uint16_t(uint8_t)>;
+
+ //! Sleep functor: sleep for the specified time
+ using sleep_fn_t = std::function<void(const uhd::time_spec_t&)>;
+
+ //! Factory
+ //
+ // \param write SPI write function object
+ // \param read SPI read function object
+ // \param sleep sleep function object
+ static sptr make(write_fn_t&& poke16, read_fn_t&& peek16, sleep_fn_t&& sleep);
+
+ //! Save state to chip
+ virtual void commit() = 0;
+
+ //! Get enabled status
+ virtual bool get_enabled() = 0;
+
+ //! Enable/disable
+ virtual void set_enabled(const bool enabled = true) = 0;
+
+ //! Performs a reset of the LMX2572 by using the software reset register
+ virtual void reset() = 0;
+
+ //! Returns True if the PLL is locked, False otherwise.
+ virtual bool get_lock_status() = 0;
+
+ //! Enables or disables the phase synchronization
+ //
+ // NOTE: This does not write anything to the device, it just sets the
+ // VCO_PHASE_SYNC_EN high.
+ virtual void set_sync_mode(const bool enable) = 0;
+
+ //! Returns the enabled/disabled state of the phase synchronization
+ virtual bool get_sync_mode() = 0;
+
+ //! Enables or disables the output on both ports
+ virtual void set_output_enable_all(const bool enable) = 0;
+
+ //! Sets output A or B (OUTA_PD or OUTB_PD)
+ virtual void set_output_enable(const output_t output, const bool enable) = 0;
+
+ //! Sets the output power
+ //
+ // \param output Choose which output to control
+ // \param power Power control bits. Higher values mean more power, but the
+ // function that maps power control bits to power is non-linear,
+ // and it is also frequency-dependent. For more detail, check
+ // the data sheet, section 8.1.5.1. Ballpark numbers: 0 dBm is
+ // at about power==27, over 35 the increase becomes "not obvious".
+ virtual void set_output_power(const output_t output, const uint8_t power) = 0;
+
+ //! Sets the OUTA_MUX or OUTB_MUX input
+ virtual void set_mux_input(const output_t output, const mux_in_t input) = 0;
+
+ //! Set the output frequency
+ //
+ // A note on phase synchronization: If set_sync_mode(true) was called
+ // previously, then this method will set up the PLL in a phase-sync mode.
+ // However, this specific implementation assumes that the SYNC pin is
+ // populated, and will be pulsed after calling this command.
+ //
+ // \param target_freq The target frequency
+ // \param ref_freq The input reference frequency
+ // \param spur_dodging Set to true to enable spur dodging
+ virtual double set_frequency(const double target_freq,
+ const double ref_freq,
+ const bool spur_dodging) = 0;
+};
diff --git a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp
index 98e7f2ac4..a40398991 100644
--- a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp
+++ b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp
@@ -6,8 +6,11 @@
#pragma once
+#include <uhd/features/ref_clk_calibration_iface.hpp>
#include <uhd/rfnoc/mb_controller.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/features/discoverable_feature_registry.hpp>
+#include <uhdlib/features/fpga_load_notification_iface.hpp>
#include <uhdlib/utils/rpc.hpp>
#include <memory>
@@ -19,7 +22,8 @@ namespace uhd { namespace rfnoc {
*
* This motherboard controller abstracts out a bunch of RPC calls.
*/
-class mpmd_mb_controller : public mb_controller
+class mpmd_mb_controller : public mb_controller,
+ public ::uhd::features::discoverable_feature_registry
{
public:
using sptr = std::shared_ptr<mpmd_mb_controller>;
@@ -113,6 +117,42 @@ private:
//! Cache of available GPIO sources
std::vector<std::string> _gpio_banks;
std::unordered_map<std::string, std::vector<std::string>> _gpio_srcs;
+
+public:
+ /*! When the FPGA is reloaded, pass the notification to every Radio block
+ * Public to allow other classes to register for notifications.
+ */
+ class fpga_onload : public uhd::features::fpga_load_notification_iface {
+ public:
+ using sptr = std::shared_ptr<fpga_onload>;
+
+ fpga_onload();
+
+ void onload() override;
+
+ void request_cb(uhd::features::fpga_load_notification_iface::sptr handler);
+
+ private:
+ std::vector<std::weak_ptr<uhd::features::fpga_load_notification_iface>> _cbs;
+ };
+
+ //! Class to expose the ref_clk_calibration discoverable feature functions.
+ class ref_clk_calibration : public uhd::features::ref_clk_calibration_iface {
+ public:
+ using sptr = std::shared_ptr<ref_clk_calibration>;
+
+ ref_clk_calibration(uhd::usrp::mpmd_rpc_iface::sptr rpcc);
+
+ void set_ref_clk_tuning_word(uint32_t tuning_word) override;
+ uint32_t get_ref_clk_tuning_word() override;
+ void store_ref_clk_tuning_word(uint32_t tuning_word) override;
+
+ private:
+ uhd::usrp::mpmd_rpc_iface::sptr _rpcc;
+ };
+
+ fpga_onload::sptr _fpga_onload;
+ ref_clk_calibration::sptr _ref_clk_cal;
};
}} // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/usrp/common/rpc.py b/host/lib/include/uhdlib/usrp/common/rpc.py
index 04a43ebce..4ca30b07d 100644
--- a/host/lib/include/uhdlib/usrp/common/rpc.py
+++ b/host/lib/include/uhdlib/usrp/common/rpc.py
@@ -9,7 +9,7 @@ import sys
from mako.template import Template
class Function:
- def __init__(self, return_type, function_name, args):
+ def __init__(self, return_type, function_name, args, no_claim=False):
self.name = function_name
self.does_return = return_type != "void"
self.return_type = return_type
@@ -17,6 +17,7 @@ class Function:
self.rpcname = f"\"{function_name}\""
self.args = [" ".join(arg) for arg in args]
self.has_rpcprefix = False
+ self.no_claim = no_claim
def enable_rpcprefix(self):
self.rpcname = f"_rpc_prefix + \"{self.name}\""
@@ -31,7 +32,7 @@ class Interface:
for fn in self.functions:
fn.enable_rpcprefix()
-def fn_from_string(function_string):
+def fn_from_string(function_string, no_claim=False):
m = re.match(r"^([a-zA-Z:<>,_0-9 ]+)\s+([a-zA-Z0-9_]+)\(([a-zA-Z0-9,_:&<> ]*)\)$", function_string)
return_type = m.group(1)
function_name = m.group(2)
@@ -39,7 +40,7 @@ def fn_from_string(function_string):
args = [arg.strip() for arg in args.split(",")]
args = [arg.split(" ") for arg in args if len(arg) > 0]
args = [(" ".join(arg[:-1]), arg[-1]) for arg in args]
- return Function(return_type, function_name, args)
+ return Function(return_type, function_name, args, no_claim)
IFACES = [
Interface("mpmd_rpc", [
@@ -66,7 +67,36 @@ IFACES = [
fn_from_string("std::map<std::string, std::string> get_mb_eeprom()"),
fn_from_string("std::vector<std::string> get_gpio_src(const std::string& bank)"),
fn_from_string("void set_gpio_src(const std::string& bank, const std::vector<std::string>& src)"),
+
+ # ref_clk_calibration
+ fn_from_string("void set_ref_clk_tuning_word(uint32_t tuning_word)"),
+ fn_from_string("uint32_t get_ref_clk_tuning_word()"),
+ fn_from_string("void store_ref_clk_tuning_word(uint32_t tuning_word)"),
+ ]),
+ Interface("x400_rpc", [
+ fn_from_string("std::vector<std::map<std::string, std::string>> get_dboard_info()", no_claim=True),
+ fn_from_string("void set_cal_frozen(bool state, size_t block_count, size_t chan)"),
+ fn_from_string("std::vector<int> get_cal_frozen(size_t block_count, size_t chan)"),
+ fn_from_string("double rfdc_set_nco_freq(const std::string& trx, size_t block_count, size_t chan, double freq)"),
+ fn_from_string("double rfdc_get_nco_freq(const std::string& trx, size_t block_count, size_t chan)"),
+ fn_from_string("double get_master_clock_rate()"),
+ fn_from_string("std::map<std::string, std::vector<uint8_t>> get_db_eeprom(size_t db_idx)"),
+ fn_from_string("bool get_threshold_status(size_t db_number, size_t chan, size_t threshold_block)"),
+ fn_from_string("void set_dac_mux_enable(size_t motherboard_channel_number, int enable)"),
+ fn_from_string("void set_dac_mux_data(size_t i, size_t q)"),
+ fn_from_string("double get_spll_freq()"),
+ fn_from_string("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)"),
+ fn_from_string("bool is_db_gpio_ifc_present(size_t db_idx)"),
]),
+ Interface("dboard_base_rpc", [
+ fn_from_string("std::vector<std::string> get_sensors(const std::string& trx)"),
+ fn_from_string("sensor_value_t::sensor_map_t get_sensor(const std::string& trx, const std::string& sensor, size_t chan)"),
+ ], has_rpcprefix=True),
+ Interface("zbx_rpc", [
+ fn_from_string("double get_dboard_prc_rate()"),
+ 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 %>\
@@ -117,11 +147,20 @@ namespace uhd { namespace usrp {
%for function in iface.functions:
${function.return_type} ${function.name}(${",".join(function.args)}) override
{
- %if function.does_return:
- return _rpcc->request_with_token<${function.return_type}>(${",".join([function.rpcname] + function.arg_names)});
+ %if function.no_claim:
+ %if function.does_return:
+ return _rpcc->request<${function.return_type}>
+ %else:
+ _rpcc->notify
+ %endif
%else:
- _rpcc->notify_with_token(${",".join([function.rpcname] + function.arg_names)});
+ %if function.does_return:
+ return _rpcc->request_with_token<${function.return_type}>
+ %else:
+ _rpcc->notify_with_token
+ %endif
%endif
+ (${",".join([function.rpcname] + function.arg_names)});
}
%endfor
diff --git a/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp b/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp
new file mode 100644
index 000000000..8d2923436
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp
@@ -0,0 +1,85 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/types/memmap_iface.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <cstdint>
+#include <memory>
+#include <vector>
+#include <string>
+
+namespace uhd { namespace rfnoc { namespace x400 {
+
+//! Control class for the RFDC components of a single daughterboard
+//
+// This class controls the NCOs and other RFDC settings. The corresponding FPGA
+// module is rfdc_timing_control.v.
+class rfdc_control
+{
+public:
+ using sptr = std::shared_ptr<rfdc_control>;
+
+ struct regmap
+ {
+ //! Address of the NCO reset register
+ static constexpr uint32_t NCO_RESET = 0;
+ //! Bit position of reset-start bit (w)
+ static constexpr uint32_t NCO_RESET_START_MSB = 0;
+ //! Bit position of reset-done bit (r)
+ static constexpr uint32_t NCO_RESET_DONE_MSB = 1;
+ //! Address of the gearbox reset register
+ static constexpr uint32_t GEARBOX_RESET = 4;
+ //! Bit position of ADC gearbox reset
+ static constexpr uint32_t ADC_RESET_MSB = 0;
+ //! Bit position of DAC gearbox reset
+ static constexpr uint32_t DAC_RESET_MSB = 1;
+ };
+
+ //! Identify the NCOs/ADCs/DACs available to this radio control
+ enum class rfdc_type { RX0, RX1, TX0, TX1 };
+
+ rfdc_control(uhd::memmap32_iface_timed&& iface, const std::string& log_id);
+
+ //! Reset the listed NCOs
+ //
+ // All NCOs that are listed in \p ncos are reset synchronously.
+ //
+ // \param ncos A list of NCOs that shall be reset at the given time
+ // \param time The time at which the reset shall occur
+ void reset_ncos(const std::vector<rfdc_type>& ncos, const uhd::time_spec_t& time);
+
+ //! Reset the listed gearboxes
+ //
+ // All gearboxes that are listed in \p gearboxes are reset synchronously.
+ //
+ // \param gearboxes A list of gearboxes that shall be reset at the given time
+ // \param time The time at which the reset shall occur. Note: If \p time is
+ // set to ASAP, the resets will still occur synchronously, but
+ // at a non-deterministic time. This will suffice for synchronizing
+ // gearboxes on a single device.
+ void reset_gearboxes(
+ const std::vector<rfdc_type>& gearboxes, const uhd::time_spec_t& time);
+
+ //! Return true if the NCO is out of reset
+ bool get_nco_reset_done();
+
+ //! Set an NCO to a specific frequency
+ //
+ // \param nco Which NCO to re-tune
+ // \param freq the new frequency to tune it to (in Hz)
+ double set_nco_freq(const rfdc_type nco, const double freq);
+
+private:
+ //! Peek/poke interface
+ memmap32_iface_timed _iface;
+
+ //! Prefix for log messages
+ const std::string _log_id;
+};
+
+}}} // namespace uhd::rfnoc::x400
diff --git a/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp
new file mode 100644
index 000000000..72ac16e28
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp
@@ -0,0 +1,592 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "x400_dboard_iface.hpp"
+#include <uhd/exception.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <string>
+
+#define UHD_LOG_SKIP_CFG() \
+ UHD_LOG_TRACE( \
+ "RFNOC::DEBUG_DB", "Skipping unsupported debug db config for " << __FUNCTION__);
+
+namespace uhd { namespace rfnoc {
+
+const static uint16_t EMPTY_DB_PID = 0x0;
+const static uint16_t DEBUG_DB_PID = 0x4001;
+const static uint16_t IF_TEST_DBOARD_PID = 0x4006;
+
+/*! \brief Implementation of common dboard_iface for IF Test and Debug dboards.
+ */
+class debug_dboard_common_impl : public uhd::usrp::x400::x400_dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<debug_dboard_common_impl>;
+
+ rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ bool is_adc_self_cal_supported() override
+ {
+ return false;
+ }
+
+ uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(double) override
+ {
+ return {
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ };
+ }
+
+ size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override
+ {
+ if (fe == "0") {
+ return 0;
+ }
+ if (fe == "1") {
+ return 1;
+ }
+ throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe);
+ }
+
+ std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override
+ {
+ if (chan == 0) {
+ return "0";
+ }
+ if (chan == 1) {
+ return "1";
+ }
+ throw uhd::lookup_error(
+ std::string("[X400] Invalid channel: ") + std::to_string(chan));
+ }
+
+ 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;
+ }
+
+ eeprom_map_t get_db_eeprom() override
+ {
+ return {};
+ }
+
+ std::string get_tx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_tx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_tx_antenna(const std::string&, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ std::string get_rx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_rx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_rx_antenna(const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ double get_tx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_frequency(const double, size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ void set_tx_tune_args(const device_addr_t&, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ freq_range_t get_tx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_frequency(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ void set_rx_tune_args(const device_addr_t&, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ freq_range_t get_rx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ std::vector<std::string> get_tx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_tx_gain_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ gain_range_t get_tx_gain_range(const std::string&, const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_tx_gain(const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double set_tx_gain(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double set_tx_gain(const double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ std::vector<std::string> get_rx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_rx_gain_range(const size_t) const override
+ {
+ UHD_LOG_SKIP_CFG()
+ return meta_range_t(0.0, 0.0);
+ }
+
+ gain_range_t get_rx_gain_range(const std::string&, const size_t) const override
+ {
+ UHD_LOG_SKIP_CFG()
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_rx_gain(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_gain(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double set_rx_gain(const double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ void set_rx_agc(const bool, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ meta_range_t get_tx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_bandwidth(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ meta_range_t get_rx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_bandwidth(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ std::vector<std::string> get_rx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string&, const size_t) const override
+ {
+ UHD_LOG_SKIP_CFG()
+ return {};
+ }
+
+ freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ void set_rx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ const std::string get_rx_lo_source(const std::string&, const size_t) override
+ {
+ return "";
+ }
+
+ void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ bool get_rx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ return false;
+ }
+
+ double set_rx_lo_freq(double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double get_rx_lo_freq(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ std::vector<std::string> get_tx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_tx_lo_sources(const std::string&, const size_t) const override
+ {
+ return {};
+ }
+
+ freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ void set_tx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ const std::string get_tx_lo_source(const std::string&, const size_t) override
+ {
+ return "";
+ }
+
+ void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ bool get_tx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ return false;
+ }
+
+ double set_tx_lo_freq(const double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double get_tx_lo_freq(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ void set_command_time(uhd::time_spec_t, const size_t) override
+ {
+ // nop
+ }
+};
+
+/*! \brief Implementation of dboard_iface for debug_db.
+ */
+class debug_dboard_impl : public debug_dboard_common_impl
+{
+ // Just an empty class for conveniently organizing class hierarchy.
+};
+
+/*! \brief Fake dboard implementation for an empty slot
+ */
+class empty_slot_dboard_impl : public debug_dboard_common_impl
+{
+ // Just an empty class for conveniently organizing class hierarchy.
+};
+
+/*! \brief Implementation of dboard_iface for IF Test dboard.
+ */
+class if_test_dboard_impl : public debug_dboard_common_impl
+{
+public:
+ /******************************************************************************
+ * Structors
+ *****************************************************************************/
+ if_test_dboard_impl(const size_t db_idx,
+ const std::string& rpc_prefix,
+ const std::string& unique_id,
+ std::shared_ptr<mpmd_mb_controller> mb_controller,
+ uhd::property_tree::sptr tree)
+ : _unique_id(unique_id)
+ , _db_idx(db_idx)
+ , _rpc_prefix(rpc_prefix)
+ , _mb_control(mb_controller)
+ , _tree(tree)
+ {
+ RFNOC_LOG_TRACE("Entering " << __FUNCTION__);
+ RFNOC_LOG_TRACE("DB ID: " << _db_idx);
+ UHD_ASSERT_THROW(_mb_control);
+ _rpcc = _mb_control->get_rpc_client();
+ UHD_ASSERT_THROW(_rpcc);
+ _init_frontend_subtree();
+ }
+
+ ~if_test_dboard_impl()
+ {
+ RFNOC_LOG_TRACE(__FUNCTION__);
+ }
+
+ // The IF Test dboard muxes a single SMA port (for each of RX and TX) like so:
+ // /---> dac0
+ // /----> dac1
+ // TX SMA port -- [mux] -----> dac2
+ // \----> dac3
+ //
+ // (and similarly with the RX SMA port and the adcs)
+
+ std::vector<std::string> get_tx_muxes(void)
+ {
+ return {"DAC0", "DAC1", "DAC2", "DAC3"};
+ }
+
+ void set_tx_mux(const std::string& mux)
+ {
+ RFNOC_LOG_TRACE("Setting TX mux to " << mux);
+ _rpcc->notify_with_token(
+ _rpc_prefix + "config_tx_path", _get_tx_path_from_mux(mux));
+ }
+
+ std::string get_tx_mux(void)
+ {
+ return _rpcc->request_with_token<std::string>(_rpc_prefix + "get_tx_path");
+ }
+
+ std::vector<std::string> get_rx_muxes(void)
+ {
+ return {"ADC0", "ADC1", "ADC2", "ADC3"};
+ }
+
+ void set_rx_mux(const std::string& mux)
+ {
+ RFNOC_LOG_TRACE("Setting RX mux to " << mux);
+ _rpcc->notify_with_token(
+ _rpc_prefix + "config_rx_path", _get_rx_path_from_mux(mux));
+ }
+
+ std::string get_rx_mux(void)
+ {
+ return _rpcc->request_with_token<std::string>(_rpc_prefix + "get_rx_path");
+ }
+
+ eeprom_map_t get_db_eeprom() override
+ {
+ return _rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", _db_idx);
+ }
+
+
+private:
+ //! Used by the RFNOC_LOG_* macros.
+ const std::string _unique_id;
+ std::string get_unique_id() const
+ {
+ return _unique_id;
+ }
+
+ //! Index of this daughterboard
+ const size_t _db_idx;
+
+ //! 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 the RPC client
+ uhd::rpc_client::sptr _rpcc;
+
+ //! 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;
+
+ std::string _get_tx_path_from_mux(const std::string mux)
+ {
+ if (mux == "DAC0") {
+ return "dac0";
+ } else if (mux == "DAC1") {
+ return "dac1";
+ } else if (mux == "DAC2") {
+ return "dac2";
+ } else if (mux == "DAC3") {
+ return "dac3";
+ } else {
+ throw uhd::value_error(
+ std::string("[RFNOC::IF_TEST_DBOARD] Invalid TX Mux Name: ") + mux);
+ }
+ }
+
+ std::string _get_rx_path_from_mux(const std::string mux)
+ {
+ if (mux == "ADC0") {
+ return "adc0";
+ } else if (mux == "ADC1") {
+ return "adc1";
+ } else if (mux == "ADC2") {
+ return "adc2";
+ } else if (mux == "ADC3") {
+ return "adc3";
+ } else {
+ throw uhd::value_error(
+ std::string("[RFNOC::IF_TEST_DBOARD] Invalid RX Mux Name: ") + mux);
+ }
+ }
+
+ void _init_frontend_subtree()
+ {
+ auto subtree = _tree->subtree(fs_path("dboard"));
+
+ // DB EEPROM
+ subtree->create<eeprom_map_t>("eeprom")
+ .add_coerced_subscriber([this](const eeprom_map_t&) {
+ throw uhd::runtime_error("Attempting to update daughterboard eeprom!");
+ })
+ .set_publisher([this]() { return get_db_eeprom(); });
+
+ static const char IF_TEST_FE_NAME[] = "IF_TEST";
+
+ const fs_path tx_fe_path = fs_path("tx_frontends/0");
+ const fs_path rx_fe_path = fs_path("rx_frontends/0");
+ RFNOC_LOG_TRACE("Adding non-RFNoC block properties"
+ << " to prop tree path " << tx_fe_path << " and " << rx_fe_path);
+
+ subtree->create<std::string>(tx_fe_path / "name").set(IF_TEST_FE_NAME);
+ subtree->create<std::string>(rx_fe_path / "name").set(IF_TEST_FE_NAME);
+
+ // TX Mux
+ subtree->create<std::string>(tx_fe_path / "mux" / "value")
+ .add_coerced_subscriber(
+ [this](const std::string& mux) { this->set_tx_mux(mux); })
+ .set_publisher([this]() { return this->get_tx_mux(); });
+ subtree->create<std::vector<std::string>>(tx_fe_path / "mux" / "options")
+ .set(get_tx_muxes())
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update mux options!");
+ });
+
+ // RX Mux
+ subtree->create<std::string>(rx_fe_path / "mux" / "value")
+ .add_coerced_subscriber(
+ [this](const std::string& mux) { this->set_rx_mux(mux); })
+ .set_publisher([this]() { return this->get_rx_mux(); });
+ subtree->create<std::vector<std::string>>(rx_fe_path / "mux" / "options")
+ .set(get_rx_muxes())
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update mux options!");
+ });
+
+ for (auto fe_path : {tx_fe_path, rx_fe_path}) {
+ // Antennas
+ const std::vector<std::string> antenna_options = {"SMA"};
+ subtree->create<std::vector<std::string>>(fe_path / "antenna" / "options")
+ .set(antenna_options)
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update antenna options!");
+ });
+
+ // Frequency range
+ const uhd::freq_range_t freq_range(0.0, 0.0);
+ subtree->create<meta_range_t>(fe_path / "freq" / "range")
+ .set(freq_range)
+ .add_coerced_subscriber([](const meta_range_t&) {
+ throw uhd::runtime_error("Attempting to update freq range!");
+ });
+
+ // Gains
+ const uhd::gain_range_t gain_range(0.0, 0.0, 1.0);
+ subtree->create<meta_range_t>(fe_path / "gains" / "all" / "range")
+ .set(gain_range)
+ .add_coerced_subscriber([](const meta_range_t&) {
+ throw uhd::runtime_error("Attempting to update gain range!");
+ });
+
+ // Connection
+ subtree->create<std::string>(fe_path / "connection").set("IQ");
+ }
+ }
+};
+
+}} // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp
new file mode 100644
index 000000000..107ccdeb0
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp
@@ -0,0 +1,361 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "x400_dboard_iface.hpp"
+#include <uhd/exception.hpp>
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <string>
+
+namespace uhd { namespace rfnoc {
+
+/*! \brief Implementation of dboard_iface for unpopulated or unsupported daughterboards
+ */
+class null_dboard_impl : public uhd::usrp::x400::x400_dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<null_dboard_impl>;
+
+ rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ bool is_adc_self_cal_supported() override
+ {
+ return false;
+ }
+
+ uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(double) override
+ {
+ return {
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ };
+ }
+
+ size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override
+ {
+ if (fe == "0") {
+ return 0;
+ }
+ if (fe == "1") {
+ return 1;
+ }
+ throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe);
+ }
+
+ std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override
+ {
+ if (chan == 0) {
+ return "0";
+ }
+ if (chan == 1) {
+ return "1";
+ }
+ throw uhd::lookup_error(
+ std::string("[X400] Invalid channel: ") + std::to_string(chan));
+ }
+
+ 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;
+ }
+
+ eeprom_map_t get_db_eeprom() override
+ {
+ return {};
+ }
+
+ std::string get_tx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_tx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_tx_antenna(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::string get_rx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_rx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_rx_antenna(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_tx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_frequency(const double, size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_tx_tune_args(const device_addr_t&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_tx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_frequency(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_tune_args(const device_addr_t&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_rx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ std::vector<std::string> get_tx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_tx_gain_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ gain_range_t get_tx_gain_range(const std::string&, const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_tx_gain(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_tx_gain(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_tx_gain(const double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::vector<std::string> get_rx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_rx_gain_range(const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ gain_range_t get_rx_gain_range(const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_rx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_rx_gain(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_gain(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_rx_gain(const double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_agc(const bool, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ meta_range_t get_tx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_bandwidth(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ meta_range_t get_rx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_bandwidth(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::vector<std::string> get_rx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ const std::string get_rx_lo_source(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ bool get_rx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_rx_lo_freq(double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_rx_lo_freq(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::vector<std::string> get_tx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_tx_lo_sources(const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_tx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ const std::string get_tx_lo_source(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ bool get_tx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_tx_lo_freq(const double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_tx_lo_freq(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_command_time(uhd::time_spec_t, const size_t) override
+ {
+ // nop
+ }
+
+private:
+ uhd::runtime_error _no_dboard_exception() const
+ {
+ const std::string msg("No daughterboard or daughterboard with unrecognized PID.");
+ return uhd::runtime_error(msg);
+ }
+};
+
+}} // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp b/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp
new file mode 100644
index 000000000..a6d52cbc0
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp
@@ -0,0 +1,42 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <memory>
+
+namespace uhd { namespace usrp { namespace x400 {
+
+/*! Parameters used for ADC self cal on the X400.
+ *
+ * If the daughterboard supports ADC self-cal, min_gain and max_gain will be the
+ * gains used in the gain auto detection algorithm.
+ */
+struct adc_self_cal_params_t
+{
+ double min_gain;
+ double max_gain;
+ double rx_freq;
+ double tx_freq;
+};
+
+/*! Interface for daughterboards which support being plugged into a X400 motherboard.
+ */
+class x400_dboard_iface : public uhd::rfnoc::rf_control::dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<x400_dboard_iface>;
+
+ //! Returns whether this dboard supports ADC self cal
+ virtual bool is_adc_self_cal_supported() = 0;
+
+ //! Returns the parameters required to generate a suitable loopback tone at
+ //! tone_freq to perform ADC self cal.
+ virtual adc_self_cal_params_t get_adc_self_cal_params(double tone_freq) = 0;
+};
+
+}}} // namespace uhd::usrp::x400
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp
new file mode 100644
index 000000000..0d1d7af7c
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp
@@ -0,0 +1,269 @@
+//
+// 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 <list>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+//! Which LO to address when peeking/poking
+// This must match the LO_SELECT values in gen_zbx_cpld_regs.py
+enum class zbx_lo_t {
+ TX0_LO1 = 0,
+ TX0_LO2 = 1,
+ TX1_LO1 = 2,
+ TX1_LO2 = 3,
+ RX0_LO1 = 4,
+ RX0_LO2 = 5,
+ RX1_LO1 = 6,
+ RX1_LO2 = 7
+};
+
+static const std::map<zbx_lo_t, std::string> ZBX_LO_LOG_ID = {
+ {zbx_lo_t::TX0_LO1, "ZBX TX0 LO1"},
+ {zbx_lo_t::TX0_LO2, "ZBX TX0 LO2"},
+ {zbx_lo_t::TX1_LO1, "ZBX TX1 LO1"},
+ {zbx_lo_t::TX1_LO2, "ZBX TX1 LO2"},
+ {zbx_lo_t::RX0_LO1, "ZBX RX0 LO1"},
+ {zbx_lo_t::RX0_LO2, "ZBX RX0 LO2"},
+ {zbx_lo_t::RX1_LO1, "ZBX RX1 LO1"},
+ {zbx_lo_t::RX1_LO2, "ZBX RX1 LO2"}};
+
+static constexpr std::array<zbx_lo_t, 8> ZBX_ALL_LO = {zbx_lo_t::TX0_LO1,
+ zbx_lo_t::TX0_LO2,
+ zbx_lo_t::TX1_LO1,
+ zbx_lo_t::TX1_LO2,
+ zbx_lo_t::RX0_LO1,
+ zbx_lo_t::RX0_LO2,
+ zbx_lo_t::RX1_LO1,
+ zbx_lo_t::RX1_LO2};
+
+
+/******************************************************************************
+ * Important: When changing values here, check if that also requires updating
+ * the manual (host/docs/zbx.dox). If it also requires changing the website or
+ * other sales/marketing material, make sure to let the appropriate people know!
+ *****************************************************************************/
+
+enum class zbx_lo_source_t { internal, external };
+static constexpr zbx_lo_source_t ZBX_DEFAULT_LO_SOURCE = zbx_lo_source_t::internal;
+
+// The ZBX 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 ZBX_DEFAULT_BANDWIDTH = 400e6; // Hz
+
+static constexpr double LMX2572_MAX_FREQ = 6.4e9; // Hz
+// LMX2572 can go lower, but on the ZBX, the analog paths limit frequencies down
+// to 3.2 GHz
+static constexpr double LMX2572_MIN_FREQ = 3.2e9; // Hz
+static constexpr double LMX2572_DEFAULT_FREQ = 4e9; // Hz
+static constexpr uint32_t ZBX_LO_LOCK_TIMEOUT_MS = 20; // milliseconds
+// This is the step size for the LO tuning relative to the PRC rate:
+static constexpr int ZBX_RELATIVE_LO_STEP_SIZE = 6;
+
+static constexpr double ZBX_MIN_FREQ = 1e6; // Hz
+static constexpr double ZBX_MAX_FREQ = 8e9; // Hz
+static constexpr double ZBX_DEFAULT_FREQ = 1e9; // Hz
+static const uhd::freq_range_t ZBX_FREQ_RANGE(ZBX_MIN_FREQ, ZBX_MAX_FREQ);
+static constexpr double ZBX_LOWBAND_FREQ = 3e9; // Hz
+
+constexpr char HW_GAIN_STAGE[] = "hw";
+
+static constexpr double RX_MIN_GAIN = 0;
+static constexpr double RX_MAX_GAIN = 60;
+static constexpr double RX_GAIN_STEP = 1;
+static constexpr double ZBX_DEFAULT_RX_GAIN = RX_MIN_GAIN;
+static const uhd::gain_range_t ZBX_RX_GAIN_RANGE(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP);
+// Rx gain is limited to [0, 38] for frequency <= 500 MHz
+static constexpr double RX_LOW_FREQ_MAX_GAIN = 38;
+static constexpr double RX_LOW_FREQ_MAX_GAIN_CUTOFF = 500e6; // Hz
+static const uhd::gain_range_t ZBX_RX_LOW_FREQ_GAIN_RANGE(
+ RX_MIN_GAIN, RX_LOW_FREQ_MAX_GAIN, RX_GAIN_STEP);
+static constexpr double TX_MIN_GAIN = 0;
+static constexpr double TX_MAX_GAIN = 60;
+static constexpr double TX_GAIN_STEP = 1;
+static constexpr double ZBX_DEFAULT_TX_GAIN = TX_MIN_GAIN;
+static const uhd::gain_range_t ZBX_TX_GAIN_RANGE(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP);
+
+static constexpr char ZBX_GAIN_PROFILE_DEFAULT[] = "default";
+static constexpr char ZBX_GAIN_PROFILE_MANUAL[] = "manual";
+static constexpr char ZBX_GAIN_PROFILE_CPLD[] = "table";
+static constexpr char ZBX_GAIN_PROFILE_CPLD_NOATR[] = "table_noatr";
+static const std::vector<std::string> ZBX_GAIN_PROFILES = {ZBX_GAIN_PROFILE_DEFAULT,
+ ZBX_GAIN_PROFILE_MANUAL,
+ ZBX_GAIN_PROFILE_CPLD,
+ ZBX_GAIN_PROFILE_CPLD_NOATR};
+
+// Maximum attenuation of the TX DSAs
+static constexpr uint8_t ZBX_TX_DSA_MAX_ATT = 31;
+// Maximum attenuation of the RX DSAs
+static constexpr uint8_t ZBX_RX_DSA_MAX_ATT = 15;
+
+static constexpr char ZBX_GAIN_STAGE_DSA1[] = "DSA1";
+static constexpr char ZBX_GAIN_STAGE_DSA2[] = "DSA2";
+static constexpr char ZBX_GAIN_STAGE_DSA3A[] = "DSA3A";
+static constexpr char ZBX_GAIN_STAGE_DSA3B[] = "DSA3B";
+static constexpr char ZBX_GAIN_STAGE_AMP[] = "AMP";
+static constexpr char ZBX_GAIN_STAGE_ALL[] = "all";
+// Not technically a gain stage, but we'll keep it
+static constexpr char ZBX_GAIN_STAGE_TABLE[] = "TABLE";
+
+static const std::vector<std::string> ZBX_RX_GAIN_STAGES = {
+ ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2, ZBX_GAIN_STAGE_DSA3A, ZBX_GAIN_STAGE_DSA3B};
+
+static const std::vector<std::string> ZBX_TX_GAIN_STAGES = {
+ ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2, ZBX_GAIN_STAGE_AMP};
+
+enum class tx_amp { BYPASS = 0, LOWBAND = 1, HIGHBAND = 2 };
+
+static constexpr double ZBX_TX_BYPASS_GAIN = 0.0;
+static constexpr double ZBX_TX_LOWBAND_GAIN = 14.0;
+static constexpr double ZBX_TX_HIGHBAND_GAIN = 21.0;
+
+// The amplifier gain varies wildly across frequency, temperature.... but we
+// need some kind of mapping for querying/setting individual gain stages by
+// dB value.
+static const std::map<tx_amp, double> ZBX_TX_AMP_GAIN_MAP = {
+ {tx_amp::BYPASS, ZBX_TX_BYPASS_GAIN},
+ {tx_amp::LOWBAND, ZBX_TX_LOWBAND_GAIN},
+ {tx_amp::HIGHBAND, ZBX_TX_HIGHBAND_GAIN}};
+static const std::map<double, tx_amp> ZBX_TX_GAIN_AMP_MAP = {
+ {ZBX_TX_BYPASS_GAIN, tx_amp::BYPASS},
+ {ZBX_TX_LOWBAND_GAIN, tx_amp::LOWBAND},
+ {ZBX_TX_HIGHBAND_GAIN, tx_amp::HIGHBAND}};
+
+
+/*** 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_TERMINATION[] = "TERMINATION"; // 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_CAL_LOOPBACK, ANTENNA_TERMINATION};
+static const std::vector<std::string> TX_ANTENNAS = {ANTENNA_TXRX, ANTENNA_CAL_LOOPBACK};
+// For branding purposes, ZBX 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}};
+
+/*** LO-related constants ****************************************************/
+//! Low-band LO
+static constexpr char ZBX_LO1[] = "LO1";
+//! LO at 2nd mixer
+static constexpr char ZBX_LO2[] = "LO2";
+
+static constexpr char RFDC_NCO[] = "rfdc";
+
+static const std::vector<std::string> ZBX_LOS = {ZBX_LO1, ZBX_LO2, RFDC_NCO};
+
+static constexpr size_t ZBX_NUM_CHANS = 2;
+static constexpr std::array<size_t, 2> ZBX_CHANNELS{0, 1};
+
+static constexpr double ZBX_MIX1_MN_THRESHOLD = 4e9;
+
+// Struct for holding band information, used by zbx_radio_control_impl.
+// This information should be selected base on requested tune frequency, and should not be
+// changed once initialized.
+struct tune_map_item_t
+{
+ double min_band_freq;
+ double max_band_freq;
+ uint8_t rf_fir;
+ uint8_t if1_fir;
+ uint8_t if2_fir;
+ int mix1_m;
+ int mix1_n;
+ int mix2_m;
+ int mix2_n;
+ double if1_freq_min;
+ double if1_freq_max;
+ double if2_freq_min;
+ double if2_freq_max;
+};
+
+// 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, 4> ATR_ADDRS{0, 1, 2, 3};
+
+// Turn clang-formatting off so it doesn't compress these tables into a mess.
+// clang-format off
+static const std::vector<tune_map_item_t> rx_tune_map = {
+// | min_band_freq | max_band_freq | rf_fir | if1_fir | if2_fir | mix1 m, n | mix2 m, n | if1_freq_min | if1_freq_max | if2_freq_min | if2_freq_max |
+ { 1e6, 200e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 200e6, 400e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 400e6, 500e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 500e6, 900e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 900e6, 1800e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 2150e6, 2150e6 },
+ { 1800e6, 2300e6, 2, 1, 1, -1, 1, -1, 1, 4100e6, 4100e6, 1060e6, 1060e6 },
+ { 2300e6, 2700e6, 3, 1, 1, -1, 1, -1, 1, 4100e6, 3700e6, 1060e6, 1060e6 },
+ { 2700e6, 3000e6, 3, 4, 2, 1, -1, 1, -1, 7000e6, 7100e6, 2050e6, 2080e6 },
+ { 3000e6, 4200e6, 0, 1, 2, 0, 0, -1, 1, 0, 0, 1850e6, 1850e6 },
+ { 4200e6, 4500e6, 0, 2, 2, 0, 0, -1, 1, 0, 0, 1850e6, 1850e6 },
+ { 4500e6, 4700e6, 0, 2, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 },
+ { 4700e6, 5300e6, 0, 2, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 },
+ { 5300e6, 5600e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 5600e6, 6800e6, 0, 3, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 6800e6, 7400e6, 0, 4, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 7400e6, 8000e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 1850e6, 1850e6 },
+};
+
+static const std::vector<tune_map_item_t> tx_tune_map = {
+// | min_band_freq | max_band_freq | rf_fir | if1_fir | if2_fir | mix1 m, n | mix2 m, n | if1_freq_min | if1_freq_max | if2_freq_min | if2_freq_max |
+ { 1e6, 200e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 200e6, 300e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 300e6, 400e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 400e6, 600e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 600e6, 800e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 800e6, 1300e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 1300e6, 1800e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 1800e6, 2300e6, 2, 1, 1, -1, 1, -1, 1, 4100e6, 4100e6, 1060e6, 1060e6 },
+ { 2300e6, 2700e6, 3, 1, 2, -1, 1, -1, 1, 3700e6, 3700e6, 2070e6, 2200e6 },
+ { 2700e6, 3000e6, 3, 5, 2, 1, -1, 1, -1, 6800e6, 7100e6, 2000e6, 2000e6 },
+ { 3000e6, 4030e6, 0, 1, 2, 0, 0, -1, 1, 0, 0, 2050e6, 2370e6 },
+ { 4030e6, 4500e6, 0, 1, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 },
+ { 4500e6, 4900e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 4900e6, 5100e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 5100e6, 5700e6, 0, 3, 2, 0, 0, 1, -1, 0, 0, 1900e6, 2300e6 },
+ { 5700e6, 6100e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 2300e6, 2500e6 },
+ { 6100e6, 6400e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 2400e6, 2500e6 },
+ { 6400e6, 7000e6, 0, 5, 2, 0, 0, 1, -1, 0, 0, 1900e6, 1950e6 },
+ { 7000e6, 7400e6, 0, 6, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 7400e6, 8000e6, 0, 6, 2, 0, 0, 1, -1, 0, 0, 1950e6, 2050e6 },
+};
+
+// Turn clang-format back on just for posterity
+// clang-format on
+
+}}} // namespace uhd::usrp::zbx
+
+
+namespace uhd { namespace experts {
+// << Operator overload for expert's node printing (zbx_lo_source_t property)
+// Any added expert nodes of type enum class will have to define this
+std::ostream& operator<<(
+ std::ostream& os, const ::uhd::usrp::zbx::zbx_lo_source_t& lo_source);
+}} // namespace uhd::experts
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp
new file mode 100644
index 000000000..03f0fa5b7
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp
@@ -0,0 +1,487 @@
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include "zbx_lo_ctrl.hpp"
+#include <uhd/types/direction.hpp>
+#include <uhd/types/serial.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <unordered_map>
+#include <zbx_cpld_regs.hpp>
+#include <array>
+#include <functional>
+#include <mutex>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+/*! ZBX CPLD Control Class
+ *
+ * A note on table indexing: Many settings take an index paramater, usually
+ * called 'idx'. These settings can be configured in 256 different ways. The
+ * configuration that is chosen out of those 256 different ones depends on the
+ * current config register (see get_current_config()). This register itself
+ * depends on the ATR mode the CPLD is in.
+ * When the ATR mode is atr_mode::CLASSIC_ATR, then only the first four indexes
+ * are used, and depend on the current ATR state of the radio (RX, TX, full duplex,
+ * or idle). If the mode is atr_mode::FPGA_STATE, then only the first 16 indexes
+ * are used, and the setting that is applied follows the four ATR pins with no
+ * specific mapping to RX or TX states. If the mode is atr_mode::SW_DEFINED,
+ * then the ATR pins are ignored, and the state is configured by set_sw_config().
+ */
+class zbx_cpld_ctrl
+{
+public:
+ enum chan_t { CHAN0, CHAN1, BOTH_CHANS, NO_CHAN };
+ enum spi_xact_t { READ, WRITE };
+ // Note: The values in this enum must match the values in the CPLD regmaps.
+ enum class atr_mode { SW_DEFINED = 0, CLASSIC_ATR = 1, FPGA_STATE = 2 };
+ enum class dsa_type { DSA1, DSA2, DSA3A, DSA3B };
+ enum class atr_mode_target { DSA, PATH_LED };
+
+ // The RX gain settings have four DSAs
+ using rx_dsa_type = std::array<uint32_t, 4>;
+ // The TX gain settings have two DSAs and one amp-path with 3 possible
+ // settings.
+ using tx_dsa_type = std::array<uint32_t, 3>;
+
+ 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&)>;
+
+ //! Maps a DSA name ("DSA1", "DSA2", etc.) to its equivalent dsa_type
+ static const std::unordered_map<std::string, dsa_type> dsa_map;
+
+ zbx_cpld_ctrl(poke_fn_type&& poke_fn,
+ peek_fn_type&& peek_fn,
+ sleep_fn_type&& sleep_fn,
+ const std::string& log_id);
+
+ ~zbx_cpld_ctrl(void) = default;
+
+ //! Write a value to the scratch register
+ void set_scratch(const uint32_t value);
+
+ //! Read back the value from the scratch register
+ uint32_t get_scratch();
+
+ /*! Configure the ATR mode of a channel
+ *
+ * This configures how the DSAs, LEDs, and switches are controlled by the
+ * ATR pins going into the CPLD.
+ *
+ * See the CPLD register map for more information. In a nutshell, this will
+ * define how the current config register is populated (ATR pins or
+ * set_sw_config()).
+ *
+ * \param channel The channel for which this setting applies (either 0 or 1)
+ * \param target The target for this setting. With atr_mode_target::DSA, it
+ * will change the mode for the attenuators. With PATH_LED, it
+ * will change the mode for the RF path and LED controls.
+ * \param mode The ATR mode for this channel and target.
+ */
+ void set_atr_mode(
+ const size_t channel, const atr_mode_target target, const atr_mode mode);
+
+ /*! Choose the SW configuration of the CPLD
+ *
+ * When the ATR mode is anything other than SW_DEFINED, this has no effect.
+ * When the ATR mode is SW_DEFINED, this will choose which DSA/LED/switch
+ * configuration to apply to hardware.
+ *
+ * \param channel The RF channel for which this applies (0 or 1)
+ * \param target The target for this setting. With atr_mode_target::DSA, it
+ * will change the mode for the attenuators. With PATH_LED, it
+ * will change the mode for the RF path and LED controls.
+ * \param rf_config The selected RF configuration
+ */
+ void set_sw_config(
+ const size_t channel, const atr_mode_target target, const uint8_t rf_config);
+
+ /*! Read back the current config register
+ *
+ * \param channel The RF channel for which this applies (0 or 1)
+ * \param target The target for this setting. With atr_mode_target::DSA, it
+ * will change the mode for the attenuators. With PATH_LED, it
+ * will change the mode for the RF path and LED controls.
+ */
+ uint8_t get_current_config(const size_t channel, const atr_mode_target target);
+
+ /*! Set all RX DSAs directly
+ *
+ * This will directly update the DSA tables at the given index. In other
+ * words, this setting will directly be applied to hardware.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param dsa_steps DSA step values
+ */
+ void set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const tx_dsa_type& dsa_steps);
+
+ /*! Set all TX DSAs directly
+ *
+ * This will directly update the DSA tables at the given index. In other
+ * words, this setting will directly be applied to hardware.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param dsa_steps DSA step values
+ */
+ void set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const rx_dsa_type& dsa_steps);
+
+ /*! Set all RX DSAs using the CPLD table
+ *
+ * This will read DSA settings from the lookup table at position \p table_idx
+ * and write them to the DSAs at position \p idx.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx DSA table index
+ * \param table_idx Lookup table index
+ */
+ void set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx);
+
+ /*! Set all TX DSAs using the CPLD table
+ *
+ * This will read DSA settings from the lookup table at position \p table_idx
+ * and write them to the DSAs at position \p idx.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx DSA table index
+ * \param table_idx Lookup table index
+ */
+ void set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx);
+
+ /*! Set a specific TX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t set_tx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type tx_dsa,
+ const uint8_t att);
+
+ /*! Set a specific RX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t set_rx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type rx_dsa,
+ const uint8_t att);
+
+ /*! Set a specific TX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t get_tx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type tx_dsa,
+ const bool update_cache = false);
+
+ /*! Set a specific RX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t get_rx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type rx_dsa,
+ const bool update_cache = false);
+
+ /*! Setting switches required for antenna mode switching, transmitting side
+ *
+ * Note: If the antenna is set to TX/RX, this also configures the TX
+ * amplifier. This unfortunate API coupling is due to the fact that the
+ * same switch that chooses the antenna path also switches the amplifier in
+ * and out.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param amp The amplifier configuration
+ * \param antenna desired antenna mode
+ */
+ void set_tx_antenna_switches(const size_t channel,
+ const uint8_t idx,
+ const std::string& antenna,
+ const tx_amp amp);
+
+ /*! Setting switches required for antenna mode switching, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param gain desired antenna mode
+ * \param is_highband highband or lowband settings
+ */
+ void set_rx_antenna_switches(
+ const size_t channel, const uint8_t idx, const std::string& antenna);
+
+ /*! Return the current amp settings
+ */
+ tx_amp get_tx_amp_settings(
+ const size_t channel, const uint8_t idx, const bool update_cache);
+
+ /*! Setting switches required for rf filter changes, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param rf_fir rf filter value
+ */
+ void set_rx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir);
+
+ /*! Setting switches required for if1 filter changes, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if1_fir if1 filter value
+ */
+ void set_rx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir);
+
+ /*! Setting switches required for if2 filter changes, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if2_fir if2 filter value
+ */
+ void set_rx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir);
+
+ /*! Setting switches required for rf filter changes, transmitting side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param rf_fir rf filter value
+ */
+ void set_tx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir);
+
+ /*! Setting switches required for if1 filter changes, transmitting side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if1_fir if1 filter value
+ */
+ void set_tx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir);
+
+ /*! Setting switches required for if2 filter changes, transmitting side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if2_fir if2 filter value
+ */
+ void set_tx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir);
+
+ /**************************************************************************
+ * LED controls
+ *************************************************************************/
+ /*! Turn the LEDs on or off
+ *
+ * There are two LEDs on ZBX, the TRX LED has a red and green component.
+ *
+ * Note that toggling any of the LED settings to 'true' won't necessarily
+ * turn on the LED. The current CPLD 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);
+
+ /**************************************************************************
+ * LO controls
+ *************************************************************************/
+ //! Write to a register on an LO
+ //
+ // Note: All eight LOs are accessed through the same CPLD register. For
+ // timed commands to the LOs, it is up to the call site to ensure that SPI
+ // writes/reads do not get interleaved.
+ //
+ // Note: This will not poll the ready bit of the CPLD. To ensure valid
+ // transactions, either manually call lo_spi_ready(), or make sure that SPI
+ // commands are timed appropriately, i.e., new SPI transaction requests reach
+ // the CPLD only after the previous transaction is comppleted.
+ //
+ // \param lo Which LO to write to.
+ // \param addr The address of the LO register (see the LMX2572 datasheet)
+ // \param data The data to write to the LO register (see the LMX2572 datasheet)
+ void lo_poke16(const zbx_lo_t lo, const uint8_t addr, const uint16_t data);
+
+ //! Read back from the LO
+ //
+ // Note: The LMX2572 has a MUXout pin, not just an SDO pin. This means the
+ // call site needs to ensure that MUXout configuration is in the correct
+ // state before calling this function (to either read back the lock status,
+ // or the SPI read return value).
+ //
+ // Note: This will not poll the ready bit of the CPLD. To ensure valid
+ // transactions, either manually call lo_spi_ready(), or make sure that SPI
+ // commands are timed appropriately, i.e., new SPI transaction requests reach
+ // the CPLD only after the previous transaction is comppleted.
+ //
+ // \param lo Which LO to read from
+ // \param addr Which address on the LO to read from (see LMX2572 datasheet)
+ // \param valid_timeout_ms After triggering the transaction, the function will
+ // wait for this many ms before throwing an exception. A zero timeout
+ // is possible, which means the first read to the LO_SPI_STATUS
+ // register must already have the ready bit high.
+ uint16_t lo_peek16(const zbx_lo_t lo, const uint8_t addr);
+
+ //! Returns true if the LO_SPI_READY bit is high, i.e., the LO SPI is ready
+ // for a transaction
+ bool lo_spi_ready();
+
+ //! LO's incoming source control (external/internal) is actually found in
+ // the CPLD path control register spaces
+ //
+ // \param idx Table index
+ // \param lo Which LO to read from
+ // \param lo_source Set LO source to internal/external
+ void set_lo_source(
+ const size_t idx, const zbx_lo_t lo, const zbx_lo_source_t lo_source);
+
+ //! Retrieve lo source
+ // \param idx Table index to read from
+ // \param lo Which LO to read from
+ zbx_lo_source_t get_lo_source(const size_t idx, zbx_lo_t lo);
+
+ //! Synchronize LOs
+ //
+ // This will assert a SYNC pulse on all the LOs listed in \p los.
+ //
+ // Note: This function will throw an exception if LO sync bypass is enabled
+ // (see set_lo_sync_bypass()).
+ //
+ // A note on timing: Like most CPLD controls, the time is inherited from the
+ // underlying register interface. That is to say, the APIs don't take a time
+ // as an argument, but assume the command time is correctly applied.
+ // The different channels of the ZBX (channel 0/1) may have different command
+ // times. Because this API potentially affects both channels at once, the
+ // channel index must be provided to determine which channel's time should
+ // be used.
+ //
+ // \param ref_chan The channel that is used as a timing reference.
+ // \param los A list of LOs to synchronize
+ // \throws uhd::runtime_error if LO sync bypass is enabled.
+ void pulse_lo_sync(const size_t ref_chan, const std::vector<zbx_lo_t>& los);
+
+ //! Enable/disable LO sync bypass
+ //
+ // This is a ZBX-specific option, which will allow synchronizing the LOs via
+ // the MB_SYNTH_SYNC pin instead of using a register. Enabling this will
+ // disable the ability to call pulse_lo_sync().
+ //
+ // \param enable If true, enables the bypass. When false, disables the bypass
+ // and pulse_lo_sync() can be called.
+ void set_lo_sync_bypass(const bool enable);
+
+ /*! Write DSA table for TX frequency to DB CPLD
+ */
+ void update_tx_dsa_settings(
+ const std::vector<uint32_t>& dsa1_table, const std::vector<uint32_t>& dsa2_table);
+
+ /*! Write DSA table for RX frequency to DB CPLD
+ */
+ void update_rx_dsa_settings(const std::vector<uint32_t>& dsa1_table,
+ const std::vector<uint32_t>& dsa2_table,
+ const std::vector<uint32_t>& dsa3a_table,
+ const std::vector<uint32_t>& dsa3b_table);
+
+private:
+ /*! Dump the state of the registers into the CPLD
+ *
+ * \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);
+
+ /*! Update a register field by peeking the corresponding CPLD register
+ *
+ * This will synchronize the state of the internal register cache with the
+ * actual value from the CPLD. This will incur a single peek (non-timed) to
+ * the chip before returning.
+ */
+ void update_field(const zbx_cpld_regs_t::zbx_cpld_field_t field, const size_t idx);
+
+ /*! Perform an LO SPI transaction (interact with the LO_SPI_STATUS register)
+ *
+ * Note: This has the ability to throttle the SPI transactions. The reason
+ * is that the peek/poke interface from UHD to the CPLD is faster than the
+ * SPI interface from the CPLD to the LO. If two SPI writes were to be
+ * sent without a throttle, the second one would clobber the first. Never
+ * call this with throttle == false if another SPI transaction is following!
+ *
+ * \param lo Which LO to address
+ * \param addr 7-bit address of the LO's register
+ * \param data 16-bit data to write (can be empty for reads)
+ * \param write If true, write, else read
+ * \param throttle If true, wait after writing, so that a following SPI
+ * transaction won't clobber the previous one
+ */
+ void _lo_spi_transact(const zbx_lo_t lo,
+ const uint8_t addr,
+ const uint16_t data,
+ const spi_xact_t xact_type,
+ const bool throttle = true);
+
+ /*! Write a list of values to a register.
+ * The list start address is searched using reg_addr_name.
+ * The method will raise an exception if values is longer than
+ * the register size.
+ * The caller is responsible to commit the data once values are written.
+ * This allows multiple vector writes with a single commit.
+ */
+ void write_register_vector(
+ const std::string& reg_addr_name, const std::vector<uint32_t>& values);
+
+ //! Poker object
+ poke_fn_type _poke32;
+
+ //! Peeker object
+ peek_fn_type _peek32;
+
+ //! Hardware-timed sleep, used to throttle pokes
+ sleep_fn_type _sleep;
+
+ // Address offset (on top of _db_cpld_offset) where the LO SPI register is
+ const uint32_t _lo_spi_offset;
+
+ // infos about the daughtherboard revision
+ std::string _db_rev_info;
+
+ // Cached register state
+ zbx_cpld_regs_t _regs = zbx_cpld_regs_t();
+
+ const std::string _log_id;
+};
+
+}}} // namespace uhd::usrp::zbx
+
+namespace uhd { namespace experts {
+// << Operator overload for expert's node printing (zbx_lo_source_t property)
+// Any added expert nodes of type enum class will have to define this
+std::ostream& operator<<(
+ std::ostream& os, const ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode& lo_source);
+}} // namespace uhd::experts
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp
new file mode 100644
index 000000000..619c4a05f
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp
@@ -0,0 +1,416 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include "zbx_cpld_ctrl.hpp"
+#include "zbx_expert.hpp"
+#include "zbx_lo_ctrl.hpp"
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/rfnoc/register_iface.hpp>
+#include <uhd/rfnoc/registry.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <uhdlib/experts/expert_factory.hpp>
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <uhdlib/usrp/common/mpmd_mb_controller.hpp>
+#include <uhdlib/usrp/common/pwr_cal_mgr.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 <stddef.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace uhd::rfnoc;
+
+namespace uhd { namespace usrp { namespace zbx {
+
+const static uint16_t ZBX_PID = 0x4002;
+
+/*! Provide access to a ZBX radio.
+ */
+class zbx_dboard_impl : public uhd::usrp::x400::x400_dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<zbx_dboard_impl>;
+ using time_accessor_fn_type = std::function<uhd::time_spec_t(size_t)>;
+
+ /************************************************************************
+ * Structors
+ ***********************************************************************/
+ zbx_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 std::string& rpc_prefix,
+ const std::string& unique_id,
+ uhd::usrp::x400_rpc_iface::sptr mb_rpcc,
+ uhd::usrp::zbx_rpc_iface::sptr rpcc,
+ uhd::rfnoc::x400::rfdc_control::sptr rfdcc,
+ uhd::property_tree::sptr tree);
+ virtual ~zbx_dboard_impl();
+
+ size_t get_chan_from_dboard_fe(
+ const std::string& fe, const uhd::direction_t) const override;
+ std::string get_dboard_fe_from_chan(
+ const size_t chan, const uhd::direction_t) const override;
+
+ /************************************************************************
+ * 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 tone_freq) override
+ {
+ // This is chosen such that the IF2 frequency is 1.06G
+ const double rx_freq = 4.7e9 - 5.12e6;
+ const double if2_freq = 1.06e9;
+ const double offset = tone_freq - if2_freq;
+
+ // Minus because this zone is inverted
+ const double tx_freq = rx_freq - offset;
+ return {
+ 10.0, // min_gain
+ 50.0, // max_gain
+ rx_freq, // rx_freq
+ tx_freq, // tx_freq
+ };
+ }
+
+ 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;
+ }
+
+ void set_tx_antenna(const std::string& ant, const size_t chan) override;
+ void set_rx_antenna(const std::string& ant, const size_t chan) override;
+ std::vector<std::string> get_tx_antennas(const size_t /*chan*/) const override
+ {
+ return TX_ANTENNAS;
+ }
+ std::vector<std::string> get_rx_antennas(const size_t /*chan*/) const override
+ {
+ return RX_ANTENNAS;
+ }
+
+ 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 ZBX_FREQ_RANGE;
+ }
+ uhd::freq_range_t get_rx_frequency_range(const size_t /*chan*/) const override
+ {
+ return ZBX_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();
+ }
+
+ double set_tx_gain(const double gain, const size_t chan) override;
+ double set_tx_gain(
+ const double gain, const std::string& name, const size_t chan) override;
+ double set_rx_gain(const double gain, const size_t chan) override;
+ double set_rx_gain(
+ const double gain, const std::string& name, const size_t chan) override;
+ double get_rx_gain(const size_t chan) override;
+ double get_tx_gain(const size_t chan) override;
+ double get_rx_gain(const std::string& name, const size_t chan) override;
+ double get_tx_gain(const std::string& name, const size_t chan) override;
+
+ uhd::gain_range_t get_tx_gain_range(const size_t /*chan*/) const override
+ {
+ return ZBX_TX_GAIN_RANGE;
+ }
+ uhd::gain_range_t get_rx_gain_range(const size_t /*chan*/) const override
+ {
+ // FIXME This should return a ZBX_RX_LOW_FREQ_GAIN_RANGE when freq is
+ // low, but this function is const
+ return ZBX_RX_GAIN_RANGE;
+ }
+
+ // LO Property Getters
+ std::vector<std::string> get_tx_lo_names(const size_t /*chan*/) const
+ {
+ return ZBX_LOS;
+ }
+ std::vector<std::string> get_rx_lo_names(const size_t /*chan*/) const
+ {
+ return ZBX_LOS;
+ }
+ std::vector<std::string> get_tx_lo_sources(
+ const std::string& /*name*/, const size_t /*chan*/) const
+ {
+ return std::vector<std::string>{"internal", "external"};
+ }
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string& /*name*/, const size_t /*chan*/) const
+ {
+ return std::vector<std::string>{"internal", "external"};
+ }
+
+ // LO Frequency Control
+ double set_tx_lo_freq(
+ const double freq, const std::string& name, const size_t chan) override;
+ double set_rx_lo_freq(
+ const double freq, const std::string& name, const size_t chan) override;
+ double get_tx_lo_freq(const std::string& name, const size_t chan) override;
+ double get_rx_lo_freq(const std::string& name, size_t chan) override;
+
+ // LO Source Control
+ void set_tx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan) override;
+ void set_rx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan) override;
+ const std::string get_tx_lo_source(
+ const std::string& name, const size_t chan) override;
+ const std::string get_rx_lo_source(
+ const std::string& name, const size_t chan) override;
+
+ 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);
+ }
+
+ // TODO: Why is this not const?
+ 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 enabled, const std::string& name, const size_t chan) override;
+ bool get_rx_lo_export_enabled(
+ const std::string& name, const size_t chan) override;
+ void set_tx_lo_export_enabled(
+ const bool enabled, const std::string& name, const size_t chan) override;
+ bool get_tx_lo_export_enabled(const std::string& name, const size_t chan) override;
+
+
+ /******************************************************************************
+ * EEPROM API
+ *****************************************************************************/
+ eeprom_map_t get_db_eeprom() override;
+
+ /**************************************************************************
+ * Radio Identification API Calls
+ *************************************************************************/
+
+ std::string get_tx_antenna(size_t chan) const override;
+ std::string get_rx_antenna(size_t chan) const override;
+ 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 uhd::device_addr_t&, const size_t) override;
+ void set_rx_tune_args(const uhd::device_addr_t&, const size_t) override;
+ std::vector<std::string> get_tx_gain_names(size_t) const override;
+ std::vector<std::string> get_rx_gain_names(size_t) const override;
+
+ uhd::gain_range_t get_tx_gain_range(
+ const std::string& name, const size_t chan) const override;
+
+ uhd::gain_range_t get_rx_gain_range(
+ const std::string& name, const size_t chan) const override;
+
+ void set_rx_agc(const bool, const size_t) override;
+
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(uhd::direction_t trx) override;
+
+private:
+ uhd::property_tree::sptr get_tree()
+ {
+ return _tree;
+ }
+
+ // Expert map, keyed by the pair of tx/rx and channel
+ uhd::experts::expert_container::sptr _expert_container;
+
+ /**************************************************************************
+ * Helpers
+ *************************************************************************/
+ //! Initialize DB-CPLD
+ void _init_cpld();
+
+ //! Initialize all the peripherals connected to this block
+ void _init_peripherals();
+
+ //! 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);
+ //! Initializing the expert properties
+ void _init_frequency_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr expert,
+ const fs_path fe_path);
+ void _init_gain_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+ void _init_antenna_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+ void _init_programming_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const fs_path fe_path);
+ void _init_lo_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ 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::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+
+ uhd::usrp::pwr_cal_mgr::sptr _init_power_cal(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();
+
+ //! Set up sensor property nodes
+ void _init_mpm_sensors(const direction_t dir, const size_t chan_idx);
+
+ //! Get subtree path for a given direction/channel
+ fs_path _get_frontend_path(const direction_t dir, const size_t chan_idx) const;
+
+ // Get all los "lock status", per enabled && locked individual LOs
+ bool _get_all_los_locked(const direction_t dir, const size_t chan);
+
+ const std::string _unique_id;
+ std::string get_unique_id() const;
+
+ freq_range_t _get_lo_freq_range(const std::string& name, const size_t chan) const;
+
+ /**************************************************************************
+ * Private attributes
+ *************************************************************************/
+
+ static constexpr size_t _num_rx_chans = 2;
+ static constexpr size_t _num_tx_chans = 2;
+
+ //! 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::zbx_rpc_iface::sptr _rpcc;
+
+ //! Reference to the RFDC controller
+ uhd::rfnoc::x400::rfdc_control::sptr _rfdcc;
+
+ //! Reference to the CPLD controls
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+
+ //! Reference to all LO controls
+ std::map<zbx_lo_t, std::shared_ptr<zbx_lo_ctrl>> _lo_ctrl_map;
+
+ //! Reference to the TX Cal data
+ std::shared_ptr<uhd::usrp::cal::zbx_tx_dsa_cal> _tx_dsa_cal;
+
+ //! Reference to the RX Cal data
+ std::shared_ptr<uhd::usrp::cal::zbx_rx_dsa_cal> _rx_dsa_cal;
+
+ //! 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;
+
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr> _rx_pwr_mgr;
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr> _tx_pwr_mgr;
+
+ rf_control::gain_profile_iface::sptr _tx_gain_profile_api;
+ rf_control::gain_profile_iface::sptr _rx_gain_profile_api;
+
+ //! Store the current RX gain profile
+ std::vector<std::string> _rx_gain_profile = {
+ ZBX_GAIN_PROFILE_DEFAULT, ZBX_GAIN_PROFILE_DEFAULT};
+ //! Store the current TX gain profile
+ std::vector<std::string> _tx_gain_profile = {
+ ZBX_GAIN_PROFILE_DEFAULT, ZBX_GAIN_PROFILE_DEFAULT};
+
+ //! The sampling rate of the RFdc, typically something close to 3 GHz
+ const double _rfdc_rate;
+
+ //! The PLL reference rate, typically something in the 50 - 64 MHz range
+ const double _prc_rate;
+};
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp
new file mode 100644
index 000000000..f386a4fdb
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp
@@ -0,0 +1,837 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include "zbx_cpld_ctrl.hpp"
+#include "zbx_lo_ctrl.hpp"
+#include <uhd/cal/container.hpp>
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhdlib/experts/expert_nodes.hpp>
+#include <uhdlib/rfnoc/rf_control/gain_profile_iface.hpp>
+#include <uhdlib/usrp/common/pwr_cal_mgr.hpp>
+#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <uhdlib/utils/rpc.hpp>
+#include <cmath>
+#include <memory>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+namespace {
+
+//! Depending on the given \p lo_step_size, this will return a valid frequency
+// range on a quantized grid for the the LOs. The lower limit of this range will
+// never be smaller than LMX2572_MIN_FREQ and the upper frequency will never be
+// larger than LMX2572_MAX_FREQ. All frequencies will be integer multiples of
+// the given \p lo_step_size.
+uhd::freq_range_t _get_quantized_lo_range(const double lo_step_size)
+{
+ const double start = std::ceil(LMX2572_MIN_FREQ / lo_step_size) * lo_step_size;
+ const double stop = std::floor(LMX2572_MAX_FREQ / lo_step_size) * lo_step_size;
+ UHD_ASSERT_THROW(start >= LMX2572_MIN_FREQ);
+ UHD_ASSERT_THROW(stop <= LMX2572_MAX_FREQ);
+ return uhd::freq_range_t(start, stop, lo_step_size);
+}
+
+} // namespace
+
+/*!---------------------------------------------------------
+ * zbx_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 zbx_scheduling_expert : public experts::worker_node_t
+{
+public:
+ zbx_scheduling_expert(const experts::node_retriever_t& db, const uhd::fs_path fe_path)
+ : experts::worker_node_t(fe_path / "zbx_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();
+
+ // Inputs
+ experts::data_reader_t<time_spec_t> _command_time;
+
+ // Outputs
+ experts::data_writer_t<time_spec_t> _frontend_time;
+};
+
+/*!---------------------------------------------------------
+ * zbx_freq_fe_expert (Frequency Front-end Expert)
+ *
+ * This expert is responsible for responding to user requests for center frequency tuning
+ *
+ * This should trigger:
+ * - relevant LO experts
+ * - adjacent MPM expert
+ * - adjacent CPLD (tx/rx) Programming expert
+ * After all of the above, the Frequency Backend expert should be triggered to returned
+ * the coerced center frequency
+ *
+ * One instance of this expert is required for each combination of Direction (TX/RX) and
+ * Channel (0,1); four total
+ * --------------------------------------------------------
+ */
+class zbx_freq_fe_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_freq_fe_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,
+ const double lo_step_size)
+ : experts::worker_node_t(fe_path / "zbx_freq_fe_expert")
+ , _desired_frequency(db, fe_path / "freq" / "desired")
+ , _desired_lo1_frequency(db, fe_path / "los" / ZBX_LO1 / "freq" / "value" / "desired")
+ , _desired_lo2_frequency(db, fe_path / "los" / ZBX_LO2 / "freq" / "value" / "desired")
+ , _lo1_enabled(db, fe_path / ZBX_LO1 / "enabled")
+ , _lo2_enabled(db, fe_path / ZBX_LO2 / "enabled")
+ , _desired_if2_frequency(db, fe_path / "if_freq" / "desired")
+ , _band_inverted(db, fe_path / "band_inverted")
+ , _is_highband(db, fe_path / "is_highband")
+ , _mixer1_m(db, fe_path / "mixer1_m")
+ , _mixer1_n(db, fe_path / "mixer1_n")
+ , _mixer2_m(db, fe_path / "mixer2_m")
+ , _mixer2_n(db, fe_path / "mixer2_n")
+ , _rf_filter(db, fe_path / "rf" / "filter")
+ , _if1_filter(db, fe_path / "if1" / "filter")
+ , _if2_filter(db, fe_path / "if2" / "filter")
+ , _rfdc_rate(rfdc_rate)
+ , _lo_freq_range(_get_quantized_lo_range(lo_step_size))
+ , _trx(trx)
+ , _chan(chan)
+ {
+ // Inputs
+ bind_accessor(_desired_frequency);
+
+ // Outputs
+ bind_accessor(_desired_lo1_frequency);
+ bind_accessor(_desired_lo2_frequency);
+ bind_accessor(_lo1_enabled);
+ bind_accessor(_lo2_enabled);
+ bind_accessor(_desired_if2_frequency);
+ bind_accessor(_band_inverted);
+ bind_accessor(_is_highband);
+ bind_accessor(_mixer1_m);
+ bind_accessor(_mixer1_n);
+ bind_accessor(_mixer2_m);
+ bind_accessor(_mixer2_n);
+ bind_accessor(_rf_filter);
+ bind_accessor(_if1_filter);
+ bind_accessor(_if2_filter);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _desired_frequency;
+
+ // Outputs
+ // From calculation, to LO expert
+ uhd::experts::data_writer_t<double> _desired_lo1_frequency;
+ uhd::experts::data_writer_t<double> _desired_lo2_frequency;
+ uhd::experts::data_writer_t<bool> _lo1_enabled;
+ uhd::experts::data_writer_t<bool> _lo2_enabled;
+ // From calculation, to MPM/RPC expert
+ uhd::experts::data_writer_t<double> _desired_if2_frequency;
+ uhd::experts::data_writer_t<bool> _band_inverted;
+ // From calculation, to Frequency Backend expert
+ uhd::experts::data_writer_t<bool> _is_highband;
+ uhd::experts::data_writer_t<int> _mixer1_m;
+ uhd::experts::data_writer_t<int> _mixer1_n;
+ uhd::experts::data_writer_t<int> _mixer2_m;
+ uhd::experts::data_writer_t<int> _mixer2_n;
+ // From calculation, to CPLD Programming expert
+ uhd::experts::data_writer_t<int> _rf_filter;
+ uhd::experts::data_writer_t<int> _if1_filter;
+ uhd::experts::data_writer_t<int> _if2_filter;
+
+ const double _rfdc_rate;
+ const uhd::freq_range_t _lo_freq_range;
+ tune_map_item_t _tune_settings;
+ // Channel properties
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_freq_be_expert (Frequency Back-end Expert)
+ *
+ * This expert is responsible for calculating the final coerced frequency and returning it
+ * to the user
+ *
+ * This should trigger:
+ * - adjacent gain expert
+ *
+ * One instance of this expert is required for each combination of Direction (TX/RX) and
+ * Channel (0,1); four total
+ * --------------------------------------------------------
+ */
+class zbx_freq_be_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_freq_be_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const uhd::direction_t trx,
+ const size_t chan)
+ : uhd::experts::worker_node_t(fe_path / "zbx_freq_be_expert")
+ , _coerced_lo1_frequency(db, fe_path / "los" / ZBX_LO1 / "freq" / "value" / "coerced")
+ , _coerced_lo2_frequency(db, fe_path / "los" / ZBX_LO2 / "freq" / "value" / "coerced")
+ , _coerced_if2_frequency(db, fe_path / "if_freq" / "coerced")
+ , _is_highband(db, fe_path / "is_highband")
+ , _mixer1_m(db, fe_path / "mixer1_m")
+ , _mixer1_n(db, fe_path / "mixer1_n")
+ , _mixer2_m(db, fe_path / "mixer2_m")
+ , _mixer2_n(db, fe_path / "mixer2_n")
+ , _coerced_frequency(db, fe_path / "freq" / "coerced")
+ , _trx(trx)
+ , _chan(chan)
+ {
+ // Inputs
+ bind_accessor(_coerced_lo1_frequency);
+ bind_accessor(_coerced_lo2_frequency);
+ bind_accessor(_coerced_if2_frequency);
+ bind_accessor(_is_highband);
+ bind_accessor(_mixer1_m);
+ bind_accessor(_mixer1_n);
+ bind_accessor(_mixer2_m);
+ bind_accessor(_mixer2_n);
+
+ // Outputs
+ bind_accessor(_coerced_frequency);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from LO expert(s)
+ uhd::experts::data_reader_t<double> _coerced_lo1_frequency;
+ uhd::experts::data_reader_t<double> _coerced_lo2_frequency;
+ // Input from MPM/RPC expert
+ uhd::experts::data_reader_t<double> _coerced_if2_frequency;
+ uhd::experts::data_reader_t<bool> _is_highband;
+ // Input from Frequency FE
+ uhd::experts::data_reader_t<int> _mixer1_m;
+ uhd::experts::data_reader_t<int> _mixer1_n;
+ uhd::experts::data_reader_t<int> _mixer2_m;
+ uhd::experts::data_reader_t<int> _mixer2_n;
+
+ // Output to user/API
+ uhd::experts::data_writer_t<double> _coerced_frequency;
+
+ // Channel properties
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_lo_expert
+ *
+ * This expert is responsible for controlling one LO on the zbx
+ * note: LO source control is handled by the CPLD Programming Expert
+ *
+ * This should trigger:
+ * - Relevant (tx/rx, channel) Frequency Back-end Expert
+ *
+ * One instance of this expert is required for each LO (lo1, lo2) per Direction (TX/RX)
+ * and Channel (0,1); eight total
+ * --------------------------------------------------------
+ */
+class zbx_lo_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_lo_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 lo,
+ std::shared_ptr<zbx_lo_ctrl> zbx_lo_ctrl)
+ : uhd::experts::worker_node_t(fe_path / "zbx_" + lo + "_expert")
+ , _desired_lo_frequency(db, fe_path / "los" / lo / "freq" / "value" / "desired")
+ , _set_is_enabled(db, fe_path / lo / "enabled")
+ , _test_mode_enabled(db, fe_path / lo / "test_mode")
+ , _coerced_lo_frequency(db, fe_path / "los" / lo / "freq" / "value" / "coerced")
+ , _lo_ctrl(zbx_lo_ctrl)
+ , _trx(trx)
+ , _chan(chan)
+ {
+ bind_accessor(_desired_lo_frequency);
+ bind_accessor(_test_mode_enabled);
+ bind_accessor(_set_is_enabled);
+ bind_accessor(_coerced_lo_frequency);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from Frequency FE expert or user/API
+ uhd::experts::data_reader_t<double> _desired_lo_frequency;
+ uhd::experts::data_reader_t<bool> _set_is_enabled;
+ // Inputs from user/API
+ uhd::experts::data_reader_t<bool> _test_mode_enabled;
+
+ // Outputs to Frequency BE expert or user/API
+ uhd::experts::data_writer_t<double> _coerced_lo_frequency;
+
+ std::shared_ptr<zbx_lo_ctrl> _lo_ctrl;
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+
+/*! DSA coercer expert
+ *
+ * Knows how to coerce a DSA value.
+ */
+class zbx_gain_coercer_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_gain_coercer_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path gain_path,
+ const uhd::meta_range_t valid_range)
+ : uhd::experts::worker_node_t(gain_path / "zbx_gain_coercer_expert")
+ , _gain_desired(db, gain_path / "desired")
+ , _gain_coerced(db, gain_path / "coerced")
+ , _valid_range(valid_range)
+ {
+ bind_accessor(_gain_desired);
+ bind_accessor(_gain_coerced);
+ }
+
+private:
+ void resolve() override;
+ // Input
+ uhd::experts::data_reader_t<double> _gain_desired;
+ // Output
+ uhd::experts::data_writer_t<double> _gain_coerced;
+ // Attributes
+ const uhd::meta_range_t _valid_range;
+};
+
+/*!---------------------------------------------------------
+ * zbx_tx_gain_expert (TX Gain Expert)
+ *
+ * This expert is responsible for controlling the gain of each TX channel.
+ * If the gain profile is set to default, then it will look up the corresponding
+ * amp and DSA values and write them to those nodes.
+ *
+ * This should trigger:
+ * - Adjacent CPLD TX Programming Expert
+ *
+ * One instance of this expert is required for each TX Channel (0,1); two total
+ * --------------------------------------------------------
+ */
+class zbx_tx_gain_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_tx_gain_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const size_t chan,
+ uhd::usrp::pwr_cal_mgr::sptr power_mgr,
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr dsa_cal)
+ : uhd::experts::worker_node_t(fe_path / "zbx_gain_expert")
+ , _gain_in(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "desired")
+ , _profile(db, fe_path / "gains" / "all" / "profile")
+ , _frequency(db, fe_path / "freq" / "coerced")
+ , _gain_out(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "coerced")
+ , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "desired")
+ , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "desired")
+ , _amp_gain(db, fe_path / "gains" / ZBX_GAIN_STAGE_AMP / "value" / "desired")
+ , _power_mgr(power_mgr)
+ , _dsa_cal(dsa_cal)
+ , _chan(chan)
+ {
+ bind_accessor(_gain_in);
+ bind_accessor(_profile);
+ bind_accessor(_frequency);
+ bind_accessor(_gain_out);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_amp_gain);
+ }
+
+private:
+ void resolve() override;
+ void _set_tx_dsa(const std::string, const uint8_t desired_gain);
+ double _set_tx_amp_by_gain(const double gain);
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _gain_in;
+ // Inputs for DSA calibration
+ uhd::experts::data_reader_t<std::string> _profile;
+ uhd::experts::data_reader_t<double> _frequency;
+
+ // Output to user/API
+ uhd::experts::data_writer_t<double> _gain_out;
+ // Outputs to CPLD programming expert
+ uhd::experts::data_writer_t<double> _dsa1;
+ uhd::experts::data_writer_t<double> _dsa2;
+ uhd::experts::data_writer_t<double> _amp_gain;
+
+ uhd::usrp::pwr_cal_mgr::sptr _power_mgr;
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr _dsa_cal;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_rx_gain_expert (RX Gain Expert)
+ *
+ * This expert is responsible for controlling the gain of each RX channel
+ *
+ * This should trigger:
+ * - Adjacent CPLD RX Programming Expert
+ *
+ * One instance of this expert is required for each RX Channel (0,1); two total
+ * --------------------------------------------------------
+ */
+class zbx_rx_gain_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_rx_gain_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const size_t chan,
+ uhd::usrp::pwr_cal_mgr::sptr power_mgr,
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr dsa_cal)
+ : uhd::experts::worker_node_t(fe_path / "zbx_gain_expert")
+ , _gain_in(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "desired")
+ , _profile(db, fe_path / "gains" / "all" / "profile")
+ , _frequency(db, fe_path / "freq" / "coerced")
+ , _gain_out(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "coerced")
+ , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "desired")
+ , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "desired")
+ , _dsa3a(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3A / "value" / "desired")
+ , _dsa3b(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3B / "value" / "desired")
+ , _power_mgr(power_mgr)
+ , _dsa_cal(dsa_cal)
+ , _chan(chan)
+ {
+ bind_accessor(_gain_in);
+ bind_accessor(_profile);
+ bind_accessor(_frequency);
+ bind_accessor(_gain_out);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_dsa3a);
+ bind_accessor(_dsa3b);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _gain_in;
+ uhd::experts::data_reader_t<std::string> _profile;
+ // Inputs for dsa calibration
+ uhd::experts::data_reader_t<double> _frequency;
+
+ // Output to user/API
+ uhd::experts::data_writer_t<double> _gain_out;
+ // Outputs to CPLD programming expert
+ uhd::experts::data_writer_t<double> _dsa1;
+ uhd::experts::data_writer_t<double> _dsa2;
+ uhd::experts::data_writer_t<double> _dsa3a;
+ uhd::experts::data_writer_t<double> _dsa3b;
+
+ uhd::usrp::pwr_cal_mgr::sptr _power_mgr;
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr _dsa_cal;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_tx_programming_expert (TX CPLD Programming Expert)
+ *
+ * This expert is responsible for programming the ZBX CPLD with parameters determined by
+ * user input or other experts This includes antenna setting, gain/dsa steps, lo source
+ * control, rf filter settings
+ *
+ * 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); two total
+ * --------------------------------------------------------
+ */
+class zbx_tx_programming_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_tx_programming_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path tx_fe_path,
+ const uhd::fs_path rx_fe_path, /*needed for shared command time*/
+ const size_t chan,
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr dsa_cal,
+ std::shared_ptr<zbx_cpld_ctrl> cpld)
+ : experts::worker_node_t(tx_fe_path / "zbx_tx_programming_expert")
+ , _antenna(db, tx_fe_path / "antenna" / "value")
+ , _atr_mode(db, tx_fe_path / "atr_mode")
+ , _profile(db, tx_fe_path / "gains" / "all" / "profile")
+ , _command_time(db, rx_fe_path / "time" / "cmd")
+ , _frequency(db, tx_fe_path / "freq" / "coerced")
+ , _dsa1(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "coerced")
+ , _dsa2(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "coerced")
+ , _amp_gain(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_AMP / "value" / "coerced")
+ , _rf_filter(db, tx_fe_path / "rf" / "filter")
+ , _if1_filter(db, tx_fe_path / "if1" / "filter")
+ , _if2_filter(db, tx_fe_path / "if2" / "filter")
+ , _is_highband(db, tx_fe_path / "is_highband")
+ , _lo1_source(db, tx_fe_path / "ch" / ZBX_LO1 / "source")
+ , _lo2_source(db, tx_fe_path / "ch" / ZBX_LO2 / "source")
+ , _dsa_cal(dsa_cal)
+ , _cpld(cpld)
+ , _chan(chan)
+ {
+ bind_accessor(_antenna);
+ bind_accessor(_atr_mode);
+ bind_accessor(_profile);
+ bind_accessor(_command_time);
+ bind_accessor(_frequency);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_amp_gain);
+ bind_accessor(_rf_filter);
+ bind_accessor(_if1_filter);
+ bind_accessor(_if2_filter);
+ bind_accessor(_is_highband);
+ bind_accessor(_lo1_source);
+ bind_accessor(_lo2_source);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<std::string> _antenna;
+ uhd::experts::data_reader_t<zbx_cpld_ctrl::atr_mode> _atr_mode;
+ uhd::experts::data_reader_t<std::string> _profile;
+
+ // 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;
+
+ // Inputs from Gain TX expert
+ uhd::experts::data_reader_t<double> _dsa1;
+ uhd::experts::data_reader_t<double> _dsa2;
+ uhd::experts::data_reader_t<double> _amp_gain;
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<int> _rf_filter;
+ uhd::experts::data_reader_t<int> _if1_filter;
+ uhd::experts::data_reader_t<int> _if2_filter;
+ uhd::experts::data_reader_t<bool> _is_highband;
+ // Inputs from LO expert(s)
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo1_source;
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo2_source;
+
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr _dsa_cal;
+ // Expects constructed cpld control objects
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_rx_programming_expert (RX CPLD Programming Expert)
+ *
+ * This expert is responsible for programming the ZBX CPLD with parameters determined by
+ * user input or other experts.
+ * This includes antenna setting, gain/dsa steps, lo source control, rf filter settings
+ *
+ * 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); two total
+ * --------------------------------------------------------
+ */
+class zbx_rx_programming_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_rx_programming_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const size_t chan,
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr dsa_cal,
+ std::shared_ptr<zbx_cpld_ctrl> cpld)
+ : experts::worker_node_t(fe_path / "zbx_rx_programming_expert")
+ , _antenna(db, fe_path / "antenna" / "value")
+ , _atr_mode(db, fe_path / "atr_mode")
+ , _profile(db, fe_path / "gains" / "all" / "profile")
+ , _command_time(db, fe_path / "time" / "cmd")
+ , _frequency(db, fe_path / "freq" / "coerced")
+ , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "coerced")
+ , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "coerced")
+ , _dsa3a(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3A / "value" / "coerced")
+ , _dsa3b(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3B / "value" / "coerced")
+ , _rf_filter(db, fe_path / "rf" / "filter")
+ , _if1_filter(db, fe_path / "if1" / "filter")
+ , _if2_filter(db, fe_path / "if2" / "filter")
+ , _is_highband(db, fe_path / "is_highband")
+ , _lo1_source(db, fe_path / "ch" / ZBX_LO1 / "source")
+ , _lo2_source(db, fe_path / "ch" / ZBX_LO2 / "source")
+ , _dsa_cal(dsa_cal)
+ , _cpld(cpld)
+ , _chan(chan)
+ {
+ bind_accessor(_antenna);
+ bind_accessor(_atr_mode);
+ bind_accessor(_profile);
+ bind_accessor(_command_time);
+ bind_accessor(_frequency);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_dsa3a);
+ bind_accessor(_dsa3b);
+ bind_accessor(_rf_filter);
+ bind_accessor(_if1_filter);
+ bind_accessor(_if2_filter);
+ bind_accessor(_is_highband);
+ bind_accessor(_lo1_source);
+ bind_accessor(_lo2_source);
+ }
+
+private:
+ void resolve() override;
+ void _update_leds();
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<std::string> _antenna;
+ uhd::experts::data_reader_t<zbx_cpld_ctrl::atr_mode> _atr_mode;
+ uhd::experts::data_reader_t<std::string> _profile;
+
+ // 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;
+
+ // Inputs from Gain expert
+ uhd::experts::data_reader_t<double> _dsa1;
+ uhd::experts::data_reader_t<double> _dsa2;
+ uhd::experts::data_reader_t<double> _dsa3a;
+ uhd::experts::data_reader_t<double> _dsa3b;
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<int> _rf_filter;
+ uhd::experts::data_reader_t<int> _if1_filter;
+ uhd::experts::data_reader_t<int> _if2_filter;
+ uhd::experts::data_reader_t<bool> _is_highband;
+ // Inputs from LO expert(s)
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo1_source;
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo2_source;
+
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr _dsa_cal;
+ // Expects constructed cpld control objects
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_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,1);
+ * four total
+ * --------------------------------------------------------
+ */
+class zbx_band_inversion_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_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 int db_idx,
+ uhd::usrp::zbx_rpc_iface::sptr rpcc)
+ : uhd::experts::worker_node_t(fe_path / "zbx_band_inversion_expert")
+ , _is_band_inverted(db, fe_path / "band_inverted")
+ , _db_idx(db_idx)
+ , _rpcc(rpcc)
+ , _trx(trx)
+ , _chan(chan)
+ {
+ bind_accessor(_is_band_inverted);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<bool> _is_band_inverted;
+
+ const size_t _db_idx;
+ uhd::usrp::zbx_rpc_iface::sptr _rpcc;
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_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,1);
+ * four total
+ * --------------------------------------------------------
+ */
+class zbx_rfdc_freq_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_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 / "zbx_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")
+ , _if2_frequency_desired(db, fe_path / "if_freq" / "desired")
+ , _if2_frequency_coerced(db, fe_path / "if_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(_if2_frequency_desired);
+ bind_accessor(_if2_frequency_coerced);
+ }
+
+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> _if2_frequency_desired;
+
+ // Outputs to Frequency BE expert
+ uhd::experts::data_writer_t<double> _if2_frequency_coerced;
+
+
+ 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 uhd::rfnoc::x400::rfdc_control;
+/*!---------------------------------------------------------
+ * zbx_sync_expert
+ *
+ * This expert is responsible for handling the phase alignment.
+ * Per channel, there are up to 4 things whose phase need syncing: The two
+ * LOs, the NCO, and the ADC/DAC gearboxes. However, the LOs share a sync
+ * register, and so do the NCOs. To minimize writes, we thus need a single sync
+ * expert at the end of the graph, who combines all LOs and all NCOs.
+ * --------------------------------------------------------
+ */
+class zbx_sync_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_sync_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path tx_fe_path,
+ const uhd::fs_path rx_fe_path,
+ rfdc_control::sptr rfdcc,
+ std::shared_ptr<zbx_cpld_ctrl> cpld)
+ : uhd::experts::worker_node_t("zbx_sync_expert")
+ , _fe_time{{db, rx_fe_path / 0 / "time/fe"}, {db, rx_fe_path / 1 / "time/fe"}}
+ , _lo_freqs{{zbx_lo_t::RX0_LO1,
+ {db, rx_fe_path / 0 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::RX0_LO2,
+ {db, rx_fe_path / 0 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX0_LO1,
+ {db, tx_fe_path / 0 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX0_LO2,
+ {db, tx_fe_path / 0 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::RX1_LO1,
+ {db, rx_fe_path / 1 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::RX1_LO2,
+ {db, rx_fe_path / 1 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX1_LO1,
+ {db, tx_fe_path / 1 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX1_LO2,
+ {db, tx_fe_path / 1 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}}}
+ , _nco_freqs{{rfdc_control::rfdc_type::RX0,
+ {db, rx_fe_path / 0 / "if_freq" / "coerced"}},
+ {rfdc_control::rfdc_type::RX1,
+ {db, rx_fe_path / 1 / "if_freq" / "coerced"}},
+ {rfdc_control::rfdc_type::TX0,
+ {db, tx_fe_path / 0 / "if_freq" / "coerced"}},
+ {rfdc_control::rfdc_type::TX1,
+ {db, tx_fe_path / 1 / "if_freq" / "coerced"}}}
+ , _rfdcc(rfdcc)
+ , _cpld(cpld)
+ {
+ for (auto& fe_time : _fe_time) {
+ bind_accessor(fe_time);
+ }
+ for (auto& lo_freq : _lo_freqs) {
+ bind_accessor(lo_freq.second);
+ }
+ for (auto& nco_freq : _nco_freqs) {
+ bind_accessor(nco_freq.second);
+ }
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ // Command time: We have 2 channels, one time spec per channel
+ std::vector<uhd::experts::data_reader_t<time_spec_t>> _fe_time;
+ // We have 8 LOs:
+ std::map<zbx_lo_t, uhd::experts::data_reader_t<double>> _lo_freqs;
+ // We have 4 NCOs
+ std::map<rfdc_control::rfdc_type, uhd::experts::data_reader_t<double>> _nco_freqs;
+
+ // This expert has no outputs.
+
+ // Attributes
+ rfdc_control::sptr _rfdcc;
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+ //! 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::zbx
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp
new file mode 100644
index 000000000..add7013ef
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp
@@ -0,0 +1,86 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include <uhd/types/direction.hpp>
+#include <uhdlib/usrp/common/lmx2572.hpp>
+#include <functional>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+class zbx_lo_ctrl final
+{
+public:
+ // Pass in our lo selection and poke/peek functions
+ zbx_lo_ctrl(zbx_lo_t lo,
+ lmx2572_iface::write_fn_t&& poke16,
+ lmx2572_iface::read_fn_t&& peek16,
+ lmx2572_iface::sleep_fn_t&& sleep,
+ const double default_frequency,
+ const double db_prc_rate,
+ const bool testing_mode_enabled);
+
+ // Passes in a desired LO frequency to the LMX driver, returns the coerced frequency
+ double set_lo_freq(const double freq);
+
+ // Returns cached LO frequency value
+ double get_lo_freq();
+
+ // Spins up a timeout loop to wait for the PLL's to lock
+ // \throws uhd::runtime_error on a failure to lock
+ void wait_for_lo_lock();
+
+ // Returns the lock status of the PLL
+ bool get_lock_status();
+
+ // Enable/disable LO port
+ // Targeted LO port depends on whether test mode is disabled/enabled
+ void set_lo_port_enabled(bool enable);
+
+ // Returns status of LO port
+ // Targeted LO port depends on whether test mode is disabled/enabled
+ bool get_lo_port_enabled();
+
+ // Enable test mode of the LO
+ void set_lo_test_mode_enabled(bool enable);
+
+ // Returns whether the test mode has been enabled
+ bool get_lo_test_mode_enabled();
+
+ static zbx_lo_t lo_string_to_enum(
+ const uhd::direction_t trx, const size_t channel, const std::string name);
+
+ // TODO: Future implementation of spur dodging
+ // void set_spur_dodging(const bool enable);
+ // bool get_spur_dodging();
+private:
+ // Returns the appropriate output port for given LO
+ lmx2572_iface::output_t _get_output_port(bool test_port);
+
+ // Specific LO that this class was constructed for
+ const zbx_lo_t _lo;
+
+ const std::string _log_id;
+
+ // LMX driver set up with this object specific LO
+ lmx2572_iface::sptr _lmx;
+
+ // Cached overall LO output frequency.
+ // TODO: seperate between coerced/desired frequencies for recalculation once LO step
+ // quantization is introduced
+ double _freq;
+
+ // Daughterboard PRC rate, used as the reference frequency
+ double _db_prc_rate;
+
+ // Set LO output mode, RF output mode is considered normal use case
+ // Testing mode is for LMX V&V
+ bool _testing_mode_enabled;
+};
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/rc/CMakeLists.txt b/host/lib/rc/CMakeLists.txt
index 52b6693c0..33dc8540b 100644
--- a/host/lib/rc/CMakeLists.txt
+++ b/host/lib/rc/CMakeLists.txt
@@ -8,6 +8,18 @@ include(CMakeRC)
cmrc_add_resource_library(uhd-resources
ALIAS uhd_rc
NAMESPACE rc
+# calibration data test file
cal/test.cal
+# ZBX revB/C TX calibration data
+ cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal
+ cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal
+# ZBX revB/C RX calibration data
+ cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal
+ cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal
+ cal/x4xx_pwr_zbx_rx_0_rx1.cal
+ cal/x4xx_pwr_zbx_rx_1_rx1.cal
+# ZBX DSA data
+ cal/zbx_dsa_tx.cal
+ cal/zbx_dsa_rx.cal
)
set_property(TARGET uhd-resources PROPERTY POSITION_INDEPENDENT_CODE ON)
diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal
new file mode 100644
index 000000000..cc25f6ded
--- /dev/null
+++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal
Binary files differ
diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal
new file mode 100644
index 000000000..6da01d48b
--- /dev/null
+++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal
Binary files differ
diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal
new file mode 100644
index 000000000..d56c740c2
--- /dev/null
+++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal
Binary files differ
diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal
new file mode 100644
index 000000000..99f4eec4d
--- /dev/null
+++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal
Binary files differ
diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal
new file mode 100644
index 000000000..349557bae
--- /dev/null
+++ b/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal
Binary files differ
diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal
new file mode 100644
index 000000000..4ecf1ba00
--- /dev/null
+++ b/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal
Binary files differ
diff --git a/host/lib/rc/cal/zbx_dsa_rx.cal b/host/lib/rc/cal/zbx_dsa_rx.cal
new file mode 100644
index 000000000..197a85b70
--- /dev/null
+++ b/host/lib/rc/cal/zbx_dsa_rx.cal
Binary files differ
diff --git a/host/lib/rc/cal/zbx_dsa_rx.json b/host/lib/rc/cal/zbx_dsa_rx.json
new file mode 100644
index 000000000..798a11987
--- /dev/null
+++ b/host/lib/rc/cal/zbx_dsa_rx.json
@@ -0,0 +1,1081 @@
+{
+ "metadata": {
+ "name": "ZBX DSA Mapping (RX)",
+ "serial": "",
+ "version_major": 2
+ },
+ "band_dsa_map": [
+ {
+ "name": "1M_to_200M",
+ "max_freq": 200000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 14, 11] },
+ { "steps": [15, 15, 13, 11] },
+ { "steps": [15, 14, 13, 11] },
+ { "steps": [15, 13, 13, 11] },
+ { "steps": [15, 12, 13, 11] },
+ { "steps": [15, 11, 13, 11] },
+ { "steps": [14, 11, 13, 11] },
+ { "steps": [13, 11, 13, 11] },
+ { "steps": [12, 11, 13, 11] },
+ { "steps": [11, 11, 13, 11] },
+ { "steps": [10, 11, 13, 11] },
+ { "steps": [ 9, 11, 13, 11] },
+ { "steps": [ 8, 11, 13, 11] },
+ { "steps": [ 7, 11, 13, 11] },
+ { "steps": [ 6, 11, 13, 11] },
+ { "steps": [ 5, 11, 13, 11] },
+ { "steps": [ 4, 11, 13, 11] },
+ { "steps": [ 3, 11, 13, 11] },
+ { "steps": [ 2, 11, 13, 11] },
+ { "steps": [ 1, 11, 13, 11] },
+ { "steps": [ 0, 11, 13, 11] },
+ { "steps": [ 0, 10, 13, 11] },
+ { "steps": [ 0, 9, 13, 11] },
+ { "steps": [ 0, 8, 13, 11] },
+ { "steps": [ 0, 7, 13, 11] },
+ { "steps": [ 0, 6, 13, 11] },
+ { "steps": [ 0, 5, 13, 11] },
+ { "steps": [ 0, 4, 13, 11] },
+ { "steps": [ 0, 3, 13, 11] },
+ { "steps": [ 0, 2, 13, 11] },
+ { "steps": [ 0, 1, 13, 11] },
+ { "steps": [ 0, 0, 13, 11] },
+ { "steps": [ 0, 0, 12, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] }
+ ],
+ },
+ {
+ "name": "200M_to_400M",
+ "max_freq": 400000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 14, 11] },
+ { "steps": [15, 15, 13, 11] },
+ { "steps": [15, 14, 13, 11] },
+ { "steps": [15, 13, 13, 11] },
+ { "steps": [15, 12, 13, 11] },
+ { "steps": [15, 11, 13, 11] },
+ { "steps": [14, 11, 13, 11] },
+ { "steps": [13, 11, 13, 11] },
+ { "steps": [12, 11, 13, 11] },
+ { "steps": [11, 11, 13, 11] },
+ { "steps": [10, 11, 13, 11] },
+ { "steps": [ 9, 11, 13, 11] },
+ { "steps": [ 8, 11, 13, 11] },
+ { "steps": [ 7, 11, 13, 11] },
+ { "steps": [ 6, 11, 13, 11] },
+ { "steps": [ 5, 11, 13, 11] },
+ { "steps": [ 4, 11, 13, 11] },
+ { "steps": [ 3, 11, 13, 11] },
+ { "steps": [ 2, 11, 13, 11] },
+ { "steps": [ 1, 11, 13, 11] },
+ { "steps": [ 0, 11, 13, 11] },
+ { "steps": [ 0, 10, 13, 11] },
+ { "steps": [ 0, 9, 13, 11] },
+ { "steps": [ 0, 8, 13, 11] },
+ { "steps": [ 0, 7, 13, 11] },
+ { "steps": [ 0, 6, 13, 11] },
+ { "steps": [ 0, 5, 13, 11] },
+ { "steps": [ 0, 4, 13, 11] },
+ { "steps": [ 0, 3, 13, 11] },
+ { "steps": [ 0, 2, 13, 11] },
+ { "steps": [ 0, 1, 13, 11] },
+ { "steps": [ 0, 0, 13, 11] },
+ { "steps": [ 0, 0, 12, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] }
+ ],
+ },
+ {
+ "name": "400M_to_500M",
+ "max_freq": 500000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 14, 11] },
+ { "steps": [15, 15, 13, 11] },
+ { "steps": [15, 14, 13, 11] },
+ { "steps": [15, 13, 13, 11] },
+ { "steps": [15, 12, 13, 11] },
+ { "steps": [15, 11, 13, 11] },
+ { "steps": [15, 10, 13, 11] },
+ { "steps": [14, 10, 13, 11] },
+ { "steps": [13, 10, 13, 11] },
+ { "steps": [12, 10, 13, 11] },
+ { "steps": [11, 10, 13, 11] },
+ { "steps": [10, 10, 13, 11] },
+ { "steps": [ 9, 10, 13, 11] },
+ { "steps": [ 8, 10, 13, 11] },
+ { "steps": [ 7, 10, 13, 11] },
+ { "steps": [ 6, 10, 13, 11] },
+ { "steps": [ 5, 10, 13, 11] },
+ { "steps": [ 4, 10, 13, 11] },
+ { "steps": [ 3, 10, 13, 11] },
+ { "steps": [ 2, 10, 13, 11] },
+ { "steps": [ 1, 10, 13, 11] },
+ { "steps": [ 0, 10, 13, 11] },
+ { "steps": [ 0, 9, 13, 11] },
+ { "steps": [ 0, 8, 13, 11] },
+ { "steps": [ 0, 7, 13, 11] },
+ { "steps": [ 0, 6, 13, 11] },
+ { "steps": [ 0, 5, 13, 11] },
+ { "steps": [ 0, 4, 13, 11] },
+ { "steps": [ 0, 3, 13, 11] },
+ { "steps": [ 0, 2, 13, 11] },
+ { "steps": [ 0, 1, 13, 11] },
+ { "steps": [ 0, 0, 13, 11] },
+ { "steps": [ 0, 0, 12, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 11, 11] }
+ ],
+ },
+ {
+ "name": "500M_to_900M",
+ "max_freq": 900000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 15, 10] },
+ { "steps": [15, 15, 15, 9] },
+ { "steps": [15, 14, 15, 9] },
+ { "steps": [15, 13, 15, 9] },
+ { "steps": [15, 12, 15, 9] },
+ { "steps": [15, 11, 15, 9] },
+ { "steps": [15, 10, 15, 9] },
+ { "steps": [14, 10, 15, 9] },
+ { "steps": [13, 10, 15, 9] },
+ { "steps": [12, 10, 15, 9] },
+ { "steps": [11, 10, 15, 9] },
+ { "steps": [10, 10, 15, 9] },
+ { "steps": [ 9, 10, 15, 9] },
+ { "steps": [ 8, 10, 15, 9] },
+ { "steps": [ 7, 10, 15, 9] },
+ { "steps": [ 6, 10, 15, 9] },
+ { "steps": [ 5, 10, 15, 9] },
+ { "steps": [ 4, 10, 15, 9] },
+ { "steps": [ 3, 10, 15, 9] },
+ { "steps": [ 2, 10, 15, 9] },
+ { "steps": [ 1, 10, 15, 9] },
+ { "steps": [ 0, 10, 15, 9] },
+ { "steps": [ 0, 9, 14, 10] },
+ { "steps": [ 0, 8, 14, 10] },
+ { "steps": [ 0, 7, 14, 10] },
+ { "steps": [ 0, 6, 14, 10] },
+ { "steps": [ 0, 5, 14, 10] },
+ { "steps": [ 0, 4, 14, 10] },
+ { "steps": [ 0, 3, 14, 10] },
+ { "steps": [ 0, 2, 14, 10] },
+ { "steps": [ 0, 1, 14, 10] },
+ { "steps": [ 0, 0, 14, 10] },
+ { "steps": [ 0, 0, 13, 10] },
+ { "steps": [ 0, 0, 12, 10] },
+ { "steps": [ 0, 0, 11, 10] },
+ { "steps": [ 0, 0, 10, 10] },
+ { "steps": [ 0, 0, 9, 10] },
+ { "steps": [ 0, 0, 8, 10] },
+ { "steps": [ 0, 0, 7, 10] },
+ { "steps": [ 0, 0, 6, 10] },
+ { "steps": [ 0, 0, 5, 10] },
+ { "steps": [ 0, 0, 4, 10] },
+ { "steps": [ 0, 0, 3, 10] },
+ { "steps": [ 0, 0, 2, 10] },
+ { "steps": [ 0, 0, 1, 10] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "900M_to_1800M",
+ "max_freq": 1800000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 15, 10] },
+ { "steps": [15, 14, 15, 10] },
+ { "steps": [15, 13, 15, 10] },
+ { "steps": [15, 12, 15, 10] },
+ { "steps": [15, 11, 15, 10] },
+ { "steps": [15, 10, 14, 11] },
+ { "steps": [15, 9, 14, 11] },
+ { "steps": [15, 8, 14, 11] },
+ { "steps": [14, 8, 14, 11] },
+ { "steps": [13, 8, 14, 11] },
+ { "steps": [12, 8, 14, 11] },
+ { "steps": [11, 8, 14, 11] },
+ { "steps": [10, 8, 14, 11] },
+ { "steps": [ 9, 8, 14, 11] },
+ { "steps": [ 8, 8, 14, 11] },
+ { "steps": [ 7, 8, 14, 11] },
+ { "steps": [ 6, 8, 14, 11] },
+ { "steps": [ 5, 8, 14, 11] },
+ { "steps": [ 4, 8, 14, 11] },
+ { "steps": [ 3, 8, 14, 11] },
+ { "steps": [ 2, 8, 14, 11] },
+ { "steps": [ 1, 8, 14, 11] },
+ { "steps": [ 0, 8, 14, 11] },
+ { "steps": [ 0, 7, 14, 11] },
+ { "steps": [ 0, 6, 14, 11] },
+ { "steps": [ 0, 5, 14, 11] },
+ { "steps": [ 0, 4, 14, 11] },
+ { "steps": [ 0, 3, 14, 11] },
+ { "steps": [ 0, 2, 14, 11] },
+ { "steps": [ 0, 1, 14, 11] },
+ { "steps": [ 0, 0, 14, 11] },
+ { "steps": [ 0, 0, 13, 11] },
+ { "steps": [ 0, 0, 12, 11] },
+ { "steps": [ 0, 0, 11, 11] },
+ { "steps": [ 0, 0, 10, 11] },
+ { "steps": [ 0, 0, 9, 11] },
+ { "steps": [ 0, 0, 8, 11] },
+ { "steps": [ 0, 0, 7, 11] },
+ { "steps": [ 0, 0, 6, 11] },
+ { "steps": [ 0, 0, 5, 11] },
+ { "steps": [ 0, 0, 4, 11] },
+ { "steps": [ 0, 0, 3, 11] },
+ { "steps": [ 0, 0, 2, 11] },
+ { "steps": [ 0, 0, 1, 11] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "1800M_to_2300M",
+ "max_freq": 2300000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 14, 12] },
+ { "steps": [15, 14, 14, 12] },
+ { "steps": [15, 12, 15, 12] },
+ { "steps": [15, 11, 15, 12] },
+ { "steps": [15, 10, 15, 12] },
+ { "steps": [15, 9, 14, 13] },
+ { "steps": [15, 8, 14, 13] },
+ { "steps": [15, 7, 14, 13] },
+ { "steps": [14, 7, 14, 13] },
+ { "steps": [13, 7, 14, 13] },
+ { "steps": [12, 7, 14, 13] },
+ { "steps": [11, 7, 14, 13] },
+ { "steps": [10, 7, 14, 13] },
+ { "steps": [ 9, 7, 14, 13] },
+ { "steps": [ 8, 7, 14, 13] },
+ { "steps": [ 7, 7, 14, 13] },
+ { "steps": [ 6, 7, 14, 13] },
+ { "steps": [ 5, 7, 14, 13] },
+ { "steps": [ 4, 7, 14, 13] },
+ { "steps": [ 3, 7, 14, 13] },
+ { "steps": [ 2, 7, 14, 13] },
+ { "steps": [ 1, 7, 14, 13] },
+ { "steps": [ 0, 7, 14, 13] },
+ { "steps": [ 0, 6, 14, 13] },
+ { "steps": [ 0, 5, 14, 13] },
+ { "steps": [ 0, 4, 14, 13] },
+ { "steps": [ 0, 3, 14, 13] },
+ { "steps": [ 0, 2, 14, 13] },
+ { "steps": [ 0, 1, 14, 13] },
+ { "steps": [ 0, 0, 14, 13] },
+ { "steps": [ 0, 0, 13, 13] },
+ { "steps": [ 0, 0, 12, 13] },
+ { "steps": [ 0, 0, 11, 13] },
+ { "steps": [ 0, 0, 10, 13] },
+ { "steps": [ 0, 0, 9, 13] },
+ { "steps": [ 0, 0, 8, 13] },
+ { "steps": [ 0, 0, 7, 13] },
+ { "steps": [ 0, 0, 6, 13] },
+ { "steps": [ 0, 0, 5, 13] },
+ { "steps": [ 0, 0, 4, 13] },
+ { "steps": [ 0, 0, 3, 13] },
+ { "steps": [ 0, 0, 2, 13] },
+ { "steps": [ 0, 0, 1, 13] },
+ { "steps": [ 0, 0, 0, 13] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "2300M_to_2700M",
+ "max_freq": 2700000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 14, 12] },
+ { "steps": [15, 14, 14, 12] },
+ { "steps": [15, 13, 14, 12] },
+ { "steps": [15, 11, 15, 12] },
+ { "steps": [15, 10, 15, 12] },
+ { "steps": [15, 9, 15, 12] },
+ { "steps": [15, 8, 15, 12] },
+ { "steps": [15, 7, 14, 13] },
+ { "steps": [15, 6, 14, 13] },
+ { "steps": [14, 6, 14, 13] },
+ { "steps": [13, 6, 14, 13] },
+ { "steps": [12, 6, 14, 13] },
+ { "steps": [11, 6, 14, 13] },
+ { "steps": [10, 6, 14, 13] },
+ { "steps": [ 9, 6, 14, 13] },
+ { "steps": [ 8, 6, 14, 13] },
+ { "steps": [ 7, 6, 14, 13] },
+ { "steps": [ 6, 6, 14, 13] },
+ { "steps": [ 5, 6, 14, 13] },
+ { "steps": [ 4, 6, 14, 13] },
+ { "steps": [ 3, 6, 14, 13] },
+ { "steps": [ 2, 6, 14, 13] },
+ { "steps": [ 1, 6, 14, 13] },
+ { "steps": [ 0, 6, 14, 13] },
+ { "steps": [ 0, 5, 14, 13] },
+ { "steps": [ 0, 4, 14, 13] },
+ { "steps": [ 0, 3, 14, 13] },
+ { "steps": [ 0, 2, 14, 13] },
+ { "steps": [ 0, 1, 14, 13] },
+ { "steps": [ 0, 0, 14, 13] },
+ { "steps": [ 0, 0, 13, 13] },
+ { "steps": [ 0, 0, 12, 13] },
+ { "steps": [ 0, 0, 11, 13] },
+ { "steps": [ 0, 0, 10, 13] },
+ { "steps": [ 0, 0, 9, 13] },
+ { "steps": [ 0, 0, 8, 13] },
+ { "steps": [ 0, 0, 7, 13] },
+ { "steps": [ 0, 0, 6, 13] },
+ { "steps": [ 0, 0, 5, 13] },
+ { "steps": [ 0, 0, 4, 13] },
+ { "steps": [ 0, 0, 3, 13] },
+ { "steps": [ 0, 0, 2, 13] },
+ { "steps": [ 0, 0, 1, 13] },
+ { "steps": [ 0, 0, 0, 13] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "2700M_to_3000M",
+ "max_freq": 3000000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 15, 10] },
+ { "steps": [15, 15, 15, 9] },
+ { "steps": [15, 15, 15, 8] },
+ { "steps": [15, 15, 14, 8] },
+ { "steps": [15, 15, 12, 9] },
+ { "steps": [15, 14, 12, 9] },
+ { "steps": [15, 12, 13, 9] },
+ { "steps": [15, 11, 13, 9] },
+ { "steps": [15, 10, 13, 9] },
+ { "steps": [15, 9, 13, 9] },
+ { "steps": [15, 8, 12, 10] },
+ { "steps": [15, 7, 12, 10] },
+ { "steps": [15, 6, 12, 10] },
+ { "steps": [15, 5, 12, 10] },
+ { "steps": [15, 4, 12, 10] },
+ { "steps": [14, 4, 12, 10] },
+ { "steps": [13, 4, 12, 10] },
+ { "steps": [12, 4, 12, 10] },
+ { "steps": [11, 4, 12, 10] },
+ { "steps": [10, 4, 12, 10] },
+ { "steps": [ 9, 4, 12, 10] },
+ { "steps": [ 8, 4, 12, 10] },
+ { "steps": [ 7, 4, 12, 10] },
+ { "steps": [ 6, 4, 12, 10] },
+ { "steps": [ 5, 4, 12, 10] },
+ { "steps": [ 4, 4, 12, 10] },
+ { "steps": [ 3, 4, 12, 10] },
+ { "steps": [ 2, 4, 12, 10] },
+ { "steps": [ 1, 4, 12, 10] },
+ { "steps": [ 0, 4, 12, 10] },
+ { "steps": [ 0, 3, 12, 10] },
+ { "steps": [ 0, 2, 12, 10] },
+ { "steps": [ 0, 1, 12, 10] },
+ { "steps": [ 0, 0, 12, 10] },
+ { "steps": [ 0, 0, 11, 10] },
+ { "steps": [ 0, 0, 10, 10] },
+ { "steps": [ 0, 0, 9, 10] },
+ { "steps": [ 0, 0, 8, 10] },
+ { "steps": [ 0, 0, 7, 10] },
+ { "steps": [ 0, 0, 6, 10] },
+ { "steps": [ 0, 0, 5, 10] },
+ { "steps": [ 0, 0, 4, 10] },
+ { "steps": [ 0, 0, 3, 10] },
+ { "steps": [ 0, 0, 2, 10] },
+ { "steps": [ 0, 0, 1, 10] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "3000M_to_4200M",
+ "max_freq": 4200000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 15, 10] },
+ { "steps": [15, 15, 15, 9] },
+ { "steps": [15, 15, 15, 8] },
+ { "steps": [15, 15, 14, 8] },
+ { "steps": [15, 15, 12, 9] },
+ { "steps": [15, 15, 11, 9] },
+ { "steps": [15, 15, 10, 9] },
+ { "steps": [15, 15, 9, 9] },
+ { "steps": [15, 15, 8, 9] },
+ { "steps": [15, 15, 7, 9] },
+ { "steps": [15, 15, 6, 9] },
+ { "steps": [15, 15, 5, 9] },
+ { "steps": [15, 15, 4, 9] },
+ { "steps": [14, 15, 4, 9] },
+ { "steps": [13, 15, 4, 9] },
+ { "steps": [12, 15, 4, 9] },
+ { "steps": [11, 15, 4, 9] },
+ { "steps": [10, 15, 4, 9] },
+ { "steps": [ 9, 15, 4, 9] },
+ { "steps": [ 8, 15, 4, 9] },
+ { "steps": [ 7, 15, 4, 9] },
+ { "steps": [ 6, 15, 4, 9] },
+ { "steps": [ 5, 15, 4, 9] },
+ { "steps": [ 4, 15, 4, 9] },
+ { "steps": [ 3, 15, 4, 9] },
+ { "steps": [ 2, 15, 4, 9] },
+ { "steps": [ 1, 15, 4, 9] },
+ { "steps": [ 0, 15, 4, 9] },
+ { "steps": [ 0, 15, 3, 9] },
+ { "steps": [ 0, 15, 2, 9] },
+ { "steps": [ 0, 15, 1, 9] },
+ { "steps": [ 0, 15, 0, 9] },
+ { "steps": [ 0, 14, 0, 9] },
+ { "steps": [ 0, 13, 0, 9] },
+ { "steps": [ 0, 12, 0, 9] },
+ { "steps": [ 0, 11, 0, 9] },
+ { "steps": [ 0, 10, 0, 9] },
+ { "steps": [ 0, 9, 0, 9] },
+ { "steps": [ 0, 7, 0, 10] },
+ { "steps": [ 0, 6, 0, 10] },
+ { "steps": [ 0, 5, 0, 10] },
+ { "steps": [ 0, 4, 0, 10] },
+ { "steps": [ 0, 3, 0, 10] },
+ { "steps": [ 0, 2, 0, 10] },
+ { "steps": [ 0, 1, 0, 10] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "4200M_to_4500M",
+ "max_freq": 4500000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 14, 11] },
+ { "steps": [15, 15, 13, 11] },
+ { "steps": [15, 15, 12, 11] },
+ { "steps": [15, 15, 11, 11] },
+ { "steps": [15, 15, 9, 12] },
+ { "steps": [15, 15, 8, 12] },
+ { "steps": [15, 15, 7, 12] },
+ { "steps": [15, 15, 6, 12] },
+ { "steps": [15, 15, 5, 12] },
+ { "steps": [15, 15, 4, 12] },
+ { "steps": [14, 15, 4, 12] },
+ { "steps": [13, 15, 4, 12] },
+ { "steps": [12, 15, 4, 12] },
+ { "steps": [11, 15, 4, 12] },
+ { "steps": [10, 15, 4, 12] },
+ { "steps": [ 9, 15, 4, 12] },
+ { "steps": [ 8, 15, 4, 12] },
+ { "steps": [ 7, 15, 4, 12] },
+ { "steps": [ 6, 15, 4, 12] },
+ { "steps": [ 5, 15, 4, 12] },
+ { "steps": [ 4, 15, 4, 12] },
+ { "steps": [ 3, 15, 4, 12] },
+ { "steps": [ 2, 15, 4, 12] },
+ { "steps": [ 1, 15, 4, 12] },
+ { "steps": [ 0, 15, 4, 12] },
+ { "steps": [ 0, 15, 3, 12] },
+ { "steps": [ 0, 15, 2, 12] },
+ { "steps": [ 0, 15, 1, 12] },
+ { "steps": [ 0, 15, 0, 12] },
+ { "steps": [ 0, 14, 0, 12] },
+ { "steps": [ 0, 13, 0, 12] },
+ { "steps": [ 0, 12, 0, 12] },
+ { "steps": [ 0, 11, 0, 12] },
+ { "steps": [ 0, 10, 0, 12] },
+ { "steps": [ 0, 9, 0, 12] },
+ { "steps": [ 0, 8, 0, 12] },
+ { "steps": [ 0, 7, 0, 12] },
+ { "steps": [ 0, 6, 0, 12] },
+ { "steps": [ 0, 5, 0, 12] },
+ { "steps": [ 0, 4, 0, 12] },
+ { "steps": [ 0, 3, 0, 12] },
+ { "steps": [ 0, 2, 0, 12] },
+ { "steps": [ 0, 1, 0, 12] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "4500M_to_4700M",
+ "max_freq": 4700000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 14, 14] },
+ { "steps": [15, 15, 12, 15] },
+ { "steps": [15, 15, 11, 15] },
+ { "steps": [15, 15, 10, 15] },
+ { "steps": [15, 15, 9, 15] },
+ { "steps": [15, 15, 8, 15] },
+ { "steps": [15, 15, 7, 15] },
+ { "steps": [15, 15, 6, 15] },
+ { "steps": [15, 15, 5, 15] },
+ { "steps": [15, 15, 4, 15] },
+ { "steps": [14, 15, 4, 15] },
+ { "steps": [13, 15, 4, 15] },
+ { "steps": [12, 15, 4, 15] },
+ { "steps": [11, 15, 4, 15] },
+ { "steps": [10, 15, 4, 15] },
+ { "steps": [ 9, 15, 4, 15] },
+ { "steps": [ 8, 15, 4, 15] },
+ { "steps": [ 7, 15, 4, 15] },
+ { "steps": [ 6, 15, 4, 15] },
+ { "steps": [ 5, 15, 4, 15] },
+ { "steps": [ 4, 15, 4, 15] },
+ { "steps": [ 3, 15, 4, 15] },
+ { "steps": [ 2, 15, 4, 15] },
+ { "steps": [ 1, 15, 4, 15] },
+ { "steps": [ 0, 15, 4, 15] },
+ { "steps": [ 0, 15, 3, 15] },
+ { "steps": [ 0, 15, 2, 15] },
+ { "steps": [ 0, 15, 1, 15] },
+ { "steps": [ 0, 15, 0, 15] },
+ { "steps": [ 0, 14, 0, 15] },
+ { "steps": [ 0, 13, 0, 15] },
+ { "steps": [ 0, 12, 0, 15] },
+ { "steps": [ 0, 11, 0, 15] },
+ { "steps": [ 0, 10, 0, 15] },
+ { "steps": [ 0, 9, 0, 15] },
+ { "steps": [ 0, 8, 0, 15] },
+ { "steps": [ 0, 7, 0, 15] },
+ { "steps": [ 0, 6, 0, 15] },
+ { "steps": [ 0, 5, 0, 15] },
+ { "steps": [ 0, 4, 0, 15] },
+ { "steps": [ 0, 3, 0, 15] },
+ { "steps": [ 0, 2, 0, 15] },
+ { "steps": [ 0, 1, 0, 15] },
+ { "steps": [ 0, 0, 0, 15] },
+ { "steps": [ 0, 0, 0, 14] },
+ { "steps": [ 0, 0, 0, 13] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "4700M_to_5300M",
+ "max_freq": 5300000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 13, 13] },
+ { "steps": [15, 15, 12, 13] },
+ { "steps": [15, 15, 11, 13] },
+ { "steps": [15, 15, 10, 13] },
+ { "steps": [15, 15, 9, 13] },
+ { "steps": [15, 15, 7, 14] },
+ { "steps": [15, 15, 6, 14] },
+ { "steps": [15, 15, 5, 14] },
+ { "steps": [15, 15, 4, 14] },
+ { "steps": [15, 15, 3, 14] },
+ { "steps": [14, 15, 3, 14] },
+ { "steps": [13, 15, 3, 14] },
+ { "steps": [12, 15, 3, 14] },
+ { "steps": [11, 15, 3, 14] },
+ { "steps": [10, 15, 3, 14] },
+ { "steps": [ 9, 15, 3, 14] },
+ { "steps": [ 8, 15, 3, 14] },
+ { "steps": [ 7, 15, 3, 14] },
+ { "steps": [ 6, 15, 3, 14] },
+ { "steps": [ 5, 15, 3, 14] },
+ { "steps": [ 4, 15, 3, 14] },
+ { "steps": [ 3, 15, 3, 14] },
+ { "steps": [ 2, 15, 3, 14] },
+ { "steps": [ 1, 15, 3, 14] },
+ { "steps": [ 0, 15, 3, 14] },
+ { "steps": [ 0, 15, 2, 14] },
+ { "steps": [ 0, 15, 1, 14] },
+ { "steps": [ 0, 15, 0, 14] },
+ { "steps": [ 0, 14, 0, 14] },
+ { "steps": [ 0, 13, 0, 14] },
+ { "steps": [ 0, 12, 0, 14] },
+ { "steps": [ 0, 11, 0, 14] },
+ { "steps": [ 0, 10, 0, 14] },
+ { "steps": [ 0, 9, 0, 14] },
+ { "steps": [ 0, 8, 0, 14] },
+ { "steps": [ 0, 7, 0, 14] },
+ { "steps": [ 0, 6, 0, 14] },
+ { "steps": [ 0, 5, 0, 14] },
+ { "steps": [ 0, 4, 0, 14] },
+ { "steps": [ 0, 3, 0, 14] },
+ { "steps": [ 0, 2, 0, 14] },
+ { "steps": [ 0, 1, 0, 14] },
+ { "steps": [ 0, 0, 0, 14] },
+ { "steps": [ 0, 0, 0, 13] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "5300M_to_5600M",
+ "max_freq": 5600000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 14, 13] },
+ { "steps": [15, 15, 12, 14] },
+ { "steps": [15, 15, 11, 14] },
+ { "steps": [15, 15, 10, 14] },
+ { "steps": [15, 15, 9, 14] },
+ { "steps": [15, 15, 8, 14] },
+ { "steps": [15, 15, 7, 14] },
+ { "steps": [15, 15, 6, 14] },
+ { "steps": [15, 15, 4, 15] },
+ { "steps": [15, 15, 3, 15] },
+ { "steps": [14, 15, 3, 15] },
+ { "steps": [13, 15, 3, 15] },
+ { "steps": [12, 15, 3, 15] },
+ { "steps": [11, 15, 3, 15] },
+ { "steps": [10, 15, 3, 15] },
+ { "steps": [ 9, 15, 3, 15] },
+ { "steps": [ 8, 15, 3, 15] },
+ { "steps": [ 7, 15, 3, 15] },
+ { "steps": [ 6, 15, 3, 15] },
+ { "steps": [ 5, 15, 3, 15] },
+ { "steps": [ 4, 15, 3, 15] },
+ { "steps": [ 3, 15, 3, 15] },
+ { "steps": [ 2, 15, 3, 15] },
+ { "steps": [ 1, 15, 3, 15] },
+ { "steps": [ 0, 15, 3, 15] },
+ { "steps": [ 0, 15, 2, 15] },
+ { "steps": [ 0, 15, 1, 15] },
+ { "steps": [ 0, 15, 0, 15] },
+ { "steps": [ 0, 14, 0, 15] },
+ { "steps": [ 0, 13, 0, 15] },
+ { "steps": [ 0, 12, 0, 15] },
+ { "steps": [ 0, 11, 0, 15] },
+ { "steps": [ 0, 10, 0, 15] },
+ { "steps": [ 0, 9, 0, 15] },
+ { "steps": [ 0, 8, 0, 15] },
+ { "steps": [ 0, 7, 0, 15] },
+ { "steps": [ 0, 6, 0, 15] },
+ { "steps": [ 0, 5, 0, 15] },
+ { "steps": [ 0, 4, 0, 15] },
+ { "steps": [ 0, 3, 0, 15] },
+ { "steps": [ 0, 2, 0, 15] },
+ { "steps": [ 0, 1, 0, 15] },
+ { "steps": [ 0, 0, 0, 15] },
+ { "steps": [ 0, 0, 0, 14] },
+ { "steps": [ 0, 0, 0, 13] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "5600M_to_6800M",
+ "max_freq": 6800000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 13, 13] },
+ { "steps": [15, 15, 12, 13] },
+ { "steps": [15, 15, 11, 13] },
+ { "steps": [15, 15, 10, 13] },
+ { "steps": [15, 15, 9, 13] },
+ { "steps": [15, 15, 7, 14] },
+ { "steps": [15, 15, 6, 14] },
+ { "steps": [15, 15, 5, 14] },
+ { "steps": [15, 15, 4, 14] },
+ { "steps": [15, 15, 3, 14] },
+ { "steps": [14, 15, 3, 14] },
+ { "steps": [13, 15, 3, 14] },
+ { "steps": [12, 15, 3, 14] },
+ { "steps": [11, 15, 3, 14] },
+ { "steps": [10, 15, 3, 14] },
+ { "steps": [ 9, 15, 3, 14] },
+ { "steps": [ 8, 15, 3, 14] },
+ { "steps": [ 7, 15, 3, 14] },
+ { "steps": [ 6, 15, 3, 14] },
+ { "steps": [ 5, 15, 3, 14] },
+ { "steps": [ 4, 15, 3, 14] },
+ { "steps": [ 3, 15, 3, 14] },
+ { "steps": [ 2, 15, 3, 14] },
+ { "steps": [ 1, 15, 3, 14] },
+ { "steps": [ 0, 15, 3, 14] },
+ { "steps": [ 0, 15, 2, 14] },
+ { "steps": [ 0, 15, 1, 14] },
+ { "steps": [ 0, 15, 0, 14] },
+ { "steps": [ 0, 14, 0, 14] },
+ { "steps": [ 0, 13, 0, 14] },
+ { "steps": [ 0, 12, 0, 14] },
+ { "steps": [ 0, 11, 0, 14] },
+ { "steps": [ 0, 10, 0, 14] },
+ { "steps": [ 0, 9, 0, 14] },
+ { "steps": [ 0, 8, 0, 14] },
+ { "steps": [ 0, 7, 0, 14] },
+ { "steps": [ 0, 6, 0, 14] },
+ { "steps": [ 0, 5, 0, 14] },
+ { "steps": [ 0, 4, 0, 14] },
+ { "steps": [ 0, 3, 0, 14] },
+ { "steps": [ 0, 2, 0, 14] },
+ { "steps": [ 0, 1, 0, 14] },
+ { "steps": [ 0, 0, 0, 14] },
+ { "steps": [ 0, 0, 0, 13] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "6800M_to_7400M",
+ "max_freq": 7400000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 15, 10] },
+ { "steps": [15, 15, 14, 10] },
+ { "steps": [15, 15, 13, 10] },
+ { "steps": [15, 15, 12, 10] },
+ { "steps": [15, 15, 11, 10] },
+ { "steps": [15, 15, 9, 11] },
+ { "steps": [15, 15, 8, 11] },
+ { "steps": [15, 15, 7, 11] },
+ { "steps": [15, 15, 6, 11] },
+ { "steps": [15, 15, 5, 11] },
+ { "steps": [15, 15, 3, 12] },
+ { "steps": [15, 15, 2, 12] },
+ { "steps": [15, 15, 1, 12] },
+ { "steps": [14, 15, 1, 12] },
+ { "steps": [13, 15, 1, 12] },
+ { "steps": [12, 15, 1, 12] },
+ { "steps": [11, 15, 1, 12] },
+ { "steps": [10, 15, 1, 12] },
+ { "steps": [ 9, 15, 1, 12] },
+ { "steps": [ 8, 15, 1, 12] },
+ { "steps": [ 7, 15, 1, 12] },
+ { "steps": [ 6, 15, 1, 12] },
+ { "steps": [ 5, 15, 1, 12] },
+ { "steps": [ 4, 15, 1, 12] },
+ { "steps": [ 3, 15, 1, 12] },
+ { "steps": [ 2, 15, 1, 12] },
+ { "steps": [ 1, 15, 1, 12] },
+ { "steps": [ 0, 15, 1, 12] },
+ { "steps": [ 0, 15, 0, 12] },
+ { "steps": [ 0, 14, 0, 12] },
+ { "steps": [ 0, 13, 0, 12] },
+ { "steps": [ 0, 12, 0, 12] },
+ { "steps": [ 0, 11, 0, 12] },
+ { "steps": [ 0, 10, 0, 12] },
+ { "steps": [ 0, 9, 0, 12] },
+ { "steps": [ 0, 8, 0, 12] },
+ { "steps": [ 0, 7, 0, 12] },
+ { "steps": [ 0, 6, 0, 12] },
+ { "steps": [ 0, 5, 0, 12] },
+ { "steps": [ 0, 4, 0, 12] },
+ { "steps": [ 0, 3, 0, 12] },
+ { "steps": [ 0, 2, 0, 12] },
+ { "steps": [ 0, 1, 0, 12] },
+ { "steps": [ 0, 0, 0, 12] },
+ { "steps": [ 0, 0, 0, 11] },
+ { "steps": [ 0, 0, 0, 10] },
+ { "steps": [ 0, 0, 0, 9] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ],
+ },
+ {
+ "name": "7400M_to_8000M",
+ "max_freq": 8000000000,
+ "gains": [
+ { "steps": [15, 15, 15, 15] },
+ { "steps": [15, 15, 15, 14] },
+ { "steps": [15, 15, 15, 13] },
+ { "steps": [15, 15, 15, 12] },
+ { "steps": [15, 15, 15, 11] },
+ { "steps": [15, 15, 15, 10] },
+ { "steps": [15, 15, 15, 9] },
+ { "steps": [15, 15, 15, 8] },
+ { "steps": [15, 15, 15, 7] },
+ { "steps": [15, 15, 15, 6] },
+ { "steps": [15, 15, 15, 5] },
+ { "steps": [15, 15, 14, 5] },
+ { "steps": [15, 15, 12, 6] },
+ { "steps": [15, 15, 11, 6] },
+ { "steps": [15, 15, 10, 6] },
+ { "steps": [15, 15, 9, 6] },
+ { "steps": [15, 15, 7, 7] },
+ { "steps": [15, 15, 6, 7] },
+ { "steps": [15, 15, 5, 7] },
+ { "steps": [15, 15, 4, 7] },
+ { "steps": [15, 15, 3, 7] },
+ { "steps": [15, 15, 2, 7] },
+ { "steps": [15, 15, 1, 7] },
+ { "steps": [14, 15, 1, 7] },
+ { "steps": [13, 15, 1, 7] },
+ { "steps": [12, 15, 1, 7] },
+ { "steps": [11, 15, 1, 7] },
+ { "steps": [10, 15, 1, 7] },
+ { "steps": [ 9, 15, 1, 7] },
+ { "steps": [ 8, 15, 1, 7] },
+ { "steps": [ 7, 15, 1, 7] },
+ { "steps": [ 6, 15, 1, 7] },
+ { "steps": [ 5, 15, 1, 7] },
+ { "steps": [ 4, 15, 1, 7] },
+ { "steps": [ 3, 15, 1, 7] },
+ { "steps": [ 2, 15, 1, 7] },
+ { "steps": [ 1, 15, 1, 7] },
+ { "steps": [ 0, 15, 1, 7] },
+ { "steps": [ 0, 15, 0, 7] },
+ { "steps": [ 0, 14, 0, 7] },
+ { "steps": [ 0, 13, 0, 7] },
+ { "steps": [ 0, 12, 0, 7] },
+ { "steps": [ 0, 10, 0, 8] },
+ { "steps": [ 0, 9, 0, 8] },
+ { "steps": [ 0, 8, 0, 8] },
+ { "steps": [ 0, 7, 0, 8] },
+ { "steps": [ 0, 6, 0, 8] },
+ { "steps": [ 0, 5, 0, 8] },
+ { "steps": [ 0, 4, 0, 8] },
+ { "steps": [ 0, 3, 0, 8] },
+ { "steps": [ 0, 2, 0, 8] },
+ { "steps": [ 0, 1, 0, 8] },
+ { "steps": [ 0, 0, 0, 8] },
+ { "steps": [ 0, 0, 0, 7] },
+ { "steps": [ 0, 0, 0, 6] },
+ { "steps": [ 0, 0, 0, 5] },
+ { "steps": [ 0, 0, 0, 4] },
+ { "steps": [ 0, 0, 0, 3] },
+ { "steps": [ 0, 0, 0, 2] },
+ { "steps": [ 0, 0, 0, 1] },
+ { "steps": [ 0, 0, 0, 0] }
+ ]
+ }
+ ]
+}
diff --git a/host/lib/rc/cal/zbx_dsa_tx.cal b/host/lib/rc/cal/zbx_dsa_tx.cal
new file mode 100644
index 000000000..16c17ac2c
--- /dev/null
+++ b/host/lib/rc/cal/zbx_dsa_tx.cal
Binary files differ
diff --git a/host/lib/rc/cal/zbx_dsa_tx.json b/host/lib/rc/cal/zbx_dsa_tx.json
new file mode 100644
index 000000000..67199d9f7
--- /dev/null
+++ b/host/lib/rc/cal/zbx_dsa_tx.json
@@ -0,0 +1,1349 @@
+{
+ "metadata": {
+ "name": "ZBX (Revision B) DSA Mapping (TX)",
+ "serial": "",
+ "version_major": 2
+ },
+ "band_dsa_map": [
+ {
+ "name": "1M_to_200M",
+ "max_freq": 200000000,
+ "gains": [
+ { "steps": [29, 31, 1] },
+ { "steps": [28, 31, 1] },
+ { "steps": [27, 31, 1] },
+ { "steps": [26, 31, 1] },
+ { "steps": [25, 31, 1] },
+ { "steps": [24, 31, 1] },
+ { "steps": [23, 31, 1] },
+ { "steps": [22, 31, 1] },
+ { "steps": [21, 31, 1] },
+ { "steps": [20, 31, 1] },
+ { "steps": [19, 31, 1] },
+ { "steps": [18, 31, 1] },
+ { "steps": [17, 31, 1] },
+ { "steps": [16, 31, 1] },
+ { "steps": [15, 31, 1] },
+ { "steps": [14, 31, 1] },
+ { "steps": [13, 31, 1] },
+ { "steps": [12, 31, 1] },
+ { "steps": [11, 31, 1] },
+ { "steps": [10, 31, 1] },
+ { "steps": [ 9, 31, 1] },
+ { "steps": [ 9, 30, 1] },
+ { "steps": [ 9, 29, 1] },
+ { "steps": [ 9, 28, 1] },
+ { "steps": [ 9, 27, 1] },
+ { "steps": [ 9, 26, 1] },
+ { "steps": [ 9, 25, 1] },
+ { "steps": [ 9, 24, 1] },
+ { "steps": [ 9, 23, 1] },
+ { "steps": [ 9, 22, 1] },
+ { "steps": [ 9, 21, 1] },
+ { "steps": [ 9, 20, 1] },
+ { "steps": [ 9, 19, 1] },
+ { "steps": [ 9, 18, 1] },
+ { "steps": [ 9, 17, 1] },
+ { "steps": [ 9, 16, 1] },
+ { "steps": [ 9, 15, 1] },
+ { "steps": [ 9, 14, 1] },
+ { "steps": [ 9, 13, 1] },
+ { "steps": [ 9, 12, 1] },
+ { "steps": [ 9, 11, 1] },
+ { "steps": [ 9, 10, 1] },
+ { "steps": [ 9, 9, 1] },
+ { "steps": [ 9, 8, 1] },
+ { "steps": [ 9, 7, 1] },
+ { "steps": [ 9, 6, 1] },
+ { "steps": [ 9, 5, 1] },
+ { "steps": [ 9, 4, 1] },
+ { "steps": [ 9, 3, 1] },
+ { "steps": [10, 1, 1] },
+ { "steps": [10, 0, 1] },
+ { "steps": [ 9, 0, 1] },
+ { "steps": [ 8, 0, 1] },
+ { "steps": [ 7, 0, 1] },
+ { "steps": [ 6, 0, 1] },
+ { "steps": [ 5, 0, 1] },
+ { "steps": [ 4, 0, 1] },
+ { "steps": [ 3, 0, 1] },
+ { "steps": [ 2, 0, 1] },
+ { "steps": [ 1, 0, 1] },
+ { "steps": [ 0, 0, 1] }
+ ],
+ },
+ {
+ "name": "200M_to_300M",
+ "max_freq": 300000000,
+ "gains": [
+ { "steps": [29, 31, 1] },
+ { "steps": [28, 31, 1] },
+ { "steps": [27, 31, 1] },
+ { "steps": [26, 31, 1] },
+ { "steps": [25, 31, 1] },
+ { "steps": [24, 31, 1] },
+ { "steps": [23, 31, 1] },
+ { "steps": [22, 31, 1] },
+ { "steps": [21, 31, 1] },
+ { "steps": [20, 31, 1] },
+ { "steps": [19, 31, 1] },
+ { "steps": [18, 31, 1] },
+ { "steps": [17, 31, 1] },
+ { "steps": [16, 31, 1] },
+ { "steps": [15, 31, 1] },
+ { "steps": [14, 31, 1] },
+ { "steps": [13, 31, 1] },
+ { "steps": [12, 31, 1] },
+ { "steps": [11, 31, 1] },
+ { "steps": [10, 31, 1] },
+ { "steps": [ 9, 31, 1] },
+ { "steps": [ 9, 30, 1] },
+ { "steps": [ 9, 29, 1] },
+ { "steps": [ 9, 28, 1] },
+ { "steps": [ 9, 27, 1] },
+ { "steps": [ 9, 26, 1] },
+ { "steps": [ 9, 25, 1] },
+ { "steps": [ 9, 24, 1] },
+ { "steps": [ 9, 23, 1] },
+ { "steps": [ 9, 22, 1] },
+ { "steps": [ 9, 21, 1] },
+ { "steps": [ 9, 20, 1] },
+ { "steps": [ 9, 19, 1] },
+ { "steps": [ 9, 18, 1] },
+ { "steps": [ 9, 17, 1] },
+ { "steps": [ 9, 16, 1] },
+ { "steps": [ 9, 15, 1] },
+ { "steps": [ 9, 14, 1] },
+ { "steps": [ 9, 13, 1] },
+ { "steps": [ 9, 12, 1] },
+ { "steps": [ 9, 11, 1] },
+ { "steps": [ 9, 10, 1] },
+ { "steps": [ 9, 9, 1] },
+ { "steps": [ 9, 8, 1] },
+ { "steps": [ 9, 7, 1] },
+ { "steps": [ 9, 6, 1] },
+ { "steps": [ 9, 5, 1] },
+ { "steps": [ 9, 4, 1] },
+ { "steps": [ 9, 3, 1] },
+ { "steps": [10, 1, 1] },
+ { "steps": [10, 0, 1] },
+ { "steps": [ 9, 0, 1] },
+ { "steps": [ 8, 0, 1] },
+ { "steps": [ 7, 0, 1] },
+ { "steps": [ 6, 0, 1] },
+ { "steps": [ 5, 0, 1] },
+ { "steps": [ 4, 0, 1] },
+ { "steps": [ 3, 0, 1] },
+ { "steps": [ 2, 0, 1] },
+ { "steps": [ 1, 0, 1] },
+ { "steps": [ 0, 0, 1] }
+ ],
+ },
+ {
+ "name": "300M_to_400M",
+ "max_freq": 400000000,
+ "gains": [
+ { "steps": [29, 31, 1] },
+ { "steps": [28, 31, 1] },
+ { "steps": [27, 31, 1] },
+ { "steps": [26, 31, 1] },
+ { "steps": [25, 31, 1] },
+ { "steps": [24, 31, 1] },
+ { "steps": [23, 31, 1] },
+ { "steps": [22, 31, 1] },
+ { "steps": [21, 31, 1] },
+ { "steps": [20, 31, 1] },
+ { "steps": [19, 31, 1] },
+ { "steps": [18, 31, 1] },
+ { "steps": [17, 31, 1] },
+ { "steps": [16, 31, 1] },
+ { "steps": [15, 31, 1] },
+ { "steps": [14, 31, 1] },
+ { "steps": [13, 31, 1] },
+ { "steps": [12, 31, 1] },
+ { "steps": [11, 31, 1] },
+ { "steps": [10, 31, 1] },
+ { "steps": [ 9, 31, 1] },
+ { "steps": [ 9, 30, 1] },
+ { "steps": [ 9, 29, 1] },
+ { "steps": [ 9, 28, 1] },
+ { "steps": [ 9, 27, 1] },
+ { "steps": [ 9, 26, 1] },
+ { "steps": [ 9, 25, 1] },
+ { "steps": [ 9, 24, 1] },
+ { "steps": [ 9, 23, 1] },
+ { "steps": [ 9, 22, 1] },
+ { "steps": [ 9, 21, 1] },
+ { "steps": [ 9, 20, 1] },
+ { "steps": [ 9, 19, 1] },
+ { "steps": [ 9, 18, 1] },
+ { "steps": [ 9, 17, 1] },
+ { "steps": [ 9, 16, 1] },
+ { "steps": [ 9, 15, 1] },
+ { "steps": [ 9, 14, 1] },
+ { "steps": [ 9, 13, 1] },
+ { "steps": [ 9, 12, 1] },
+ { "steps": [ 9, 11, 1] },
+ { "steps": [ 9, 10, 1] },
+ { "steps": [ 9, 9, 1] },
+ { "steps": [ 9, 8, 1] },
+ { "steps": [ 9, 7, 1] },
+ { "steps": [ 9, 6, 1] },
+ { "steps": [ 9, 5, 1] },
+ { "steps": [ 9, 4, 1] },
+ { "steps": [10, 2, 1] },
+ { "steps": [10, 1, 1] },
+ { "steps": [10, 0, 1] },
+ { "steps": [ 9, 0, 1] },
+ { "steps": [ 8, 0, 1] },
+ { "steps": [ 7, 0, 1] },
+ { "steps": [ 6, 0, 1] },
+ { "steps": [ 5, 0, 1] },
+ { "steps": [ 4, 0, 1] },
+ { "steps": [ 3, 0, 1] },
+ { "steps": [ 2, 0, 1] },
+ { "steps": [ 1, 0, 1] },
+ { "steps": [ 0, 0, 1] }
+ ],
+ },
+ {
+ "name": "400M_to_600M",
+ "max_freq": 600000000,
+ "gains": [
+ { "steps": [29, 31, 1] },
+ { "steps": [28, 31, 1] },
+ { "steps": [27, 31, 1] },
+ { "steps": [26, 31, 1] },
+ { "steps": [25, 31, 1] },
+ { "steps": [24, 31, 1] },
+ { "steps": [23, 31, 1] },
+ { "steps": [22, 31, 1] },
+ { "steps": [21, 31, 1] },
+ { "steps": [20, 31, 1] },
+ { "steps": [19, 31, 1] },
+ { "steps": [18, 31, 1] },
+ { "steps": [17, 31, 1] },
+ { "steps": [16, 31, 1] },
+ { "steps": [15, 31, 1] },
+ { "steps": [14, 31, 1] },
+ { "steps": [13, 31, 1] },
+ { "steps": [12, 31, 1] },
+ { "steps": [11, 31, 1] },
+ { "steps": [10, 31, 1] },
+ { "steps": [ 9, 31, 1] },
+ { "steps": [ 9, 30, 1] },
+ { "steps": [ 9, 29, 1] },
+ { "steps": [ 9, 28, 1] },
+ { "steps": [ 9, 27, 1] },
+ { "steps": [ 9, 26, 1] },
+ { "steps": [ 9, 25, 1] },
+ { "steps": [ 9, 24, 1] },
+ { "steps": [ 9, 23, 1] },
+ { "steps": [ 9, 22, 1] },
+ { "steps": [ 9, 21, 1] },
+ { "steps": [ 9, 20, 1] },
+ { "steps": [ 9, 19, 1] },
+ { "steps": [ 9, 18, 1] },
+ { "steps": [ 9, 17, 1] },
+ { "steps": [ 9, 16, 1] },
+ { "steps": [ 9, 15, 1] },
+ { "steps": [ 9, 14, 1] },
+ { "steps": [ 9, 13, 1] },
+ { "steps": [ 9, 12, 1] },
+ { "steps": [ 9, 11, 1] },
+ { "steps": [ 9, 10, 1] },
+ { "steps": [ 9, 9, 1] },
+ { "steps": [ 9, 8, 1] },
+ { "steps": [ 9, 7, 1] },
+ { "steps": [ 9, 6, 1] },
+ { "steps": [ 9, 5, 1] },
+ { "steps": [ 9, 4, 1] },
+ { "steps": [ 9, 3, 1] },
+ { "steps": [10, 1, 1] },
+ { "steps": [10, 0, 1] },
+ { "steps": [ 9, 0, 1] },
+ { "steps": [ 8, 0, 1] },
+ { "steps": [ 7, 0, 1] },
+ { "steps": [ 6, 0, 1] },
+ { "steps": [ 5, 0, 1] },
+ { "steps": [ 4, 0, 1] },
+ { "steps": [ 3, 0, 1] },
+ { "steps": [ 2, 0, 1] },
+ { "steps": [ 1, 0, 1] },
+ { "steps": [ 0, 0, 1] }
+ ],
+ },
+ {
+ "name": "600M_to_800M",
+ "max_freq": 800000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 9, 30, 2] },
+ { "steps": [ 9, 29, 2] },
+ { "steps": [ 9, 28, 2] },
+ { "steps": [ 9, 27, 2] },
+ { "steps": [ 9, 26, 2] },
+ { "steps": [ 9, 25, 2] },
+ { "steps": [ 9, 24, 2] },
+ { "steps": [ 9, 23, 2] },
+ { "steps": [ 9, 22, 2] },
+ { "steps": [ 9, 21, 2] },
+ { "steps": [ 9, 20, 2] },
+ { "steps": [ 9, 19, 2] },
+ { "steps": [ 9, 18, 2] },
+ { "steps": [ 9, 17, 2] },
+ { "steps": [ 9, 16, 2] },
+ { "steps": [ 9, 15, 2] },
+ { "steps": [ 9, 14, 2] },
+ { "steps": [ 9, 13, 2] },
+ { "steps": [ 9, 12, 2] },
+ { "steps": [ 9, 11, 2] },
+ { "steps": [10, 9, 2] },
+ { "steps": [10, 8, 2] },
+ { "steps": [10, 7, 2] },
+ { "steps": [10, 6, 2] },
+ { "steps": [10, 5, 2] },
+ { "steps": [10, 4, 2] },
+ { "steps": [10, 3, 2] },
+ { "steps": [10, 2, 2] },
+ { "steps": [11, 0, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "800M_to_1300M",
+ "max_freq": 1300000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 9, 30, 2] },
+ { "steps": [ 9, 29, 2] },
+ { "steps": [ 9, 28, 2] },
+ { "steps": [ 9, 27, 2] },
+ { "steps": [ 9, 26, 2] },
+ { "steps": [ 9, 25, 2] },
+ { "steps": [ 9, 24, 2] },
+ { "steps": [ 9, 23, 2] },
+ { "steps": [ 9, 22, 2] },
+ { "steps": [ 9, 21, 2] },
+ { "steps": [ 9, 20, 2] },
+ { "steps": [ 9, 19, 2] },
+ { "steps": [ 9, 18, 2] },
+ { "steps": [ 9, 17, 2] },
+ { "steps": [ 9, 16, 2] },
+ { "steps": [ 9, 15, 2] },
+ { "steps": [ 9, 14, 2] },
+ { "steps": [ 9, 13, 2] },
+ { "steps": [ 9, 12, 2] },
+ { "steps": [ 9, 11, 2] },
+ { "steps": [ 9, 10, 2] },
+ { "steps": [ 9, 9, 2] },
+ { "steps": [ 9, 8, 2] },
+ { "steps": [10, 6, 2] },
+ { "steps": [10, 5, 2] },
+ { "steps": [10, 4, 2] },
+ { "steps": [10, 3, 2] },
+ { "steps": [10, 2, 2] },
+ { "steps": [10, 1, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "1300M_to_1800M",
+ "max_freq": 1800000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 9, 30, 2] },
+ { "steps": [ 9, 29, 2] },
+ { "steps": [ 9, 28, 2] },
+ { "steps": [ 9, 27, 2] },
+ { "steps": [ 9, 26, 2] },
+ { "steps": [ 9, 25, 2] },
+ { "steps": [ 9, 24, 2] },
+ { "steps": [ 9, 23, 2] },
+ { "steps": [ 9, 22, 2] },
+ { "steps": [ 9, 21, 2] },
+ { "steps": [ 9, 20, 2] },
+ { "steps": [ 9, 19, 2] },
+ { "steps": [ 9, 18, 2] },
+ { "steps": [ 9, 17, 2] },
+ { "steps": [ 9, 16, 2] },
+ { "steps": [ 9, 15, 2] },
+ { "steps": [ 9, 14, 2] },
+ { "steps": [ 9, 13, 2] },
+ { "steps": [ 9, 12, 2] },
+ { "steps": [ 9, 11, 2] },
+ { "steps": [ 9, 10, 2] },
+ { "steps": [ 9, 9, 2] },
+ { "steps": [ 9, 8, 2] },
+ { "steps": [ 9, 7, 2] },
+ { "steps": [10, 5, 2] },
+ { "steps": [10, 4, 2] },
+ { "steps": [10, 3, 2] },
+ { "steps": [10, 2, 2] },
+ { "steps": [10, 1, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "1800M_to_2300M",
+ "max_freq": 2300000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [11, 30, 2] },
+ { "steps": [11, 29, 2] },
+ { "steps": [11, 28, 2] },
+ { "steps": [11, 27, 2] },
+ { "steps": [11, 26, 2] },
+ { "steps": [11, 25, 2] },
+ { "steps": [11, 24, 2] },
+ { "steps": [11, 23, 2] },
+ { "steps": [11, 22, 2] },
+ { "steps": [11, 21, 2] },
+ { "steps": [11, 20, 2] },
+ { "steps": [11, 19, 2] },
+ { "steps": [11, 18, 2] },
+ { "steps": [11, 17, 2] },
+ { "steps": [11, 16, 2] },
+ { "steps": [11, 15, 2] },
+ { "steps": [12, 13, 2] },
+ { "steps": [12, 12, 2] },
+ { "steps": [12, 11, 2] },
+ { "steps": [12, 10, 2] },
+ { "steps": [12, 9, 2] },
+ { "steps": [12, 8, 2] },
+ { "steps": [12, 7, 2] },
+ { "steps": [12, 6, 2] },
+ { "steps": [12, 5, 2] },
+ { "steps": [12, 4, 2] },
+ { "steps": [12, 3, 2] },
+ { "steps": [12, 2, 2] },
+ { "steps": [12, 1, 2] },
+ { "steps": [12, 0, 2] },
+ { "steps": [11, 0, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "2300M_to_2700M",
+ "max_freq": 2700000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [11, 30, 2] },
+ { "steps": [11, 29, 2] },
+ { "steps": [11, 28, 2] },
+ { "steps": [11, 27, 2] },
+ { "steps": [11, 26, 2] },
+ { "steps": [11, 25, 2] },
+ { "steps": [11, 24, 2] },
+ { "steps": [11, 23, 2] },
+ { "steps": [11, 22, 2] },
+ { "steps": [11, 21, 2] },
+ { "steps": [11, 20, 2] },
+ { "steps": [11, 19, 2] },
+ { "steps": [11, 18, 2] },
+ { "steps": [11, 17, 2] },
+ { "steps": [11, 16, 2] },
+ { "steps": [11, 15, 2] },
+ { "steps": [11, 14, 2] },
+ { "steps": [11, 13, 2] },
+ { "steps": [11, 12, 2] },
+ { "steps": [11, 11, 2] },
+ { "steps": [11, 10, 2] },
+ { "steps": [11, 9, 2] },
+ { "steps": [11, 8, 2] },
+ { "steps": [11, 7, 2] },
+ { "steps": [11, 6, 2] },
+ { "steps": [11, 5, 2] },
+ { "steps": [11, 4, 2] },
+ { "steps": [11, 3, 2] },
+ { "steps": [12, 1, 2] },
+ { "steps": [12, 0, 2] },
+ { "steps": [11, 0, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "2700M_to_3000M",
+ "max_freq": 3000000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 7, 30, 2] },
+ { "steps": [ 7, 29, 2] },
+ { "steps": [ 7, 28, 2] },
+ { "steps": [ 7, 27, 2] },
+ { "steps": [ 7, 26, 2] },
+ { "steps": [ 7, 25, 2] },
+ { "steps": [ 7, 24, 2] },
+ { "steps": [ 7, 23, 2] },
+ { "steps": [ 7, 22, 2] },
+ { "steps": [ 7, 21, 2] },
+ { "steps": [ 7, 20, 2] },
+ { "steps": [ 7, 19, 2] },
+ { "steps": [ 7, 18, 2] },
+ { "steps": [ 7, 17, 2] },
+ { "steps": [ 7, 16, 2] },
+ { "steps": [ 7, 15, 2] },
+ { "steps": [ 7, 14, 2] },
+ { "steps": [ 7, 13, 2] },
+ { "steps": [ 7, 12, 2] },
+ { "steps": [ 7, 11, 2] },
+ { "steps": [ 7, 10, 2] },
+ { "steps": [ 7, 9, 2] },
+ { "steps": [ 7, 8, 2] },
+ { "steps": [ 7, 7, 2] },
+ { "steps": [ 7, 6, 2] },
+ { "steps": [ 7, 5, 2] },
+ { "steps": [ 8, 3, 2] },
+ { "steps": [ 8, 2, 2] },
+ { "steps": [ 8, 1, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "3000M_to_4100M",
+ "max_freq": 4100000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 6, 30, 2] },
+ { "steps": [ 6, 29, 2] },
+ { "steps": [ 6, 28, 2] },
+ { "steps": [ 6, 27, 2] },
+ { "steps": [ 6, 26, 2] },
+ { "steps": [ 6, 25, 2] },
+ { "steps": [ 6, 24, 2] },
+ { "steps": [ 6, 23, 2] },
+ { "steps": [ 6, 22, 2] },
+ { "steps": [ 6, 21, 2] },
+ { "steps": [ 6, 20, 2] },
+ { "steps": [ 6, 19, 2] },
+ { "steps": [ 6, 18, 2] },
+ { "steps": [ 6, 17, 2] },
+ { "steps": [ 6, 16, 2] },
+ { "steps": [ 7, 14, 2] },
+ { "steps": [ 7, 13, 2] },
+ { "steps": [ 7, 12, 2] },
+ { "steps": [ 7, 11, 2] },
+ { "steps": [ 8, 9, 2] },
+ { "steps": [ 8, 8, 2] },
+ { "steps": [ 8, 7, 2] },
+ { "steps": [ 9, 5, 2] },
+ { "steps": [ 9, 4, 2] },
+ { "steps": [10, 2, 2] },
+ { "steps": [10, 1, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "4100M_to_45000M",
+ "max_freq": 4500000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 4, 31, 2] },
+ { "steps": [ 3, 31, 2] },
+ { "steps": [ 3, 30, 2] },
+ { "steps": [ 3, 29, 2] },
+ { "steps": [ 3, 28, 2] },
+ { "steps": [ 3, 27, 2] },
+ { "steps": [ 3, 26, 2] },
+ { "steps": [ 3, 25, 2] },
+ { "steps": [ 3, 24, 2] },
+ { "steps": [ 3, 23, 2] },
+ { "steps": [ 3, 22, 2] },
+ { "steps": [ 3, 21, 2] },
+ { "steps": [ 3, 20, 2] },
+ { "steps": [ 3, 19, 2] },
+ { "steps": [ 4, 17, 2] },
+ { "steps": [ 4, 16, 2] },
+ { "steps": [ 4, 15, 2] },
+ { "steps": [ 4, 14, 2] },
+ { "steps": [ 4, 13, 2] },
+ { "steps": [ 5, 11, 2] },
+ { "steps": [ 5, 10, 2] },
+ { "steps": [ 5, 9, 2] },
+ { "steps": [ 5, 8, 2] },
+ { "steps": [ 6, 6, 2] },
+ { "steps": [ 6, 5, 2] },
+ { "steps": [ 7, 3, 2] },
+ { "steps": [ 7, 2, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "4500M_to_4900M",
+ "max_freq": 4900000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 5, 30, 2] },
+ { "steps": [ 5, 29, 2] },
+ { "steps": [ 5, 28, 2] },
+ { "steps": [ 5, 27, 2] },
+ { "steps": [ 5, 26, 2] },
+ { "steps": [ 5, 25, 2] },
+ { "steps": [ 5, 24, 2] },
+ { "steps": [ 5, 23, 2] },
+ { "steps": [ 5, 22, 2] },
+ { "steps": [ 5, 21, 2] },
+ { "steps": [ 5, 20, 2] },
+ { "steps": [ 5, 19, 2] },
+ { "steps": [ 5, 18, 2] },
+ { "steps": [ 5, 17, 2] },
+ { "steps": [ 6, 15, 2] },
+ { "steps": [ 6, 14, 2] },
+ { "steps": [ 6, 13, 2] },
+ { "steps": [ 6, 12, 2] },
+ { "steps": [ 6, 11, 2] },
+ { "steps": [ 7, 9, 2] },
+ { "steps": [ 7, 8, 2] },
+ { "steps": [ 7, 7, 2] },
+ { "steps": [ 8, 5, 2] },
+ { "steps": [ 8, 4, 2] },
+ { "steps": [ 9, 2, 2] },
+ { "steps": [ 9, 1, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "4900M_to_5100M",
+ "max_freq": 5100000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 6, 30, 2] },
+ { "steps": [ 6, 29, 2] },
+ { "steps": [ 6, 28, 2] },
+ { "steps": [ 6, 27, 2] },
+ { "steps": [ 6, 26, 2] },
+ { "steps": [ 6, 25, 2] },
+ { "steps": [ 6, 24, 2] },
+ { "steps": [ 6, 23, 2] },
+ { "steps": [ 6, 22, 2] },
+ { "steps": [ 6, 21, 2] },
+ { "steps": [ 6, 20, 2] },
+ { "steps": [ 6, 19, 2] },
+ { "steps": [ 6, 18, 2] },
+ { "steps": [ 6, 17, 2] },
+ { "steps": [ 7, 15, 2] },
+ { "steps": [ 7, 14, 2] },
+ { "steps": [ 7, 13, 2] },
+ { "steps": [ 7, 12, 2] },
+ { "steps": [ 7, 11, 2] },
+ { "steps": [ 8, 9, 2] },
+ { "steps": [ 8, 8, 2] },
+ { "steps": [ 8, 7, 2] },
+ { "steps": [ 9, 5, 2] },
+ { "steps": [ 9, 4, 2] },
+ { "steps": [ 9, 3, 2] },
+ { "steps": [10, 1, 2] },
+ { "steps": [10, 0, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "5100M_to_5700M",
+ "max_freq": 5700000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 4, 31, 2] },
+ { "steps": [ 4, 30, 2] },
+ { "steps": [ 4, 29, 2] },
+ { "steps": [ 4, 28, 2] },
+ { "steps": [ 4, 27, 2] },
+ { "steps": [ 4, 26, 2] },
+ { "steps": [ 4, 25, 2] },
+ { "steps": [ 4, 24, 2] },
+ { "steps": [ 4, 23, 2] },
+ { "steps": [ 4, 22, 2] },
+ { "steps": [ 4, 21, 2] },
+ { "steps": [ 4, 20, 2] },
+ { "steps": [ 4, 19, 2] },
+ { "steps": [ 4, 18, 2] },
+ { "steps": [ 5, 16, 2] },
+ { "steps": [ 5, 15, 2] },
+ { "steps": [ 5, 14, 2] },
+ { "steps": [ 5, 13, 2] },
+ { "steps": [ 5, 12, 2] },
+ { "steps": [ 5, 11, 2] },
+ { "steps": [ 6, 9, 2] },
+ { "steps": [ 6, 8, 2] },
+ { "steps": [ 6, 7, 2] },
+ { "steps": [ 7, 5, 2] },
+ { "steps": [ 7, 4, 2] },
+ { "steps": [ 8, 2, 2] },
+ { "steps": [ 8, 1, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "5700M_to_6100M",
+ "max_freq": 6100000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 4, 31, 2] },
+ { "steps": [ 4, 30, 2] },
+ { "steps": [ 4, 29, 2] },
+ { "steps": [ 4, 28, 2] },
+ { "steps": [ 4, 27, 2] },
+ { "steps": [ 4, 26, 2] },
+ { "steps": [ 4, 25, 2] },
+ { "steps": [ 4, 24, 2] },
+ { "steps": [ 4, 23, 2] },
+ { "steps": [ 4, 22, 2] },
+ { "steps": [ 5, 20, 2] },
+ { "steps": [ 5, 19, 2] },
+ { "steps": [ 5, 18, 2] },
+ { "steps": [ 5, 17, 2] },
+ { "steps": [ 5, 16, 2] },
+ { "steps": [ 5, 15, 2] },
+ { "steps": [ 5, 14, 2] },
+ { "steps": [ 5, 13, 2] },
+ { "steps": [ 6, 11, 2] },
+ { "steps": [ 6, 10, 2] },
+ { "steps": [ 6, 9, 2] },
+ { "steps": [ 6, 8, 2] },
+ { "steps": [ 7, 6, 2] },
+ { "steps": [ 7, 5, 2] },
+ { "steps": [ 8, 3, 2] },
+ { "steps": [ 8, 2, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "6100M_to_6400M",
+ "max_freq": 6400000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 4, 31, 2] },
+ { "steps": [ 3, 31, 2] },
+ { "steps": [ 2, 31, 2] },
+ { "steps": [ 2, 30, 2] },
+ { "steps": [ 2, 29, 2] },
+ { "steps": [ 2, 28, 2] },
+ { "steps": [ 2, 27, 2] },
+ { "steps": [ 2, 26, 2] },
+ { "steps": [ 3, 24, 2] },
+ { "steps": [ 3, 23, 2] },
+ { "steps": [ 3, 22, 2] },
+ { "steps": [ 3, 21, 2] },
+ { "steps": [ 3, 20, 2] },
+ { "steps": [ 3, 19, 2] },
+ { "steps": [ 3, 18, 2] },
+ { "steps": [ 3, 17, 2] },
+ { "steps": [ 3, 16, 2] },
+ { "steps": [ 3, 15, 2] },
+ { "steps": [ 3, 14, 2] },
+ { "steps": [ 3, 13, 2] },
+ { "steps": [ 4, 11, 2] },
+ { "steps": [ 4, 10, 2] },
+ { "steps": [ 4, 9, 2] },
+ { "steps": [ 5, 7, 2] },
+ { "steps": [ 5, 6, 2] },
+ { "steps": [ 5, 5, 2] },
+ { "steps": [ 6, 3, 2] },
+ { "steps": [ 6, 2, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "6400M_to_7000M",
+ "max_freq": 7000000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 4, 31, 2] },
+ { "steps": [ 4, 30, 2] },
+ { "steps": [ 4, 29, 2] },
+ { "steps": [ 4, 28, 2] },
+ { "steps": [ 4, 27, 2] },
+ { "steps": [ 4, 26, 2] },
+ { "steps": [ 4, 25, 2] },
+ { "steps": [ 4, 24, 2] },
+ { "steps": [ 4, 23, 2] },
+ { "steps": [ 4, 22, 2] },
+ { "steps": [ 4, 21, 2] },
+ { "steps": [ 4, 20, 2] },
+ { "steps": [ 4, 19, 2] },
+ { "steps": [ 4, 18, 2] },
+ { "steps": [ 4, 17, 2] },
+ { "steps": [ 5, 15, 2] },
+ { "steps": [ 5, 14, 2] },
+ { "steps": [ 5, 13, 2] },
+ { "steps": [ 5, 12, 2] },
+ { "steps": [ 5, 11, 2] },
+ { "steps": [ 5, 10, 2] },
+ { "steps": [ 6, 8, 2] },
+ { "steps": [ 6, 7, 2] },
+ { "steps": [ 6, 6, 2] },
+ { "steps": [ 7, 4, 2] },
+ { "steps": [ 7, 3, 2] },
+ { "steps": [ 8, 1, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "7000M_to_7400M",
+ "max_freq": 7400000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 6, 30, 2] },
+ { "steps": [ 6, 29, 2] },
+ { "steps": [ 6, 28, 2] },
+ { "steps": [ 6, 27, 2] },
+ { "steps": [ 6, 26, 2] },
+ { "steps": [ 6, 25, 2] },
+ { "steps": [ 6, 24, 2] },
+ { "steps": [ 6, 23, 2] },
+ { "steps": [ 6, 22, 2] },
+ { "steps": [ 6, 21, 2] },
+ { "steps": [ 6, 20, 2] },
+ { "steps": [ 6, 19, 2] },
+ { "steps": [ 6, 18, 2] },
+ { "steps": [ 6, 17, 2] },
+ { "steps": [ 6, 16, 2] },
+ { "steps": [ 6, 15, 2] },
+ { "steps": [ 6, 14, 2] },
+ { "steps": [ 7, 12, 2] },
+ { "steps": [ 7, 11, 2] },
+ { "steps": [ 7, 10, 2] },
+ { "steps": [ 7, 9, 2] },
+ { "steps": [ 7, 8, 2] },
+ { "steps": [ 8, 6, 2] },
+ { "steps": [ 8, 5, 2] },
+ { "steps": [ 8, 4, 2] },
+ { "steps": [ 9, 2, 2] },
+ { "steps": [ 9, 1, 2] },
+ { "steps": [ 9, 0, 2] },
+ { "steps": [ 8, 0, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ],
+ },
+ {
+ "name": "7400M_to_8000M",
+ "max_freq": 8000000000,
+ "gains": [
+ { "steps": [29, 31, 2] },
+ { "steps": [28, 31, 2] },
+ { "steps": [27, 31, 2] },
+ { "steps": [26, 31, 2] },
+ { "steps": [25, 31, 2] },
+ { "steps": [24, 31, 2] },
+ { "steps": [23, 31, 2] },
+ { "steps": [22, 31, 2] },
+ { "steps": [21, 31, 2] },
+ { "steps": [20, 31, 2] },
+ { "steps": [19, 31, 2] },
+ { "steps": [18, 31, 2] },
+ { "steps": [17, 31, 2] },
+ { "steps": [16, 31, 2] },
+ { "steps": [15, 31, 2] },
+ { "steps": [14, 31, 2] },
+ { "steps": [13, 31, 2] },
+ { "steps": [12, 31, 2] },
+ { "steps": [11, 31, 2] },
+ { "steps": [10, 31, 2] },
+ { "steps": [ 9, 31, 2] },
+ { "steps": [ 8, 31, 2] },
+ { "steps": [ 7, 31, 2] },
+ { "steps": [ 6, 31, 2] },
+ { "steps": [ 5, 31, 2] },
+ { "steps": [ 4, 31, 2] },
+ { "steps": [ 3, 31, 2] },
+ { "steps": [ 3, 30, 2] },
+ { "steps": [ 3, 29, 2] },
+ { "steps": [ 3, 28, 2] },
+ { "steps": [ 3, 27, 2] },
+ { "steps": [ 3, 26, 2] },
+ { "steps": [ 3, 25, 2] },
+ { "steps": [ 3, 24, 2] },
+ { "steps": [ 3, 23, 2] },
+ { "steps": [ 4, 21, 2] },
+ { "steps": [ 4, 20, 2] },
+ { "steps": [ 4, 19, 2] },
+ { "steps": [ 4, 18, 2] },
+ { "steps": [ 4, 17, 2] },
+ { "steps": [ 4, 16, 2] },
+ { "steps": [ 4, 15, 2] },
+ { "steps": [ 4, 14, 2] },
+ { "steps": [ 4, 13, 2] },
+ { "steps": [ 4, 12, 2] },
+ { "steps": [ 4, 11, 2] },
+ { "steps": [ 4, 10, 2] },
+ { "steps": [ 5, 8, 2] },
+ { "steps": [ 5, 7, 2] },
+ { "steps": [ 5, 6, 2] },
+ { "steps": [ 6, 4, 2] },
+ { "steps": [ 6, 3, 2] },
+ { "steps": [ 6, 2, 2] },
+ { "steps": [ 7, 0, 2] },
+ { "steps": [ 6, 0, 2] },
+ { "steps": [ 5, 0, 2] },
+ { "steps": [ 4, 0, 2] },
+ { "steps": [ 3, 0, 2] },
+ { "steps": [ 2, 0, 2] },
+ { "steps": [ 1, 0, 2] },
+ { "steps": [ 0, 0, 2] }
+ ]
+ }
+ ]
+}
diff --git a/host/lib/rfnoc/rf_control/gain_profile.cpp b/host/lib/rfnoc/rf_control/gain_profile.cpp
index 54ccb9006..9d415cb45 100644
--- a/host/lib/rfnoc/rf_control/gain_profile.cpp
+++ b/host/lib/rfnoc/rf_control/gain_profile.cpp
@@ -21,13 +21,16 @@ std::vector<std::string> default_gain_profile::get_gain_profile_names(const size
return {DEFAULT_GAIN_PROFILE};
}
-void default_gain_profile::set_gain_profile(const std::string& profile, const size_t)
+void default_gain_profile::set_gain_profile(const std::string& profile, const size_t chan)
{
if (profile != DEFAULT_GAIN_PROFILE) {
throw uhd::value_error(
std::string("set_tx_gain_profile(): Unknown gain profile: `") + profile
+ "'");
}
+ if (_sub) {
+ _sub(profile, chan);
+ }
}
std::string default_gain_profile::get_gain_profile(const size_t) const
@@ -35,6 +38,12 @@ std::string default_gain_profile::get_gain_profile(const size_t) const
return DEFAULT_GAIN_PROFILE;
}
+
+void default_gain_profile::add_subscriber(subscriber_type&& sub)
+{
+ _sub = std::move(sub);
+}
+
enumerated_gain_profile::enumerated_gain_profile(
const std::vector<std::string>& possible_profiles,
const std::string& default_profile,
@@ -52,6 +61,9 @@ void enumerated_gain_profile::set_gain_profile(
throw uhd::key_error(err_msg);
}
_gain_profile.at(chan) = profile;
+ if (_sub) {
+ _sub(profile, chan);
+ }
}
std::string enumerated_gain_profile::get_gain_profile(const size_t chan) const
@@ -65,4 +77,9 @@ std::vector<std::string> enumerated_gain_profile::get_gain_profile_names(
return _possible_profiles;
}
+void enumerated_gain_profile::add_subscriber(subscriber_type&& sub)
+{
+ _sub = std::move(sub);
+}
+
}}} // namespace uhd::rfnoc::rf_control
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt
index f15324608..570972f16 100644
--- a/host/lib/usrp/CMakeLists.txt
+++ b/host/lib/usrp/CMakeLists.txt
@@ -41,3 +41,4 @@ INCLUDE_SUBDIRECTORY(usrp2)
INCLUDE_SUBDIRECTORY(b100)
INCLUDE_SUBDIRECTORY(x300)
INCLUDE_SUBDIRECTORY(b200)
+INCLUDE_SUBDIRECTORY(x400)
diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt
index a61738743..e37a38110 100644
--- a/host/lib/usrp/common/CMakeLists.txt
+++ b/host/lib/usrp/common/CMakeLists.txt
@@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp
${CMAKE_CURRENT_SOURCE_DIR}/adf535x.cpp
${CMAKE_CURRENT_SOURCE_DIR}/lmx2592.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/lmx2572.cpp
${CMAKE_CURRENT_SOURCE_DIR}/apply_corrections.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp
${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp
diff --git a/host/lib/usrp/common/lmx2572.cpp b/host/lib/usrp/common/lmx2572.cpp
new file mode 100644
index 000000000..c56478b1d
--- /dev/null
+++ b/host/lib/usrp/common/lmx2572.cpp
@@ -0,0 +1,1030 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+
+#include "lmx2572_regs.hpp"
+#include <uhd/exception.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/usrp/common/lmx2572.hpp>
+#include <uhdlib/utils/interpolation.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <cmath>
+#include <limits>
+#include <map>
+
+namespace {
+// LOG ID
+constexpr char LOG_ID[] = "LMX2572";
+// Highest LO / output frequency
+constexpr double MAX_OUT_FREQ = 6.4e9; // Hz
+// Lowest LO / output frequency
+constexpr double MIN_OUT_FREQ = 12.5e6; // Hz
+// Target loop bandwidth
+constexpr double TARGET_LOOP_BANDWIDTH = 75e3; // Hz
+// Loop Filter gain setting resistor
+constexpr double LOOP_GAIN_SETTING_RESISTANCE = 150; // ohm
+// Delay after powerup. TI recommends a 10 ms delay after clearing powerdown
+// (not documented in the datasheet).
+const uhd::time_spec_t POWERUP_DELAY = uhd::time_spec_t(10e-3);
+// Conservative estimate for PLL to lock which includes VCO calibration and PLL settling
+constexpr double PLL_LOCK_TIME = 200e-6; // s
+
+// Valid input/reference frequencies (fOSC)
+//
+// NOTE: These frequencies are valid for X400/ZBX. If we need to use this
+// driver elsewhere, this part needs to be refactored.
+const std::set<double> VALID_FOSC{61.44e6, 64e6, 62.5e6, 50e6};
+
+
+}; // namespace
+
+//! Control interface for an LMX2572 synthesizer
+class lmx2572_impl : public lmx2572_iface
+{
+public:
+ enum class muxout_state_t { LOCKDETECT, SDO };
+
+ explicit lmx2572_impl(
+ write_fn_t&& poke_fn, read_fn_t&& peek_fn, sleep_fn_t&& sleep_fn)
+ : _poke16(std::move(poke_fn))
+ , _peek16(std::move(peek_fn))
+ , _sleep(std::move(sleep_fn))
+ , _regs()
+ {
+ _regs.save_state();
+ }
+
+ void commit() override
+ {
+ UHD_LOG_TRACE(LOG_ID, "Storing register cache to LMX2572...");
+ const auto changed_addrs = _regs.get_changed_addrs<uint8_t>();
+ for (const auto addr : changed_addrs) {
+ // We write R0 last, for double-buffering
+ if (addr == 0) {
+ continue;
+ }
+ _poke16(addr, _regs.get_reg(addr));
+ }
+ _poke16(0, _regs.get_reg(0));
+ _regs.save_state();
+ UHD_LOG_TRACE(LOG_ID,
+ "Storing cache complete: Updated " << changed_addrs.size() << " registers.");
+ }
+
+ bool get_enabled() override
+ {
+ // Chip is either in normal operation mode or power down mode
+ return _regs.powerdown == lmx2572_regs_t::powerdown_t::POWERDOWN_NORMAL_OPERATION;
+ }
+
+ void set_enabled(const bool enabled) override
+ {
+ const bool prev_enabled = get_enabled();
+
+ _regs.powerdown = enabled
+ ? lmx2572_regs_t::powerdown_t::POWERDOWN_NORMAL_OPERATION
+ : lmx2572_regs_t::powerdown_t::POWERDOWN_POWER_DOWN;
+ _poke16(0, _regs.get_reg(0));
+
+ if (enabled && !prev_enabled) {
+ _sleep(POWERUP_DELAY);
+ }
+ }
+
+ void reset() override
+ {
+ // Power-on Programming Sequence described in the datasheet,
+ // Section 7.5.1.1
+ _regs = lmx2572_regs_t{};
+ _regs.reset = lmx2572_regs_t::reset_t::RESET_RESET;
+ _poke16(0, _regs.get_reg(0));
+ // Reset bit is self-clearing, it does not need to be poked twice. We
+ // manually reset it in the SW cache so we don't accidentally reset again
+ _regs.reset = lmx2572_regs_t::reset_t::RESET_NORMAL_OPERATION;
+ // Also enable register readback so we can read back the magic number
+ // register
+ _enable_register_readback(true);
+ // If the LO was previously powered down, the reset above will power it up. On
+ // power-up, we need to wait for the power up delay recommended by TI.
+ _sleep(POWERUP_DELAY);
+ // Check we can read back the last register, which always returns a
+ // magic constant:
+ const auto magic125 = _peek16(125);
+ if (magic125 != 0x2288) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Unable to communicate with LMX2572! Expected R125==0x2288, got: "
+ << std::hex << magic125 << std::dec);
+ throw uhd::runtime_error("Unable to communicate to LMX2572!");
+ }
+ UHD_LOG_TRACE(LOG_ID, "Communication with LMX2572 successful.");
+ // Now set _regs into a sensible state
+ _set_defaults();
+ // Now write the regs in reverse order, skipping RO regs
+ const auto ro_regs = _regs.get_ro_regs();
+ // Write R0 last for the double buffering
+ for (int addr = _regs.get_num_regs() - 2; addr >= 0; addr--) {
+ if (ro_regs.count(addr)) {
+ continue;
+ }
+ _poke16(uhd::narrow_cast<uint8_t>(addr), _regs.get_reg(addr));
+ }
+ _regs.save_state();
+ }
+
+ bool get_lock_status() override
+ {
+ // Disable register readback which implicitly enables lock detect mode
+ _enable_register_readback(false);
+ // If the PLL is locked we expect to read 0xFFFF from any read
+ return _peek16(0) == 0xFFFF;
+ }
+
+ uint16_t peek16(const uint8_t addr)
+ {
+ _enable_register_readback(true);
+ return _peek16(addr);
+ }
+
+ void set_sync_mode(const bool enable) override
+ {
+ _sync_mode = enable;
+ }
+
+ //! Returns the enabled/disabled state of the phase synchronization
+ bool get_sync_mode() override
+ {
+ return _sync_mode;
+ }
+
+ void set_output_enable_all(const bool enable) override
+ {
+ set_output_enable(RF_OUTPUT_A, enable);
+ set_output_enable(RF_OUTPUT_B, enable);
+ }
+
+ void set_output_enable(const output_t output, const bool enable) override
+ {
+ if (output == RF_OUTPUT_A) {
+ _regs.outa_pd = enable ? lmx2572_regs_t::outa_pd_t::OUTA_PD_NORMAL_OPERATION
+ : lmx2572_regs_t::outa_pd_t::OUTA_PD_POWER_DOWN;
+ return;
+ }
+ if (output == RF_OUTPUT_B) {
+ _regs.outb_pd = enable ? lmx2572_regs_t::outb_pd_t::OUTB_PD_NORMAL_OPERATION
+ : lmx2572_regs_t::outb_pd_t::OUTB_PD_POWER_DOWN;
+ return;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ void set_output_power(const output_t output, const uint8_t power) override
+ {
+ if (output == RF_OUTPUT_A) {
+ _regs.outa_pwr = power;
+ return;
+ }
+ if (output == RF_OUTPUT_B) {
+ _regs.outb_pwr = power;
+ return;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ void set_mux_input(const output_t output, const mux_in_t input) override
+ {
+ switch (output) {
+ case RF_OUTPUT_A: {
+ switch (input) {
+ case mux_in_t::DIVIDER:
+ _regs.outa_mux =
+ lmx2572_regs_t::outa_mux_t::OUTA_MUX_CHANNEL_DIVIDER;
+ return;
+ case mux_in_t::VCO:
+ _regs.outa_mux = lmx2572_regs_t::outa_mux_t::OUTA_MUX_VCO;
+ return;
+ case mux_in_t::HIGH_IMPEDANCE:
+ _regs.outa_mux =
+ lmx2572_regs_t::outa_mux_t::OUTA_MUX_HIGH_IMPEDANCE;
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+ case RF_OUTPUT_B: {
+ switch (input) {
+ case mux_in_t::DIVIDER:
+ _regs.outb_mux =
+ lmx2572_regs_t::outb_mux_t::OUTB_MUX_CHANNEL_DIVIDER;
+ return;
+ case mux_in_t::VCO:
+ _regs.outb_mux = lmx2572_regs_t::outb_mux_t::OUTB_MUX_VCO;
+ return;
+ case mux_in_t::HIGH_IMPEDANCE:
+ _regs.outb_mux =
+ lmx2572_regs_t::outb_mux_t::OUTB_MUX_HIGH_IMPEDANCE;
+ return;
+ case mux_in_t::SYSREF:
+ _regs.outb_mux = lmx2572_regs_t::outb_mux_t::OUTB_MUX_SYSREF;
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ double set_frequency(const double target_freq,
+ const double fOSC,
+ const bool spur_dodging) override
+ {
+ // Sanity check
+ if (target_freq > MAX_OUT_FREQ || target_freq < MIN_OUT_FREQ) {
+ UHD_LOG_ERROR(LOG_ID,
+ "Invalid LMX2572 target frequency! Must be in ["
+ << (MIN_OUT_FREQ / 1e6) << " MHz, " << (MAX_OUT_FREQ / 1e6)
+ << " MHz]!");
+ throw uhd::value_error("Invalid LMX2572 target frequency!");
+ }
+ UHD_ASSERT_THROW(VALID_FOSC.count(fOSC));
+ // Create an integer version of fOSC for some of the following
+ // calculations
+ const uint64_t fOSC_int = static_cast<uint64_t>(fOSC);
+
+ // 1. Set up output/channel divider value and the output mux
+ const uint16_t out_D = _set_output_divider(target_freq);
+ const double fVCO = target_freq * out_D;
+ UHD_ASSERT_THROW(3200e6 <= fVCO && fVCO <= 6400e6);
+
+ // 2. Configure the reference dividers/multipliers
+ _set_pll_div_and_mult(target_freq, fVCO, fOSC_int);
+
+ // Calculate phase detector frequency
+ // See datasheet (Section 7.3.2):
+ // Equation (1): fPD = fOSC × OSC_2X × MULT / (PLL_R_PRE × PLL_R)
+ const double fPD =
+ fOSC * (_regs.osc_2x + 1) * _regs.mult / (_regs.pll_r_pre * _regs.pll_r);
+
+ // pre-3. Identify SYNC category.
+ // Based on the category, we need to set VCO_PHASE_SYNC_EN appropriately
+ // and update our p-multiplier.
+ // Note: In the line below, we use target_freq and not actual_freq. That
+ // is OK, because we know that _get_sync_cat() only does a check to see
+ // if target_freq is an integer multiple of fOSC. If that's the case,
+ // then rounding/coercion errors won't happen between target_freq and
+ // actual_freq because we can always exactly produce frequencies that
+ // are integer multiples of fOSC. This way, we don't have a circular
+ // dependency (because actual_freq depends on p indirectly).
+ const int p =
+ _set_phase_sync(_get_sync_cat(_regs.mult, fOSC, target_freq, out_D));
+ // P is introduced in Section 7.3.12 - calculate P with adaptation of
+ // Equation (3). It also comes up again in 8.1.6, although it's not
+ // called P there any more. There, it is the factor between N' and N.
+ // P == 2 whenever we're in a sync category where we need to program the
+ // N-divider with half the "normal" values. In TICS PRO, this value is
+ // described as "Calculated Included Channel Divide".
+
+ // 3. Calculate N, PLL_NUM and PLL_DEN
+ const double delta_fVCO = spur_dodging ? 2e6 : 1.0;
+ // In the next statement, we:
+ // - Estimate PLL_DEN by PLL_DEN = ceil(fPD * p / delta_fVCO)
+ // - This value can exceed the limits of uint32_t, so we clamp it between
+ // 1 and 0xFFFFFFFF (the denominator can also not be zero, so we need
+ // to catch rounding errors)
+ // - Finally, convert to uint32_t
+ const uint32_t PLL_DEN = static_cast<uint32_t>(std::max(1.0,
+ std::min(std::ceil(fPD * p / delta_fVCO),
+ double(std::numeric_limits<uint32_t>::max()))));
+ UHD_ASSERT_THROW(PLL_DEN > 0);
+ // This is where we do the N=N'/2 division from Section 8.1.6:
+ const double N_real = fVCO / (fPD * p);
+ const uint32_t N = static_cast<uint32_t>(std::floor(N_real));
+ const uint32_t PLL_NUM = std::round((N_real - double(N)) * PLL_DEN);
+
+ // See datasheet (Section 7.3.4):
+ // Equation (2): fVCO = fPD * [PLL_N + (PLL_NUM / PLL_DEN)] * p
+ // Note that p here is the "extra divider in SYNC mode" that is in the
+ // text, but not listed in Eq. (2) in this section.
+ const double fVCO_actual = fPD * p * (N + (static_cast<double>(PLL_NUM) / PLL_DEN));
+ UHD_ASSERT_THROW(3200e6 <= fVCO_actual && fVCO_actual <= 6400e6);
+ const double actual_freq = fVCO_actual / out_D;
+ // clang-format off
+ UHD_LOG_TRACE(LOG_ID,
+ "Calculating settings for fTARGET=" << (target_freq / 1e6)
+ << " MHz, fOSC=" << (fOSC / 1e6)
+ << " MHz: Target fVCO=" << (fVCO / 1e6)
+ << " MHz, actual fVCO=" << (fVCO_actual / 1e6)
+ << " MHz. R_pre=" << _regs.pll_r_pre
+ << " OSC2X=" << _regs.osc_2x
+ << " MULT=" << std::to_string(_regs.mult)
+ << " PLL_R=" << std::to_string(_regs.pll_r)
+ << " P=" << p
+ << " N=" << N
+ << " PLL_DEN=" << PLL_DEN
+ << " PLL_NUM=" << PLL_NUM
+ << " CHDIV=" << out_D);
+ // clang-format on
+
+ // 4. Set frequency dependent registers
+ _compute_and_set_mult_hi(fOSC);
+ _set_pll_n(N); // Note: N-divider values already account for
+ _set_pll_num(PLL_NUM); // N-divider adaptations at this point. No more
+ _set_pll_den(PLL_DEN); // divide-by-2 necessary.
+ _set_fcal_hpfd_adj(fPD);
+ _set_fcal_lpfd_adj(fPD);
+ _set_pfd_dly(fVCO_actual);
+ _set_mash_seed(spur_dodging, PLL_NUM, fPD);
+
+ if (get_sync_mode()) {
+ // From R69 register field description (Table 77):
+ // The delay should be at least 4 times the PLL lock time. The
+ // delay is expressed in state machine clock periods where
+ // state_machine_clock_period = 2^(CAL_CLK_DIV) / fOSC and
+ // CAL_CLK_DIV is one of {0, 1}
+ const double period = ((_regs.cal_clk_div == 0) ? 1 : 2) / fOSC;
+ const uint32_t mash_rst_count =
+ static_cast<uint32_t>(std::ceil(4 * PLL_LOCK_TIME / period));
+ _set_mash_rst_count(mash_rst_count);
+ }
+
+ // 5. Calculate charge pump gain
+ _compute_and_set_charge_pump_gain(fVCO_actual, N_real);
+
+ // 6. Calculate VCO calibration values
+ _compute_and_set_vco_cal(fVCO_actual);
+
+ // 7. Set amplitude on enabled outputs
+ if (_get_output_enabled(RF_OUTPUT_A)) {
+ _find_and_set_lo_power(actual_freq, RF_OUTPUT_A);
+ }
+ if (_get_output_enabled(RF_OUTPUT_B)) {
+ _find_and_set_lo_power(actual_freq, RF_OUTPUT_B);
+ }
+
+ return actual_freq;
+ }
+
+private:
+ /**************************************************************************
+ * Attributes
+ *************************************************************************/
+ write_fn_t _poke16;
+ read_fn_t _peek16;
+ sleep_fn_t _sleep;
+ lmx2572_regs_t _regs = lmx2572_regs_t();
+ muxout_state_t _mux_state = muxout_state_t::SDO;
+ bool _sync_mode = false;
+
+ /**************************************************************************
+ * Private Methods
+ *************************************************************************/
+ //! Identify sync category according to Section 8.1.6 of the datasheet. This
+ // function implements the flowchart (Fig. 170).
+ sync_cat _get_sync_cat(
+ const uint8_t M, const double fOSC, const double fOUT, const uint16_t CHDIV)
+ {
+ if (!get_sync_mode()) {
+ return NONE;
+ }
+ // Right-hand path of the flowchart:
+ if (M > 1) {
+ if (CHDIV > 2) {
+ return CAT4;
+ }
+ if (std::fmod(fOUT, fOSC * M) != 0) {
+ return CAT4;
+ }
+ // In the flow chart, there's a third condition (PLL_NUM == 0) but
+ // that is implied in the previous condition. Here's the proof:
+ // 1) Because of the previous condition, we know that
+ // f_OUT = f_OSC * M * K, where K is an integer.
+ // We also know that that the doubler is disabled, so we can
+ // ignore it here.
+ // 2) PLL_NUM must be zero when f_VCO / f_PD is an integer value:
+ //
+ // f_VCO
+ // N = -----
+ // f_PD
+ //
+ // 3) We can insert
+ // f_VCO = f_OUT * D
+ // and
+ // f_PD = f_OSC * M / R where R is an integer (R-divider)
+ // which yields:
+ //
+ // f_OUT * D * R
+ // N = -------------
+ // f_OSC * M
+ //
+ // 4) Now we can insert 1), which yields
+ //
+ // N = K * D * R
+ //
+ // D is either 1 or 2, and K and R are integers. Therefore, N
+ // is an integer too, and PLL_NUM == 0. _
+ // |_|
+ //
+ // Note: We could simply calculate N here, but that would require
+ // knowing K and R, which we can avoid with this simple comment.
+ }
+ // Left-hand path of the flowchart:
+ if (M == 1 && std::fmod(fOUT, fOSC) != 0) {
+ return CAT3;
+ }
+ if (M == 1 && CHDIV > 2) {
+ return CAT2;
+ }
+ if (CHDIV == 2) {
+ return CAT1B;
+ }
+ return CAT1A;
+ }
+
+ //! Enable/disable register readback mode enabled
+ // SPI MISO is multiplexed to lock detect and register readback. Reading
+ // any register when the mux is set to lock detect will return just the
+ // lock detect signal, so ensure we're in readback mode if reads desired
+ void _enable_register_readback(const bool enable)
+ {
+ auto desired_state = enable
+ ? lmx2572_regs_t::muxout_ld_sel_t::MUXOUT_LD_SEL_REGISTER_READBACK
+ : lmx2572_regs_t::muxout_ld_sel_t::MUXOUT_LD_SEL_LOCK_DETECT;
+ if (_regs.muxout_ld_sel != desired_state) {
+ _regs.muxout_ld_sel = desired_state;
+ _poke16(0, _regs.get_reg(0));
+ }
+ }
+
+ //! Sets the output divider registers
+ //
+ // Configures both the output divider and the output mux. If the divider is
+ // used, the mux input is set to CHDIV, otherwise, it's set to VCO.
+ uint16_t _set_output_divider(const double freq)
+ {
+ // clang-format off
+ // Map the desired output / LO frequency to output divider settings
+ const std::map<
+ double,
+ std::tuple<uint16_t, lmx2572_regs_t::chdiv_t>
+ > out_div_map {
+ // freq outD chdiv
+ {25e6, {256, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_256}},
+ {50e6, {128, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_128}},
+ {100e6, {64, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_64 }},
+ {200e6, {32, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_32 }},
+ {400e6, {16, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_16 }},
+ {800e6, {8, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_8 }},
+ {1.6e9, {4, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_4 }},
+ {3.2e9, {2, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_2 }},
+ // CHDIV isn't used for out_divider == 1 so use DIVIDE_BY_2
+ // We use +1 as an epsilon value here. Upon entering this function
+ // we already know that that freq <= 6.4e9. We increase the
+ // boundary here so that upper_bound() will not fail on the
+ // corner case freq == 6.4e9.
+ {6.4e9+1, {1, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_2 }}
+ };
+ // clang-format on
+
+ uint16_t out_D;
+ lmx2572_regs_t::chdiv_t chdiv;
+ auto out_div_it = out_div_map.upper_bound(freq);
+ UHD_ASSERT_THROW(out_div_it != out_div_map.end());
+ std::tie(out_D, chdiv) = out_div_it->second;
+ _regs.chdiv = lmx2572_regs_t::chdiv_t(chdiv);
+ // If we're using the output divider, map it to the corresponding output
+ // mux. Otherwise, connect the VCO directly to the mux.
+ const mux_in_t input = (out_D > 1) ? mux_in_t::DIVIDER : mux_in_t::VCO;
+ if (_get_output_enabled(RF_OUTPUT_A)) {
+ set_mux_input(RF_OUTPUT_A, input);
+ }
+ if (_get_output_enabled(RF_OUTPUT_B)) {
+ set_mux_input(RF_OUTPUT_B, input);
+ }
+
+ return out_D;
+ }
+
+ //! Returns the output enabled status of output
+ bool _get_output_enabled(const output_t output)
+ {
+ if (output == RF_OUTPUT_A) {
+ return _regs.outa_pd == lmx2572_regs_t::outa_pd_t::OUTA_PD_NORMAL_OPERATION;
+ } else {
+ return _regs.outb_pd == lmx2572_regs_t::outb_pd_t::OUTB_PD_NORMAL_OPERATION;
+ }
+ }
+
+ //! Sets the MASH_RST_COUNT registers
+ void _set_mash_rst_count(const uint32_t mash_rst_count)
+ {
+ _regs.mash_rst_count_upper = uhd::narrow_cast<uint16_t>(mash_rst_count >> 16);
+ _regs.mash_rst_count_lower = uhd::narrow_cast<uint16_t>(mash_rst_count);
+ }
+
+ //! Calculate and set the mult_hi register
+ //
+ // Sets the MULT_HI bit (needs to be high if the multiplier output frequency
+ // is larger than 100 MHz).
+ //
+ // \param ref_frequency The OSCin signal's frequency.
+ void _compute_and_set_mult_hi(const double fOSC)
+ {
+ const double fMULTout =
+ (fOSC * (int(_regs.osc_2x) + 1) * _regs.mult) / _regs.pll_r_pre;
+ _regs.mult_hi = (_regs.mult > 1 && fMULTout > 100e6)
+ ? lmx2572_regs_t::mult_hi_t::MULT_HI_GREATER_THAN_100M
+ : lmx2572_regs_t::mult_hi_t::MULT_HI_LESS_THAN_EQUAL_TO_100M;
+ }
+
+ //! Sets the mash seed value based on fPD and whether spur dodging is enabled
+ void _set_mash_seed(const bool spur_dodging, const uint32_t PLL_NUM, const double pfd)
+ {
+ uint32_t mash_seed = 0;
+ if (spur_dodging || PLL_NUM == 0) {
+ // Leave mash_seed set to 0
+ }
+ else {
+ const std::map<double, uint32_t> seed_map = {
+ {25e6, 4999},
+ {30.72e6, 5531},
+ {31.25e6, 5591},
+ {32e6, 5657},
+ {50e6, 7096},
+ {61.44e6, 7841},
+ {62.5e6, 7907},
+ {64e6, 7993}
+ };
+ mash_seed = seed_map.lower_bound(pfd)->second;
+ }
+ _regs.mash_seed_upper = uhd::narrow_cast<uint16_t>(mash_seed >> 16);
+ _regs.mash_seed_lower = uhd::narrow_cast<uint16_t>(mash_seed);
+ }
+
+ void _find_and_set_lo_power(const double freq, const output_t output)
+ {
+ if (freq < 3e9) {
+ set_output_power(output, 25);
+ } else if (3e9 <= freq && freq < 4e9) {
+ constexpr double slope = 5.0;
+ constexpr double segment_range = 1e9;
+ constexpr int power_base = 25;
+ const double offset = freq - 3e9;
+ const uint8_t power =
+ std::round<uint8_t>(power_base + ((offset / segment_range) * slope));
+ set_output_power(output, power);
+ } else if (4e9 <= freq && freq < 5e9) {
+ constexpr double slope = 10.0;
+ constexpr double segment_range = 1e9;
+ constexpr int power_base = 30;
+ const double offset = freq - 4e9;
+ const uint8_t power =
+ std::round<uint8_t>(power_base + ((offset / segment_range) * slope));
+ set_output_power(output, power);
+ } else if (5e9 <= freq && freq < 6.4e9) {
+ constexpr double slope = 5 / 1.4;
+ constexpr double segment_range = 1.4e9;
+ constexpr int power_base = 40;
+ const double offset = freq - 5e9;
+ const uint8_t power =
+ std::round<uint8_t>(power_base + ((offset / segment_range) * slope));
+ set_output_power(output, power);
+ } else if (freq >= 6.4e9) {
+ set_output_power(output, 45);
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+ //! Sets the FCAL_HPFD_ADJ value based on fPD
+ void _set_fcal_hpfd_adj(const double pfd)
+ {
+ // These frequency constants are from the data sheet (Section 7.6.1)
+ if (pfd <= 37.5e6) {
+ _regs.fcal_hpfd_adj = 0x0;
+ } else if (37.5e6 < pfd && pfd <= 75e6) {
+ _regs.fcal_hpfd_adj = 0x1;
+ } else if (75e6 < pfd && pfd <= 100e6) {
+ _regs.fcal_hpfd_adj = 0x2;
+ } else { // 100 MHz > pfd
+ _regs.fcal_hpfd_adj = 0x3;
+ }
+ }
+
+ //! Sets the FCAL_LPFD_ADJ value based on the fPD (Section 7.6.1)
+ void _set_fcal_lpfd_adj(const double pfd)
+ {
+ // These frequency constants are from the data sheet (Section 7.6.1)
+ if (pfd >= 10e6) {
+ _regs.fcal_lpfd_adj = 0x0;
+ } else if (10e6 > pfd && pfd >= 5e6) {
+ _regs.fcal_lpfd_adj = 0x1;
+ } else if (5e6 > pfd && pfd >= 2.5e6) {
+ _regs.fcal_lpfd_adj = 0x2;
+ } else { // pfd > 2.5MHz
+ _regs.fcal_lpfd_adj = 0x3;
+ }
+ }
+
+ //! Sets the PFD Delay value based on fVCO (Section 7.3.4)
+ void _set_pfd_dly(const double fVCO)
+ {
+ UHD_ASSERT_THROW(_regs.mash_order == lmx2572_regs_t::MASH_ORDER_THIRD_ORDER);
+ // Thse constants / values come from the data sheet (Table 3)
+ if (3.2e9 <= fVCO && fVCO < 4e9) {
+ _regs.pfd_dly_sel = 2;
+ } else if (4e9 <= fVCO && fVCO < 4.9e9) {
+ _regs.pfd_dly_sel = 2;
+ } else if (4.9e9 <= fVCO && fVCO <= 6.4e9) {
+ _regs.pfd_dly_sel = 3;
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+ //! Sets the PLL divider and multiplier values
+ void _set_pll_div_and_mult(
+ const double fTARGET, const double fVCO, const uint64_t fOSC_int)
+ {
+ // We want to avoid SYNC category 4 (device unreliable in SYNC mode) so
+ // fix the pre-divider and multiplier to 1
+ // See datasheet (Section 8.1.6)
+ _regs.pll_r_pre = 1;
+ _regs.mult = 1;
+ // Doubler fixed to disabled
+ _regs.osc_2x = lmx2572_regs_t::osc_2x_t::OSC_2X_DISABLED;
+ // Post-divider
+ uint8_t pll_r = 0;
+ // NOTE: This calculation is designed for the ZBX daughterboard. Should
+ // we want to reuse this driver elsewhere, we need to factor this out
+ // and make it a bit nicer.
+ if (get_sync_mode()) {
+ if (fTARGET < 3200e6) {
+ switch (fOSC_int) {
+ case 61440000: {
+ if (3200e6 <= fVCO && fVCO < 3950e6) {
+ pll_r = 2;
+ } else if (3950e6 <= fVCO && fVCO <= 6400e6) {
+ pll_r = 1;
+ }
+ break;
+ }
+ case 64000000: {
+ if (3200e6 <= fVCO && fVCO < 4100e6) {
+ pll_r = 2;
+ } else if (4150e6 < fVCO && fVCO <= 6400e6) {
+ pll_r = 1;
+ }
+ break;
+ }
+ case 62500000: {
+ if (3200e6 <= fVCO && fVCO < 4000e6) {
+ pll_r = 2;
+ } else if (4050e6 <= fVCO && fVCO <= 6400e6) {
+ pll_r = 1;
+ }
+ break;
+ }
+ case 50000000: {
+ pll_r = 1;
+ break;
+ }
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ } // end switch
+ } // end if (fTARGET < 3200e6)
+ else {
+ pll_r = 1;
+ }
+ } else {
+ pll_r = 1;
+ }
+ UHD_ASSERT_THROW(pll_r > 0);
+ _regs.pll_r = pll_r;
+ // Section 7.3.2 states to not use both the double and the multiplier
+ // (M), so let's check we're doing that
+ UHD_ASSERT_THROW(
+ _regs.mult == 1 || _regs.osc_2x == lmx2572_regs_t::osc_2x_t::OSC_2X_DISABLED);
+ }
+
+ //! Set the value of VCO_PHASE_SYNC_EN according to our sync category
+ //
+ // Assumption: outa_mux and outb_mux have already been appropriately
+ // programmed for this use case.
+ //
+ // Also calculates the P-value (see set_frequency() for more discussion on
+ // that value).
+ int _set_phase_sync(const sync_cat cat)
+ {
+ int P = 1;
+ // We always set the default value for VCO_PHASE_SYNC_EN here. Some
+ // sync categories do not, in fact, require this bit to be asserted.
+ // By resetting it here, we can exactly follow the datasheet in the
+ // following switch statement.
+ _regs.vco_phase_sync_en =
+ lmx2572_regs_t::vco_phase_sync_en_t::VCO_PHASE_SYNC_EN_NORMAL_OPERATION;
+ // This switch statement implements Table 137 from Section 8.1.6 of the
+ // datasheet.
+ switch (cat) {
+ case CAT1A:
+ UHD_LOG_TRACE(LOG_ID, "Sync Category: 1A");
+ // Nothing required in this mode, input and output are always
+ // at a deterministic phase relationship.
+ break;
+ case CAT1B:
+ UHD_LOG_TRACE(LOG_ID, "Sync Category: 1B");
+ // Set VCO_PHASE_SYNC_EN = 1
+ _regs.vco_phase_sync_en = lmx2572_regs_t::vco_phase_sync_en_t::
+ VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE;
+ P = 2;
+ break;
+ case CAT2:
+ UHD_LOG_TRACE(LOG_ID, "Sync Category: 2");
+ // Note: We assume the existence and usage of the SYNC pin here.
+ // This means there are no more steps required (Steps 3-6 are
+ // skipped).
+ break;
+ case CAT3:
+ UHD_LOG_TRACE(LOG_ID, "Sync Category: 3");
+ // In this category, we assume that the SYNC signal will be
+ // applied afterwards, and that timing requirements are met.
+ if (_regs.outa_mux == lmx2572_regs_t::outa_mux_t::OUTA_MUX_CHANNEL_DIVIDER
+ || _regs.outb_mux
+ == lmx2572_regs_t::outb_mux_t::OUTB_MUX_CHANNEL_DIVIDER) {
+ P = 2;
+ }
+ _regs.vco_phase_sync_en = lmx2572_regs_t::vco_phase_sync_en_t::
+ VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE;
+ break;
+ case CAT4:
+ UHD_LOG_TRACE(LOG_ID, "Sync Category: 4");
+ UHD_LOG_WARNING(LOG_ID,
+ "PLL programming does not allow reliable phase synchronization!");
+ break;
+ case NONE:
+ // No phase sync, we're done
+ break;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ return P;
+ }
+
+ //! Compute and set charge pump gain register
+ // TODO: Charge pump settings will eventually come from a
+ // lookup table in the Cal EEPROM for Charge Pump setting vs. F_CORE VCO_.
+ void _compute_and_set_charge_pump_gain(const double fVCO_actual, const double N_real)
+ {
+ // clang-format off
+ // Table 135 (VCO Gain)
+ const std::map<
+ double,
+ std::tuple<double, double, uint8_t, uint8_t, uint8_t>
+ > vco_gain_map {
+ // fmax fmin fmax vco kmin kmax
+ {3.65e9, {3.2e9, 3.65e9, 1, 32, 47}},
+ {4.2e9, {3.65e9, 4.2e9, 2, 35, 54}},
+ {4.65e9, {4.2e9, 4.65e9, 3, 47, 64}},
+ {5.2e9, {4.65e9, 5.2e9, 4, 50, 73}},
+ {5.75e9, {5.2e9, 5.75e9, 5, 61, 82}},
+ {6.4e9, {5.75e9, 6.4e9, 6, 57, 79}}
+ };
+ // clang-format on
+ double fmin, fmax;
+ int VCO_CORE;
+ int KvcoMin, KvcoMax;
+ auto vco_gain_it = vco_gain_map.lower_bound(fVCO_actual);
+ UHD_ASSERT_THROW(vco_gain_it != vco_gain_map.end());
+ std::tie(fmin, fmax, VCO_CORE, KvcoMin, KvcoMax) = vco_gain_it->second;
+ double Kvco = uhd::math::linear_interp<double>(fVCO_actual, fmin, KvcoMin, fmax, KvcoMax);
+
+ // Calculate the optimal charge pump current (uA)
+ const double icp = 2 * uhd::math::PI * TARGET_LOOP_BANDWIDTH * N_real / (Kvco * LOOP_GAIN_SETTING_RESISTANCE);
+
+ // clang-format off
+ // Table 2 (Charge Pump Gain)
+ const std::map<double, uint8_t> cpg_map = {
+ // gain cpg
+ { 0, 0},
+ { 625, 1},
+ {1250, 2},
+ {1875, 3},
+ {2500, 4},
+ {3125, 5},
+ {3750, 6},
+ {4375, 7},
+ {5000, 12},
+ {5625, 13},
+ {6250, 14},
+ {6875, 15}
+ };
+ // clang-format on
+ const uint8_t cpg = uhd::math::at_nearest(cpg_map, icp);
+ _regs.cpg = cpg;
+ }
+
+ //! Compute and set VCO calibration values
+ // This method implements VCO partial assist calibration
+ // See datasheet (Section 8.1.4.1)
+ void _compute_and_set_vco_cal(const double fVCO_actual)
+ {
+ // clang-format off
+ // Table 136
+ const std::map<
+ double,
+ std::tuple<double, double, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t>
+ > vco_partial_assist_map{
+ // fmax fmin fmax vco Cmin Cmax Amin Amax
+ {3.65e9, {3.2e9, 3.65e9, 1, 131, 19, 138, 137}},
+ {4.2e9, {3.65e9, 4.2e9, 2, 143, 25, 162, 142}},
+ {4.65e9, {4.2e9, 4.65e9, 3, 135, 34, 126, 114}},
+ {5.2e9, {4.65e9, 5.2e9, 4, 136, 25, 195, 172}},
+ {5.75e9, {5.2e9, 5.75e9, 5, 133, 20, 190, 163}},
+ {6.4e9, {5.75e9, 6.4e9, 6, 151, 27, 256, 204}}
+ };
+ // clang-format on
+ double fmin, fmax;
+ uint8_t VCO_CORE, Cmin, Cmax;
+ uint16_t Amin, Amax;
+ auto vco_cal_it = vco_partial_assist_map.lower_bound(fVCO_actual);
+ UHD_ASSERT_THROW(vco_cal_it != vco_partial_assist_map.end());
+ std::tie(fmin, fmax, VCO_CORE, Cmin, Cmax, Amin, Amax) = vco_cal_it->second;
+
+ uint16_t VCO_CAPCTRL_STRT =
+ std::round(Cmin - (fVCO_actual - fmin) * (Cmin - Cmax) / (fmax - fmin));
+ // From R78 register field description (Table 86)
+ const uint16_t VCO_CAPCTRL_STRT_MAX = 183;
+ VCO_CAPCTRL_STRT = std::min(VCO_CAPCTRL_STRT_MAX, VCO_CAPCTRL_STRT);
+
+ uint16_t VCO_DACISET_STRT =
+ std::round(Amin - ((fVCO_actual - fmin) * (Amin - Amax) / (fmax - fmin)));
+ // From R17 register field description (Table 25), 9-bit register
+ const uint16_t VCO_DACISET_STRT_MAX = 511; // 0x1FF
+ VCO_DACISET_STRT = std::min(VCO_DACISET_STRT, VCO_DACISET_STRT_MAX);
+
+ _regs.vco_sel = VCO_CORE;
+ _regs.vco_capctrl_strt = VCO_CAPCTRL_STRT;
+ _regs.vco_daciset_strt = VCO_DACISET_STRT;
+ }
+
+ void _set_pll_n(const uint32_t n)
+ {
+ UHD_ASSERT_THROW((n & 0x7FFFF) == n);
+ // The regs object masks internally, this 0x7 is just for the sake of
+ // reading
+ _regs.pll_n_upper_3_bits = uhd::narrow_cast<uint16_t>((n >> 16) & 0x7);
+ _regs.pll_n_lower_16_bits = uhd::narrow_cast<uint16_t>(n);
+ }
+
+ void _set_pll_num(const uint32_t num)
+ {
+ _regs.pll_num_upper = uhd::narrow_cast<uint16_t>(num >> 16);
+ _regs.pll_num_lower = uhd::narrow_cast<uint16_t>(num);
+ }
+
+ void _set_pll_den(const uint32_t den)
+ {
+ _regs.pll_den_upper = uhd::narrow_cast<uint16_t>(den >> 16);
+ _regs.pll_den_lower = uhd::narrow_cast<uint16_t>(den);
+ }
+
+
+ // NOTE: Some of these defaults are just sensible defaults, and get
+ // overwritten as soon as anything interesting happens. Other defaults are
+ // specific to X400/ZBX. If we want to use this driver for other dboards,
+ // we should add APIs to set those other things in order not to have a
+ // leaky abstraction (we'd like to contain lmx2572_regs_t within this file).
+ void _set_defaults()
+ {
+ _regs.ramp_en = lmx2572_regs_t::ramp_en_t::RAMP_EN_NORMAL_OPERATION;
+ _regs.vco_phase_sync_en =
+ lmx2572_regs_t::vco_phase_sync_en_t::VCO_PHASE_SYNC_EN_NORMAL_OPERATION;
+ _regs.add_hold = 0;
+ _regs.out_mute = lmx2572_regs_t::out_mute_t::OUT_MUTE_MUTED;
+ _regs.fcal_hpfd_adj = 1;
+ _regs.fcal_lpfd_adj = 0;
+ _regs.fcal_en = lmx2572_regs_t::fcal_en_t::FCAL_EN_ENABLE;
+ _regs.muxout_ld_sel = lmx2572_regs_t::muxout_ld_sel_t::MUXOUT_LD_SEL_LOCK_DETECT;
+ _regs.reset = lmx2572_regs_t::reset_t::RESET_NORMAL_OPERATION;
+ _regs.powerdown = lmx2572_regs_t::powerdown_t::POWERDOWN_NORMAL_OPERATION;
+
+ _regs.cal_clk_div = 0;
+
+ _regs.ipbuf_type = lmx2572_regs_t::ipbuf_type_t::IPBUF_TYPE_DIFFERENTIAL;
+ _regs.ipbuf_term = lmx2572_regs_t::ipbuf_term_t::IPBUF_TERM_INTERNALLY_TERMINATED;
+
+ _regs.out_force = lmx2572_regs_t::out_force_t::OUT_FORCE_USE_OUT_MUTE;
+
+ // set_frequency() implements VCO Partial assist so set the correct modes of
+ // operation and some defaults (defaults will get overwritten)
+ // See datasheet (Section 8.1.4)
+ _regs.vco_daciset_force =
+ lmx2572_regs_t::vco_daciset_force_t::VCO_DACISET_FORCE_NORMAL_OPERATION;
+ _regs.vco_capctrl_force =
+ lmx2572_regs_t::vco_capctrl_force_t::VCO_CAPCTRL_FORCE_NORMAL_OPERATION;
+ _regs.vco_sel_force = lmx2572_regs_t::vco_sel_force_t::VCO_SEL_FORCE_DISABLED;
+ _regs.vco_daciset_strt = 0x096;
+ _regs.vco_sel = 0x6;
+ _regs.vco_capctrl_strt = 0;
+
+ _regs.mult_hi = lmx2572_regs_t::mult_hi_t::MULT_HI_LESS_THAN_EQUAL_TO_100M;
+ _regs.osc_2x = lmx2572_regs_t::osc_2x_t::OSC_2X_DISABLED;
+
+ _regs.mult = 1;
+
+ _regs.pll_r = 1;
+
+ _regs.pll_r_pre = 1;
+
+ _regs.cpg = 7;
+
+ _regs.pll_n_upper_3_bits = 0;
+ _regs.pll_n_lower_16_bits = 0x28;
+
+ _regs.mash_seed_en = lmx2572_regs_t::mash_seed_en_t::MASH_SEED_EN_ENABLED;
+ _regs.pfd_dly_sel = 0x2;
+
+ _regs.pll_den_upper = 0;
+ _regs.pll_den_lower = 0;
+
+ _regs.mash_seed_upper = 0;
+ _regs.mash_seed_lower = 0;
+
+ _regs.pll_num_upper = 0;
+ _regs.pll_num_lower = 0;
+
+ _regs.outa_pwr = 0;
+ _regs.outb_pd = lmx2572_regs_t::outb_pd_t::OUTB_PD_POWER_DOWN;
+ _regs.outa_pd = lmx2572_regs_t::outa_pd_t::OUTA_PD_POWER_DOWN;
+ _regs.mash_reset_n =
+ lmx2572_regs_t::mash_reset_n_t::MASH_RESET_N_NORMAL_OPERATION;
+ _regs.mash_order = lmx2572_regs_t::mash_order_t::MASH_ORDER_THIRD_ORDER;
+
+ _regs.outa_mux = lmx2572_regs_t::outa_mux_t::OUTA_MUX_VCO;
+ _regs.outb_pwr = 0;
+
+ _regs.outb_mux = lmx2572_regs_t::outb_mux_t::OUTB_MUX_VCO;
+
+ _regs.inpin_ignore = 0;
+ _regs.inpin_hyst = lmx2572_regs_t::inpin_hyst_t::INPIN_HYST_DISABLED;
+ _regs.inpin_lvl = lmx2572_regs_t::inpin_lvl_t::INPIN_LVL_INVALID;
+ _regs.inpin_fmt =
+ lmx2572_regs_t::inpin_fmt_t::INPIN_FMT_SYNC_EQUALS_SYSREFREQ_EQUALS_CMOS2;
+
+ _regs.ld_type = lmx2572_regs_t::ld_type_t::LD_TYPE_VTUNE_AND_VCOCAL;
+
+ _regs.ld_dly = 100;
+ // See Table 144 (LDO_DLY Setting)
+ _regs.ldo_dly = 3;
+
+ _regs.dblbuf_en_0 = lmx2572_regs_t::dblbuf_en_0_t::DBLBUF_EN_0_ENABLED;
+ _regs.dblbuf_en_1 = lmx2572_regs_t::dblbuf_en_1_t::DBLBUF_EN_1_ENABLED;
+ _regs.dblbuf_en_2 = lmx2572_regs_t::dblbuf_en_2_t::DBLBUF_EN_2_ENABLED;
+ _regs.dblbuf_en_3 = lmx2572_regs_t::dblbuf_en_3_t::DBLBUF_EN_3_ENABLED;
+ _regs.dblbuf_en_4 = lmx2572_regs_t::dblbuf_en_4_t::DBLBUF_EN_4_ENABLED;
+ _regs.dblbuf_en_5 = lmx2572_regs_t::dblbuf_en_5_t::DBLBUF_EN_5_ENABLED;
+
+ _regs.mash_rst_count_upper = 0;
+ _regs.mash_rst_count_lower = 0;
+
+ // Always gets set by set_frequency()
+ _regs.chdiv = lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_2;
+
+ _regs.ramp_thresh_33rd = 0;
+ _regs.quick_recal_en = 0;
+
+ _regs.ramp_thresh_upper = 0;
+ _regs.ramp_thresh_lower = 0;
+
+ _regs.ramp_limit_high_33rd = 0;
+ _regs.ramp_limit_high_upper = 0;
+ _regs.ramp_limit_high_lower = 0;
+
+ _regs.ramp_limit_low_33rd = 0;
+ _regs.ramp_limit_low_upper = 0;
+ _regs.ramp_limit_low_lower = 0;
+
+ // Per the datasheet, the following fields need to be programmed to specific
+ // constants which differ from the defaults after a reset occurs
+ _regs.reg29_reserved0 = 0;
+ _regs.reg30_reserved0 = 0x18A6;
+ _regs.reg52_reserved0 = 0x421;
+ _regs.reg57_reserved0 = 0x20;
+ _regs.reg78_reserved0 = 1;
+ }
+};
+
+lmx2572_iface::sptr lmx2572_iface::make(lmx2572_iface::write_fn_t&& poke_fn,
+ lmx2572_iface::read_fn_t&& peek_fn,
+ lmx2572_iface::sleep_fn_t&& sleep_fn)
+{
+ return std::make_shared<lmx2572_impl>(
+ std::move(poke_fn), std::move(peek_fn), std::move(sleep_fn));
+}
diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt
index 2dd4e7e26..1d31d6930 100644
--- a/host/lib/usrp/dboard/CMakeLists.txt
+++ b/host/lib/usrp/dboard/CMakeLists.txt
@@ -49,3 +49,10 @@ endif(ENABLE_N300)
if(ENABLE_N320)
INCLUDE_SUBDIRECTORY(rhodium)
endif(ENABLE_N320)
+if(ENABLE_MPMD AND ENABLE_EISCAT)
+ INCLUDE_SUBDIRECTORY(eiscat)
+endif(ENABLE_MPMD AND ENABLE_EISCAT)
+
+if(ENABLE_X400)
+ INCLUDE_SUBDIRECTORY(zbx)
+endif(ENABLE_X400)
diff --git a/host/lib/usrp/dboard/zbx/CMakeLists.txt b/host/lib/usrp/dboard/zbx/CMakeLists.txt
new file mode 100644
index 000000000..4a4a39d56
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/CMakeLists.txt
@@ -0,0 +1,17 @@
+#
+# 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}/zbx_dboard.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/zbx_dboard_init.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/zbx_cpld_ctrl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/zbx_lo_ctrl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/zbx_expert.cpp
+ )
+ LIBUHD_APPEND_SOURCES(${X410_SOURCES})
+ENDIF(ENABLE_X400)
+
diff --git a/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp b/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
new file mode 100644
index 000000000..8899f2a18
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
@@ -0,0 +1,931 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/utils/log.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp>
+#include <chrono>
+#include <map>
+#include <thread>
+
+namespace {
+//! The time we need to wait after sending a SPI command
+const uhd::time_spec_t SPI_THROTTLE_TIME = uhd::time_spec_t(2e-6);
+} // namespace
+
+namespace uhd { namespace usrp { namespace zbx {
+
+// clang-format off
+const std::unordered_map<size_t, std::unordered_map<zbx_cpld_ctrl::dsa_type, zbx_cpld_regs_t::zbx_cpld_field_t>>
+ RX_DSA_CPLD_MAP
+{
+ {0, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA2},
+ {zbx_cpld_ctrl::dsa_type::DSA3A, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA3_A},
+ {zbx_cpld_ctrl::dsa_type::DSA3B, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA3_B}
+ }},
+ {1, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA2},
+ {zbx_cpld_ctrl::dsa_type::DSA3A, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA3_A},
+ {zbx_cpld_ctrl::dsa_type::DSA3B, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA3_B}
+ }}
+};
+
+const std::unordered_map<size_t, std::unordered_map<zbx_cpld_ctrl::dsa_type, zbx_cpld_regs_t::zbx_cpld_field_t>>
+ TX_DSA_CPLD_MAP
+{
+ {0, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::TX0_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::TX0_DSA2}
+ }},
+ {1, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::TX1_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::TX1_DSA2}
+ }}
+};
+// clang-format on
+
+
+const std::unordered_map<std::string, zbx_cpld_ctrl::dsa_type> zbx_cpld_ctrl::dsa_map{
+ {ZBX_GAIN_STAGE_DSA1, zbx_cpld_ctrl::dsa_type::DSA1},
+ {ZBX_GAIN_STAGE_DSA2, zbx_cpld_ctrl::dsa_type::DSA2},
+ {ZBX_GAIN_STAGE_DSA3A, zbx_cpld_ctrl::dsa_type::DSA3A},
+ {ZBX_GAIN_STAGE_DSA3B, zbx_cpld_ctrl::dsa_type::DSA3B}};
+
+zbx_cpld_ctrl::zbx_cpld_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))
+ , _lo_spi_offset(_regs.get_addr("SPI_READY"))
+ , _log_id(log_id)
+{
+ UHD_LOG_TRACE(_log_id, "Entering CPLD ctor...");
+ // Reset and stash the regs state. We can't assume the defaults in
+ // gen_zbx_cpld_regs.py match what's on the hardware.
+ commit(NO_CHAN, true);
+ _regs.save_state();
+}
+
+void zbx_cpld_ctrl::set_scratch(const uint32_t value)
+{
+ _regs.SCRATCH = value;
+ commit(NO_CHAN);
+}
+
+uint32_t zbx_cpld_ctrl::get_scratch()
+{
+ return _peek32(_regs.get_addr("SCRATCH"));
+}
+
+void zbx_cpld_ctrl::set_atr_mode(
+ const size_t channel, const atr_mode_target target, const atr_mode mode)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ if (target == atr_mode_target::DSA) {
+ if (channel == 0) {
+ _regs.RF0_DSA_OPTION = static_cast<zbx_cpld_regs_t::RF0_DSA_OPTION_t>(mode);
+ } else {
+ _regs.RF1_DSA_OPTION = static_cast<zbx_cpld_regs_t::RF1_DSA_OPTION_t>(mode);
+ }
+ } else {
+ if (channel == 0) {
+ _regs.RF0_OPTION = static_cast<zbx_cpld_regs_t::RF0_OPTION_t>(mode);
+ } else {
+ _regs.RF1_OPTION = static_cast<zbx_cpld_regs_t::RF1_OPTION_t>(mode);
+ }
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_sw_config(
+ const size_t channel, const atr_mode_target target, const uint8_t rf_config)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ // clang-format off
+ static const std::map<std::pair<size_t, atr_mode_target>, zbx_cpld_regs_t::zbx_cpld_field_t>
+ mode_map{
+ {{0, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF0_CONFIG },
+ {{1, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF1_CONFIG },
+ {{0, atr_mode_target::DSA }, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF0_DSA_CONFIG},
+ {{1, atr_mode_target::DSA }, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF1_DSA_CONFIG}
+ };
+ // clang-format on
+ _regs.set_field(mode_map.at({channel, target}), rf_config);
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+uint8_t zbx_cpld_ctrl::get_current_config(
+ const size_t channel, const atr_mode_target target)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ const uint16_t addr = _regs.get_addr("CURRENT_RF0_CONFIG");
+ const uint32_t config_reg = _peek32(addr);
+ _regs.set_reg(addr, config_reg);
+ _regs.save_state();
+ // clang-format off
+ static const std::map<std::pair<size_t, atr_mode_target>, zbx_cpld_regs_t::zbx_cpld_field_t>
+ mode_map{
+ {{0, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF0_CONFIG },
+ {{1, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF1_CONFIG },
+ {{0, atr_mode_target::DSA}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF0_DSA_CONFIG},
+ {{1, atr_mode_target::DSA}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF1_DSA_CONFIG}
+ };
+ // clang-format on
+ return _regs.get_field(mode_map.at({channel, target}));
+}
+
+void zbx_cpld_ctrl::set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const tx_dsa_type& dsa_steps)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+
+ UHD_LOG_TRACE(_log_id,
+ "Set TX DSA for channel " << channel << ": DSA1=" << dsa_steps[0] << ", DSA2="
+ << dsa_steps[1] << ", AMP=" << dsa_steps[2]);
+ if (channel == 0) {
+ _regs.TX0_DSA1[idx] = dsa_steps[0];
+ _regs.TX0_DSA2[idx] = dsa_steps[1];
+ } else if (channel == 1) {
+ _regs.TX1_DSA1[idx] = dsa_steps[0];
+ _regs.TX1_DSA2[idx] = dsa_steps[1];
+ }
+ // Correct amp path gets configured by switch_tx_antenna_switches()
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const rx_dsa_type& dsa_steps)
+{
+ UHD_LOG_TRACE(_log_id,
+ "Setting RX DSA for channel "
+ << channel << ": DSA1=" << dsa_steps[0] << ", DSA2=" << dsa_steps[1]
+ << ", DSA3A=" << dsa_steps[2] << ", DSA3B=" << dsa_steps[3]);
+ if (channel == 0) {
+ _regs.RX0_DSA1[idx] = dsa_steps[0];
+ _regs.RX0_DSA2[idx] = dsa_steps[1];
+ _regs.RX0_DSA3_A[idx] = dsa_steps[2];
+ _regs.RX0_DSA3_B[idx] = dsa_steps[3];
+ } else if (channel == 1) {
+ _regs.RX1_DSA1[idx] = dsa_steps[0];
+ _regs.RX1_DSA2[idx] = dsa_steps[1];
+ _regs.RX1_DSA3_A[idx] = dsa_steps[2];
+ _regs.RX1_DSA3_B[idx] = dsa_steps[3];
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ UHD_LOG_TRACE(_log_id,
+ "Setting RX DSA for channel " << channel << " from table index " << table_idx);
+ if (channel == 0) {
+ _regs.RX0_TABLE_SELECT[idx] = table_idx;
+ } else {
+ _regs.RX1_TABLE_SELECT[idx] = table_idx;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ UHD_LOG_TRACE(_log_id,
+ "Setting TX DSA for channel " << channel << " from table index " << table_idx);
+ if (channel == 0) {
+ _regs.TX0_TABLE_SELECT[idx] = table_idx;
+ } else {
+ _regs.TX1_TABLE_SELECT[idx] = table_idx;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+uint8_t zbx_cpld_ctrl::set_tx_dsa(
+ const size_t channel, const uint8_t idx, const dsa_type tx_dsa, const uint8_t att)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ UHD_ASSERT_THROW(tx_dsa == dsa_type::DSA1 || tx_dsa == dsa_type::DSA2);
+ const uint8_t att_coerced = std::min(att, ZBX_TX_DSA_MAX_ATT);
+ _regs.set_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), att_coerced, idx);
+ commit(channel == 0 ? CHAN0 : CHAN1);
+ return att_coerced;
+}
+
+uint8_t zbx_cpld_ctrl::set_rx_dsa(
+ const size_t channel, const uint8_t idx, const dsa_type rx_dsa, const uint8_t att)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ const uint8_t att_coerced = std::min(att, ZBX_RX_DSA_MAX_ATT);
+ _regs.set_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), att_coerced, idx);
+ commit(channel == 0 ? CHAN0 : CHAN1);
+ return att_coerced;
+}
+
+uint8_t zbx_cpld_ctrl::get_tx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type tx_dsa,
+ const bool update_cache)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ UHD_ASSERT_THROW(tx_dsa == dsa_type::DSA1 || tx_dsa == dsa_type::DSA2);
+ if (update_cache) {
+ update_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), idx);
+ }
+ return _regs.get_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), idx);
+}
+
+uint8_t zbx_cpld_ctrl::get_rx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type rx_dsa,
+ const bool update_cache)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ if (update_cache) {
+ update_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), idx);
+ }
+ return _regs.get_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), idx);
+}
+
+void zbx_cpld_ctrl::set_tx_antenna_switches(
+ const size_t channel, const uint8_t idx, const std::string& antenna, const tx_amp amp)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ UHD_ASSERT_THROW(
+ amp == tx_amp::BYPASS || amp == tx_amp::LOWBAND || amp == tx_amp::HIGHBAND);
+
+ // Antenna settings: TX/RX, CAL_LOOPBACK
+ if (channel == 0) {
+ if (antenna == ANTENNA_TXRX) {
+ // clang-format off
+ static const std::map<tx_amp,
+ std::pair<zbx_cpld_regs_t::TX0_ANT_11_t, zbx_cpld_regs_t::TX0_ANT_10_t>> amp_map{
+ {tx_amp::BYPASS, {zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP, zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP}},
+ {tx_amp::LOWBAND, {zbx_cpld_regs_t::TX0_ANT_11_LOWBAND_AMP, zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP}},
+ {tx_amp::HIGHBAND, {zbx_cpld_regs_t::TX0_ANT_11_HIGHBAND_AMP, zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP}}
+ };
+ // clang-format on
+ std::tie(_regs.TX0_ANT_11[idx], _regs.TX0_ANT_10[idx]) = amp_map.at(amp);
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.TX0_ANT_10[idx] = zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK;
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_CAL_LOOPBACK;
+ _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: TX Antenna setting not recognized: \"" << antenna.c_str()
+ << "\"");
+ }
+ } else {
+ // Antenna settings: TX/RX, CAL_LOOPBACK
+ if (antenna == ANTENNA_TXRX) {
+ // clang-format off
+ static const std::map<tx_amp,
+ std::pair<zbx_cpld_regs_t::TX1_ANT_11_t, zbx_cpld_regs_t::TX1_ANT_10_t>> amp_map{
+ {tx_amp::BYPASS, {zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP, zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP}},
+ {tx_amp::LOWBAND, {zbx_cpld_regs_t::TX1_ANT_11_LOWBAND_AMP, zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP}},
+ {tx_amp::HIGHBAND, {zbx_cpld_regs_t::TX1_ANT_11_HIGHBAND_AMP, zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP}}
+ };
+ // clang-format on
+ std::tie(_regs.TX1_ANT_11[idx], _regs.TX1_ANT_10[idx]) = amp_map.at(amp);
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.TX1_ANT_10[idx] = zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK;
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_CAL_LOOPBACK;
+ _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: TX Antenna setting not recognized: \"" << antenna << "\"");
+ }
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_antenna_switches(
+ const size_t channel, const uint8_t idx, const std::string& antenna)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+
+ // Antenna settings: RX2, TX/RX, CAL_LOOPBACK, TERMINATION
+ if (channel == 0) {
+ if (antenna == ANTENNA_TXRX) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_TX_RX;
+ _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_TX_RX;
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_CAL_LOOPBACK;
+ _regs.TX0_ANT_10[idx] = zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK;
+ _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP;
+ } else if (antenna == ANTENNA_TERMINATION) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_TERMINATION;
+ } else if (antenna == ANTENNA_RX) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_RX2;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: RX Antenna setting not recognized: \"" << antenna << "\"");
+ }
+ } else {
+ if (antenna == ANTENNA_TXRX) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_TX_RX;
+ _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_TX_RX;
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_CAL_LOOPBACK;
+ _regs.TX1_ANT_10[idx] = zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK;
+ _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP;
+ } else if (antenna == ANTENNA_TERMINATION) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_TERMINATION;
+ } else if (antenna == ANTENNA_RX) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_RX2;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: RX Antenna setting not recognized: \"" << antenna << "\"");
+ }
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+tx_amp zbx_cpld_ctrl::get_tx_amp_settings(
+ const size_t channel, const uint8_t idx, const bool update_cache)
+{
+ if (channel == 0) {
+ if (update_cache) {
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_10, idx);
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_11, idx);
+ }
+ if ((_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP
+ && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP)
+ || (_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_HIGHBAND_AMP
+ && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP)
+ || (_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_LOWBAND_AMP
+ && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP)) {
+ UHD_LOG_WARNING(
+ _log_id, "Detected inconsistency in the TX amp switch settings.");
+ }
+ // clang-format off
+ static const std::map<zbx_cpld_regs_t::TX0_ANT_10_t, tx_amp> amp_map{
+ {zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP , tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK, tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP , tx_amp::LOWBAND },
+ {zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP, tx_amp::HIGHBAND}
+ };
+ // clang-format on
+ return amp_map.at(_regs.TX0_ANT_10[idx]);
+ }
+ if (channel == 1) {
+ if (update_cache) {
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_10, idx);
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_11, idx);
+ }
+ if ((_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP
+ && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP)
+ || (_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_HIGHBAND_AMP
+ && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP)
+ || (_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_LOWBAND_AMP
+ && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP)) {
+ UHD_LOG_WARNING(
+ _log_id, "Detected inconsistency in the TX amp switch settings.");
+ }
+ // clang-format off
+ static const std::map<zbx_cpld_regs_t::TX1_ANT_10_t, tx_amp> amp_map{
+ {zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP , tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK, tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP , tx_amp::LOWBAND },
+ {zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP, tx_amp::HIGHBAND}
+ };
+ // clang-format on
+ return amp_map.at(_regs.TX1_ANT_10[idx]);
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+void zbx_cpld_ctrl::set_rx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && rf_fir < 4);
+
+ if (rf_fir == 0) {
+ if (channel == 0) {
+ _regs.RX0_4[idx] = zbx_cpld_regs_t::RX0_4_HIGHBAND;
+ _regs.RX0_2[idx] = zbx_cpld_regs_t::RX0_2_HIGHBAND;
+ } else {
+ _regs.RX1_4[idx] = zbx_cpld_regs_t::RX1_4_HIGHBAND;
+ _regs.RX1_2[idx] = zbx_cpld_regs_t::RX1_2_HIGHBAND;
+ }
+ } else {
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.RX0_4[idx] = zbx_cpld_regs_t::RX0_4_LOWBAND;
+ _regs.RX0_2[idx] = zbx_cpld_regs_t::RX0_2_LOWBAND;
+ _regs.RX0_RF_11[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX0_RF_11_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX0_RF_11_RF_2
+ : zbx_cpld_regs_t::RX0_RF_11_RF_3;
+ _regs.RX0_RF_3[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX0_RF_3_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX0_RF_3_RF_2
+ : zbx_cpld_regs_t::RX0_RF_3_RF_3;
+ } else {
+ _regs.RX1_4[idx] = zbx_cpld_regs_t::RX1_4_LOWBAND;
+ _regs.RX1_2[idx] = zbx_cpld_regs_t::RX1_2_LOWBAND;
+ _regs.RX1_RF_11[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX1_RF_11_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX1_RF_11_RF_2
+ : zbx_cpld_regs_t::RX1_RF_11_RF_3;
+ _regs.RX1_RF_3[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX1_RF_3_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX1_RF_3_RF_2
+ : zbx_cpld_regs_t::RX1_RF_3_RF_3;
+ }
+ // clang-format on
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if1_fir != 0 && if1_fir < 5);
+
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.RX0_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_3
+ : zbx_cpld_regs_t::RX0_IF1_5_FILTER_4;
+
+ _regs.RX0_IF1_6[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_3
+ : zbx_cpld_regs_t::RX0_IF1_6_FILTER_4;
+ } else {
+ _regs.RX1_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_3
+ : zbx_cpld_regs_t::RX1_IF1_5_FILTER_4;
+
+ _regs.RX1_IF1_6[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_3
+ : zbx_cpld_regs_t::RX1_IF1_6_FILTER_4;
+ }
+ // clang-format on
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if2_fir != 0 && if2_fir < 3);
+
+ if (channel == 0) {
+ _regs.RX0_IF2_7_8[idx] = if2_fir == 1 ? zbx_cpld_regs_t::RX0_IF2_7_8_FILTER_1
+ : zbx_cpld_regs_t::RX0_IF2_7_8_FILTER_2;
+ } else {
+ _regs.RX1_IF2_7_8[idx] = if2_fir == 1 ? zbx_cpld_regs_t::RX1_IF2_7_8_FILTER_1
+ : zbx_cpld_regs_t::RX1_IF2_7_8_FILTER_2;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && rf_fir < 4);
+
+ if (rf_fir == 0) {
+ if (channel == 0) {
+ _regs.TX0_RF_9[idx] = zbx_cpld_regs_t::TX0_RF_9_HIGHBAND;
+ _regs.TX0_7[idx] = zbx_cpld_regs_t::TX0_7_HIGHBAND;
+ } else {
+ _regs.TX1_RF_9[idx] = zbx_cpld_regs_t::TX1_RF_9_HIGHBAND;
+ _regs.TX1_7[idx] = zbx_cpld_regs_t::TX1_7_HIGHBAND;
+ }
+ } else {
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.TX0_RF_9[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX0_RF_9_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX0_RF_9_RF_2
+ : zbx_cpld_regs_t::TX0_RF_9_RF_3;
+
+ _regs.TX0_RF_8[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX0_RF_8_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX0_RF_8_RF_2
+ : zbx_cpld_regs_t::TX0_RF_8_RF_3;
+ _regs.TX0_7[idx] = zbx_cpld_regs_t::TX0_7_LOWBAND;
+ } else {
+ _regs.TX1_RF_9[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX1_RF_9_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX1_RF_9_RF_2
+ : zbx_cpld_regs_t::TX1_RF_9_RF_3;
+
+ _regs.TX1_RF_8[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX1_RF_8_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX1_RF_8_RF_2
+ : zbx_cpld_regs_t::TX1_RF_8_RF_3;
+ _regs.TX1_7[idx] = zbx_cpld_regs_t::TX1_7_LOWBAND;
+ }
+ // clang-format on
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if1_fir != 0 && if1_fir < 7);
+
+ if (if1_fir < 4) {
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.TX0_IF1_6[idx] = zbx_cpld_regs_t::TX0_IF1_6_FILTER_0_3;
+ _regs.TX0_IF1_3[idx] = zbx_cpld_regs_t::TX0_IF1_3_FILTER_0_3;
+ _regs.TX0_IF1_4[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX0_IF1_4_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX0_IF1_4_FILTER_2
+ : zbx_cpld_regs_t::TX0_IF1_4_FILTER_3;
+
+ _regs.TX0_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX0_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX0_IF1_5_FILTER_2
+ : zbx_cpld_regs_t::TX0_IF1_5_FILTER_3;
+ } else {
+ _regs.TX1_IF1_6[idx] = zbx_cpld_regs_t::TX1_IF1_6_FILTER_0_3;
+ _regs.TX1_IF1_3[idx] = zbx_cpld_regs_t::TX1_IF1_3_FILTER_0_3;
+ _regs.TX1_IF1_4[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX1_IF1_4_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX1_IF1_4_FILTER_2
+ : zbx_cpld_regs_t::TX1_IF1_4_FILTER_3;
+
+ _regs.TX1_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX1_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX1_IF1_5_FILTER_2
+ : zbx_cpld_regs_t::TX1_IF1_5_FILTER_3;
+ }
+ } else {
+ if (channel == 0) {
+ _regs.TX0_IF1_4[idx] = zbx_cpld_regs_t::TX0_IF1_4_TERMINATION;
+ _regs.TX0_IF1_5[idx] = zbx_cpld_regs_t::TX0_IF1_5_TERMINATION;
+ _regs.TX0_IF1_3[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX0_IF1_3_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX0_IF1_3_FILTER_5
+ : zbx_cpld_regs_t::TX0_IF1_3_FILTER_6;
+
+ _regs.TX0_IF1_6[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX0_IF1_6_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX0_IF1_6_FILTER_5
+ : zbx_cpld_regs_t::TX0_IF1_6_FILTER_6;
+ } else {
+ _regs.TX1_IF1_4[idx] = zbx_cpld_regs_t::TX1_IF1_4_TERMINATION;
+ _regs.TX1_IF1_5[idx] = zbx_cpld_regs_t::TX1_IF1_5_TERMINATION;
+ _regs.TX1_IF1_3[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX1_IF1_3_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX1_IF1_3_FILTER_5
+ : zbx_cpld_regs_t::TX1_IF1_3_FILTER_6;
+
+ _regs.TX1_IF1_6[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX1_IF1_6_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX1_IF1_6_FILTER_5
+ : zbx_cpld_regs_t::TX1_IF1_6_FILTER_6;
+ }
+ // clang-format on
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if2_fir != 0 && if2_fir < 3);
+
+ if (channel == 0) {
+ _regs.TX0_IF2_1_2[idx] = if2_fir == 1 ? zbx_cpld_regs_t::TX0_IF2_1_2_FILTER_1
+ : zbx_cpld_regs_t::TX0_IF2_1_2_FILTER_2;
+ } else {
+ _regs.TX1_IF2_1_2[idx] = if2_fir == 1 ? zbx_cpld_regs_t::TX1_IF2_1_2_FILTER_1
+ : zbx_cpld_regs_t::TX1_IF2_1_2_FILTER_2;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+/******************************************************************************
+ * LED control
+ *****************************************************************************/
+void zbx_cpld_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 < ZBX_NUM_CHANS);
+ if (channel == 0) {
+ _regs.RX0_RX_LED[idx] = rx ? zbx_cpld_regs_t::RX0_RX_LED_ENABLE
+ : zbx_cpld_regs_t::RX0_RX_LED_DISABLE;
+ _regs.RX0_TRX_LED[idx] = trx_rx ? zbx_cpld_regs_t::RX0_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::RX0_TRX_LED_DISABLE;
+ _regs.TX0_TRX_LED[idx] = trx_tx ? zbx_cpld_regs_t::TX0_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::TX0_TRX_LED_DISABLE;
+ } else {
+ _regs.RX1_RX_LED[idx] = rx ? zbx_cpld_regs_t::RX1_RX_LED_ENABLE
+ : zbx_cpld_regs_t::RX1_RX_LED_DISABLE;
+ _regs.RX1_TRX_LED[idx] = trx_rx ? zbx_cpld_regs_t::RX1_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::RX1_TRX_LED_DISABLE;
+ _regs.TX1_TRX_LED[idx] = trx_tx ? zbx_cpld_regs_t::TX1_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::TX1_TRX_LED_DISABLE;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+/******************************************************************************
+ * LO control
+ *****************************************************************************/
+void zbx_cpld_ctrl::lo_poke16(const zbx_lo_t lo, const uint8_t addr, const uint16_t data)
+{
+ _lo_spi_transact(lo, addr, data, spi_xact_t::WRITE, true);
+ // We always sleep here, in the assumption that the next poke to the CPLD is
+ // also a
+ // SPI transaction.
+ // Note that this causes minor inefficiencies when stacking SPI writes with
+ // other, non-SPI pokes (because the last SPI poke will still be followed by
+ // a sleep, which isn't even necessary). If this becomes an issue, this
+ // function can be changed to include a flag as an argument whether or not
+ // to throttle.
+}
+
+uint16_t zbx_cpld_ctrl::lo_peek16(const zbx_lo_t lo, const uint8_t addr)
+{
+ _lo_spi_transact(lo, addr, 0, spi_xact_t::READ, true);
+ // Now poll the LO_SPI_READY register until we have good return value
+ const auto timeout = std::chrono::steady_clock::now()
+ + std::chrono::milliseconds(ZBX_LO_LOCK_TIMEOUT_MS);
+ while (std::chrono::steady_clock::now() < timeout) {
+ _regs.set_reg(_lo_spi_offset, _peek32(_lo_spi_offset));
+ if (_regs.DATA_VALID) {
+ break;
+ }
+ }
+
+ // Mark this register clean again
+ _regs.save_state();
+ if (!_regs.DATA_VALID) {
+ const std::string err_msg =
+ "Unable to read back from LO SPI! Transaction timed out after "
+ + std::to_string(ZBX_LO_LOCK_TIMEOUT_MS) + " ms.";
+ UHD_LOG_ERROR(_log_id, err_msg);
+ throw uhd::io_error(err_msg);
+ }
+ // The read worked. Now we run some sanity checks to make sure we got what
+ // we expected
+ UHD_ASSERT_THROW(_regs.ADDRESS == addr);
+ UHD_ASSERT_THROW(_regs.LO_SELECT == zbx_cpld_regs_t::LO_SELECT_t(lo));
+ // All good, return the read value
+ return _regs.DATA;
+}
+
+bool zbx_cpld_ctrl::lo_spi_ready()
+{
+ return _peek32(_lo_spi_offset) & (1 << 30);
+}
+
+void zbx_cpld_ctrl::set_lo_source(
+ const size_t idx, const zbx_lo_t lo, const zbx_lo_source_t lo_source)
+{
+ // LO source is either internal or external
+ const bool internal = lo_source == zbx_lo_source_t::internal;
+ switch (lo) {
+ case zbx_lo_t::TX0_LO1:
+ _regs.TX0_LO_14[idx] = internal ? zbx_cpld_regs_t::TX0_LO_14_INTERNAL
+ : zbx_cpld_regs_t::TX0_LO_14_EXTERNAL;
+ break;
+ case zbx_lo_t::TX0_LO2:
+ _regs.TX0_LO_13[idx] = internal ? zbx_cpld_regs_t::TX0_LO_13_INTERNAL
+ : zbx_cpld_regs_t::TX0_LO_13_EXTERNAL;
+ break;
+ case zbx_lo_t::TX1_LO1:
+ _regs.TX1_LO_14[idx] = internal ? zbx_cpld_regs_t::TX1_LO_14_INTERNAL
+ : zbx_cpld_regs_t::TX1_LO_14_EXTERNAL;
+ break;
+ case zbx_lo_t::TX1_LO2:
+ _regs.TX1_LO_13[idx] = internal ? zbx_cpld_regs_t::TX1_LO_13_INTERNAL
+ : zbx_cpld_regs_t::TX1_LO_13_EXTERNAL;
+ break;
+ case zbx_lo_t::RX0_LO1:
+ _regs.RX0_LO_9[idx] = internal ? zbx_cpld_regs_t::RX0_LO_9_INTERNAL
+ : zbx_cpld_regs_t::RX0_LO_9_EXTERNAL;
+ break;
+ case zbx_lo_t::RX0_LO2:
+ _regs.RX0_LO_10[idx] = internal ? zbx_cpld_regs_t::RX0_LO_10_INTERNAL
+ : zbx_cpld_regs_t::RX0_LO_10_EXTERNAL;
+ break;
+ case zbx_lo_t::RX1_LO1:
+ _regs.RX1_LO_9[idx] = internal ? zbx_cpld_regs_t::RX1_LO_9_INTERNAL
+ : zbx_cpld_regs_t::RX1_LO_9_EXTERNAL;
+ break;
+ case zbx_lo_t::RX1_LO2:
+ _regs.RX1_LO_10[idx] = internal ? zbx_cpld_regs_t::RX1_LO_10_INTERNAL
+ : zbx_cpld_regs_t::RX1_LO_10_EXTERNAL;
+ break;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ if (lo == zbx_lo_t::TX0_LO1 || lo == zbx_lo_t::TX0_LO2 || lo == zbx_lo_t::RX0_LO1
+ || lo == zbx_lo_t::RX0_LO2) {
+ commit(CHAN0);
+ } else {
+ commit(CHAN1);
+ }
+}
+
+zbx_lo_source_t zbx_cpld_ctrl::get_lo_source(const size_t idx, zbx_lo_t lo)
+{
+ switch (lo) {
+ case zbx_lo_t::TX0_LO1:
+ return _regs.TX0_LO_14[idx] == zbx_cpld_regs_t::TX0_LO_14_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::TX0_LO2:
+ return _regs.TX0_LO_13[idx] == zbx_cpld_regs_t::TX0_LO_13_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::TX1_LO1:
+ return _regs.TX1_LO_14[idx] == zbx_cpld_regs_t::TX1_LO_14_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::TX1_LO2:
+ return _regs.TX1_LO_13[idx] == zbx_cpld_regs_t::TX1_LO_13_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX0_LO1:
+ return _regs.RX0_LO_9[idx] == zbx_cpld_regs_t::RX0_LO_9_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX0_LO2:
+ return _regs.RX0_LO_10[idx] == zbx_cpld_regs_t::RX0_LO_10_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX1_LO1:
+ return _regs.RX1_LO_9[idx] == zbx_cpld_regs_t::RX1_LO_9_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX1_LO2:
+ return _regs.RX1_LO_10[idx] == zbx_cpld_regs_t::RX1_LO_10_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+void zbx_cpld_ctrl::pulse_lo_sync(const size_t ref_chan, const std::vector<zbx_lo_t>& los)
+{
+ if (_regs.BYPASS_SYNC_REGISTER == zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_ENABLE) {
+ const std::string err_msg = "Cannot pulse LO SYNC when bypass is enabled!";
+ UHD_LOG_ERROR(_log_id, err_msg);
+ throw uhd::runtime_error(_log_id + err_msg);
+ }
+ // Assert a 1 for all LOs to be sync'd
+ static const std::unordered_map<zbx_lo_t, zbx_cpld_regs_t::zbx_cpld_field_t>
+ lo_pulse_map{{
+ {zbx_lo_t::TX0_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX0_LO1_SYNC},
+ {zbx_lo_t::TX0_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX0_LO2_SYNC},
+ {zbx_lo_t::TX1_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX1_LO1_SYNC},
+ {zbx_lo_t::TX1_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX1_LO2_SYNC},
+ {zbx_lo_t::RX0_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX0_LO1_SYNC},
+ {zbx_lo_t::RX0_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX0_LO2_SYNC},
+ {zbx_lo_t::RX1_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX1_LO1_SYNC},
+ {zbx_lo_t::RX1_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX1_LO2_SYNC},
+ }};
+ for (const auto lo : los) {
+ _regs.set_field(lo_pulse_map.at(lo), 1);
+ }
+ commit(ref_chan == 0 ? CHAN0 : CHAN1);
+ // The bits are strobed, they self-clear. We reflect that here by resetting
+ // them without another commit:
+ for (const auto lo_it : lo_pulse_map) {
+ _regs.set_field(lo_it.second, 0);
+ }
+ _regs.save_state();
+}
+
+void zbx_cpld_ctrl::set_lo_sync_bypass(const bool enable)
+{
+ _regs.BYPASS_SYNC_REGISTER = enable ? zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_ENABLE
+ : zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_DISABLE;
+ commit(NO_CHAN);
+}
+
+void zbx_cpld_ctrl::update_tx_dsa_settings(
+ const std::vector<uint32_t>& dsa1_table, const std::vector<uint32_t>& dsa2_table)
+{
+ write_register_vector("TX0_TABLE_DSA1", dsa1_table);
+ write_register_vector("TX0_TABLE_DSA2", dsa2_table);
+ write_register_vector("TX1_TABLE_DSA1", dsa1_table);
+ write_register_vector("TX1_TABLE_DSA2", dsa2_table);
+ commit(NO_CHAN);
+}
+
+void zbx_cpld_ctrl::update_rx_dsa_settings(const std::vector<uint32_t>& dsa1_table,
+ const std::vector<uint32_t>& dsa2_table,
+ const std::vector<uint32_t>& dsa3a_table,
+ const std::vector<uint32_t>& dsa3b_table)
+{
+ write_register_vector("RX0_TABLE_DSA1", dsa1_table);
+ write_register_vector("RX0_TABLE_DSA2", dsa2_table);
+ write_register_vector("RX0_TABLE_DSA3_A", dsa3a_table);
+ write_register_vector("RX0_TABLE_DSA3_B", dsa3b_table);
+ write_register_vector("RX1_TABLE_DSA1", dsa1_table);
+ write_register_vector("RX1_TABLE_DSA2", dsa2_table);
+ write_register_vector("RX1_TABLE_DSA3_A", dsa3a_table);
+ write_register_vector("RX1_TABLE_DSA3_B", dsa3b_table);
+ commit(NO_CHAN);
+}
+
+/******************************************************************************
+ * Private methods
+ *****************************************************************************/
+void zbx_cpld_ctrl::_lo_spi_transact(const zbx_lo_t lo,
+ const uint8_t addr,
+ const uint16_t data,
+ const spi_xact_t xact_type,
+ const bool throttle)
+{
+ // Look up the channel based on the LO, so we can load the correct command
+ // time for the poke
+ const chan_t chan = (lo == zbx_lo_t::TX0_LO1 || lo == zbx_lo_t::TX0_LO2
+ || lo == zbx_lo_t::RX0_LO1 || lo == zbx_lo_t::RX0_LO2)
+ ? CHAN0
+ : CHAN1;
+ // Note: For SPI transactions, we can't also be lugging around other
+ // registers. This means that we assume that the state of _regs is clean.
+ _regs.ADDRESS = addr;
+ _regs.DATA = data;
+ _regs.READ_FLAG = (xact_type == spi_xact_t::WRITE) ? zbx_cpld_regs_t::READ_FLAG_WRITE
+ : zbx_cpld_regs_t::READ_FLAG_READ;
+ _regs.LO_SELECT = zbx_cpld_regs_t::LO_SELECT_t(lo);
+ _regs.START_TRANSACTION = zbx_cpld_regs_t::START_TRANSACTION_ENABLE;
+ _poke32(_lo_spi_offset, _regs.get_reg(_lo_spi_offset), chan);
+ _regs.START_TRANSACTION = zbx_cpld_regs_t::START_TRANSACTION_DISABLE;
+ _regs.save_state();
+ // Write complete. Now we need to send a sleep to throttle the SPI
+ // transactions:
+ if (throttle) {
+ _sleep(SPI_THROTTLE_TIME);
+ }
+}
+
+void zbx_cpld_ctrl::write_register_vector(
+ const std::string& reg_addr_name, const std::vector<uint32_t>& values)
+{
+ UHD_LOG_DEBUG(
+ _log_id, "Write " << values.size() << " values to register " << reg_addr_name);
+ zbx_cpld_regs_t::zbx_cpld_field_t type = _regs.get_field_type(reg_addr_name);
+ if (values.size() > _regs.get_array_size(type)) {
+ const std::string err_msg = "Number of values passed for register vector("
+ + std::to_string(values.size())
+ + ") exceeds size of register ("
+ + std::to_string(_regs.get_array_size(type)) + ")";
+ UHD_LOG_ERROR(_log_id, err_msg);
+ throw uhd::runtime_error(err_msg);
+ }
+ for (size_t i = 0; i < values.size(); i++) {
+ _regs.set_field(type, values[i], i);
+ }
+}
+
+void zbx_cpld_ctrl::commit(const chan_t chan, const bool save_all)
+{
+ UHD_LOG_TRACE(_log_id,
+ "Storing register cache " << (save_all ? "completely" : "selectively")
+ << " to CPLD...");
+ const auto changed_addrs = save_all ? _regs.get_all_addrs()
+ : _regs.get_changed_addrs<size_t>();
+ for (const auto addr : changed_addrs) {
+ _poke32(addr, _regs.get_reg(addr), save_all ? NO_CHAN : chan);
+ }
+ _regs.save_state();
+ UHD_LOG_TRACE(_log_id,
+ "Storing cache complete: "
+ "Updated "
+ << changed_addrs.size() << " registers.");
+}
+
+void zbx_cpld_ctrl::update_field(
+ const zbx_cpld_regs_t::zbx_cpld_field_t field, const size_t idx)
+{
+ const uint16_t addr = _regs.get_addr(field) + 4 * idx;
+ const uint32_t chip_val = _peek32(addr);
+ _regs.set_reg(addr, chip_val);
+ const auto changed_addrs = _regs.get_changed_addrs<size_t>();
+ // If this is the only change in our register stack, then we call save_state()
+ // because we don't want to write this value we just read from the CPLD back
+ // to it. However, if there are other changes queued up, we'll have to wait
+ // until the next commit() call. If this is not desired, we need to update
+ // the regmap code to selectively save state.
+ if (changed_addrs.empty()
+ || (changed_addrs.size() == 1 && changed_addrs.count(addr))) {
+ _regs.save_state();
+ } else {
+ UHD_LOG_DEBUG(_log_id,
+ "Not saving register state after calling update_field(). This may "
+ "cause unnecessary writes in the future.");
+ }
+}
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/usrp/dboard/zbx/zbx_dboard.cpp b/host/lib/usrp/dboard/zbx/zbx_dboard.cpp
new file mode 100644
index 000000000..d41302c8f
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/zbx_dboard.cpp
@@ -0,0 +1,758 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/types/direction.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <cstdlib>
+#include <sstream>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+/******************************************************************************
+ * Structors
+ *****************************************************************************/
+zbx_dboard_impl::zbx_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 std::string& rpc_prefix,
+ const std::string& unique_id,
+ uhd::usrp::x400_rpc_iface::sptr mb_rpcc,
+ uhd::usrp::zbx_rpc_iface::sptr rpcc,
+ uhd::rfnoc::x400::rfdc_control::sptr rfdcc,
+ uhd::property_tree::sptr tree)
+ : _unique_id(unique_id)
+ , _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())
+ , _prc_rate(_rpcc->get_dboard_prc_rate())
+{
+ RFNOC_LOG_TRACE("Entering zbx_dboard_impl ctor...");
+ RFNOC_LOG_TRACE("Radio slot: " << _radio_slot);
+
+ _tx_gain_profile_api = std::make_shared<rf_control::enumerated_gain_profile>(
+ ZBX_GAIN_PROFILES, ZBX_GAIN_PROFILE_DEFAULT, ZBX_NUM_CHANS);
+ _rx_gain_profile_api = std::make_shared<rf_control::enumerated_gain_profile>(
+ ZBX_GAIN_PROFILES, ZBX_GAIN_PROFILE_DEFAULT, ZBX_NUM_CHANS);
+
+ _expert_container =
+ uhd::experts::expert_factory::create_container("zbx_radio_" + _radio_slot);
+ _init_cpld();
+ _init_peripherals();
+ // Prop tree requires the initialization of certain peripherals
+ _init_prop_tree();
+ _expert_container->resolve_all();
+}
+
+zbx_dboard_impl::~zbx_dboard_impl()
+{
+ RFNOC_LOG_TRACE("zbx_dboard::dtor() ");
+}
+
+void zbx_dboard_impl::deinit()
+{
+ _wb_ifaces.clear();
+}
+
+void zbx_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 zbx_dboard_impl::get_unique_id() const
+{
+ return _unique_id;
+}
+
+
+/******************************************************************************
+ * API Calls
+ *****************************************************************************/
+void zbx_dboard_impl::set_tx_antenna(const std::string& ant, const size_t chan)
+{
+ RFNOC_LOG_TRACE("Setting TX antenna to " << ant << " for chan " << chan);
+ if (!TX_ANTENNA_NAME_COMPAT_MAP.count(ant)) {
+ assert_has(TX_ANTENNAS, ant, "tx antenna");
+ }
+ const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
+
+ _tree->access<std::string>(fe_path / "antenna" / "value").set(ant);
+}
+
+void zbx_dboard_impl::set_rx_antenna(const std::string& ant, const size_t chan)
+{
+ RFNOC_LOG_TRACE("Setting RX antenna to " << ant << " for chan " << chan);
+ if (!RX_ANTENNA_NAME_COMPAT_MAP.count(ant)) {
+ assert_has(RX_ANTENNAS, ant, "rx antenna");
+ }
+
+ const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
+
+ _tree->access<std::string>(fe_path / "antenna" / "value").set(ant);
+}
+
+double zbx_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);
+
+ // Our power manager sets a new gain value via the API, based on its new calculations.
+ // Since the expert nodes are protected by a mutex, it will hang if we try to call
+ // update_power() from inside the expert resolve methods (resolve() -> update_power()
+ // -> set_tx_gain -> resolve())
+ _tx_pwr_mgr.at(chan)->update_power();
+
+ return _tree->access<double>(fe_path / "freq").get();
+}
+
+double zbx_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);
+
+ // Our power manager sets a new gain value via the API, based on its new calculations.
+ // Since the expert nodes are protected by a mutex, it will hang if we try to call
+ // update_power() from inside the expert resolve methods (resolve() -> update_power()
+ // -> set_rx_gain -> resolve())
+ _rx_pwr_mgr.at(chan)->update_power();
+
+ return _tree->access<double>(fe_path / "freq").get();
+}
+
+double zbx_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 zbx_dboard_impl::get_tx_bandwidth(size_t chan)
+{
+ return _tree
+ ->access<double>(_get_frontend_path(TX_DIRECTION, chan) / "bandwidth/value")
+ .get();
+}
+
+double zbx_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 zbx_dboard_impl::get_rx_bandwidth(size_t chan)
+{
+ return _tree
+ ->access<double>(_get_frontend_path(RX_DIRECTION, chan) / "bandwidth/value")
+ .get();
+}
+
+double zbx_dboard_impl::set_tx_gain(
+ const double gain, const std::string& name_, const size_t chan)
+{
+ // We have to accept the empty string for "all", because that's widely used
+ // (e.g. by multi_usrp)
+ const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_;
+ const fs_path gains_path = _get_frontend_path(TX_DIRECTION, chan) / "gains";
+ const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan);
+ // Default gain profile: Setting anything other than 'all' is forbidden
+ if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT && name != ZBX_GAIN_STAGE_ALL) {
+ throw uhd::key_error("Invalid gain name for gain profile 'default': " + name);
+ }
+ // Also, when the gain name is all, we have to be in default mode.
+ if (gain_profile != ZBX_GAIN_PROFILE_DEFAULT && name == ZBX_GAIN_STAGE_ALL) {
+ throw uhd::key_error(
+ "Setting overall gain is only valid in gain profile 'default'!");
+ }
+ // The combination of the no-ATR profile, and any gain name other than 'table'
+ // is not valid.
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR && name != ZBX_GAIN_STAGE_TABLE) {
+ throw uhd::key_error("set_tx_gain(): Invalid combination of gain profile "
+ + gain_profile + " and gain name " + name);
+ }
+ // First, we handle the 'table' gain name. It's handled a bit differently
+ // than the rest.
+ if (name == ZBX_GAIN_STAGE_TABLE) {
+ static const uhd::meta_range_t table_range(0, 255, 1);
+ const uint8_t table_idx = uhd::narrow<uint8_t>(table_range.clip(gain, true));
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ _cpld->set_sw_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA, table_idx);
+ return static_cast<double>(table_idx);
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_MANUAL
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD) {
+ _cpld->set_tx_gain_switches(chan, ATR_ADDR_TX, table_idx);
+ _cpld->set_tx_gain_switches(chan, ATR_ADDR_XX, table_idx);
+ return static_cast<double>(table_idx);
+ }
+ // That covers all the gain profiles for gain name 'table'.
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ // Sanity check key. Note we do this after the previous gain stage, because
+ // it's not a property node.
+ if (!_tree->exists(gains_path / name)) {
+ throw uhd::key_error("Invalid TX gain stage: " + name);
+ }
+ // This leaves directly setting either the DSAs or the amplifier. This is
+ // possible in both the manual and CPLD gain profiles.
+ return _tree->access<double>(gains_path / name / "value").set(gain).get();
+}
+
+double zbx_dboard_impl::set_rx_gain(
+ const double gain, const std::string& name_, const size_t chan)
+{
+ // We have to accept the empty string for "all", because that's widely used
+ // (e.g. by multi_usrp).
+ const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_;
+ const fs_path gains_path = _get_frontend_path(RX_DIRECTION, chan) / "gains";
+ const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan);
+
+ // Default gain profile: Setting anything other than ZBX_GAIN_STAGE_ALL is forbidden
+ if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT && name != ZBX_GAIN_STAGE_ALL) {
+ throw uhd::key_error("Invalid gain name for gain profile 'default': " + name);
+ }
+ // Also, when the gain name is all, we have to be in default mode.
+ if (gain_profile != ZBX_GAIN_PROFILE_DEFAULT && name == ZBX_GAIN_STAGE_ALL) {
+ throw uhd::key_error(
+ "Setting overall gain is only valid in gain profile 'default'!");
+ }
+ // The combination of the no-ATR profile, and any gain name other than 'table'
+ // is not valid.
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR && name != ZBX_GAIN_STAGE_TABLE) {
+ throw uhd::key_error("set_rx_gain(): Invalid combination of gain profile "
+ + gain_profile + " and gain name " + name);
+ }
+ // First, we handle the 'table' gain name. It's a bit different from the
+ // rest.
+ if (name == ZBX_GAIN_STAGE_TABLE) {
+ static const uhd::meta_range_t table_range(0, 255, 1);
+ const uint8_t table_idx = uhd::narrow<uint8_t>(table_range.clip(gain, true));
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ _cpld->set_sw_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA, table_idx);
+ return static_cast<double>(table_idx);
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_MANUAL
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD) {
+ _cpld->set_rx_gain_switches(chan, ATR_ADDR_RX, table_idx);
+ _cpld->set_rx_gain_switches(chan, ATR_ADDR_XX, table_idx);
+ return static_cast<double>(table_idx);
+ }
+ // That covers all the gain profiles for gain name 'table'.
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ // Sanity check key. Note we do this after the previous gain stage, because
+ // it's not a property node.
+ if (!_tree->exists(gains_path / name / "value")) {
+ throw uhd::key_error("Invalid RX gain stage: " + name);
+ }
+ return _tree->access<double>(gains_path / name / "value").set(gain).get();
+}
+
+double zbx_dboard_impl::set_tx_gain(const double gain, const size_t chan)
+{
+ const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan);
+ if (gain_profile == ZBX_GAIN_PROFILE_MANUAL) {
+ const std::string err_msg = "When using 'manual' gain mode, calling "
+ "set_tx_gain() without a gain name is not allowed!";
+ RFNOC_LOG_ERROR(err_msg);
+ throw uhd::runtime_error(err_msg);
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ return set_tx_gain(gain, ZBX_GAIN_STAGE_TABLE, chan);
+ }
+ return set_tx_gain(gain, ZBX_GAIN_STAGE_ALL, chan);
+}
+
+double zbx_dboard_impl::set_rx_gain(const double gain, const size_t chan)
+{
+ const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan);
+ if (gain_profile == ZBX_GAIN_PROFILE_MANUAL) {
+ const std::string err_msg = "When using 'manual' gain mode, calling "
+ "set_rx_gain() without a gain name is not allowed!";
+ RFNOC_LOG_ERROR(err_msg);
+ throw uhd::runtime_error(err_msg);
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ return set_rx_gain(gain, ZBX_GAIN_STAGE_TABLE, chan);
+ }
+ return set_rx_gain(gain, ZBX_GAIN_STAGE_ALL, chan);
+}
+
+double zbx_dboard_impl::get_tx_gain(const size_t chan)
+{
+ const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan);
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ return get_tx_gain(ZBX_GAIN_STAGE_TABLE, chan);
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) {
+ return get_tx_gain(ZBX_GAIN_STAGE_ALL, chan);
+ }
+ throw uhd::runtime_error(
+ "get_tx_gain(): When in 'manual' gain profile, a gain name is required!");
+}
+
+double zbx_dboard_impl::get_rx_gain(const size_t chan)
+{
+ const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan);
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ return get_rx_gain(ZBX_GAIN_STAGE_TABLE, chan);
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) {
+ return get_rx_gain(ZBX_GAIN_STAGE_ALL, chan);
+ }
+ throw uhd::runtime_error(
+ "get_rx_gain(): When in 'manual' gain profile, a gain name is required!");
+}
+
+double zbx_dboard_impl::get_tx_gain(const std::string& name_, const size_t chan)
+{
+ // We have to accept the empty string for "all", because that's widely used
+ // (e.g. by multi_usrp)
+ const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_;
+ const fs_path gains_path = _get_frontend_path(TX_DIRECTION, chan) / "gains";
+ const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan);
+ // Overall gain: Only reliable in 'default' mode. We warn, not throw, in
+ // the other modes. That's because reading back the overall gain is common
+ // diagnostic for many existing applications.
+ if (name == ZBX_GAIN_STAGE_ALL && gain_profile != ZBX_GAIN_PROFILE_DEFAULT) {
+ RFNOC_LOG_WARNING("get_tx_gain(): Trying to read back overall gain in "
+ "non-default gain profile is undefined.");
+ }
+ // Table gain: Returns the current DSA table index.
+ if (name == ZBX_GAIN_STAGE_TABLE) {
+ return static_cast<double>(
+ _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA));
+ }
+ // Otherwise: DSA or amp. Sanity check key is valid. Because the table gain
+ // is not a property tree node, this check comes after the previous if-clause.
+ if (!_tree->exists(gains_path / name / "value")) {
+ RFNOC_LOG_ERROR("get_tx_gain(): Invalid gain name `" << name << "'");
+ throw uhd::key_error(std::string("get_tx_gain(): Invalid gain name: ") + name);
+ }
+ // We're not yet done: If we're in CPLD/table profiles, we peek the current
+ // DSA settings and apply them to the local cache.
+ // Note: This means we have a different behaviour between directly accessing
+ // the prop tree, or accessing the C++ API.
+ if ((name == ZBX_GAIN_STAGE_DSA1 || name == ZBX_GAIN_STAGE_DSA2)
+ && (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR)) {
+ const uint8_t idx =
+ (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR)
+ ? _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA)
+ : ATR_ADDR_TX;
+ constexpr bool update_cache = true; // Make sure to peek the actual value
+ const auto dsa = (name == ZBX_GAIN_STAGE_DSA1) ? zbx_cpld_ctrl::dsa_type::DSA1
+ : zbx_cpld_ctrl::dsa_type::DSA2;
+ const uint8_t dsa_val = _cpld->get_tx_dsa(chan, idx, dsa, update_cache);
+ // Update the tree because we're good citizens, and if we switch the
+ // gain profile from 'table' to 'manual', we want everything to be
+ // consistent. This will not cause a poke to the CPLD, b/c the experts
+ // won't write gains in this gain profile.
+ // Note that the other DSA values in the tree are not updated automatically,
+ // which is why we can't write DSA values to the CPLD in this mode. If
+ // we want to allow writing DSA values in this mode, we need to update
+ // everything here, or put some more cleverness into the programming
+ // expert.
+ _tree->access<double>(gains_path / name / "value")
+ .set(ZBX_TX_DSA_MAX_ATT - dsa_val);
+ }
+ // Now return the value from the tree
+ return _tree->access<double>(gains_path / name / "value").get();
+}
+
+double zbx_dboard_impl::get_rx_gain(const std::string& name_, const size_t chan)
+{
+ // We have to accept the empty string for "all", because that's widely used
+ // (e.g. by multi_usrp)
+ const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_;
+ const fs_path gains_path = _get_frontend_path(RX_DIRECTION, chan) / "gains";
+ const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan);
+ // Overall gain: Only reliable in 'default' mode. We warn, not throw, in
+ // the other modes. That's because reading back the overall gain is common
+ // diagnostic for many existing applications.
+ if (name == ZBX_GAIN_STAGE_ALL && gain_profile != ZBX_GAIN_PROFILE_DEFAULT) {
+ RFNOC_LOG_WARNING("get_rx_gain(): Trying to read back overall gain in "
+ "non-default gain profile is undefined.");
+ }
+ // Table gain: Returns the current DSA table index.
+ if (name == ZBX_GAIN_STAGE_TABLE) {
+ return static_cast<double>(
+ _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA));
+ }
+ // Otherwise: DSA. Sanity check key is valid. Because the table gain is not
+ // a property tree node, this check comes after the previous if-clause.
+ if (!_tree->exists(gains_path / name / "value")) {
+ RFNOC_LOG_ERROR("get_rx_gain(): Invalid gain name `" << name << "'");
+ throw uhd::key_error(std::string("get_rx_gain(): Invalid gain name: ") + name);
+ }
+ // We're not yet done: If we're in CPLD/table profiles, we peek the current
+ // DSA settings and apply them to the local cache.
+ // Note: This means we have a different behaviour between directly accessing
+ // the prop tree, or accessing the C++ API.
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ const uint8_t idx =
+ (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR)
+ ? _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA)
+ : ATR_ADDR_RX;
+ constexpr bool update_cache = true; // Make sure to peek the actual value
+ static const std::map<std::string, zbx_cpld_ctrl::dsa_type> dsa_map{
+ {ZBX_GAIN_STAGE_DSA1, zbx_cpld_ctrl::dsa_type::DSA1},
+ {ZBX_GAIN_STAGE_DSA2, zbx_cpld_ctrl::dsa_type::DSA2},
+ {ZBX_GAIN_STAGE_DSA3A, zbx_cpld_ctrl::dsa_type::DSA3A},
+ {ZBX_GAIN_STAGE_DSA3B, zbx_cpld_ctrl::dsa_type::DSA3B},
+ };
+ const auto dsa = dsa_map.at(name);
+ const uint8_t dsa_val = _cpld->get_rx_dsa(chan, idx, dsa, update_cache);
+ // Update the tree because we're good citizens, and if we switch the
+ // gain profile from 'table' to 'manual', we want everything to be
+ // consistent. This will not cause a poke to the CPLD, b/c the experts
+ // won't write gains in this gain profile.
+ // Note that the other DSA values in the tree are not updated automatically,
+ // which is why we can't write DSA values to the CPLD in this profile. If
+ // we want to allow writing DSA values in this profile, we need to update
+ // everything here, or put some more cleverness into the programming
+ // expert.
+ _tree->access<double>(gains_path / name / "value")
+ .set(static_cast<double>(ZBX_RX_DSA_MAX_ATT - dsa_val));
+ }
+ return _tree->access<double>(gains_path / name / "value").get();
+}
+
+std::vector<std::string> zbx_dboard_impl::get_tx_gain_names(const size_t chan) const
+{
+ UHD_ASSERT_THROW(chan < ZBX_NUM_CHANS);
+ const std::string gain_profile = _tx_gain_profile_api->get_gain_profile(chan);
+
+ if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) {
+ return {ZBX_GAIN_STAGE_ALL};
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ return {ZBX_GAIN_STAGE_TABLE};
+ }
+ return ZBX_TX_GAIN_STAGES;
+}
+
+std::vector<std::string> zbx_dboard_impl::get_rx_gain_names(const size_t chan) const
+{
+ UHD_ASSERT_THROW(chan < ZBX_NUM_CHANS);
+ const std::string gain_profile = _rx_gain_profile_api->get_gain_profile(chan);
+
+ if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) {
+ return {ZBX_GAIN_STAGE_ALL};
+ }
+ if (gain_profile == ZBX_GAIN_PROFILE_CPLD
+ || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) {
+ return {ZBX_GAIN_STAGE_TABLE};
+ }
+ return ZBX_RX_GAIN_STAGES;
+}
+
+const std::string zbx_dboard_impl::get_tx_lo_source(
+ const std::string& name, const size_t chan)
+{
+ const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
+ if (!_tree->exists(fe_path / "ch" / name)) {
+ throw uhd::value_error("get_tx_lo_source(): Invalid LO name: " + name);
+ }
+
+ const zbx_lo_source_t lo_source =
+ _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source").get();
+ return lo_source == zbx_lo_source_t::internal ? "internal" : "external";
+}
+
+const std::string zbx_dboard_impl::get_rx_lo_source(
+ const std::string& name, const size_t chan)
+{
+ const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
+ if (!_tree->exists(fe_path / "ch" / name)) {
+ throw uhd::value_error("get_rx_lo_source(): Invalid LO name: " + name);
+ }
+
+ const zbx_lo_source_t lo_source =
+ _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source").get();
+ return lo_source == zbx_lo_source_t::internal ? "internal" : "external";
+}
+
+void zbx_dboard_impl::set_rx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan)
+{
+ RFNOC_LOG_TRACE("set_rx_lo_source(name=" << name << ", src=" << src << ")");
+ const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
+ if (!_tree->exists(fe_path / "ch" / name)) {
+ throw uhd::value_error("set_rx_lo_source(): Invalid LO name: " + name);
+ }
+
+ _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source")
+ .set(src == "internal" ? zbx_lo_source_t::internal : zbx_lo_source_t::external);
+}
+
+void zbx_dboard_impl::set_tx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan)
+{
+ RFNOC_LOG_TRACE("set_tx_lo_source(name=" << name << ", src=" << src << ")");
+ const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
+ if (!_tree->exists(fe_path / "ch" / name)) {
+ throw uhd::value_error("set_tx_lo_source(): Invalid LO name: " + name);
+ }
+
+ _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source")
+ .set(src == "internal" ? zbx_lo_source_t::internal : zbx_lo_source_t::external);
+}
+
+double zbx_dboard_impl::set_tx_lo_freq(
+ double freq, const std::string& name, const size_t chan)
+{
+ RFNOC_LOG_TRACE("set_tx_lo_freq(freq=" << freq << ", name=" << name << ")");
+ const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
+ assert_has(ZBX_LOS, name);
+
+ return _tree->access<double>(fe_path / "los" / name / "freq" / "value").set(freq).get();
+}
+
+double zbx_dboard_impl::get_tx_lo_freq(const std::string& name, const size_t chan)
+{
+ RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ")");
+ const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
+ assert_has(ZBX_LOS, name);
+
+ return _tree->access<double>(fe_path / "los" / name / "freq" / "value").get();
+}
+
+freq_range_t zbx_dboard_impl::_get_lo_freq_range(
+ const std::string& name, const size_t /*chan*/) const
+{
+ if (name == ZBX_LO1 || name == ZBX_LO2) {
+ // Note this doesn't include the LO step size. The LO step size is only
+ // used when the LO frequencies are automatically calculated (which is
+ // the normal use case). When setting LO frequencies manually, it is
+ // possible to set LOs to values outside of the step size.
+ return freq_range_t{LMX2572_MIN_FREQ, LMX2572_MAX_FREQ};
+ }
+ if (name == RFDC_NCO) {
+ // It might make sense to constrain the possible NCO values more, since
+ // the bandpass filters for IF2 only allow a certain range. Note that LO1
+ // and LO2 freq ranges are also constrained by their analog filters.
+ // But in principle, this is the range for the NCO... so why not.
+ return freq_range_t{0.0, _rfdc_rate};
+ }
+ throw uhd::value_error("Invalid LO name: " + name);
+}
+
+double zbx_dboard_impl::set_rx_lo_freq(
+ double freq, const std::string& name, const size_t chan)
+{
+ RFNOC_LOG_TRACE("set_rx_lo_freq(freq=" << freq << ", name=" << name << ")");
+ const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
+ assert_has(ZBX_LOS, name);
+
+ return _tree->access<double>(fe_path / "los" / name / "freq" / "value")
+ .set(freq)
+ .get();
+}
+
+double zbx_dboard_impl::get_rx_lo_freq(const std::string& name, size_t chan)
+{
+ RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ")");
+ const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
+ assert_has(ZBX_LOS, name);
+
+ return _tree->access<double>(fe_path / "los" / name / "freq" / "value").get();
+}
+
+std::string zbx_dboard_impl::get_tx_antenna(size_t chan) const
+{
+ const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan);
+ return _tree->access<std::string>(fe_path / "antenna" / "value").get();
+}
+
+std::string zbx_dboard_impl::get_rx_antenna(size_t chan) const
+{
+ const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan);
+ return _tree->access<std::string>(fe_path / "antenna" / "value").get();
+}
+
+double zbx_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 zbx_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();
+}
+
+void zbx_dboard_impl::set_tx_tune_args(const uhd::device_addr_t&, const size_t)
+{
+ RFNOC_LOG_TRACE("tune_args not supported by this radio.");
+}
+
+void zbx_dboard_impl::set_rx_tune_args(const uhd::device_addr_t&, const size_t)
+{
+ RFNOC_LOG_TRACE("tune_args not supported by this radio.");
+}
+
+void zbx_dboard_impl::set_rx_agc(const bool, const size_t)
+{
+ throw uhd::not_implemented_error("set_rx_agc() is not supported on this radio!");
+}
+
+uhd::gain_range_t zbx_dboard_impl::get_tx_gain_range(
+ const std::string& name, const size_t chan) const
+{
+ // We have to accept the empty string for "all", because that's widely used
+ // (e.g. by multi_usrp)
+ if (!name.empty() && name != ZBX_GAIN_STAGE_ALL) {
+ throw uhd::value_error(
+ std::string("get_tx_gain_range(): Unknown gain name '") + name + "'!");
+ }
+ return get_tx_gain_range(chan);
+}
+
+uhd::gain_range_t zbx_dboard_impl::get_rx_gain_range(
+ const std::string& name, const size_t chan) const
+{
+ // We have to accept the empty string for "all", because that's widely used
+ // (e.g. by multi_usrp)
+ if (!name.empty() && name != ZBX_GAIN_STAGE_ALL) {
+ throw uhd::value_error(
+ std::string("get_rx_gain_range(): Unknown gain name '") + name + "'!");
+ }
+ return get_rx_gain_range(chan);
+}
+
+void zbx_dboard_impl::set_rx_lo_export_enabled(bool, const std::string&, const size_t)
+{
+ throw uhd::not_implemented_error(
+ "set_rx_lo_export_enabled is not supported on this radio");
+}
+
+bool zbx_dboard_impl::get_rx_lo_export_enabled(const std::string&, const size_t)
+{
+ return false;
+}
+
+void zbx_dboard_impl::set_tx_lo_export_enabled(bool, const std::string&, const size_t)
+{
+ throw uhd::not_implemented_error(
+ "set_rx_lo_export_enabled is not supported on this radio");
+}
+
+bool zbx_dboard_impl::get_tx_lo_export_enabled(const std::string&, const size_t)
+{
+ return false;
+}
+
+/******************************************************************************
+ * EEPROM API
+ *****************************************************************************/
+eeprom_map_t zbx_dboard_impl::get_db_eeprom()
+{
+ return _mb_rpcc->get_db_eeprom(_db_idx);
+}
+
+size_t zbx_dboard_impl::get_chan_from_dboard_fe(
+ const std::string& fe, const uhd::direction_t) const
+{
+ if (fe == "0") {
+ return 0;
+ }
+ if (fe == "1") {
+ return 1;
+ }
+ throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe);
+}
+
+std::string zbx_dboard_impl::get_dboard_fe_from_chan(
+ const size_t chan, const uhd::direction_t) const
+{
+ if (chan == 0) {
+ return "0";
+ }
+ if (chan == 1) {
+ return "1";
+ }
+ throw uhd::lookup_error(
+ std::string("[X400] Invalid channel: ") + std::to_string(chan));
+}
+
+/*********************************************************************
+ * Private misc/calculative helper functions
+ **********************************************************************/
+
+bool zbx_dboard_impl::_get_all_los_locked(const direction_t dir, const size_t chan)
+{
+ const fs_path fe_path = _get_frontend_path(dir, chan);
+
+ const bool is_lo1_enabled = _tree->access<bool>(fe_path / ZBX_LO1 / "enabled").get();
+ const bool is_lo1_locked =
+ _lo_ctrl_map.at(zbx_lo_ctrl::lo_string_to_enum(dir, chan, ZBX_LO1))
+ ->get_lock_status();
+ // LO2 is always enabled via center frequency tuning, but users may manually disable
+ // it
+ const bool is_lo2_enabled = _tree->access<bool>(fe_path / ZBX_LO2 / "enabled").get();
+ const bool is_lo2_locked =
+ _lo_ctrl_map.at(zbx_lo_ctrl::lo_string_to_enum(dir, chan, ZBX_LO2))
+ ->get_lock_status();
+ // We only care about the lock status if it's enabled (lowband center frequency)
+ // That means we have set it to true if is_lo[1,2]_enabled is *false*, but check for
+ // the lock if is_lo[1,2]_enabled is *true*
+ return (!is_lo1_enabled || is_lo1_locked) && (!is_lo2_enabled || is_lo2_locked);
+}
+
+fs_path zbx_dboard_impl::_get_frontend_path(
+ const direction_t dir, const size_t chan_idx) const
+{
+ UHD_ASSERT_THROW(chan_idx < ZBX_NUM_CHANS);
+ const std::string frontend = dir == TX_DIRECTION ? "tx_frontends" : "rx_frontends";
+ return fs_path("dboard") / frontend / chan_idx;
+}
+
+std::vector<uhd::usrp::pwr_cal_mgr::sptr>& zbx_dboard_impl::get_pwr_mgr(
+ uhd::direction_t trx)
+{
+ switch (trx) {
+ case uhd::RX_DIRECTION:
+ return _rx_pwr_mgr;
+ case uhd::TX_DIRECTION:
+ return _tx_pwr_mgr;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp b/host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp
new file mode 100644
index 000000000..e6bbf2798
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp
@@ -0,0 +1,685 @@
+//
+// Copyright 2020 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/property_tree.hpp>
+#include <uhd/property_tree.ipp>
+#include <uhd/rfnoc/register_iface.hpp>
+#include <uhd/transport/chdr.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/experts/expert_container.hpp>
+#include <uhdlib/experts/expert_factory.hpp>
+#include <uhdlib/rfnoc/reg_iface_adapter.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_constants.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_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 experts {
+
+std::ostream& operator<<(
+ std::ostream& os, const ::uhd::usrp::zbx::zbx_lo_source_t& lo_source)
+{
+ switch (lo_source) {
+ case ::uhd::usrp::zbx::zbx_lo_source_t::internal:
+ os << "internal";
+ return os;
+ case ::uhd::usrp::zbx::zbx_lo_source_t::external:
+ os << "external";
+ return os;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+std::ostream& operator<<(
+ std::ostream& os, const ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode& atr)
+{
+ switch (atr) {
+ case ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode::SW_DEFINED:
+ os << "SW_DEFINED";
+ return os;
+ case ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode::CLASSIC_ATR:
+ os << "CLASSIC ATR";
+ return os;
+ case ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode::FPGA_STATE:
+ os << "FPGA_STATE";
+ return os;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+}} // namespace uhd::experts
+
+namespace uhd { namespace usrp { namespace zbx {
+
+void zbx_dboard_impl::_init_cpld()
+{
+ // CPLD
+ RFNOC_LOG_TRACE("Initializing CPLD...");
+ _cpld = std::make_shared<zbx_cpld_ctrl>(
+ [this](
+ const uint32_t addr, const uint32_t data, const zbx_cpld_ctrl::chan_t chan) {
+ const auto time_spec = (chan == zbx_cpld_ctrl::NO_CHAN)
+ ? time_spec_t::ASAP
+ : (chan == zbx_cpld_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() + "::CPLD");
+ UHD_ASSERT_THROW(_cpld);
+ // We don't have access to the scratch register, so we use the config
+ // registers to test communication. This also does some basic sanity check
+ // of the CPLDs logic.
+ RFNOC_LOG_TRACE("Testing CPLD communication...");
+ const uint32_t random_value = static_cast<uint32_t>(time(NULL));
+ _cpld->set_scratch(random_value);
+ UHD_ASSERT_THROW(_cpld->get_scratch() == random_value);
+ // Now go to classic ATR mode
+ RFNOC_LOG_TRACE("CPLD communication good. Switching to classic ATR mode.");
+ for (size_t i = 0; i < ZBX_NUM_CHANS; ++i) {
+ _cpld->set_atr_mode(
+ i, zbx_cpld_ctrl::atr_mode_target::DSA, zbx_cpld_ctrl::atr_mode::CLASSIC_ATR);
+ _cpld->set_atr_mode(i,
+ zbx_cpld_ctrl::atr_mode_target::PATH_LED,
+ zbx_cpld_ctrl::atr_mode::CLASSIC_ATR);
+ }
+}
+
+void zbx_dboard_impl::_init_peripherals()
+{
+ RFNOC_LOG_TRACE("Initializing peripherals...");
+ // Load DSA cal data (rx and tx)
+ constexpr char dsa_step_filename_tx[] = "zbx_dsa_tx";
+ constexpr char dsa_step_filename_rx[] = "zbx_dsa_rx";
+ uhd::eeprom_map_t eeprom_map = get_db_eeprom();
+ const std::string db_serial(eeprom_map["serial"].begin(), eeprom_map["serial"].end());
+ if (uhd::usrp::cal::database::has_cal_data(
+ dsa_step_filename_tx, db_serial, uhd::usrp::cal::source::ANY)) {
+ RFNOC_LOG_TRACE("load binary TX DSA steps from database...");
+ const auto tx_dsa_data = uhd::usrp::cal::database::read_cal_data(
+ dsa_step_filename_tx, db_serial, uhd::usrp::cal::source::ANY);
+ RFNOC_LOG_TRACE("create TX DSA object...");
+ _tx_dsa_cal = uhd::usrp::cal::zbx_tx_dsa_cal::make();
+ RFNOC_LOG_TRACE("store deserialized TX DSA data into object...");
+ _tx_dsa_cal->deserialize(tx_dsa_data);
+ } else {
+ RFNOC_LOG_ERROR("Could not find TX DSA cal data!");
+ throw uhd::runtime_error("Could not find TX DSA cal data!");
+ }
+ if (uhd::usrp::cal::database::has_cal_data(
+ dsa_step_filename_rx, db_serial, uhd::usrp::cal::source::ANY)) {
+ // read binary blob without knowledge about content
+ RFNOC_LOG_TRACE("load binary RX DSA steps from database...");
+ const auto rx_dsa_data = uhd::usrp::cal::database::read_cal_data(
+ dsa_step_filename_rx, db_serial, uhd::usrp::cal::source::ANY);
+
+ RFNOC_LOG_TRACE("create RX DSA object...");
+ _rx_dsa_cal = uhd::usrp::cal::zbx_rx_dsa_cal::make();
+
+ RFNOC_LOG_TRACE("store deserialized RX DSA data into object...");
+ _rx_dsa_cal->deserialize(rx_dsa_data);
+ } else {
+ RFNOC_LOG_ERROR("Could not find RX DSA cal data!");
+ throw uhd::runtime_error("Could not find RX DSA cal data!");
+ }
+}
+
+void zbx_dboard_impl::_init_prop_tree()
+{
+ auto subtree = get_tree()->subtree(fs_path("dboard"));
+
+ // Construct RX frontend
+ for (size_t chan_idx = 0; chan_idx < ZBX_NUM_CHANS; 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<zbx_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 < ZBX_NUM_CHANS; 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:
+ expert_factory::add_worker_node<zbx_sync_expert>(_expert_container,
+ _expert_container->node_retriever(),
+ fs_path("tx_frontends"),
+ fs_path("rx_frontends"),
+ _rfdcc,
+ _cpld);
+
+ subtree->create<eeprom_map_t>("eeprom")
+ .add_coerced_subscriber([this](const eeprom_map_t&) {
+ throw uhd::runtime_error("Attempting to update daughterboard eeprom!");
+ })
+ .set_publisher([this]() { return get_db_eeprom(); });
+}
+
+void zbx_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 ZBX_FE_NAME[] = "ZBX";
+
+ 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(ZBX_FE_NAME);
+ subtree->create<std::string>(fe_path / "connection").set("IQ");
+
+ _init_frequency_prop_tree(subtree, _expert_container, fe_path);
+ _init_gain_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path);
+ _init_antenna_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path);
+ _init_lo_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path);
+ _init_programming_prop_tree(subtree, _expert_container, fe_path);
+ _init_experts(subtree, _expert_container, trx, chan_idx, fe_path);
+}
+
+
+uhd::usrp::pwr_cal_mgr::sptr zbx_dboard_impl::_init_power_cal(
+ uhd::property_tree::sptr subtree,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path)
+{
+ const std::string DIR = (trx == TX_DIRECTION) ? "TX" : "RX";
+
+ uhd::eeprom_map_t eeprom_map = get_db_eeprom();
+ /* The cal serial is the DB serial plus the FE name */
+ const std::string db_serial(eeprom_map["serial"].begin(), eeprom_map["serial"].end());
+ const std::string cal_serial =
+ db_serial + "#" + subtree->access<std::string>(fe_path / "name").get();
+ /* Now create a gain group for this. */
+ /* _?x_gain_groups won't work, because it doesn't group the */
+ /* gains the way we want them to be grouped. */
+ auto ggroup = uhd::gain_group::make();
+ ggroup->register_fcns(HW_GAIN_STAGE,
+ {[this, trx, chan_idx]() {
+ return trx == TX_DIRECTION ? get_tx_gain_range(chan_idx)
+ : get_rx_gain_range(chan_idx);
+ },
+ [this, trx, chan_idx]() {
+ return trx == TX_DIRECTION ? get_tx_gain(ZBX_GAIN_STAGE_ALL, chan_idx)
+ : get_rx_gain(ZBX_GAIN_STAGE_ALL, chan_idx);
+ },
+ [this, trx, chan_idx](const double gain) {
+ trx == TX_DIRECTION ? set_tx_gain(gain, chan_idx)
+ : set_rx_gain(gain, chan_idx);
+ }},
+ 10 /* High priority */);
+ /* If we had a digital (baseband) gain, we would register it here,*/
+ /* so that the power manager would know to use it as a */
+ /* backup gain stage. */
+ /* Note that such a baseband gain might not be available */
+ /* on the LV version. */
+ return uhd::usrp::pwr_cal_mgr::make(
+ cal_serial,
+ "X400-CAL-" + DIR,
+ [this, trx, chan_idx]() {
+ return trx == TX_DIRECTION ? get_tx_frequency(chan_idx)
+ : get_rx_frequency(chan_idx);
+ },
+ [this,
+ trx_str = (trx == TX_DIRECTION ? "tx" : "rx"),
+ fe_path,
+ subtree,
+ chan_str = std::to_string(chan_idx)]() -> std::string {
+ const std::string antenna = pwr_cal_mgr::sanitize_antenna_name(
+ subtree->access<std::string>(fe_path / "antenna/value").get());
+ // The lookup key for X410 + ZBX shall start with x4xx_pwr_zbx.
+ // Should we rev the ZBX in a way that would make generic cal data
+ // unsuitable between revs, then we need to check the rev (or PID)
+ // here and generate a different key prefix (e.g. x4xx_pwr_zbxD_ or
+ // something like that).
+ return std::string("x4xx_pwr_zbx_") + trx_str + "_" + chan_str + "_"
+ + antenna;
+ },
+ ggroup);
+}
+
+void zbx_dboard_impl::_init_experts(uhd::property_tree::sptr subtree,
+ 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...");
+
+ get_pwr_mgr(trx).insert(get_pwr_mgr(trx).begin() + chan_idx,
+ _init_power_cal(subtree, trx, chan_idx, fe_path));
+
+ // NOTE: THE ORDER OF EXPERT INITIALIZATION MATTERS
+ // After construction, all nodes (properties and experts) are marked dirty. Any
+ // subsequent calls to the container will trigger a resolve_all(), in which case
+ // the nodes are all resolved in REVERSE ORDER of construction, like a stack. With
+ // that in mind, we have to initialize the experts in line with that reverse order,
+ // because some experts rely on each other's construction/resolution to avoid
+ // errors (e.g., gain expert's dsa_cal is dependant on frequency be's coerced
+ // frequency, which is nan on dual_prop_node construction) After construction and
+ // subsequent resolution, the nodes will follow simple topological ruling as long
+ // as we only change one property at a time.
+
+ // The current order should be:
+ // Frequency FE Expert -> LO Expert(s) -> MPM Expert -> Frequency BE Expert -> Gain
+ // Expert -> Programming Expert
+
+ if (trx == TX_DIRECTION) {
+ expert_factory::add_worker_node<zbx_tx_programming_expert>(expert,
+ expert->node_retriever(),
+ fe_path,
+ fs_path("rx_frontends") / chan_idx,
+ chan_idx,
+ _tx_dsa_cal,
+ _cpld);
+
+ expert_factory::add_worker_node<zbx_tx_gain_expert>(expert,
+ expert->node_retriever(),
+ fe_path,
+ chan_idx,
+ get_pwr_mgr(trx).at(chan_idx),
+ _tx_dsa_cal);
+ } else {
+ expert_factory::add_worker_node<zbx_rx_programming_expert>(
+ expert, expert->node_retriever(), fe_path, chan_idx, _rx_dsa_cal, _cpld);
+
+ expert_factory::add_worker_node<zbx_rx_gain_expert>(expert,
+ expert->node_retriever(),
+ fe_path,
+ chan_idx,
+ get_pwr_mgr(trx).at(chan_idx),
+ _rx_dsa_cal);
+ }
+
+ expert_factory::add_worker_node<zbx_freq_be_expert>(
+ expert, expert->node_retriever(), fe_path, trx, chan_idx);
+
+ expert_factory::add_worker_node<zbx_band_inversion_expert>(
+ expert, expert->node_retriever(), fe_path, trx, chan_idx, _db_idx, _rpcc);
+
+
+ // Initialize our LO Control Experts
+ for (auto lo_select : ZBX_LOS) {
+ if (lo_select == RFDC_NCO) {
+ expert_factory::add_worker_node<zbx_rfdc_freq_expert>(expert,
+ expert->node_retriever(),
+ fe_path,
+ trx,
+ chan_idx,
+ _rpc_prefix,
+ _db_idx,
+ _mb_rpcc);
+ } else {
+ const zbx_lo_t lo = zbx_lo_ctrl::lo_string_to_enum(trx, chan_idx, lo_select);
+ std::shared_ptr<zbx_lo_ctrl> lo_ctrl = std::make_shared<zbx_lo_ctrl>(
+ lo,
+ [this, lo](const uint32_t addr, const uint16_t data) {
+ _cpld->lo_poke16(lo, addr, data);
+ },
+ [this, lo](const uint32_t addr) { return _cpld->lo_peek16(lo, addr); },
+ [this](const uhd::time_spec_t& sleep_time) { _regs.sleep(sleep_time); },
+ LMX2572_DEFAULT_FREQ,
+ _prc_rate,
+ false);
+ expert_factory::add_worker_node<zbx_lo_expert>(expert,
+ expert->node_retriever(),
+ fe_path,
+ trx,
+ chan_idx,
+ lo_select,
+ lo_ctrl);
+ _lo_ctrl_map.insert({lo, lo_ctrl});
+ }
+ }
+
+ const double lo_step_size = _prc_rate / ZBX_RELATIVE_LO_STEP_SIZE;
+ RFNOC_LOG_DEBUG("LO step size: " << (lo_step_size / 1e6) << " MHz.")
+ expert_factory::add_worker_node<zbx_freq_fe_expert>(expert,
+ expert->node_retriever(),
+ fe_path,
+ trx,
+ chan_idx,
+ _rfdc_rate,
+ lo_step_size);
+ RFNOC_LOG_TRACE(fe_path + ", Experts created");
+}
+
+void zbx_dboard_impl::_init_frequency_prop_tree(uhd::property_tree::sptr subtree,
+ expert_container::sptr expert,
+ const fs_path fe_path)
+{
+ expert_factory::add_dual_prop_node<double>(
+ expert, subtree, fe_path / "freq", ZBX_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_data_node<bool>(expert, fe_path / "is_highband", false);
+ expert_factory::add_data_node<int>(
+ expert, fe_path / "mixer1_m", 0, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_data_node<int>(
+ expert, fe_path / "mixer1_n", 0, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_data_node<int>(
+ expert, fe_path / "mixer2_m", 0, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_data_node<int>(
+ expert, fe_path / "mixer2_n", 0, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_data_node<bool>(
+ expert, fe_path / "band_inverted", false, AUTO_RESOLVE_ON_WRITE);
+
+ subtree->create<double>(fe_path / "bandwidth" / "value")
+ .set(ZBX_DEFAULT_BANDWIDTH)
+ .set_coercer([](const double) { return ZBX_DEFAULT_BANDWIDTH; });
+ subtree->create<meta_range_t>(fe_path / "bandwidth" / "range")
+ .set({ZBX_DEFAULT_BANDWIDTH, ZBX_DEFAULT_BANDWIDTH})
+ .set_coercer([](const meta_range_t&) {
+ return meta_range_t(ZBX_DEFAULT_BANDWIDTH, ZBX_DEFAULT_BANDWIDTH);
+ });
+ subtree->create<meta_range_t>(fe_path / "freq" / "range")
+ .set(ZBX_FREQ_RANGE)
+ .add_coerced_subscriber([](const meta_range_t&) {
+ throw uhd::runtime_error("Attempting to update freq range!");
+ });
+}
+
+void zbx_dboard_impl::_init_gain_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)
+{
+ // First, overall gain nodes
+ const auto gain_base_path = fe_path / "gains";
+ expert_factory::add_dual_prop_node<double>(expert,
+ subtree,
+ gain_base_path / ZBX_GAIN_STAGE_ALL / "value",
+ trx == TX_DIRECTION ? TX_MIN_GAIN : RX_MIN_GAIN,
+ 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, trx, chan_idx]() {
+ return (trx == TX_DIRECTION) ? this->get_tx_gain_range(chan_idx)
+ : this->get_rx_gain_range(chan_idx);
+ });
+ // Then, individual DSA/amp gain nodes
+ if (trx == TX_DIRECTION) {
+ // DSAs
+ for (const auto dsa : {ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2}) {
+ const auto gain_path = gain_base_path / dsa;
+ expert_factory::add_dual_prop_node<double>(
+ expert, subtree, gain_path / "value", 0, AUTO_RESOLVE_ON_WRITE);
+ subtree->create<meta_range_t>(gain_path / "range")
+ .set(uhd::meta_range_t(0, ZBX_TX_DSA_MAX_ATT, 1.0));
+ expert_factory::add_worker_node<zbx_gain_coercer_expert>(_expert_container,
+ _expert_container->node_retriever(),
+ gain_path / "value",
+ uhd::meta_range_t(0, ZBX_TX_DSA_MAX_ATT, 1.0));
+ }
+ // Amp
+ const auto amp_path = gain_base_path / ZBX_GAIN_STAGE_AMP;
+ expert_factory::add_dual_prop_node<double>(expert,
+ subtree,
+ amp_path / "value",
+ ZBX_TX_LOWBAND_GAIN,
+ AUTO_RESOLVE_ON_WRITE);
+ uhd::meta_range_t amp_gain_range;
+ for (const auto tx_gain_pair : ZBX_TX_GAIN_AMP_MAP) {
+ amp_gain_range.push_back(uhd::range_t(tx_gain_pair.first));
+ }
+ subtree->create<meta_range_t>(amp_path / "range").set(amp_gain_range);
+ expert_factory::add_worker_node<zbx_gain_coercer_expert>(_expert_container,
+ _expert_container->node_retriever(),
+ amp_path / "value",
+ amp_gain_range);
+ } else {
+ // RX only has DSAs
+ for (const auto dsa : {ZBX_GAIN_STAGE_DSA1,
+ ZBX_GAIN_STAGE_DSA2,
+ ZBX_GAIN_STAGE_DSA3A,
+ ZBX_GAIN_STAGE_DSA3B}) {
+ const auto gain_path = gain_base_path / dsa;
+ expert_factory::add_dual_prop_node<double>(
+ expert, subtree, gain_path / "value", 0, AUTO_RESOLVE_ON_WRITE);
+ subtree->create<meta_range_t>(gain_path / "range")
+ .set(uhd::meta_range_t(0, ZBX_RX_DSA_MAX_ATT, 1.0));
+ expert_factory::add_worker_node<zbx_gain_coercer_expert>(_expert_container,
+ _expert_container->node_retriever(),
+ gain_path / "value",
+ uhd::meta_range_t(0, ZBX_RX_DSA_MAX_ATT, 1.0));
+ }
+ }
+
+ const uhd::fs_path gain_profile_path = gain_base_path / "all" / "profile";
+ expert_factory::add_prop_node<std::string>(expert,
+ subtree,
+ gain_profile_path,
+ ZBX_GAIN_PROFILE_DEFAULT,
+ AUTO_RESOLVE_ON_WRITE);
+ auto& gain_profile = (trx == TX_DIRECTION) ? _tx_gain_profile_api
+ : _rx_gain_profile_api;
+ auto& other_dir_gp = (trx == TX_DIRECTION) ? _rx_gain_profile_api
+ : _tx_gain_profile_api;
+ auto gain_profile_subscriber = [this, other_dir_gp, trx](
+ const std::string& profile, const size_t chan) {
+ // Upon changing the gain profile, we need to import the new value into
+ // the property tree.
+ const auto path = fs_path("dboard")
+ / (trx == TX_DIRECTION ? "tx_frontends" : "rx_frontends") / chan
+ / "gains" / "all" / "profile";
+ get_tree()->access<std::string>(path).set(profile);
+ // The CPLD does not have the option to have different ATR modes for RX
+ // and TX (it does have different modes for channel 0 and 1 though).
+ // This means we have to match up the gain profiles between RX and TX.
+ // The ZBX_GAIN_PROFILE_CPLD_NOATR profile uses the SW_DEFINED mode,
+ // and all the others use CLASSIC_ATR. So either both match
+ // ZBX_GAIN_PROFILE_CPLD_NOATR, or none do.
+ // This will not cause a loop, because the other_dir_gp will already
+ // match this one by the time we call it.
+ if ((profile == ZBX_GAIN_PROFILE_CPLD_NOATR
+ && other_dir_gp->get_gain_profile(chan) != ZBX_GAIN_PROFILE_CPLD_NOATR)
+ || (profile != ZBX_GAIN_PROFILE_CPLD_NOATR
+ && other_dir_gp->get_gain_profile(chan) == ZBX_GAIN_PROFILE_CPLD_NOATR)) {
+ RFNOC_LOG_DEBUG("Channel " << chan << ": Setting gain profile to `" << profile
+ << "' for both TX and RX.");
+ other_dir_gp->set_gain_profile(profile, chan);
+ }
+ };
+
+ gain_profile->add_subscriber(std::move(gain_profile_subscriber));
+}
+
+void zbx_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 zbx_dboard_impl::_init_programming_prop_tree(uhd::property_tree::sptr subtree,
+ expert_container::sptr expert,
+ const fs_path fe_path)
+{
+ expert_factory::add_prop_node<int>(
+ expert, subtree, fe_path / "rf" / "filter", 1, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<int>(
+ expert, subtree, fe_path / "if1" / "filter", 1, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<int>(
+ expert, subtree, fe_path / "if2" / "filter", 1, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<zbx_cpld_ctrl::atr_mode>(expert,
+ subtree,
+ fe_path / "atr_mode",
+ zbx_cpld_ctrl::atr_mode::CLASSIC_ATR,
+ AUTO_RESOLVE_ON_WRITE);
+}
+
+void zbx_dboard_impl::_init_lo_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)
+{
+ // Analog LO Specific
+ for (const std::string lo : {ZBX_LO1, ZBX_LO2}) {
+ expert_factory::add_prop_node<zbx_lo_source_t>(expert,
+ subtree,
+ fe_path / "ch" / lo / "source",
+ ZBX_DEFAULT_LO_SOURCE,
+ AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<bool>(
+ expert, subtree, fe_path / lo / "enabled", false, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<bool>(
+ expert, subtree, fe_path / lo / "test_mode", false, AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_dual_prop_node<double>(expert,
+ subtree,
+ fe_path / "los" / lo / "freq" / "value",
+ LMX2572_DEFAULT_FREQ,
+ AUTO_RESOLVE_ON_WRITE);
+
+ subtree->create<meta_range_t>(fe_path / "los" / lo / "freq/range")
+ .set_publisher(
+ [this, lo, chan_idx]() { return this->_get_lo_freq_range(lo, chan_idx); })
+ .add_coerced_subscriber([](const meta_range_t&) {
+ throw uhd::runtime_error("Attempting to update freq range!");
+ });
+ subtree->create<std::vector<std::string>>(fe_path / "los" / lo / "source/options")
+ .set_publisher([this, lo, trx, chan_idx]() {
+ return trx == TX_DIRECTION ? this->get_tx_lo_sources(lo, chan_idx)
+ : this->get_rx_lo_sources(lo, chan_idx);
+ })
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update LO source options!");
+ });
+
+ subtree
+ ->create<sensor_value_t>(
+ fe_path / "sensors" / boost::algorithm::to_lower_copy(lo) + "_locked")
+ .add_coerced_subscriber([](const sensor_value_t&) {
+ throw uhd::runtime_error("Attempting to write to sensor!");
+ })
+ .set_publisher([this, lo, trx, chan_idx]() {
+ return sensor_value_t(lo,
+ this->_lo_ctrl_map
+ .at(zbx_lo_ctrl::lo_string_to_enum(trx, chan_idx, lo))
+ ->get_lock_status(),
+ "locked",
+ "unlocked");
+ });
+ }
+
+ // The NCO gets a sub-node called 'reset'. It is read/write: Write will
+ // perform a reset, and read will return the reset status. The latter is
+ // also returned in the 'locked' sensor for the NCO, but the 'nco_locked'
+ // sensor node is read-only, and returns a sensor_value_t (not a bool).
+ // This node is primarily used for debugging, but can also serve as a manual
+ // reset line for the NCOs.
+ const auto nco = (trx == TX_DIRECTION)
+ ? (chan_idx == 0 ? rfdc_control::rfdc_type::TX0
+ : rfdc_control::rfdc_type::TX1)
+ : (chan_idx == 0 ? rfdc_control::rfdc_type::RX0
+ : rfdc_control::rfdc_type::RX1);
+ subtree->create<bool>(fe_path / "los" / RFDC_NCO / "reset")
+ .set_publisher([this]() { return this->_rfdcc->get_nco_reset_done(); })
+ .add_coerced_subscriber([this, nco, chan_idx](const bool&) {
+ RFNOC_LOG_TRACE("Resetting NCO " << size_t(nco) << ", chan " << chan_idx);
+ this->_rfdcc->reset_ncos({nco}, this->_time_accessor(chan_idx));
+ });
+
+ 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);
+
+ expert_factory::add_prop_node<zbx_lo_source_t>(expert,
+ subtree,
+ fe_path / "ch" / RFDC_NCO / "source",
+ ZBX_DEFAULT_LO_SOURCE,
+ AUTO_RESOLVE_ON_WRITE);
+
+ // LO lock sensor
+ // We can't make this its own property value because it has to have access to two
+ // containers (two instances of zbx lo expert)
+ 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",
+ this->_get_all_los_locked(trx, chan_idx),
+ "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::zbx
diff --git a/host/lib/usrp/dboard/zbx/zbx_expert.cpp b/host/lib/usrp/dboard/zbx/zbx_expert.cpp
new file mode 100644
index 000000000..79e13e230
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/zbx_expert.cpp
@@ -0,0 +1,672 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_expert.hpp>
+#include <uhdlib/utils/interpolation.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <algorithm>
+#include <array>
+
+using namespace uhd;
+
+namespace uhd { namespace usrp { namespace zbx {
+
+namespace {
+
+/*********************************************************************
+ * Misc/calculative helper functions
+ **********************************************************************/
+bool _is_band_highband(const tune_map_item_t tune_setting)
+{
+ // Lowband frequency paths do not utilize an RF filter
+ return tune_setting.rf_fir == 0;
+}
+
+tune_map_item_t _get_tune_settings(const double freq, const uhd::direction_t trx)
+{
+ auto tune_setting = trx == RX_DIRECTION ? rx_tune_map.begin() : tx_tune_map.begin();
+
+ auto tune_settings_end = trx == RX_DIRECTION ? rx_tune_map.end() : tx_tune_map.end();
+
+ for (; tune_setting != tune_settings_end; ++tune_setting) {
+ if (tune_setting->max_band_freq >= freq) {
+ return *tune_setting;
+ }
+ }
+ // Didn't find a tune setting. This frequency should have been clipped, this is an
+ // internal error.
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+bool _is_band_inverted(const uhd::direction_t trx,
+ const double if2_freq,
+ const double rfdc_rate,
+ const tune_map_item_t tune_setting)
+{
+ const bool is_if2_nyquist2 = if2_freq > (rfdc_rate / 2);
+
+ // We count the number of inversions introduced by the signal chain, starting
+ // at the RFDC
+ const int num_inversions =
+ // If we're in the second Nyquist zone, we're inverted
+ int(is_if2_nyquist2) +
+ // LO2 mixer may invert
+ int(tune_setting.mix2_m == -1) +
+ // LO1 mixer can only invert in the lowband
+ int(!_is_band_highband(tune_setting) && tune_setting.mix1_m == -1);
+
+ // In the RX direction, an extra inversion is needed
+ // TODO: We don't know where this is coming from
+ const bool num_inversions_is_odd = num_inversions % 2 != 0;
+ if (trx == RX_DIRECTION) {
+ return !num_inversions_is_odd;
+ } else {
+ return num_inversions_is_odd;
+ }
+}
+
+double _calc_lo2_freq(
+ const double if1_freq, const double if2_freq, const int mix2_m, const int mix2_n)
+{
+ return (if2_freq - (mix2_m * if1_freq)) / mix2_n;
+}
+
+double _calc_if2_freq(
+ const double if1_freq, const double lo2_freq, const int mix2_m, const int mix2_n)
+{
+ return mix2_n * lo2_freq + mix2_m * if1_freq;
+}
+
+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();
+ }
+}
+
+// For various RF performance considerations (such as spur reduction), different bands
+// vary between using fixed IF1 and/or IF2 or using variable IF1 and/or IF2. Bands with a
+// fixed IF1/IF2 have ifX_freq_min == IFX_freq_max, and _calc_ifX_freq() will return that
+// single value. Bands with variable IF1/IF2 will shift the IFX based on where in the RF
+// band we are tuning by using linear interpolation. (if1 calculation takes place only if
+// tune frequency is lowband)
+double _calc_if1_freq(const double tune_freq, const tune_map_item_t tune_setting)
+{
+ if (tune_setting.if1_freq_min == tune_setting.if1_freq_max) {
+ return tune_setting.if1_freq_min;
+ }
+
+ return uhd::math::linear_interp(tune_freq,
+ tune_setting.min_band_freq,
+ tune_setting.if1_freq_min,
+ tune_setting.max_band_freq,
+ tune_setting.if1_freq_max);
+}
+
+double _calc_ideal_if2_freq(const double tune_freq, const tune_map_item_t tune_setting)
+{
+ // linear_interp() wants to interpolate and will throw if these are identical:
+ if (tune_setting.if2_freq_min == tune_setting.if2_freq_max) {
+ return tune_setting.if2_freq_min;
+ }
+
+ return uhd::math::linear_interp(tune_freq,
+ tune_setting.min_band_freq,
+ tune_setting.if2_freq_min,
+ tune_setting.max_band_freq,
+ tune_setting.if2_freq_max);
+}
+
+} // 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 zbx_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 zbx_freq_fe_expert::resolve()
+{
+ const double tune_freq = ZBX_FREQ_RANGE.clip(_desired_frequency);
+ _tune_settings = _get_tune_settings(tune_freq, _trx);
+
+ // Set mixer values so the backend expert knows how to calculate final frequency
+ _mixer1_m = _tune_settings.mix1_m;
+ _mixer1_n = _tune_settings.mix1_n;
+ _mixer2_m = _tune_settings.mix2_m;
+ _mixer2_n = _tune_settings.mix2_n;
+
+ _is_highband = _is_band_highband(_tune_settings);
+ _lo1_enabled = !_is_highband.get();
+
+ double if1_freq = tune_freq;
+ const double lo_step = _lo_freq_range.step();
+ // If we need to apply an offset to avoid injection locking, we need to
+ // offset in different directions for different channels on the same zbx
+ const double lo_offset_sign = (_chan == 0) ? -1 : 1;
+ // In high band, LO1 is not needed (the signal is already at a high enough
+ // frequency for the second stage)
+ if (_lo1_enabled) {
+ // Calculate the ideal IF1:
+ if1_freq = _calc_if1_freq(tune_freq, _tune_settings);
+ // We calculate the LO1 frequency by first shifting the tune frequency to the
+ // desired IF, and then applying an offset such that CH0 and CH1 tune to distinct
+ // LO1 frequencies: This is done to prevent the LO's from interfering with each
+ // other in a phenomenon known as injection locking.
+ const double lo1_freq =
+ if1_freq + (_tune_settings.mix1_n * tune_freq) + (lo_offset_sign * lo_step);
+ // Now, quantize the LO frequency to the nearest valid value:
+ _desired_lo1_frequency = _lo_freq_range.clip(lo1_freq, true);
+ // Because LO1 frequency probably changed during quantization, we simply
+ // re-calculate the now-valid IF1 (the following equation is the same as
+ // the LO1 frequency calculation, but solved for if1_freq):
+ if1_freq = _desired_lo1_frequency - (_tune_settings.mix1_n * tune_freq);
+ }
+
+ _lo2_enabled = true;
+ // Calculate ideal IF2 frequency:
+ const double if2_freq = _calc_ideal_if2_freq(tune_freq, _tune_settings);
+ // Calculate LO2 frequency from that:
+ _desired_lo2_frequency = _calc_lo2_freq(if1_freq, if2_freq, _mixer2_m, _mixer2_n);
+ // Similar to LO1, apply an offset such that CH0 and CH1 tune to distinct LO2
+ // frequencies to prevent potential interference between CH0 and CH1 LO2's from
+ // injection locking: In highband (LO1 disabled), this must explicitly be done below.
+ // In lowband (LO1 enabled), the LO1 will have already been shifted and, as a result,
+ // the LO2's will have already been shifted to compensate for LO1 in previous
+ // function. Note that in lowband, the LO1's and LO2's will be offset between CH0 and
+ // CH1; however, they will be offset in opposite direction such that the NCO frequency
+ // will be the same between CH0 and CH1. This is not the case for highband (only LO2
+ // and they must be offset).
+ if (!_lo1_enabled) {
+ _desired_lo2_frequency = _desired_lo2_frequency + (lo_offset_sign * lo_step);
+ }
+ // Now, quantize the LO frequency to the nearest valid value:
+ _desired_lo2_frequency = _lo_freq_range.clip(_desired_lo2_frequency, true);
+ // Calculate actual IF2 frequency from LO2 and IF1 frequencies:
+ _desired_if2_frequency =
+ _calc_if2_freq(if1_freq, _desired_lo2_frequency, _mixer2_m, _mixer2_n);
+
+ // If the frequency is in a different tuning band, we need to switch filters
+ _rf_filter = _tune_settings.rf_fir;
+ _if1_filter = _tune_settings.if1_fir;
+ _if2_filter = _tune_settings.if2_fir;
+ _band_inverted =
+ _is_band_inverted(_trx, _desired_if2_frequency, _rfdc_rate, _tune_settings);
+}
+
+
+void zbx_freq_be_expert::resolve()
+{
+ if (_is_highband) {
+ _coerced_frequency =
+ ((_coerced_if2_frequency - (_coerced_lo2_frequency * _mixer2_n)) / _mixer2_m);
+ } else {
+ _coerced_frequency =
+ (_coerced_lo1_frequency
+ + ((_coerced_lo2_frequency * _mixer2_n - _coerced_if2_frequency)
+ / _mixer2_m))
+ / _mixer1_n;
+ }
+
+ // Users may change individual settings (LO frequencies, if2 frequencies) and throw
+ // the output frequency out of range. We have to stop here so that the gain API
+ // doesn't panic (Clipping here would have no effect on the actual output signal)
+ using namespace uhd::math::fp_compare;
+ if (fp_compare_delta<double>(_coerced_frequency.get()) < ZBX_MIN_FREQ
+ || fp_compare_delta<double>(_coerced_frequency.get()) > ZBX_MAX_FREQ) {
+ UHD_LOG_WARNING(get_name(),
+ "Resulting coerced frequency " << _coerced_frequency.get()
+ << " is out of range!");
+ }
+}
+
+void zbx_lo_expert::resolve()
+{
+ if (_test_mode_enabled.is_dirty()) {
+ _lo_ctrl->set_lo_test_mode_enabled(_test_mode_enabled);
+ }
+
+ if (_set_is_enabled.is_dirty()) {
+ _lo_ctrl->set_lo_port_enabled(_set_is_enabled);
+ }
+
+ if (_set_is_enabled && _desired_lo_frequency.is_dirty()) {
+ const double clipped_lo_freq = std::max(
+ LMX2572_MIN_FREQ, std::min(_desired_lo_frequency.get(), LMX2572_MAX_FREQ));
+ _coerced_lo_frequency = _lo_ctrl->set_lo_freq(clipped_lo_freq);
+ UHD_LOG_TRACE(get_name(),
+ "Requested " << _get_trx_string(_trx) << _chan << " frequency "
+ << (_desired_lo_frequency / 1e6) << "MHz was coerced to "
+ << (_coerced_lo_frequency / 1e6) << "MHz");
+ }
+}
+
+void zbx_gain_coercer_expert::resolve()
+{
+ _gain_coerced = _valid_range.clip(_gain_desired, true);
+}
+
+void zbx_tx_gain_expert::resolve()
+{
+ if (_profile != ZBX_GAIN_PROFILE_DEFAULT) {
+ return;
+ }
+
+ // If a user passes in a gain value, we have to set the Power API tracking mode
+ if (_gain_in.is_dirty()) {
+ _power_mgr->set_tracking_mode(uhd::usrp::pwr_cal_mgr::tracking_mode::TRACK_GAIN);
+ }
+
+ // Now we do the overall gain setting
+ // Look up DSA values by gain
+ _gain_out = ZBX_TX_GAIN_RANGE.clip(_gain_in, true);
+ const size_t gain_idx = _gain_out / TX_GAIN_STEP;
+ // Clip _frequency to valid ZBX range to avoid errors in the scenario when user
+ // manually configures LO frequencies and causes an illegal overall frequency
+ auto dsa_settings =
+ _dsa_cal->get_dsa_setting(ZBX_FREQ_RANGE.clip(_frequency), gain_idx);
+ // Now write to downstream nodes, converting attenuations to gains:
+ _dsa1 = static_cast<double>(ZBX_TX_DSA_MAX_ATT - dsa_settings[0]);
+ _dsa2 = static_cast<double>(ZBX_TX_DSA_MAX_ATT - dsa_settings[1]);
+ // Convert amp index to gain
+ _amp_gain = ZBX_TX_AMP_GAIN_MAP.at(static_cast<tx_amp>(dsa_settings[2]));
+}
+
+void zbx_rx_gain_expert::resolve()
+{
+ if (_profile != ZBX_GAIN_PROFILE_DEFAULT) {
+ return;
+ }
+
+ // If a user passes in a gain value, we have to set the Power API tracking mode
+ if (_gain_in.is_dirty()) {
+ _power_mgr->set_tracking_mode(uhd::usrp::pwr_cal_mgr::tracking_mode::TRACK_GAIN);
+ }
+
+ // Now we do the overall gain setting
+ if (_frequency.get() <= RX_LOW_FREQ_MAX_GAIN_CUTOFF) {
+ _gain_out = ZBX_RX_LOW_FREQ_GAIN_RANGE.clip(_gain_in, true);
+ } else {
+ _gain_out = ZBX_RX_GAIN_RANGE.clip(_gain_in, true);
+ }
+ // Now we do the overall gain setting
+ // Look up DSA values by gain
+ const size_t gain_idx = _gain_out / RX_GAIN_STEP;
+ // Clip _frequency to valid ZBX range to avoid errors in the scenario when user
+ // manually configures LO frequencies and causes an illegal overall frequency
+ auto dsa_settings =
+ _dsa_cal->get_dsa_setting(ZBX_FREQ_RANGE.clip(_frequency), gain_idx);
+ // Now write to downstream nodes, converting attenuation to gains:
+ _dsa1 = ZBX_RX_DSA_MAX_ATT - dsa_settings[0];
+ _dsa2 = ZBX_RX_DSA_MAX_ATT - dsa_settings[1];
+ _dsa3a = ZBX_RX_DSA_MAX_ATT - dsa_settings[2];
+ _dsa3b = ZBX_RX_DSA_MAX_ATT - dsa_settings[3];
+}
+
+void zbx_tx_programming_expert::resolve()
+{
+ if (_profile.is_dirty()) {
+ if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL
+ || _profile == ZBX_GAIN_PROFILE_CPLD) {
+ _cpld->set_atr_mode(_chan,
+ zbx_cpld_ctrl::atr_mode_target::DSA,
+ zbx_cpld_ctrl::atr_mode::CLASSIC_ATR);
+ } else {
+ _cpld->set_atr_mode(_chan,
+ zbx_cpld_ctrl::atr_mode_target::DSA,
+ zbx_cpld_ctrl::atr_mode::SW_DEFINED);
+ }
+ }
+
+ // If we're in any of the table modes, then we don't write DSA and amp values
+ // A note on caching: The CPLD object caches state, and only pokes the CPLD
+ // if it's changed. However, all DSAs are on the same register. That means
+ // the DSA register changes, all DSA values written to the CPLD will come
+ // from the input data nodes to this worker node. This can overwrite DSA
+ // values if the cached version and the actual value on the CPLD differ.
+ if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL) {
+ // Convert gains back to attenuation
+ zbx_cpld_ctrl::tx_dsa_type dsa_settings = {
+ uhd::narrow_cast<uint32_t>(ZBX_TX_DSA_MAX_ATT - _dsa1.get()),
+ uhd::narrow_cast<uint32_t>(ZBX_TX_DSA_MAX_ATT - _dsa2.get())};
+ _cpld->set_tx_gain_switches(_chan, ATR_ADDR_TX, dsa_settings);
+ _cpld->set_tx_gain_switches(_chan, ATR_ADDR_XX, dsa_settings);
+ }
+
+ // If frequency changed, we might have changed bands and the CPLD dsa tables need to
+ // be reloaded
+ // TODO: This is a major hack, and these tables should be loaded outside of the
+ // tuning call. This means every tuning request involves a large amount of CPLD
+ // writes.
+ // We only write when we aren't using a command time, otherwise all those CPLD
+ // commands will line up in the CPLD command queue, and diminish any purpose
+ // of timed commands in the first place
+ // Clip _frequency to valid ZBX range to avoid errors in the scenario when user
+ // manually configures LO frequencies and causes an illegal overall frequency
+ if (_command_time == 0.0) {
+ _cpld->update_tx_dsa_settings(
+ _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 0 /*dsa1*/),
+ _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 1 /*dsa2*/));
+ }
+
+ for (const size_t idx : ATR_ADDRS) {
+ _cpld->set_lo_source(idx,
+ zbx_lo_ctrl::lo_string_to_enum(TX_DIRECTION, _chan, ZBX_LO1),
+ _lo1_source);
+ _cpld->set_lo_source(idx,
+ zbx_lo_ctrl::lo_string_to_enum(TX_DIRECTION, _chan, ZBX_LO2),
+ _lo2_source);
+
+ _cpld->set_tx_rf_filter(_chan, idx, _rf_filter);
+ _cpld->set_tx_if1_filter(_chan, idx, _if1_filter);
+ _cpld->set_tx_if2_filter(_chan, idx, _if2_filter);
+ }
+
+ // Convert amp gain to amp index
+ UHD_ASSERT_THROW(ZBX_TX_GAIN_AMP_MAP.count(_amp_gain.get()));
+ const tx_amp amp = ZBX_TX_GAIN_AMP_MAP.at(_amp_gain.get());
+ _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_0X, _antenna, tx_amp::BYPASS);
+ _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_RX, _antenna, tx_amp::BYPASS);
+ _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_TX, _antenna, amp);
+ _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_XX, _antenna, amp);
+
+ // We do not update LEDs on switching TX antenna value by definition
+}
+
+void zbx_rx_programming_expert::resolve()
+{
+ if (_profile.is_dirty()) {
+ if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL
+ || _profile == ZBX_GAIN_PROFILE_CPLD) {
+ _cpld->set_atr_mode(_chan,
+ zbx_cpld_ctrl::atr_mode_target::DSA,
+ zbx_cpld_ctrl::atr_mode::CLASSIC_ATR);
+ } else {
+ _cpld->set_atr_mode(_chan,
+ zbx_cpld_ctrl::atr_mode_target::DSA,
+ zbx_cpld_ctrl::atr_mode::SW_DEFINED);
+ }
+ }
+
+ // If we're in any of the table modes, then we don't write DSA values
+ // A note on caching: The CPLD object caches state, and only pokes the CPLD
+ // if it's changed. However, all DSAs are on the same register. That means
+ // the DSA register changes, all DSA values written to the CPLD will come
+ // from the input data nodes to this worker node. This can overwrite DSA
+ // values if the cached version and the actual value on the CPLD differ.
+ if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL) {
+ zbx_cpld_ctrl::rx_dsa_type dsa_settings = {
+ uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa1.get()),
+ uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa2.get()),
+ uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa3a.get()),
+ uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa3b.get())};
+ _cpld->set_rx_gain_switches(_chan, ATR_ADDR_RX, dsa_settings);
+ _cpld->set_rx_gain_switches(_chan, ATR_ADDR_XX, dsa_settings);
+ }
+
+
+ // If frequency changed, we might have changed bands and the CPLD dsa tables need to
+ // be reloaded
+ // TODO: This is a major hack, and these tables should be loaded outside of the
+ // tuning call. This means every tuning request involves a large amount of CPLD
+ // writes.
+ // We only write when we aren't using a command time, otherwise all those CPLD
+ // commands will line up in the CPLD command queue, and diminish any purpose
+ // of timed commands in the first place
+ // Clip _frequency to valid ZBX range to avoid errors in the scenario when user
+ // manually configures LO frequencies and causes an illegal overall frequency
+ if (_command_time == 0.0) {
+ _cpld->update_rx_dsa_settings(
+ _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 0 /*dsa1*/),
+ _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 1 /*dsa2*/),
+ _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 2 /*dsa3a*/),
+ _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 3 /*dsa3b*/));
+ }
+
+ for (const size_t idx : ATR_ADDRS) {
+ _cpld->set_lo_source(idx,
+ zbx_lo_ctrl::lo_string_to_enum(RX_DIRECTION, _chan, ZBX_LO1),
+ _lo1_source);
+ _cpld->set_lo_source(idx,
+ zbx_lo_ctrl::lo_string_to_enum(RX_DIRECTION, _chan, ZBX_LO2),
+ _lo2_source);
+
+ // 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) {
+ _cpld->set_rx_antenna_switches(_chan, idx, _antenna);
+ }
+
+ _cpld->set_rx_rf_filter(_chan, idx, _rf_filter);
+ _cpld->set_rx_if1_filter(_chan, idx, _if1_filter);
+ _cpld->set_rx_if2_filter(_chan, idx, _if2_filter);
+ }
+
+ _update_leds();
+}
+
+void zbx_rx_programming_expert::_update_leds()
+{
+ if (_atr_mode != zbx_cpld_ctrl::atr_mode::CLASSIC_ATR) {
+ return;
+ }
+ // We default to the RX1 LED 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
+ _cpld->set_leds(_chan, ATR_ADDR_0X, false, false, false);
+ _cpld->set_leds(_chan, ATR_ADDR_RX, !rx_on_trx, rx_on_trx, false);
+ _cpld->set_leds(_chan, ATR_ADDR_TX, false, false, true );
+ _cpld->set_leds(_chan, ATR_ADDR_XX, !rx_on_trx, rx_on_trx, true );
+ // clang-format on
+}
+
+void zbx_band_inversion_expert::resolve()
+{
+ _rpcc->enable_iq_swap(_is_band_inverted.get(), _get_trx_string(_trx), _chan);
+}
+
+void zbx_rfdc_freq_expert::resolve()
+{
+ // Because we can configure both IF2 and the RFDC NCO frequency, these may
+ // come into conflict. We choose IF2 over RFDC in that case. In other words
+ // the only time we choose the desired RFDC frequency over the IF2 (when in
+ // conflict) is when the RFDC freq was changed directly.
+ const double desired_rfdc_freq = [&]() -> double {
+ if (_rfdc_freq_desired.is_dirty() && !_if2_frequency_desired.is_dirty()) {
+ return _rfdc_freq_desired;
+ }
+ return _if2_frequency_desired;
+ }();
+
+ _rfdc_freq_coerced = _rpcc->rfdc_set_nco_freq(
+ _get_trx_string(_trx), _db_idx, _chan, desired_rfdc_freq);
+ _if2_frequency_coerced = _rfdc_freq_coerced;
+}
+
+void zbx_sync_expert::resolve()
+{
+ // Some local helper consts
+ // clang-format off
+ constexpr std::array<std::array<zbx_lo_t, 4>, 2> los{{{
+ zbx_lo_t::RX0_LO1,
+ zbx_lo_t::RX0_LO2,
+ zbx_lo_t::TX0_LO1,
+ zbx_lo_t::TX0_LO2
+ }, {
+ zbx_lo_t::RX1_LO1,
+ zbx_lo_t::RX1_LO2,
+ zbx_lo_t::TX1_LO1,
+ zbx_lo_t::TX1_LO2
+ }}};
+ constexpr std::array<std::array<rfdc_control::rfdc_type, 2>, 2> ncos{{
+ {rfdc_control::rfdc_type::RX0, rfdc_control::rfdc_type::TX0},
+ {rfdc_control::rfdc_type::RX1, rfdc_control::rfdc_type::TX1}
+ }};
+ // clang-format on
+
+ // Now do some timing checks
+ const std::vector<bool> chan_needs_sync = {_fe_time.at(0) != uhd::time_spec_t::ASAP,
+ _fe_time.at(1) != uhd::time_spec_t::ASAP};
+ // If there's no command time, no need to synchronize anything
+ if (!chan_needs_sync[0] && !chan_needs_sync[1]) {
+ UHD_LOG_TRACE(get_name(), "No command time: Skipping phase sync.");
+ return;
+ }
+ const bool times_match = _fe_time.at(0) == _fe_time.at(1);
+
+ // ** Find LOs to synchronize *********************************************
+ // Find dirty LOs which need sync'ing
+ std::set<zbx_lo_t> los_to_sync;
+ for (const size_t chan : ZBX_CHANNELS) {
+ if (chan_needs_sync[chan]) {
+ for (const auto& lo_idx : los[chan]) {
+ if (_lo_freqs.at(lo_idx).is_dirty()) {
+ los_to_sync.insert(lo_idx);
+ }
+ }
+ }
+ }
+
+ // ** Find NCOs to synchronize ********************************************
+ // Same rules apply as for LOs.
+ std::set<rfdc_control::rfdc_type> ncos_to_sync;
+ for (const size_t chan : ZBX_CHANNELS) {
+ if (chan_needs_sync[chan]) {
+ for (const auto& nco_idx : ncos[chan]) {
+ if (_nco_freqs.at(nco_idx).is_dirty()) {
+ ncos_to_sync.insert(nco_idx);
+ }
+ }
+ }
+ }
+
+ // ** Find ADC/DAC gearboxes to synchronize *******************************
+ // 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.
+ std::set<rfdc_control::rfdc_type> gearboxes_to_sync;
+ if (!_adcs_synced) {
+ for (const auto rfdc :
+ {rfdc_control::rfdc_type::RX0, rfdc_control::rfdc_type::RX1}) {
+ if (ncos_to_sync.count(rfdc)) {
+ gearboxes_to_sync.insert(rfdc);
+ // 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;
+ }
+ }
+ }
+ if (!_dacs_synced) {
+ for (const auto rfdc :
+ {rfdc_control::rfdc_type::TX0, rfdc_control::rfdc_type::TX1}) {
+ if (ncos_to_sync.count(rfdc)) {
+ gearboxes_to_sync.insert(rfdc);
+ // 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;
+ }
+ }
+ }
+
+ // ** Do synchronization **************************************************
+ // This is where we orchestrate the sync commands. If sync commands happen
+ // at different times, we make sure to send out the earlier one first.
+ // If we need to schedule things a bit differently, e.g., we need to
+ // manually calculate offsets from the command time so that LO and NCO sync
+ // pulses line up, it most likely makes sense to use the scheduling expert
+ // for that, and calculate different times for different events there.
+ if (times_match) {
+ UHD_LOG_TRACE(get_name(),
+ "Syncing all channels: " << los_to_sync.size() << " LO(s), "
+ << ncos_to_sync.size() << " NCO(s), and "
+ << gearboxes_to_sync.size() << " gearbox(es).")
+ if (!gearboxes_to_sync.empty()) {
+ _rfdcc->reset_gearboxes(
+ std::vector<rfdc_control::rfdc_type>(
+ gearboxes_to_sync.cbegin(), gearboxes_to_sync.cend()),
+ _fe_time.at(0).get());
+ }
+ if (!los_to_sync.empty()) {
+ _cpld->pulse_lo_sync(
+ 0, std::vector<zbx_lo_t>(los_to_sync.cbegin(), los_to_sync.cend()));
+ }
+ if (!ncos_to_sync.empty()) {
+ _rfdcc->reset_ncos(std::vector<rfdc_control::rfdc_type>(
+ ncos_to_sync.cbegin(), ncos_to_sync.cend()),
+ _fe_time.at(0).get());
+ }
+ } else {
+ // If the command times differ, we need to manually reorder the commands
+ // such that the channel with the earlier time gets precedence
+ const size_t first_sync_chan =
+ (times_match || (_fe_time.at(0) <= _fe_time.at(1))) ? 0 : 1;
+ const auto sync_order = (first_sync_chan == 0) ? std::vector<size_t>{0, 1}
+ : std::vector<size_t>{1, 0};
+ for (const size_t chan : sync_order) {
+ std::vector<zbx_lo_t> this_chan_los;
+ for (const zbx_lo_t lo_idx : los[chan]) {
+ if (los_to_sync.count(lo_idx)) {
+ this_chan_los.push_back(lo_idx);
+ }
+ }
+
+ std::vector<rfdc_control::rfdc_type> this_chan_ncos;
+ for (const auto nco_idx : ncos[chan]) {
+ if (ncos_to_sync.count(nco_idx)) {
+ this_chan_ncos.push_back(nco_idx);
+ }
+ }
+ std::vector<rfdc_control::rfdc_type> this_chan_gearboxes;
+ for (const auto gb_idx : ncos[chan]) {
+ if (gearboxes_to_sync.count(gb_idx)) {
+ this_chan_gearboxes.push_back(gb_idx);
+ }
+ }
+ UHD_LOG_TRACE(get_name(),
+ "Syncing channel " << chan << ": " << this_chan_los.size()
+ << " LO(s) and " << this_chan_ncos.size()
+ << " NCO(s).");
+ if (!this_chan_gearboxes.empty()) {
+ UHD_LOG_TRACE(get_name(),
+ "Resetting " << this_chan_gearboxes.size() << " gearboxes.");
+ _rfdcc->reset_gearboxes(this_chan_gearboxes, _fe_time.at(chan).get());
+ }
+ if (!this_chan_los.empty()) {
+ _cpld->pulse_lo_sync(chan, this_chan_los);
+ }
+ if (!this_chan_ncos.empty()) {
+ _rfdcc->reset_ncos(this_chan_ncos, _fe_time.at(chan).get());
+ }
+ }
+ }
+} // zbx_sync_expert::resolve()
+
+// End expert resolve sections
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp b/host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
new file mode 100644
index 000000000..1af665207
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
@@ -0,0 +1,162 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/exception.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp>
+#include <thread>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+zbx_lo_ctrl::zbx_lo_ctrl(zbx_lo_t lo,
+ lmx2572_iface::write_fn_t&& poke16,
+ lmx2572_iface::read_fn_t&& peek16,
+ lmx2572_iface::sleep_fn_t&& sleep,
+ const double default_frequency,
+ const double db_prc_rate,
+ const bool testing_mode_enabled)
+ : _lo(lo)
+ , _log_id(ZBX_LO_LOG_ID.at(lo))
+ , _freq(default_frequency)
+ , _db_prc_rate(db_prc_rate)
+ , _testing_mode_enabled(testing_mode_enabled)
+{
+ _lmx = lmx2572_iface::make(std::move(poke16), std::move(peek16), std::move(sleep));
+ UHD_ASSERT_THROW(_lmx);
+ UHD_LOG_TRACE(_log_id, "LO initialized...");
+ _lmx->reset();
+
+ set_lo_port_enabled(true);
+ // In ZBX, we always run the LOs in sync mode. It is theoretically possible
+ // to not do so, but we gain nothing by doing that.
+ _lmx->set_sync_mode(true);
+ set_lo_freq(LMX2572_DEFAULT_FREQ);
+ wait_for_lo_lock();
+}
+
+double zbx_lo_ctrl::set_lo_freq(const double freq)
+{
+ UHD_ASSERT_THROW(_lmx);
+ UHD_LOG_TRACE(_log_id, "Setting LO frequency " << freq / 1e6 << " MHz");
+
+ _freq = _lmx->set_frequency(freq,
+ _db_prc_rate,
+ false /*TODO: get_spur_dodging()*/);
+ _lmx->commit();
+ return _freq;
+}
+
+double zbx_lo_ctrl::get_lo_freq()
+{
+ return _freq;
+}
+
+void zbx_lo_ctrl::wait_for_lo_lock()
+{
+ UHD_LOG_TRACE(_log_id, "Waiting for LO lock, " << ZBX_LO_LOCK_TIMEOUT_MS << " ms");
+ const auto timeout = std::chrono::steady_clock::now()
+ + std::chrono::milliseconds(ZBX_LO_LOCK_TIMEOUT_MS);
+ while (std::chrono::steady_clock::now() < timeout && !get_lock_status()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ if (!get_lock_status()) {
+ // If we can't lock our LO, this could be a lot of possible issues
+ throw uhd::runtime_error(_log_id + " has failed to lock!");
+ }
+}
+
+bool zbx_lo_ctrl::get_lock_status()
+{
+ return _lmx->get_lock_status();
+}
+
+void zbx_lo_ctrl::set_lo_port_enabled(bool enable)
+{
+ UHD_LOG_TRACE(_log_id,
+ "Enabling LO " << (_testing_mode_enabled ? "test" : "output") << " port");
+
+ // We want to set the output port regardless of test mode being enabled
+ _lmx->set_output_enable(_get_output_port(false), enable);
+
+ if (_testing_mode_enabled && enable) {
+ // If testing mode is enabled, also set the test port
+ _lmx->set_output_enable(_get_output_port(true), true);
+ } else {
+ // If testing mode is disabled, test port should be disabled
+ _lmx->set_output_enable(_get_output_port(true), false);
+ _lmx->set_mux_input(
+ _get_output_port(true), lmx2572_iface::mux_in_t::HIGH_IMPEDANCE);
+ }
+
+ _lmx->set_enabled(enable);
+ _lmx->commit();
+}
+
+bool zbx_lo_ctrl::get_lo_port_enabled()
+{
+ return _lmx->get_enabled();
+}
+
+void zbx_lo_ctrl::set_lo_test_mode_enabled(bool enable)
+{
+ _testing_mode_enabled = enable;
+ set_lo_port_enabled(get_lo_port_enabled());
+}
+
+bool zbx_lo_ctrl::get_lo_test_mode_enabled()
+{
+ return _testing_mode_enabled;
+}
+
+zbx_lo_t zbx_lo_ctrl::lo_string_to_enum(
+ const uhd::direction_t trx, const size_t channel, const std::string name)
+{
+ if (trx == TX_DIRECTION) {
+ if (channel == 0) {
+ if (name == ZBX_LO1) {
+ return zbx_lo_t::TX0_LO1;
+ } else if (name == ZBX_LO2) {
+ return zbx_lo_t::TX0_LO2;
+ }
+ } else if (channel == 1) {
+ if (name == ZBX_LO1) {
+ return zbx_lo_t::TX1_LO1;
+ } else if (name == ZBX_LO2) {
+ return zbx_lo_t::TX1_LO2;
+ }
+ }
+ } else {
+ if (channel == 0) {
+ if (name == ZBX_LO1) {
+ return zbx_lo_t::RX0_LO1;
+ } else if (name == ZBX_LO2) {
+ return zbx_lo_t::RX0_LO2;
+ }
+ } else if (channel == 1) {
+ if (name == ZBX_LO1) {
+ return zbx_lo_t::RX1_LO1;
+ } else if (name == ZBX_LO2) {
+ return zbx_lo_t::RX1_LO2;
+ }
+ }
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+lmx2572_iface::output_t zbx_lo_ctrl::_get_output_port(bool testing_mode)
+{
+ // Note: The LO output ports here are dependent to the LO and zbx hardware
+ // configuration, in no particular order (zbx radio configuration output vs.
+ // test port output)
+ if (!testing_mode) {
+ // Rev B has all LO outputs on Port A
+ return lmx2572_iface::output_t::RF_OUTPUT_A;
+ } else {
+ return lmx2572_iface::output_t::RF_OUTPUT_B;
+ }
+}
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/usrp/mpmd/mpmd_devices.hpp b/host/lib/usrp/mpmd/mpmd_devices.hpp
index 9cc046037..689e5c981 100644
--- a/host/lib/usrp/mpmd/mpmd_devices.hpp
+++ b/host/lib/usrp/mpmd/mpmd_devices.hpp
@@ -19,6 +19,6 @@ static const std::vector<std::string> MPM_DEVICE_TYPES = {
MPM_CATCHALL_DEVICE_TYPE,
"n3xx",
"e3xx",
-};
+ "x4xx"};
#endif /* INCLUDED_MPMD_DEVICES_HPP */
diff --git a/host/lib/usrp/mpmd/mpmd_image_loader.cpp b/host/lib/usrp/mpmd/mpmd_image_loader.cpp
index 145ad70c9..c15c4832c 100644
--- a/host/lib/usrp/mpmd/mpmd_image_loader.cpp
+++ b/host/lib/usrp/mpmd/mpmd_image_loader.cpp
@@ -10,10 +10,13 @@
#include <uhd/device.hpp>
#include <uhd/exception.hpp>
#include <uhd/image_loader.hpp>
+#include <uhd/rfnoc/radio_control.hpp>
+#include <uhd/rfnoc_graph.hpp>
#include <uhd/types/component_file.hpp>
#include <uhd/types/eeprom.hpp>
#include <uhd/utils/paths.hpp>
#include <uhd/utils/static.hpp>
+#include <uhdlib/features/fpga_load_notification_iface.hpp>
#include <uhdlib/utils/prefs.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
@@ -368,6 +371,19 @@ static bool mpmd_image_loader(const image_loader::image_loader_args_t& image_loa
mpmd_send_fpga_to_device(image_loader_args, dev_addr);
+ {
+ // All MPM devices use RFNoC
+ auto graph = rfnoc::rfnoc_graph::make(find_hint);
+ for (size_t mb_index = 0; mb_index < graph->get_num_mboards(); mb_index++) {
+ auto mboard = graph->get_mb_controller(mb_index);
+ if (mboard->has_feature<uhd::features::fpga_load_notification_iface>()) {
+ auto& notifier =
+ mboard->get_feature<uhd::features::fpga_load_notification_iface>();
+ notifier.onload();
+ }
+ }
+ }
+
return true;
}
@@ -385,4 +401,5 @@ UHD_STATIC_BLOCK(register_mpm_image_loader)
// time being
image_loader::register_image_loader("n3xx", mpmd_image_loader, recovery_instructions);
image_loader::register_image_loader("e3xx", mpmd_image_loader, recovery_instructions);
+ image_loader::register_image_loader("x4xx", mpmd_image_loader, recovery_instructions);
}
diff --git a/host/lib/usrp/mpmd/mpmd_mb_controller.cpp b/host/lib/usrp/mpmd/mpmd_mb_controller.cpp
index dba8713c4..9bbd324b0 100644
--- a/host/lib/usrp/mpmd/mpmd_mb_controller.cpp
+++ b/host/lib/usrp/mpmd/mpmd_mb_controller.cpp
@@ -14,6 +14,44 @@ namespace {
constexpr size_t MPMD_DEFAULT_LONG_TIMEOUT = 30000; // ms
} // namespace
+mpmd_mb_controller::fpga_onload::fpga_onload()
+{}
+
+void mpmd_mb_controller::fpga_onload::onload()
+{
+ for (auto& cb : _cbs)
+ {
+ if (auto spt = cb.lock())
+ {
+ spt->onload();
+ }
+ }
+}
+
+void mpmd_mb_controller::fpga_onload::request_cb(uhd::features::fpga_load_notification_iface::sptr handler)
+{
+ _cbs.emplace_back(handler);
+}
+
+mpmd_mb_controller::ref_clk_calibration::ref_clk_calibration(uhd::usrp::mpmd_rpc_iface::sptr rpcc)
+ : _rpcc(rpcc)
+{}
+
+void mpmd_mb_controller::ref_clk_calibration::set_ref_clk_tuning_word(uint32_t tuning_word)
+{
+ _rpcc->set_ref_clk_tuning_word(tuning_word);
+}
+
+uint32_t mpmd_mb_controller::ref_clk_calibration::get_ref_clk_tuning_word()
+{
+ return _rpcc->get_ref_clk_tuning_word();
+}
+
+void mpmd_mb_controller::ref_clk_calibration::store_ref_clk_tuning_word(uint32_t tuning_word)
+{
+ _rpcc->store_ref_clk_tuning_word(tuning_word);
+}
+
mpmd_mb_controller::mpmd_mb_controller(
uhd::usrp::mpmd_rpc_iface::sptr rpcc, uhd::device_addr_t device_info)
: _rpc(rpcc), _device_info(device_info)
@@ -33,6 +71,14 @@ mpmd_mb_controller::mpmd_mb_controller(
for (const auto& bank : _gpio_banks) {
_gpio_srcs.insert({bank, _rpc->get_gpio_srcs(bank)});
}
+
+ _fpga_onload = std::make_shared<fpga_onload>();
+ register_feature(_fpga_onload);
+
+ if (_rpc->supports_feature("ref_clk_calibration")) {
+ _ref_clk_cal = std::make_shared<ref_clk_calibration>(_rpc);
+ register_feature(_ref_clk_cal);
+ }
}
/******************************************************************************
@@ -142,16 +188,22 @@ std::vector<device_addr_t> mpmd_mb_controller::get_sync_sources()
return result;
}
-void mpmd_mb_controller::set_clock_source_out(const bool /*enb*/)
+void mpmd_mb_controller::set_clock_source_out(const bool enb)
{
- throw uhd::not_implemented_error(
- "set_clock_source_out() not implemented on this device!");
+ _rpc->set_clock_source_out(enb);
}
-void mpmd_mb_controller::set_time_source_out(const bool /*enb*/)
+void mpmd_mb_controller::set_time_source_out(const bool enb)
{
- throw uhd::not_implemented_error(
- "set_time_source_out() not implemented on this device!");
+ if (_rpc->supports_feature("time_export"))
+ {
+ _rpc->set_trigger_io(enb ? "pps_output" : "off");
+ }
+ else
+ {
+ throw uhd::not_implemented_error(
+ "set_time_source_out() not implemented on this device!");
+ }
}
sensor_value_t mpmd_mb_controller::get_sensor(const std::string& name)
diff --git a/host/lib/usrp/x300/x300_mb_controller.hpp b/host/lib/usrp/x300/x300_mb_controller.hpp
index 6cfa3f525..a0ffb0654 100644
--- a/host/lib/usrp/x300/x300_mb_controller.hpp
+++ b/host/lib/usrp/x300/x300_mb_controller.hpp
@@ -11,6 +11,7 @@
#include "x300_device_args.hpp"
#include "x300_radio_mbc_iface.hpp"
#include "x300_regs.hpp"
+#include <uhdlib/features/discoverable_feature_registry.hpp>
#include <uhd/rfnoc/mb_controller.hpp>
#include <uhd/types/sensors.hpp>
#include <uhd/types/wb_iface.hpp>
@@ -28,7 +29,8 @@ namespace uhd { namespace rfnoc {
* - Controlling all time- and clock-related settings
* - Initialize and hold the GPS control
*/
-class x300_mb_controller : public mb_controller
+class x300_mb_controller : public mb_controller,
+ public ::uhd::features::discoverable_feature_registry
{
public:
/**************************************************************************
diff --git a/host/lib/usrp/x400/CMakeLists.txt b/host/lib/usrp/x400/CMakeLists.txt
new file mode 100644
index 000000000..b3efe3b60
--- /dev/null
+++ b/host/lib/usrp/x400/CMakeLists.txt
@@ -0,0 +1,20 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+
+LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/adc_self_calibration.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/x400_radio_control.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/x400_rfdc_control.cpp
+)
+
+if(ENABLE_DPDK)
+ include_directories(${DPDK_INCLUDE_DIRS})
+ add_definitions(-DHAVE_DPDK)
+endif(ENABLE_DPDK)
diff --git a/host/lib/usrp/x400/adc_self_calibration.cpp b/host/lib/usrp/x400/adc_self_calibration.cpp
new file mode 100644
index 000000000..7deba7725
--- /dev/null
+++ b/host/lib/usrp/x400/adc_self_calibration.cpp
@@ -0,0 +1,196 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "adc_self_calibration.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/scope_exit.hpp>
+#include <chrono>
+#include <thread>
+
+using namespace std::chrono_literals;
+
+namespace uhd { namespace features {
+
+adc_self_calibration::adc_self_calibration(uhd::usrp::x400_rpc_iface::sptr rpcc,
+ const std::string rpc_prefix,
+ const std::string unique_id,
+ size_t db_number,
+ uhd::usrp::x400::x400_dboard_iface::sptr daughterboard)
+ : _rpcc(rpcc)
+ , _rpc_prefix(rpc_prefix)
+ , _db_number(db_number)
+ , _daughterboard(daughterboard)
+ , _unique_id(unique_id)
+{
+}
+
+void adc_self_calibration::run(size_t chan)
+{
+ const auto tx_gain_profile =
+ _daughterboard->get_tx_gain_profile_api()->get_gain_profile(chan);
+ const auto rx_gain_profile =
+ _daughterboard->get_rx_gain_profile_api()->get_gain_profile(chan);
+ if (tx_gain_profile != "default" || rx_gain_profile != "default") {
+ throw uhd::runtime_error("Cannot run ADC self calibration when gain "
+ "profile for RX or TX is not 'default'.");
+ }
+
+ // The frequency that we need to feed into the ADC is, by decree,
+ // 13109 / 32768 times the ADC sample rate. (approx. 1.2GHz for a 3Gsps rate)
+ const double spll_freq = _rpcc->get_spll_freq();
+ const double cal_tone_freq = spll_freq * 13109.0 / 32768.0;
+
+ const auto cal_params = _daughterboard->get_adc_self_cal_params(cal_tone_freq);
+
+ // Switch to CAL_LOOPBACK and save the current antenna
+ const auto rx_antenna = _daughterboard->get_rx_antenna(chan);
+ const auto tx_antenna = _daughterboard->get_tx_antenna(chan);
+
+ auto reset_antennas = uhd::utils::scope_exit::make([&]() {
+ _daughterboard->set_rx_antenna(rx_antenna, chan);
+ _daughterboard->set_tx_antenna(tx_antenna, chan);
+
+ // Waiting here allows some CPLD registers to be set. It's not clear
+ // to me why we require this wait. See azdo #1473824
+ constexpr auto fudge_time = 100ms;
+ std::this_thread::sleep_for(fudge_time);
+ });
+
+ _daughterboard->set_rx_antenna("CAL_LOOPBACK", chan);
+ _daughterboard->set_tx_antenna("CAL_LOOPBACK", chan);
+
+ // Configure the output DAC mux to output 1/2 full scale
+ // set_dac_mux_data uses 16-bit values.
+ _rpcc->set_dac_mux_data(32768 / 2, 0);
+
+ const size_t motherboard_channel_number = _db_number * 2 + chan;
+ _rpcc->set_dac_mux_enable(motherboard_channel_number, 1);
+ auto disable_dac_mux = uhd::utils::scope_exit::make(
+ [&]() { _rpcc->set_dac_mux_enable(motherboard_channel_number, 0); });
+
+ // Save all of the LO frequencies & sources
+ const double original_rx_freq = _daughterboard->get_rx_frequency(chan);
+ std::map<std::string, std::tuple<std::string, double>> rx_lo_state;
+ for (auto rx_lo : _daughterboard->get_rx_lo_names(chan)) {
+ const std::string source(_daughterboard->get_rx_lo_source(rx_lo, chan));
+ const double freq = _daughterboard->get_rx_lo_freq(rx_lo, chan);
+ rx_lo_state.emplace(std::piecewise_construct,
+ std::forward_as_tuple(rx_lo),
+ std::forward_as_tuple(source, freq));
+ }
+ auto restore_rx_los = uhd::utils::scope_exit::make([&]() {
+ _daughterboard->set_rx_frequency(original_rx_freq, chan);
+ for (auto entry : rx_lo_state) {
+ auto& lo = std::get<0>(entry);
+ auto& state = std::get<1>(entry);
+
+ auto& source = std::get<0>(state);
+ const double freq = std::get<1>(state);
+ _daughterboard->set_rx_lo_source(source, lo, chan);
+ _daughterboard->set_rx_lo_freq(freq, lo, chan);
+ }
+ });
+
+ const double original_tx_freq = _daughterboard->get_tx_frequency(chan);
+ std::map<std::string, std::tuple<std::string, double>> tx_lo_state;
+ for (auto tx_lo : _daughterboard->get_tx_lo_names(chan)) {
+ const std::string source(_daughterboard->get_tx_lo_source(tx_lo, chan));
+ const double freq = _daughterboard->get_tx_lo_freq(tx_lo, chan);
+ tx_lo_state.emplace(std::piecewise_construct,
+ std::forward_as_tuple(tx_lo),
+ std::forward_as_tuple(source, freq));
+ }
+ auto restore_tx_los = uhd::utils::scope_exit::make([&]() {
+ _daughterboard->set_tx_frequency(original_tx_freq, chan);
+ for (auto entry : tx_lo_state) {
+ auto& lo = std::get<0>(entry);
+ auto& state = std::get<1>(entry);
+
+ auto& source = std::get<0>(state);
+ const double freq = std::get<1>(state);
+ _daughterboard->set_tx_lo_source(source, lo, chan);
+ _daughterboard->set_tx_lo_freq(freq, lo, chan);
+ }
+ });
+
+ _daughterboard->set_tx_frequency(cal_params.tx_freq, chan);
+ _daughterboard->set_rx_frequency(cal_params.rx_freq, chan);
+
+ // Set & restore the gain
+ const double tx_gain = _daughterboard->get_tx_gain(chan);
+ const double rx_gain = _daughterboard->get_rx_gain(chan);
+ auto restore_gains = uhd::utils::scope_exit::make([&]() {
+ _daughterboard->get_rx_gain_profile_api()->set_gain_profile("default", chan);
+ _daughterboard->get_tx_gain_profile_api()->set_gain_profile("default", chan);
+
+ _daughterboard->set_tx_gain(tx_gain, chan);
+ _daughterboard->set_rx_gain(rx_gain, chan);
+ });
+
+ // Set the threshold to detect half-scale
+ // The setup_threshold call uses 14-bit ADC values
+ constexpr int hysteresis_reset_time = 100;
+ constexpr int hysteresis_reset_threshold = 8000;
+ constexpr int hysteresis_set_threshold = 8192;
+ _rpcc->setup_threshold(_db_number,
+ chan,
+ 0,
+ "hysteresis",
+ hysteresis_reset_time,
+ hysteresis_reset_threshold,
+ hysteresis_set_threshold);
+ bool found_gain = false;
+ for (double i = cal_params.min_gain; i <= cal_params.max_gain; i += 1.0) {
+ _daughterboard->get_rx_gain_profile_api()->set_gain_profile("default", chan);
+ _daughterboard->get_tx_gain_profile_api()->set_gain_profile("default", chan);
+
+ _daughterboard->set_tx_gain(i, chan);
+ _daughterboard->set_rx_gain(i, chan);
+
+ // Set the daughterboard to use the duplex entry in the DSA table which was
+ // configured in the set_?x_gain call. (note that with a LabVIEW FPGA, we don't
+ // control the ATR lines, hence why we override them here)
+ _daughterboard->get_rx_gain_profile_api()->set_gain_profile("table_noatr", chan);
+ _daughterboard->get_tx_gain_profile_api()->set_gain_profile("table_noatr", chan);
+
+ _daughterboard->set_rx_gain(0b11, chan);
+ _daughterboard->set_tx_gain(0b11, chan);
+
+ // Wait for it to settle
+ constexpr auto settle_time = 10ms;
+ std::this_thread::sleep_for(settle_time);
+
+ try {
+ const bool threshold_status =
+ _rpcc->get_threshold_status(_db_number, chan, 0);
+ if (threshold_status) {
+ found_gain = true;
+ break;
+ }
+ } catch (uhd::runtime_error&) {
+ // Catch & eat this error, the user has a 5.0 FPGA and so can't auto-gain
+ return;
+ }
+ }
+
+ if (!found_gain) {
+ throw uhd::runtime_error(
+ "Could not find appropriate gain for performing ADC self cal");
+ }
+
+ // (if required) unfreeze calibration
+ const std::vector<int> current_frozen_state = _rpcc->get_cal_frozen(_db_number, chan);
+ _rpcc->set_cal_frozen(0, _db_number, chan);
+ auto refreeze_adcs = uhd::utils::scope_exit::make(
+ [&]() { _rpcc->set_cal_frozen(current_frozen_state[0], _db_number, chan); });
+
+ // Let the ADC calibrate
+ // 2000ms was found experimentally to be sufficient
+ constexpr auto calibration_time = 2000ms;
+ std::this_thread::sleep_for(calibration_time);
+}
+
+}} // namespace uhd::features
diff --git a/host/lib/usrp/x400/adc_self_calibration.hpp b/host/lib/usrp/x400/adc_self_calibration.hpp
new file mode 100644
index 000000000..474c14e07
--- /dev/null
+++ b/host/lib/usrp/x400/adc_self_calibration.hpp
@@ -0,0 +1,46 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/features/adc_self_calibration_iface.hpp>
+#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/usrp/dboard/x400_dboard_iface.hpp>
+#include <string>
+
+namespace uhd { namespace features {
+
+using namespace uhd::rfnoc;
+
+class adc_self_calibration : public adc_self_calibration_iface
+{
+public:
+ adc_self_calibration(uhd::usrp::x400_rpc_iface::sptr rpcc,
+ const std::string rpc_prefix,
+ const std::string unique_id,
+ size_t db_number,
+ uhd::usrp::x400::x400_dboard_iface::sptr daughterboard);
+
+ void run(const size_t channel) override;
+
+private:
+ //! Reference to the RPC client
+ uhd::usrp::x400_rpc_iface::sptr _rpcc;
+
+ const std::string _rpc_prefix;
+
+ const size_t _db_number;
+
+ uhd::usrp::x400::x400_dboard_iface::sptr _daughterboard;
+
+ const std::string _unique_id;
+ std::string get_unique_id() const
+ {
+ return _unique_id;
+ }
+};
+
+}} // namespace uhd::features
diff --git a/host/lib/usrp/x400/x400_radio_control.cpp b/host/lib/usrp/x400/x400_radio_control.cpp
new file mode 100644
index 000000000..281ae7916
--- /dev/null
+++ b/host/lib/usrp/x400/x400_radio_control.cpp
@@ -0,0 +1,752 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "x400_radio_control.hpp"
+#include <uhd/rfnoc/registry.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <uhdlib/usrp/dboard/debug_dboard.hpp>
+#include <uhdlib/usrp/dboard/null_dboard.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
+
+namespace uhd { namespace rfnoc {
+
+x400_radio_control_impl::fpga_onload::fpga_onload(size_t num_channels,
+ uhd::features::adc_self_calibration_iface::sptr adc_self_cal,
+ std::string unique_id)
+ : _num_channels(num_channels), _adc_self_cal(adc_self_cal), _unique_id(unique_id)
+{
+}
+
+void x400_radio_control_impl::fpga_onload::onload()
+{
+ for (size_t channel = 0; channel < _num_channels; channel++) {
+ if (_adc_self_cal) {
+ try {
+ _adc_self_cal->run(channel);
+ } catch (uhd::runtime_error& e) {
+ RFNOC_LOG_WARNING("Failure while running self cal on channel "
+ << channel << ": " << e.what());
+ }
+ }
+ }
+}
+
+x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
+ : radio_control_impl(std::move(make_args))
+{
+ RFNOC_LOG_TRACE("Initializing x400_radio_control");
+
+ UHD_ASSERT_THROW(get_block_id().get_block_count() < 2);
+ constexpr char radio_slot_name[2] = {'A', 'B'};
+ _radio_slot = radio_slot_name[get_block_id().get_block_count()];
+ _rpc_prefix = get_block_id().get_block_count() == 1 ? "db_1_" : "db_0_";
+
+ UHD_ASSERT_THROW(get_mb_controller());
+ _mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller());
+
+ _x4xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>(
+ _mb_control->get_timekeeper(0));
+ 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());
+ }
+
+ _db_rpcc = _mb_control->dynamic_cast_rpc_as<uhd::usrp::dboard_base_rpc_iface>();
+ if (!_db_rpcc) {
+ _db_rpcc = std::make_shared<uhd::usrp::dboard_base_rpc>(
+ _mb_control->get_rpc_client(), _rpc_prefix);
+ }
+
+ const auto all_dboard_info = _rpcc->get_dboard_info();
+ RFNOC_LOG_TRACE("Hardware detected " << all_dboard_info.size() << " daughterboards.");
+
+ // If we have two radio blocks, but there is only one dboard plugged in,
+ // we skip initialization. The board needs to be in slot A
+ if (all_dboard_info.size() <= get_block_id().get_block_count()) {
+ RFNOC_LOG_WARNING("The number of discovered daughterboards did not match the "
+ "number of radio blocks. Skipping front end initialization.");
+ _daughterboard = std::make_shared<null_dboard_impl>();
+ return;
+ }
+
+ const double master_clock_rate = _rpcc->get_master_clock_rate();
+ set_tick_rate(master_clock_rate);
+ _x4xx_timekeeper->update_tick_rate(master_clock_rate);
+ radio_control_impl::set_rate(master_clock_rate);
+
+ for (auto& samp_rate_prop : _samp_rate_in) {
+ set_property(samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info());
+ }
+ for (auto& samp_rate_prop : _samp_rate_out) {
+ set_property(samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info());
+ }
+
+ _validate_master_clock_rate_args();
+ _init_mpm();
+
+ RFNOC_LOG_TRACE("Initializing RFDC controls...");
+ _rfdcc = std::make_shared<x400::rfdc_control>(
+ // clang-format off
+ uhd::memmap32_iface_timed{
+ [this](const uint32_t addr, const uint32_t data, const uhd::time_spec_t& time_spec) {
+ regs().poke32(addr + x400_regs::RFDC_CTRL_BASE, data, time_spec);
+ },
+ [this](const uint32_t addr) {
+ return regs().peek32(addr + x400_regs::RFDC_CTRL_BASE);
+ }
+ },
+ // clang-format on
+ get_unique_id() + "::RFDC");
+
+ const auto& dboard = all_dboard_info[get_block_id().get_block_count()];
+ const std::string pid(dboard.at("pid").begin(), dboard.at("pid").end());
+ RFNOC_LOG_TRACE("Initializing daughterboard driver for PID " << pid);
+
+ // We may have physical daughterboards in the system, but no GPIO interface to the
+ // daughterboard in the FPGA. In this case, just instantiate the null daughterboard.
+ if (!_rpcc->is_db_gpio_ifc_present(get_block_id().get_block_count())) {
+ RFNOC_LOG_WARNING(
+ "Skipping daughterboard initialization, no GPIO interface in FPGA");
+ _daughterboard = std::make_shared<null_dboard_impl>();
+ return;
+ }
+
+ if (std::stol(pid) == uhd::usrp::zbx::ZBX_PID) {
+ auto zbx_rpc_sptr = _mb_control->dynamic_cast_rpc_as<uhd::usrp::zbx_rpc_iface>();
+ if (!zbx_rpc_sptr) {
+ zbx_rpc_sptr = std::make_shared<uhd::usrp::zbx_rpc>(
+ _mb_control->get_rpc_client(), _rpc_prefix);
+ }
+ _daughterboard = std::make_shared<uhd::usrp::zbx::zbx_dboard_impl>(
+ regs(),
+ regmap::PERIPH_BASE,
+ [this](const size_t instance) { return get_command_time(instance); },
+ get_block_id().get_block_count(),
+ _radio_slot,
+ _rpc_prefix,
+ get_unique_id(),
+ _rpcc,
+ zbx_rpc_sptr,
+ _rfdcc,
+ get_tree());
+ } 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) {
+ _daughterboard =
+ std::make_shared<if_test_dboard_impl>(get_block_id().get_block_count(),
+ _rpc_prefix,
+ get_unique_id(),
+ _mb_control,
+ get_tree());
+ } else if (std::stol(pid) == uhd::rfnoc::EMPTY_DB_PID) {
+ _daughterboard = std::make_shared<empty_slot_dboard_impl>();
+ set_num_output_ports(0);
+ set_num_input_ports(0);
+ } else {
+ RFNOC_LOG_WARNING("Skipping Daughterboard initialization for unsupported PID "
+ << "0x" << std::hex << std::stol(pid));
+ _daughterboard = std::make_shared<null_dboard_impl>();
+ return;
+ }
+
+ _init_prop_tree();
+
+ _rx_pwr_mgr = _daughterboard->get_pwr_mgr(uhd::RX_DIRECTION);
+ _tx_pwr_mgr = _daughterboard->get_pwr_mgr(uhd::TX_DIRECTION);
+
+ _tx_gain_profile_api = _daughterboard->get_tx_gain_profile_api();
+ _rx_gain_profile_api = _daughterboard->get_rx_gain_profile_api();
+
+ if (_daughterboard->is_adc_self_cal_supported()) {
+ _adc_self_calibration =
+ std::make_shared<uhd::features::adc_self_calibration>(_rpcc,
+ _rpc_prefix,
+ get_unique_id(),
+ get_block_id().get_block_count(),
+ _daughterboard);
+ register_feature(_adc_self_calibration);
+ }
+
+ _fpga_onload = std::make_shared<fpga_onload>(
+ get_num_output_ports(), _adc_self_calibration, get_unique_id());
+ register_feature(_fpga_onload);
+ _mb_control->_fpga_onload->request_cb(_fpga_onload);
+}
+
+void x400_radio_control_impl::_init_prop_tree()
+{
+ auto subtree = get_tree()->subtree(fs_path("mboard"));
+
+ for (size_t chan_idx = 0; chan_idx < get_num_output_ports(); chan_idx++) {
+ const fs_path rx_codec_path =
+ fs_path("rx_codec") / get_dboard_fe_from_chan(chan_idx, uhd::RX_DIRECTION);
+ const fs_path tx_codec_path =
+ fs_path("tx_codec") / get_dboard_fe_from_chan(chan_idx, uhd::TX_DIRECTION);
+ RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel "
+ << chan_idx << " to prop tree paths " << rx_codec_path << " and "
+ << tx_codec_path);
+
+ // ADC calibration state attributes
+ subtree->create<bool>(rx_codec_path / "calibration_frozen")
+ .add_coerced_subscriber([this, chan_idx](bool state) {
+ _rpcc->set_cal_frozen(state, get_block_id().get_block_count(), chan_idx);
+ })
+ .set_publisher([this, chan_idx]() {
+ const auto freeze_states =
+ _rpcc->get_cal_frozen(get_block_id().get_block_count(), chan_idx);
+ return freeze_states.at(0) == 1;
+ });
+
+ // RFDC NCO
+ // RX
+ subtree->create<double>(rx_codec_path / "rfdc" / "freq/value")
+ .add_desired_subscriber([this, chan_idx](double freq) {
+ _rpcc->rfdc_set_nco_freq(_get_trx_string(RX_DIRECTION),
+ get_block_id().get_block_count(),
+ chan_idx,
+ freq);
+ })
+ .set_publisher([this, chan_idx]() {
+ const auto nco_freq =
+ _rpcc->rfdc_get_nco_freq(_get_trx_string(RX_DIRECTION),
+ get_block_id().get_block_count(),
+ chan_idx);
+ return nco_freq;
+ });
+
+ // TX
+ subtree->create<double>(tx_codec_path / "rfdc" / "freq/value")
+ .add_desired_subscriber([this, chan_idx](double freq) {
+ _rpcc->rfdc_set_nco_freq(_get_trx_string(TX_DIRECTION),
+ get_block_id().get_block_count(),
+ chan_idx,
+ freq);
+ })
+ .set_publisher([this, chan_idx]() {
+ const auto nco_freq =
+ _rpcc->rfdc_get_nco_freq(_get_trx_string(TX_DIRECTION),
+ get_block_id().get_block_count(),
+ chan_idx);
+ return nco_freq;
+ });
+ }
+}
+
+void x400_radio_control_impl::_validate_master_clock_rate_args()
+{
+ auto block_args = get_block_args();
+
+ // Note: MCR gets set during the init() call (prior to this), which takes
+ // in arguments from the device args. So if block_args contains a
+ // master_clock_rate key, then it should better be whatever the device is
+ // configured to do.
+ const double master_clock_rate = _rpcc->get_master_clock_rate();
+ if (!uhd::math::frequencies_are_equal(get_rate(), master_clock_rate)) {
+ throw uhd::runtime_error(
+ str(boost::format("Master clock rate mismatch. Device returns %f MHz, "
+ "but should have been %f MHz.")
+ % (master_clock_rate / 1e6) % (get_rate() / 1e6)));
+ }
+ RFNOC_LOG_DEBUG("Master Clock Rate is: " << (master_clock_rate / 1e6) << " MHz.");
+}
+
+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++) {
+ _init_mpm_sensors(dir, chan_idx);
+ }
+ }
+}
+
+// @TODO: This should be a method on direction_t
+// (or otherwise not duplicated from the implementation in zbx)
+std::string x400_radio_control_impl::_get_trx_string(const direction_t dir) const
+{
+ if (dir == RX_DIRECTION) {
+ return "rx";
+ } else if (dir == TX_DIRECTION) {
+ return "tx";
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+
+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);
+ const std::string trx = _get_trx_string(dir);
+ const fs_path fe_path = fs_path("dboard")
+ / (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends")
+ / chan_idx;
+ auto sensor_list = _db_rpcc->get_sensors(trx);
+ RFNOC_LOG_TRACE("Chan " << chan_idx << ": Found " << sensor_list.size() << " " << trx
+ << " sensors.");
+ for (const auto& sensor_name : sensor_list) {
+ RFNOC_LOG_TRACE("Adding " << trx << " sensor " << sensor_name);
+ get_tree()
+ ->create<sensor_value_t>(fe_path / "sensors" / sensor_name)
+ .add_coerced_subscriber([](const sensor_value_t&) {
+ throw uhd::runtime_error("Attempting to write to sensor!");
+ })
+ .set_publisher([this, trx, sensor_name, chan_idx]() {
+ return sensor_value_t(
+ this->_db_rpcc->get_sensor(trx, sensor_name, chan_idx));
+ });
+ }
+}
+
+fs_path x400_radio_control_impl::_get_db_fe_path(
+ const size_t chan, const direction_t dir) const
+{
+ const std::string trx = _get_trx_string(dir);
+ return DB_PATH / (trx + "_frontends") / get_dboard_fe_from_chan(chan, dir);
+}
+
+
+double x400_radio_control_impl::set_rate(const double rate)
+{
+ // X400 does not support runtime rate changes
+ if (!uhd::math::frequencies_are_equal(rate, get_rate())) {
+ RFNOC_LOG_WARNING("Requesting invalid sampling rate from device: "
+ << (rate / 1e6) << " MHz. Actual rate is: "
+ << (get_rate() / 1e6) << " MHz.");
+ }
+ return get_rate();
+}
+
+eeprom_map_t x400_radio_control_impl::get_db_eeprom()
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_db_eeprom();
+}
+
+std::vector<uhd::usrp::pwr_cal_mgr::sptr> x400_radio_control_impl::get_pwr_mgr(
+ const uhd::direction_t trx)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_pwr_mgr(trx);
+}
+
+std::string x400_radio_control_impl::get_tx_antenna(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_antenna(chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_tx_antennas(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_antennas(chan);
+}
+
+void x400_radio_control_impl::set_tx_antenna(const std::string& ant, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_tx_antenna(ant, chan);
+}
+
+std::string x400_radio_control_impl::get_rx_antenna(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_antenna(chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_rx_antennas(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_antennas(chan);
+}
+
+void x400_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_rx_antenna(ant, chan);
+}
+
+double x400_radio_control_impl::get_tx_frequency(const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_frequency(chan);
+}
+
+double x400_radio_control_impl::set_tx_frequency(const double freq, size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_tx_frequency(freq, chan);
+}
+
+void x400_radio_control_impl::set_tx_tune_args(
+ const uhd::device_addr_t& args, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_tx_tune_args(args, chan);
+}
+
+uhd::freq_range_t x400_radio_control_impl::get_tx_frequency_range(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_frequency_range(chan);
+}
+
+double x400_radio_control_impl::get_rx_frequency(const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_frequency(chan);
+}
+
+double x400_radio_control_impl::set_rx_frequency(const double freq, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_rx_frequency(freq, chan);
+}
+
+void x400_radio_control_impl::set_rx_tune_args(
+ const uhd::device_addr_t& args, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_rx_tune_args(args, chan);
+}
+
+uhd::freq_range_t x400_radio_control_impl::get_rx_frequency_range(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_frequency_range(chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_tx_gain_names(
+ const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_gain_names(chan);
+}
+
+uhd::gain_range_t x400_radio_control_impl::get_tx_gain_range(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_gain_range(chan);
+}
+
+uhd::gain_range_t x400_radio_control_impl::get_tx_gain_range(
+ const std::string& name, const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_gain_range(name, chan);
+}
+
+double x400_radio_control_impl::get_tx_gain(const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_gain(chan);
+}
+
+double x400_radio_control_impl::get_tx_gain(const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_gain(name, chan);
+}
+
+double x400_radio_control_impl::set_tx_gain(const double gain, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_tx_gain(gain, chan);
+}
+
+double x400_radio_control_impl::set_tx_gain(
+ const double gain, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_tx_gain(gain, name, chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_rx_gain_names(
+ const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_gain_names(chan);
+}
+
+uhd::gain_range_t x400_radio_control_impl::get_rx_gain_range(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_gain_range(chan);
+}
+
+uhd::gain_range_t x400_radio_control_impl::get_rx_gain_range(
+ const std::string& name, const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_gain_range(name, chan);
+}
+
+double x400_radio_control_impl::get_rx_gain(const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_gain(chan);
+}
+
+double x400_radio_control_impl::get_rx_gain(const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_gain(name, chan);
+}
+
+double x400_radio_control_impl::set_rx_gain(const double gain, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_rx_gain(gain, chan);
+}
+
+double x400_radio_control_impl::set_rx_gain(
+ const double gain, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_rx_gain(gain, name, chan);
+}
+
+void x400_radio_control_impl::set_rx_agc(const bool enable, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_rx_agc(enable, chan);
+}
+
+meta_range_t x400_radio_control_impl::get_tx_bandwidth_range(size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_bandwidth_range(chan);
+}
+
+double x400_radio_control_impl::get_tx_bandwidth(const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_bandwidth(chan);
+}
+
+double x400_radio_control_impl::set_tx_bandwidth(
+ const double bandwidth, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_tx_bandwidth(bandwidth, chan);
+}
+
+meta_range_t x400_radio_control_impl::get_rx_bandwidth_range(size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_bandwidth_range(chan);
+}
+
+double x400_radio_control_impl::get_rx_bandwidth(const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_bandwidth(chan);
+}
+
+double x400_radio_control_impl::set_rx_bandwidth(
+ const double bandwidth, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_rx_bandwidth(bandwidth, chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_rx_lo_names(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_lo_names(chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_rx_lo_sources(
+ const std::string& name, const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_lo_sources(name, chan);
+}
+
+freq_range_t x400_radio_control_impl::get_rx_lo_freq_range(
+ const std::string& name, const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_lo_freq_range(name, chan);
+}
+
+void x400_radio_control_impl::set_rx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_rx_lo_source(src, name, chan);
+}
+
+const std::string x400_radio_control_impl::get_rx_lo_source(
+ const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_lo_source(name, chan);
+}
+
+void x400_radio_control_impl::set_rx_lo_export_enabled(
+ bool enabled, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_rx_lo_export_enabled(enabled, name, chan);
+}
+
+bool x400_radio_control_impl::get_rx_lo_export_enabled(
+ const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_lo_export_enabled(name, chan);
+}
+
+double x400_radio_control_impl::set_rx_lo_freq(
+ double freq, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_rx_lo_freq(freq, name, chan);
+}
+
+double x400_radio_control_impl::get_rx_lo_freq(const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_rx_lo_freq(name, chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_tx_lo_names(const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_lo_names(chan);
+}
+
+std::vector<std::string> x400_radio_control_impl::get_tx_lo_sources(
+ const std::string& name, const size_t chan) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_lo_sources(name, chan);
+}
+
+freq_range_t x400_radio_control_impl::get_tx_lo_freq_range(
+ const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_lo_freq_range(name, chan);
+}
+
+void x400_radio_control_impl::set_tx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_tx_lo_source(src, name, chan);
+}
+
+const std::string x400_radio_control_impl::get_tx_lo_source(
+ const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_lo_source(name, chan);
+}
+
+void x400_radio_control_impl::set_tx_lo_export_enabled(
+ const bool enabled, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ _daughterboard->set_tx_lo_export_enabled(enabled, name, chan);
+}
+
+bool x400_radio_control_impl::get_tx_lo_export_enabled(
+ const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_lo_export_enabled(name, chan);
+}
+
+double x400_radio_control_impl::set_tx_lo_freq(
+ const double freq, const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->set_tx_lo_freq(freq, name, chan);
+}
+
+double x400_radio_control_impl::get_tx_lo_freq(const std::string& name, const size_t chan)
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_tx_lo_freq(name, chan);
+}
+
+void x400_radio_control_impl::set_command_time(uhd::time_spec_t time, const size_t chan)
+{
+ node_t::set_command_time(time, chan);
+ _daughterboard->set_command_time(time, chan);
+}
+
+/**************************************************************************
+ * Sensor API
+ *************************************************************************/
+std::vector<std::string> x400_radio_control_impl::get_rx_sensor_names(
+ const size_t chan) const
+{
+ const fs_path sensor_path = _get_db_fe_path(chan, RX_DIRECTION) / "sensors";
+ if (get_tree()->exists(sensor_path)) {
+ return get_tree()->list(sensor_path);
+ }
+ return {};
+}
+
+uhd::sensor_value_t x400_radio_control_impl::get_rx_sensor(
+ const std::string& name, const size_t chan)
+{
+ return get_tree()
+ ->access<uhd::sensor_value_t>(
+ _get_db_fe_path(chan, RX_DIRECTION) / "sensors" / name)
+ .get();
+}
+
+std::vector<std::string> x400_radio_control_impl::get_tx_sensor_names(
+ const size_t chan) const
+{
+ const fs_path sensor_path = _get_db_fe_path(chan, TX_DIRECTION) / "sensors";
+ if (get_tree()->exists(sensor_path)) {
+ return get_tree()->list(sensor_path);
+ }
+ return {};
+}
+
+uhd::sensor_value_t x400_radio_control_impl::get_tx_sensor(
+ const std::string& name, const size_t chan)
+{
+ return get_tree()
+ ->access<uhd::sensor_value_t>(
+ _get_db_fe_path(chan, TX_DIRECTION) / "sensors" / name)
+ .get();
+}
+
+/**************************************************************************
+ * Radio Identification API Calls
+ *************************************************************************/
+size_t x400_radio_control_impl::get_chan_from_dboard_fe(
+ const std::string& fe, const uhd::direction_t direction) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_chan_from_dboard_fe(fe, direction);
+}
+
+std::string x400_radio_control_impl::get_dboard_fe_from_chan(
+ const size_t chan, const uhd::direction_t direction) const
+{
+ std::lock_guard<std::recursive_mutex> l(_lock);
+ return _daughterboard->get_dboard_fe_from_chan(chan, direction);
+}
+
+UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT(
+ x400_radio_control, RADIO_BLOCK, X400, "Radio", true, "radio_clk", "ctrl_clk")
+
+}} // namespace uhd::rfnoc
diff --git a/host/lib/usrp/x400/x400_radio_control.hpp b/host/lib/usrp/x400/x400_radio_control.hpp
new file mode 100644
index 000000000..65b37cc2b
--- /dev/null
+++ b/host/lib/usrp/x400/x400_radio_control.hpp
@@ -0,0 +1,198 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "adc_self_calibration.hpp"
+#include <uhd/rfnoc/noc_block_base.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhdlib/features/fpga_load_notification_iface.hpp>
+#include <uhdlib/rfnoc/radio_control_impl.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/utils/rpc.hpp>
+#include <stddef.h>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+namespace uhd { namespace rfnoc {
+
+namespace x400_regs {
+
+//! Base address for the rf_timing_control module. This controls the NCOs and
+// other things in the RFDC.
+constexpr uint32_t RFDC_CTRL_BASE = radio_control_impl::regmap::PERIPH_BASE + 0x8000;
+
+} // namespace x400_regs
+
+class x400_radio_control_impl : public radio_control_impl
+{
+public:
+ using sptr = std::shared_ptr<x400_radio_control_impl>;
+
+ /************************************************************************
+ * Structors
+ ***********************************************************************/
+ x400_radio_control_impl(make_args_ptr make_args);
+ virtual ~x400_radio_control_impl() = default;
+
+ std::string get_slot_name() const override
+ {
+ return _radio_slot;
+ }
+
+ size_t get_chan_from_dboard_fe(const std::string&, uhd::direction_t) const override;
+ std::string get_dboard_fe_from_chan(size_t chan, uhd::direction_t) const override;
+
+ uhd::eeprom_map_t get_db_eeprom() override;
+
+ // Shim calls for every method in rf_control_core
+ double set_rate(const double rate) override;
+ std::string get_tx_antenna(const size_t chan) const override;
+ std::vector<std::string> get_tx_antennas(const size_t chan) const override;
+ void set_tx_antenna(const std::string& ant, const size_t chan) override;
+ std::string get_rx_antenna(const size_t chan) const override;
+ std::vector<std::string> get_rx_antennas(const size_t chan) const override;
+ void set_rx_antenna(const std::string& ant, const size_t chan) override;
+ double get_tx_frequency(const size_t chan) override;
+ double set_tx_frequency(const double freq, size_t chan) override;
+ void set_tx_tune_args(const uhd::device_addr_t& args, const size_t chan) override;
+ uhd::freq_range_t get_tx_frequency_range(const size_t chan) const override;
+ double get_rx_frequency(const size_t chan) override;
+ double set_rx_frequency(const double freq, const size_t chan) override;
+ void set_rx_tune_args(const uhd::device_addr_t& args, const size_t chan) override;
+ uhd::freq_range_t get_rx_frequency_range(const size_t chan) const override;
+ std::vector<std::string> get_tx_gain_names(const size_t chan) const override;
+ uhd::gain_range_t get_tx_gain_range(const size_t chan) const override;
+ uhd::gain_range_t get_tx_gain_range(
+ const std::string& name, const size_t chan) const override;
+ double get_tx_gain(const size_t chan) override;
+ double get_tx_gain(const std::string& name, const size_t chan) override;
+ double set_tx_gain(const double gain, const size_t chan) override;
+ double set_tx_gain(
+ const double gain, const std::string& name, const size_t chan) override;
+ std::vector<std::string> get_rx_gain_names(const size_t chan) const override;
+ uhd::gain_range_t get_rx_gain_range(const size_t chan) const override;
+ uhd::gain_range_t get_rx_gain_range(
+ const std::string& name, const size_t chan) const override;
+ double get_rx_gain(const size_t chan) override;
+ double get_rx_gain(const std::string& name, const size_t chan) override;
+ double set_rx_gain(const double gain, const size_t chan) override;
+ double set_rx_gain(
+ const double gain, const std::string& name, const size_t chan) override;
+ void set_rx_agc(const bool enable, const size_t chan) override;
+ meta_range_t get_tx_bandwidth_range(size_t chan) const override;
+ double get_tx_bandwidth(const size_t chan) override;
+ double set_tx_bandwidth(const double bandwidth, const size_t chan) override;
+ meta_range_t get_rx_bandwidth_range(size_t chan) const override;
+ double get_rx_bandwidth(const size_t chan) override;
+ double set_rx_bandwidth(const double bandwidth, const size_t chan) override;
+
+ std::vector<std::string> get_rx_lo_names(const size_t chan) const override;
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string& name, const size_t chan) const override;
+ freq_range_t get_rx_lo_freq_range(
+ const std::string& name, const size_t chan) const override;
+ void set_rx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan) override;
+ const std::string get_rx_lo_source(
+ const std::string& name, const size_t chan) override;
+ void set_rx_lo_export_enabled(
+ bool enabled, const std::string& name, const size_t chan) override;
+ bool get_rx_lo_export_enabled(
+ const std::string& name, const size_t chan) override;
+ double set_rx_lo_freq(
+ double freq, const std::string& name, const size_t chan) override;
+ double get_rx_lo_freq(const std::string& name, const size_t chan) override;
+ std::vector<std::string> get_tx_lo_names(const size_t chan) const override;
+ std::vector<std::string> get_tx_lo_sources(
+ const std::string& name, const size_t chan) const override;
+ freq_range_t get_tx_lo_freq_range(
+ const std::string& name, const size_t chan) override;
+ void set_tx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan) override;
+ const std::string get_tx_lo_source(
+ const std::string& name, const size_t chan) override;
+ void set_tx_lo_export_enabled(
+ const bool enabled, const std::string& name, const size_t chan) override;
+ bool get_tx_lo_export_enabled(const std::string& name, const size_t chan) override;
+ double set_tx_lo_freq(
+ const double freq, const std::string& name, const size_t chan) override;
+ double get_tx_lo_freq(const std::string& name, const size_t chan) override;
+ std::vector<std::string> get_rx_sensor_names(size_t chan) const override;
+ uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan) override;
+ std::vector<std::string> get_tx_sensor_names(size_t chan) const override;
+ uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan) override;
+
+ void set_command_time(uhd::time_spec_t time, const size_t chan) override;
+
+ // Non-API methods
+ // This is used for x4xx radio block unit tests
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr> get_pwr_mgr(const uhd::direction_t trx);
+
+private:
+ //! Locks access to the API
+ mutable std::recursive_mutex _lock;
+
+ std::string _get_trx_string(const direction_t dir) const;
+
+ void _validate_master_clock_rate_args();
+ void _init_mpm();
+ void _init_mpm_sensors(const direction_t dir, const size_t chan_idx);
+ void _init_prop_tree();
+ fs_path _get_db_fe_path(const size_t chan, const uhd::direction_t dir) const;
+
+ //! Reference to the MB controller
+ uhd::rfnoc::mpmd_mb_controller::sptr _mb_control;
+
+ //! Reference to the RPC client
+ uhd::usrp::x400_rpc_iface::sptr _rpcc;
+ uhd::usrp::dboard_base_rpc_iface::sptr _db_rpcc;
+
+ //! Reference to the MB timekeeper
+ uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _x4xx_timekeeper;
+
+ std::string _radio_slot;
+ std::string _rpc_prefix;
+
+ //! Reference to this radio block's RFDC control
+ x400::rfdc_control::sptr _rfdcc;
+
+ uhd::usrp::x400::x400_dboard_iface::sptr _daughterboard;
+
+ uhd::features::adc_self_calibration_iface::sptr _adc_self_calibration;
+
+ class fpga_onload : public uhd::features::fpga_load_notification_iface
+ {
+ public:
+ using sptr = std::shared_ptr<fpga_onload>;
+
+ fpga_onload(size_t num_channels,
+ uhd::features::adc_self_calibration_iface::sptr adc_self_cal,
+ std::string unique_id);
+
+ void onload() override;
+
+ private:
+ const size_t _num_channels;
+ uhd::features::adc_self_calibration_iface::sptr _adc_self_cal;
+ const std::string _unique_id;
+ std::string get_unique_id() const
+ {
+ return _unique_id;
+ }
+ };
+
+ fpga_onload::sptr _fpga_onload;
+};
+
+}} // namespace uhd::rfnoc
diff --git a/host/lib/usrp/x400/x400_rfdc_control.cpp b/host/lib/usrp/x400/x400_rfdc_control.cpp
new file mode 100644
index 000000000..b963c0a46
--- /dev/null
+++ b/host/lib/usrp/x400/x400_rfdc_control.cpp
@@ -0,0 +1,73 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/utils/log.hpp>
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <unordered_map>
+
+using namespace uhd::rfnoc::x400;
+
+rfdc_control::rfdc_control(uhd::memmap32_iface_timed&& iface, const std::string& log_id)
+ : _iface(std::move(iface)), _log_id(log_id)
+{
+ // nop
+}
+
+void rfdc_control::reset_ncos(
+ const std::vector<rfdc_type>& ncos, const uhd::time_spec_t& time)
+{
+ if (ncos.empty()) {
+ UHD_LOG_WARNING(_log_id,
+ "reset_ncos() called with empty NCO list! "
+ "Not resetting NCOs.");
+ return;
+ }
+ UHD_LOG_TRACE(_log_id, "Resetting " << ncos.size() << " NCOs...");
+
+ const uint32_t reset_word = 1;
+ // TODO: When the FPGA supports it, map the list of ncos into bits onto
+ // reset_word to set the bit for the specific NCOs that will be reset.
+
+ _iface.poke32(regmap::NCO_RESET, reset_word, time);
+}
+
+void rfdc_control::reset_gearboxes(
+ const std::vector<rfdc_type>& gearboxes, const uhd::time_spec_t& time)
+{
+ if (gearboxes.empty()) {
+ UHD_LOG_WARNING(_log_id,
+ "reset_gearboxes() called with empty gearbox list! "
+ "Not resetting gearboxes.");
+ return;
+ }
+ // This is intentionally at INFO: It should typically happen once per session.
+ UHD_LOG_INFO(_log_id, "Resetting " << gearboxes.size() << " gearbox(es)...");
+ // TODO: Either the FPGA supports resetting gearboxes individually, or we
+ // remove this TODO
+ 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::RX0, regmap::ADC_RESET_MSB},
+ {rfdc_type::RX1, regmap::ADC_RESET_MSB}};
+
+ uint32_t reset_word = 0;
+ for (const auto gb : gearboxes) {
+ reset_word = reset_word | (1 << gb_map.at(gb));
+ }
+
+ _iface.poke32(regmap::GEARBOX_RESET, reset_word, time);
+}
+
+bool rfdc_control::get_nco_reset_done()
+{
+ return bool(_iface.peek32(regmap::NCO_RESET) & (1 << regmap::NCO_RESET_DONE_MSB));
+}
+
+double rfdc_control::set_nco_freq(const rfdc_type, const double freq)
+{
+ UHD_LOG_WARNING(_log_id, "set_nco_freq() called but not yet implemented!");
+ return freq;
+}
diff --git a/host/python/uhd/imgbuilder/image_builder.py b/host/python/uhd/imgbuilder/image_builder.py
index 626d1bbf7..9ce57d0e0 100755
--- a/host/python/uhd/imgbuilder/image_builder.py
+++ b/host/python/uhd/imgbuilder/image_builder.py
@@ -42,6 +42,8 @@ DEVICE_DIR_MAP = {
'n300': 'n3xx',
'n310': 'n3xx',
'n320': 'n3xx',
+ 'x400': 'x400',
+ 'x410': 'x400',
}
# Picks the default make target per device
@@ -522,8 +524,13 @@ def convert_to_image_config(grc, grc_config_path):
result["stream_endpoints"] = {}
for sep in seps.values():
result["stream_endpoints"][sep["name"]] = {"ctrl": bool(sep["parameters"]["ctrl"]),
- "data": bool(sep["parameters"]["data"]),
- "buff_size": int(sep["parameters"]["buff_size"])}
+ "data": bool(sep["parameters"]["data"])}
+ if "buff_size" in sep["parameters"]:
+ result["stream_endpoints"][sep["name"]]["buff_size"] = \
+ int(sep["parameters"]["buff_size"])
+ if "buff_size_bytes" in sep["parameters"]:
+ result["stream_endpoints"][sep["name"]]["buff_size_bytes"] = \
+ int(sep["parameters"]["buff_size_bytes"])
result["noc_blocks"] = {}
for block in blocks.values():
@@ -875,7 +882,7 @@ def build_image(config, fpga_path, config_path, device, **args):
requested.
:param config: A dictionary containing the image configuration options.
- This must obey the rfnoc_imagebuilder_args schema.
+ This must obey the rfnoc_image_builder_args schema.
:param fpga_path: A path that holds the FPGA IP sources.
:param device: The device to build for.
:param **args: Additional options including
diff --git a/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako b/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako
index b8c18fba9..50fa09373 100644
--- a/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako
+++ b/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako
@@ -18,15 +18,25 @@
\
%for i, sep in enumerate(seps):
<%
-# If buff_size == 0, then we assume that we will never transmit through this SEP
-buff_size = seps[sep]["buff_size"]
-if buff_size > 0:
- buff_size = int(math.ceil(math.log(buff_size, 2)))
- # FIXME MTU assumed to be 10 here -- forcing to at least accommodate 2 pkts
- buff_size = max(buff_size, 10+1)
+# The schema allows buff_size_bytes or buff_size, but not both.
+if "buff_size_bytes" in seps[sep]:
+ # Use buffer size in bytes
+ buff_size = "(" + str(seps[sep]["buff_size_bytes"]) + ")/(CHDR_W/8)"
+elif "buff_size" in seps[sep]:
+ # Use buffer size in CHDR words
+ buff_size = str(seps[sep]["buff_size"])
else:
- buff_size = 5
+ # Min SRL-based FIFO size
+ buff_size = str("32")
%>\
+ // If requested buffer size is 0, use the minimum SRL-based FIFO size.
+ // Otherwise, make sure it's at least two MTU-sized packets.
+ localparam REQ_BUFF_SIZE_${sep.upper()} = ${buff_size};
+ localparam INGRESS_BUFF_SIZE_${sep.upper()} =
+ REQ_BUFF_SIZE_${sep.upper()} == 0 ? 5 :
+ REQ_BUFF_SIZE_${sep.upper()} < 2*(2**MTU) ? MTU+1 :
+ $clog2(REQ_BUFF_SIZE_${sep.upper()});
+
wire [CHDR_W-1:0] ${axis_outputs[sep].format(sep,"tdata")};
wire ${axis_outputs[sep].format(sep,"tlast")};
wire ${axis_outputs[sep].format(sep,"tvalid")};
@@ -49,7 +59,7 @@ else:
.NUM_DATA_O (${int(seps[sep]["num_data_o"])}),
.INST_NUM (${i}),
.CTRL_XBAR_PORT (${i+1}),
- .INGRESS_BUFF_SIZE (${buff_size}),
+ .INGRESS_BUFF_SIZE (INGRESS_BUFF_SIZE_${sep.upper()}),
.MTU (MTU),
.REPORT_STRM_ERRS (1)
) ${sep}_i (
diff --git a/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako b/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako
index a5a019e16..b27e21fc4 100644
--- a/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako
+++ b/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako
@@ -26,19 +26,12 @@
`default_nettype none
-<%
- if hasattr(config, 'image_core_name'):
- image_core_name = config.image_core_name
- else:
- image_core_name = config.device.type
-%>\
-`include "${image_core_name}_rfnoc_image_core.vh"
-
module rfnoc_image_core #(
- parameter CHDR_W = `CHDR_WIDTH,
- parameter MTU = 10,
- parameter [15:0] PROTOVER = {8'd1, 8'd0}
+ parameter CHDR_W = ${config.chdr_width},
+ parameter MTU = 10,
+ parameter [15:0] PROTOVER = {8'd1, 8'd0},
+ parameter RADIO_NIPC = 1
) (
// Clocks
input wire chdr_aclk,
@@ -59,11 +52,6 @@ module rfnoc_image_core #(
wire rfnoc_chdr_clk, rfnoc_chdr_rst;
wire rfnoc_ctrl_clk, rfnoc_ctrl_rst;
- // Check that CHDR_W parameter matches value used by RFNoC Image Builder
- if (CHDR_W != `CHDR_WIDTH) begin
- ERROR_CHDR_W_values_do_not_match();
- end
-
//---------------------------------------------------------------------------
// CHDR Crossbar
diff --git a/host/python/uhd/usrp/cal/libtypes.py b/host/python/uhd/usrp/cal/libtypes.py
index 1eb9ff360..448feec88 100644
--- a/host/python/uhd/usrp/cal/libtypes.py
+++ b/host/python/uhd/usrp/cal/libtypes.py
@@ -18,5 +18,7 @@ database = lib.cal.database
Source = lib.cal.source
IQCal = lib.cal.iq_cal
PwrCal = lib.cal.pwr_cal
+ZbxTxDsaCal = lib.cal.zbx_tx_dsa_cal
+ZbxRxDsaCal = lib.cal.zbx_rx_dsa_cal
InterpMode = lib.cal.interp_mode
# pylint: enable=invalid-name
diff --git a/host/python/uhd/utils/mpmtools.py b/host/python/uhd/utils/mpmtools.py
index 90dd63be0..f03da82bb 100644
--- a/host/python/uhd/utils/mpmtools.py
+++ b/host/python/uhd/utils/mpmtools.py
@@ -16,7 +16,7 @@ from mprpc.exceptions import RPCError
MPM_RPC_PORT = 49601
-def _claim_loop(host, port, cmd_q, token_q):
+def _claim_loop(client, cmd_q, token_q):
"""
Process that runs a claim loop.
@@ -39,7 +39,6 @@ def _claim_loop(host, port, cmd_q, token_q):
signal.signal(signal.SIGTERM, _sig_term_handler)
- client = RPCClient(host, port, pack_params={'use_bin_type': True})
try:
while not exit_loop:
try:
@@ -84,10 +83,11 @@ class MPMClaimer:
self.token = None
self._cmd_q = multiprocessing.Queue()
self._token_q = multiprocessing.Queue()
+ client = RPCClient(host, port, pack_params={'use_bin_type': True})
self._claim_loop = multiprocessing.Process(
target=_claim_loop,
name="Claimer Loop",
- args=(host, port, self._cmd_q, self._token_q)
+ args=(client, self._cmd_q, self._token_q)
)
self._claim_loop.daemon = True
self._claim_loop.start()
@@ -181,6 +181,12 @@ class MPMClient:
"""
self._claimer.unclaim()
+ def exit(self):
+ """
+ Use claimer (instead of RPC method) to unclaim MPM device and exit claim loop
+ """
+ self._claimer.exit()
+
def _add_command(self, command, docs, requires_token):
"""
Add a command to the current session
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 980f87d75..9b27e03c1 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -29,6 +29,7 @@ set(test_sources
cal_data_iq_test.cpp
cal_data_gain_pwr_test.cpp
chdr_parse_test.cpp
+ cal_data_dsa_test.cpp
chdr_test.cpp
constrained_device_args_test.cpp
convert_test.cpp
@@ -42,7 +43,6 @@ set(test_sources
isatty_test.cpp
log_test.cpp
math_test.cpp
- mb_controller_test.cpp
narrow_cast_test.cpp
property_test.cpp
ranges_test.cpp
@@ -150,6 +150,8 @@ macro(UHD_ADD_RFNOC_BLOCK_TEST)
EXTRA_SOURCES
${test_EXTRA_SOURCES}
${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp
+ INCLUDE_DIRS
+ ${test_INCLUDE_DIRS}
)
endmacro(UHD_ADD_RFNOC_BLOCK_TEST)
@@ -275,6 +277,24 @@ UHD_ADD_NONAPI_TEST(
${CMAKE_SOURCE_DIR}/lib/rfnoc/client_zero.cpp
)
+UHD_ADD_NONAPI_TEST(
+ TARGET zbx_cpld_test.cpp
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
+ INCLUDE_DIRS
+ ${CMAKE_BINARY_DIR}/lib/ic_reg_maps
+)
+
+UHD_ADD_NONAPI_TEST(
+ TARGET lmx2572_test.cpp
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
+ INCLUDE_DIRS
+ ${CMAKE_BINARY_DIR}/lib/ic_reg_maps
+)
+
set_source_files_properties(
${CMAKE_SOURCE_DIR}/lib/utils/system_time.cpp
PROPERTIES COMPILE_DEFINITIONS
@@ -329,7 +349,6 @@ UHD_ADD_RFNOC_BLOCK_TEST(
TARGET siggen_block_test.cpp
)
-
UHD_ADD_RFNOC_BLOCK_TEST(
TARGET split_stream_block_test.cpp
)
@@ -346,6 +365,35 @@ UHD_ADD_RFNOC_BLOCK_TEST(
TARGET window_block_test.cpp
)
+UHD_ADD_RFNOC_BLOCK_TEST(
+ TARGET x4xx_radio_block_test.cpp
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/pwr_cal_mgr.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_radio_control.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/adc_self_calibration.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/mpmd/mpmd_mb_controller.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard_init.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_expert.cpp
+ ${CMAKE_SOURCE_DIR}/lib/utils/compat_check.cpp
+ ${CMAKE_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
+ $<TARGET_OBJECTS:uhd_rpclib>
+ INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/deps/rpclib/include
+ INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/deps/flatbuffers/include
+)
+
+UHD_ADD_NONAPI_TEST(
+ TARGET "mb_controller_test.cpp"
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
+)
+
UHD_ADD_NONAPI_TEST(
TARGET "transport_test.cpp"
EXTRA_SOURCES
@@ -382,6 +430,12 @@ UHD_ADD_NONAPI_TEST(
${CMAKE_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp
)
+UHD_ADD_NONAPI_TEST(
+ TARGET "x400_rfdc_control_test.cpp"
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp
+)
+
########################################################################
# demo of a loadable module
########################################################################
diff --git a/host/tests/cal_data_dsa_test.cpp b/host/tests/cal_data_dsa_test.cpp
new file mode 100644
index 000000000..d3c224f88
--- /dev/null
+++ b/host/tests/cal_data_dsa_test.cpp
@@ -0,0 +1,91 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/exception.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+#include <fstream>
+
+using namespace uhd::usrp::cal;
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_api)
+{
+ const std::string name = "Mock Gain/Power Data";
+ const std::string serial = "ABC1234";
+ const uint64_t timestamp = 0x12340000;
+
+ auto dsa_data = zbx_tx_dsa_cal::make(name, serial, timestamp);
+ BOOST_CHECK_EQUAL(dsa_data->get_name(), name);
+ BOOST_CHECK_EQUAL(dsa_data->get_serial(), serial);
+ BOOST_CHECK_EQUAL(dsa_data->get_timestamp(), timestamp);
+
+ BOOST_REQUIRE_THROW(dsa_data->get_dsa_setting(0, 0), uhd::runtime_error);
+
+ std::array<std::array<uint32_t, 3>, 61> gains1{{{1, 2}, {3, 4}, {5, 6}}};
+ std::array<std::array<uint32_t, 3>, 61> gains2{{{7, 8}, {9, 0}, {1, 2}}};
+
+ dsa_data->add_frequency_band(1E9, "low", gains1);
+ dsa_data->add_frequency_band(4E9, "high", gains2);
+
+ auto expected = gains1[0];
+ auto calculated = dsa_data->get_dsa_setting(1E9, 0);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ calculated = dsa_data->get_dsa_setting(1E8, 0);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ expected = gains1[1];
+ calculated = dsa_data->get_dsa_setting(1E9, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ calculated = dsa_data->get_dsa_setting(1E8, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ expected = gains2[1];
+ calculated = dsa_data->get_dsa_setting(3E9, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ calculated = dsa_data->get_dsa_setting(4E9, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ BOOST_REQUIRE_THROW(dsa_data->get_dsa_setting(5E9, 1), uhd::value_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_serdes)
+{
+ const std::string name = "Mock Gain/DSA Data";
+ const std::string serial = "FOOBAR";
+ const uint64_t timestamp = 0xCAFEBABE;
+ auto cal_data = zbx_tx_dsa_cal::make(name, serial, timestamp);
+
+ std::vector<double> freqs{2e9, 6e9};
+
+ int i = 0;
+ for (auto freq : freqs) {
+ std::array<std::array<uint32_t, 3>, 61> gains;
+ for (auto& gain : gains) {
+ for (auto& step : gain) {
+ step = i++;
+ }
+ }
+ cal_data->add_frequency_band(freq, std::to_string(freq), gains);
+ }
+
+ const auto serialized = cal_data->serialize();
+ BOOST_REQUIRE_THROW(container::make<zbx_rx_dsa_cal>(serialized), uhd::runtime_error);
+ auto des_cal_data = container::make<zbx_tx_dsa_cal>(serialized);
+ BOOST_CHECK_EQUAL(des_cal_data->get_name(), cal_data->get_name());
+ BOOST_CHECK_EQUAL(des_cal_data->get_serial(), cal_data->get_serial());
+ BOOST_CHECK_EQUAL(des_cal_data->get_timestamp(), cal_data->get_timestamp());
+}
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_des_fail)
+{
+ std::vector<uint8_t> not_actual_data(42, 23);
+
+ BOOST_REQUIRE_THROW(container::make<zbx_tx_dsa_cal>(not_actual_data), uhd::runtime_error);
+}
diff --git a/host/tests/devtest/CMakeLists.txt b/host/tests/devtest/CMakeLists.txt
index e6cef17fe..3f09620bf 100644
--- a/host/tests/devtest/CMakeLists.txt
+++ b/host/tests/devtest/CMakeLists.txt
@@ -50,5 +50,8 @@ endif(ENABLE_N300)
if(ENABLE_E320)
ADD_DEVTEST("e320" "e3xx" "E32x")
endif(ENABLE_E320)
+if(ENABLE_X400)
+ ADD_DEVTEST("x4x0" "x4xx" "X4x0")
+endif(ENABLE_X400)
# Formatting
message(STATUS "")
diff --git a/host/tests/devtest/devtest_x4x0.py b/host/tests/devtest/devtest_x4x0.py
new file mode 100644
index 000000000..c0b8bf4aa
--- /dev/null
+++ b/host/tests/devtest/devtest_x4x0.py
@@ -0,0 +1,75 @@
+#
+# Copyright 2015 Ettus Research LLC
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Run device tests for the x4x0 series.
+"""
+
+# pylint: disable=wrong-import-position
+# pylint: disable=unused-import
+from benchmark_rate_test import uhd_benchmark_rate_test
+uhd_benchmark_rate_test.tests = {
+ 'mimo_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0,1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 500,
+ 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*1e6,
+ },
+ 'mimo_fast': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0,1',
+ 'rate': 4.096e6,
+ 'acceptable-underruns': 500,
+ 'tx_buffer': (0.1*12.288e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*12.288e6,
+ },
+ 'siso_chan0_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0',
+ 'rate': 1e6,
+ 'acceptable-underruns': 10,
+ 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*1e6,
+ },
+ 'siso_chan1_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 10,
+ 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*1e6,
+ },
+}
+
+from tx_waveforms_test import uhd_tx_waveforms_test
+uhd_tx_waveforms_test.tests = {
+ 'chan0': {
+ 'chan': '0',
+ },
+ 'chan1': {
+ 'chan': '0',
+ },
+ 'both_chans': {
+ 'chan': '0,1',
+ },
+}
+
+from rx_samples_to_file_test import rx_samples_to_file_test
+from tx_bursts_test import uhd_tx_bursts_test
+from test_pps_test import uhd_test_pps_test
+
+# Enable these when GPIO API is enabled
+# from gpio_test import gpio_test
+# from bitbang_test import bitbang_test
+
+from list_sensors_test import list_sensors_test
+from python_api_test import uhd_python_api_test
diff --git a/host/tests/devtest/multi_usrp_test.py b/host/tests/devtest/multi_usrp_test.py
index b9add8381..943733ba9 100755
--- a/host/tests/devtest/multi_usrp_test.py
+++ b/host/tests/devtest/multi_usrp_test.py
@@ -754,6 +754,25 @@ def get_device_config(usrp_type, device_config_path=None):
'get_gpio_banks',
],
}
+ if usrp_type == 'x410':
+ return {
+ 'skip': [
+ # No AGC on ZBX
+ 'set_rx_agc',
+ # No IQ imbalance on ZBX
+ 'set_rx_iq_balance',
+ 'set_tx_iq_balance',
+ # No DC offset on ZBX
+ 'set_rx_dc_offset',
+ 'set_tx_dc_offset',
+ # No LO source control on ZBX
+ 'set_rx_lo_source',
+ 'set_tx_lo_source',
+ 'set_rx_lo_export_enabled',
+ 'set_tx_lo_export_enabled',
+ ],
+ 'clock_sources': ['internal', 'mboard'],
+ }
return {}
def dump_defaults(usrp_type):
diff --git a/host/tests/lmx2572_test.cpp b/host/tests/lmx2572_test.cpp
new file mode 100644
index 000000000..21ed1e7f5
--- /dev/null
+++ b/host/tests/lmx2572_test.cpp
@@ -0,0 +1,150 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "lmx2572_regs.hpp"
+#include <uhdlib/usrp/common/lmx2572.hpp>
+#include <boost/test/unit_test.hpp>
+#include <map>
+
+
+class lmx2572_mem
+{
+public:
+ lmx2572_mem()
+ {
+ // Copy silicone defaults into mem
+ for (uint8_t addr = 0; addr < regs.get_num_regs(); addr++) {
+ mem[addr] = regs.get_reg(addr);
+ }
+ }
+
+ void poke16(const uint8_t addr, const uint16_t data)
+ {
+ if (regs.get_ro_regs().count(addr)) {
+ throw uhd::runtime_error("Writing to RO reg!");
+ }
+ mem[addr] = data;
+ }
+
+ uint16_t peek16(const uint8_t addr)
+ {
+ return mem.at(addr);
+ }
+
+ lmx2572_regs_t regs;
+ std::map<uint8_t, uint16_t> mem;
+};
+
+
+BOOST_AUTO_TEST_CASE(lmx_init_test)
+{
+ auto mem = lmx2572_mem{};
+ auto lo = lmx2572_iface::make(
+ [&](const uint8_t addr, const uint16_t data) { mem.poke16(addr, data); },
+ [&](const uint8_t addr) -> uint16_t { return mem.peek16(addr); },
+ [](const uhd::time_spec_t&) {});
+ lo->reset();
+}
+
+void UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t> expected, std::map<uint8_t, uint16_t> actual)
+{
+ for (const auto& expected_r : expected) {
+ // Little hack so if this fails, we see all the info:
+ const std::string exp_str = "R" + std::to_string(expected_r.first)
+ + "==" + std::to_string(expected_r.second);
+ const std::string act_str = "R" + std::to_string(expected_r.first)
+ + "==" + std::to_string(actual.at(expected_r.first));
+ BOOST_CHECK_EQUAL(exp_str, act_str);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(lmx_sync_tune_test)
+{
+ auto mem = lmx2572_mem{};
+ auto lo = lmx2572_iface::make(
+ [&](const uint8_t addr, const uint16_t data) { mem.poke16(addr, data); },
+ [&](const uint8_t addr) -> uint16_t { return mem.peek16(addr); },
+ [](const uhd::time_spec_t&) {});
+ lo->reset();
+ // Mimick ZBX settings:
+ constexpr bool zbx_spur_dodging = false;
+ lo->set_sync_mode(true);
+ lo->set_output_enable(lmx2572_iface::output_t::RF_OUTPUT_A, true);
+ lo->set_output_enable(lmx2572_iface::output_t::RF_OUTPUT_B, false);
+ // Test Category 1A + SYNC:
+ lo->set_frequency(
+ 50 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ // These values are generated with TICS PRO. We don't check all the values,
+ // mainly the ones related to sync operation.
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0032}, // Lower bits of N-divider, integer part
+ {42, 0x0000}, // PLL_NUM upper
+ {43, 0x0000}, // PLL_NUM lower
+ },
+ mem.mem);
+ // Test max frequency just to test boundary conditions:
+ lo->set_frequency(
+ 100 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+
+ // Test Category 1B + SYNC:
+ // Will set CHDIV to 2.
+ lo->set_frequency(
+ 40 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0028},
+ {42, 0x0000},
+ {43, 0x0000},
+ },
+ mem.mem);
+
+ // Test Category 2 + SYNC:
+ // Will set CHDIV to 8.
+ lo->set_frequency(
+ 10 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0050},
+ {42, 0x0000},
+ {43, 0x0000},
+ },
+ mem.mem);
+ // VCO_PHASE_SYNC_EN must be off in this case, b/c we're using the SYNC pin
+ BOOST_CHECK_EQUAL(mem.mem[0] & (1 << 14), 0);
+
+ // Test Category 3 + SYNC:
+ // Will set CHDIV to 1.
+ lo->set_frequency(
+ 50.5 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0032},
+ },
+ mem.mem);
+ // VCO_PHASE_SYNC_EN must be on in this case
+ BOOST_CHECK(mem.mem[0] & (1 << 14));
+
+ // Will set CHDIV to 2.
+ lo->set_frequency(
+ 50.5 * 64e6 / 2, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {11, 0xB028}, // PLL_R == 2. Note this is a ZBX-specific design choice.
+ {36, 0x0032}, // With PLL_R == 2, you would expect this to be 100, but it's
+ // only half that!
+ },
+ mem.mem);
+ // VCO_PHASE_SYNC_EN must be on in this case
+ BOOST_CHECK(mem.mem[0] & (1 << 14));
+}
diff --git a/host/tests/mb_controller_test.cpp b/host/tests/mb_controller_test.cpp
index 188dddecc..30b9f64f3 100644
--- a/host/tests/mb_controller_test.cpp
+++ b/host/tests/mb_controller_test.cpp
@@ -5,6 +5,7 @@
//
#include <uhd/rfnoc/mb_controller.hpp>
+#include <uhdlib/features/discoverable_feature_registry.hpp>
#include <boost/test/unit_test.hpp>
#include <iostream>
@@ -49,7 +50,8 @@ private:
}
};
-class mock_mb_controller : public mb_controller
+class mock_mb_controller : public mb_controller,
+ public ::uhd::features::discoverable_feature_registry
{
public:
mock_mb_controller()
diff --git a/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp
new file mode 100644
index 000000000..2f2e51d60
--- /dev/null
+++ b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp
@@ -0,0 +1,1222 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "../../lib/usrp/x400/x400_radio_control.hpp"
+#include "../rfnoc_graph_mock_nodes.hpp"
+#include "x4xx_zbx_mpm_mock.hpp"
+#include <uhd/rfnoc/actions.hpp>
+#include <uhd/rfnoc/defaults.hpp>
+#include <uhd/rfnoc/mock_block.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/rfnoc/graph.hpp>
+#include <uhdlib/rfnoc/node_accessor.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_constants.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <math.h>
+#include <boost/test/unit_test.hpp>
+#include <chrono>
+#include <cmath>
+#include <iomanip>
+#include <iostream>
+#include <thread>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+using namespace std::chrono_literals;
+using namespace uhd::usrp::zbx;
+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 x4xx_radio_mock_reg_iface_t : public mock_reg_iface_t
+{
+ // Start address of CPLD register space
+ static constexpr uint32_t cpld_offset = radio_control_impl::regmap::PERIPH_BASE;
+ // Start address of RFDC control register space
+ static constexpr uint32_t rfdc_offset =
+ radio_control_impl::regmap::PERIPH_BASE + 0x8000;
+
+public:
+ x4xx_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 */;
+ }
+
+ 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;
+ }
+
+ // Are we on the CPLD?
+ if (addr >= cpld_offset && addr < rfdc_offset) {
+ _poke_cpld_cb(addr, data);
+ return;
+ }
+
+ // Are we poking the RFDC controls?
+ if (addr >= rfdc_offset) {
+ _poke_rfdc_cb(addr, data);
+ return;
+ }
+ }
+
+ void _poke_cpld_cb(const uint32_t addr, const uint32_t data)
+ {
+ switch (addr - cpld_offset) {
+ /// CURRENT_CONFIG_REG
+ case 0x1000:
+ // FIXME: We write to all regs during init
+ // BOOST_REQUIRE(false); // Not a write-register
+ break;
+ /// SW_CONFIG
+ case 0x1008: {
+ // This register is RW so update read_memory
+ read_memory[addr] = data;
+ // If we're in SW-defined mode, also update CURRENT_CONFIG_REG
+ uint32_t& rf_opt = read_memory[cpld_offset + 0x1004];
+ uint32_t& ccr = read_memory[cpld_offset + 0x1000];
+ // Check if RF0_OPTION is SW_DEFINED
+ if ((rf_opt & 0x00FF) == 0) {
+ ccr = (ccr & 0xFF00) | (data & 0x00FF);
+ }
+ // Check if RF1_OPTION is SW_DEFINED
+ if ((rf_opt & 0xFF00) == 0) {
+ ccr = (ccr & 0x00FF) | (data & 0xFF00);
+ }
+ } break;
+ /// LO SPI transactions
+ case 0x1020:
+ _poke_lo_spi(addr, data);
+ return;
+ /// LO SYNC
+ case 0x1024:
+ // We make these bits sticky, because they might get strobed in
+ // multiple calls. In order to see what was strobed within an
+ // API call, we keep bits as they are.
+ read_memory[addr] |= data;
+ return;
+ // TX0 Table Select
+ case 0x4000:
+ case 0x4004:
+ case 0x4008:
+ case 0x400C:
+ case 0x4010:
+ case 0x4014: {
+ read_memory[addr] = data;
+ const uint32_t src_table_offset = data * 4;
+ const uint32_t dst_table_offset = (addr - cpld_offset) - 0x4000;
+ // Now we fake the transaction that copies ?X?_TABLE_* to
+ // ?X?_DSA*
+ read_memory[cpld_offset + 0x3000 + dst_table_offset] =
+ read_memory[cpld_offset + 0x5000 + src_table_offset];
+ }
+ return;
+ // RX0 Table Select
+ case 0x4800:
+ case 0x4804:
+ case 0x4808:
+ case 0x480C:
+ case 0x4810:
+ case 0x4814: {
+ read_memory[addr] = data;
+ const uint32_t src_table_offset = data * 4;
+ const uint32_t dst_table_offset = (addr - cpld_offset) - 0x4800;
+ // Now we fake the transaction that copies ?X?_TABLE_* to
+ // ?X?_DSA*
+ read_memory[cpld_offset + 0x3800 + dst_table_offset] =
+ read_memory[cpld_offset + 0x5800 + src_table_offset];
+ }
+ return;
+ default: // All other CPLD registers are read-write
+ read_memory[addr] = data;
+ return;
+ }
+ }
+
+ void _poke_rfdc_cb(const uint32_t addr, const uint32_t data)
+ {
+ read_memory[addr] |= data;
+ }
+
+ void _poke_lo_spi(const uint32_t addr, const uint32_t data)
+ {
+ // UHD_LOG_INFO("TEST", "Detected LO SPI transaction!");
+ const uint16_t spi_data = data & 0xFFFF;
+ const uint8_t spi_addr = (data >> 16) & 0x7F;
+ const bool read = bool(data & (1 << 23));
+ const uint8_t lo_sel = (data >> 24) & 0x7;
+ const bool start_xact = bool(data & (1 << 28));
+ // UHD_LOG_INFO("TEST",
+ // "Transaction record: Read: "
+ // << (read ? "yes" : "no") << " Address: " << int(spi_addr) << std::hex
+ // << " Data: 0x" << spi_data << " LO sel: " << int(lo_sel) << std::dec
+ // << " Start Transaction: " << start_xact);
+ if (!start_xact) {
+ // UHD_LOG_INFO("TEST", "Register probably just initialized. Ignoring.");
+ return;
+ }
+ switch (spi_addr) {
+ case 0:
+ _muxout_to_lock = spi_data & (1 << 2);
+ break;
+ case 125:
+ BOOST_REQUIRE(read);
+ read_memory[addr] = 0x2288;
+ break;
+ default:
+ break;
+ }
+ if (read) {
+ read_memory[addr] = (read_memory[addr] & 0xFFFF) | (spi_addr << 16)
+ | (lo_sel << 24) | (1 << 31);
+ }
+ if (_muxout_to_lock) {
+ // UHD_LOG_INFO("TEST", "Muxout set to lock. Returning all ones.");
+ read_memory[addr] = 0xFFFF;
+ return;
+ }
+ return;
+ }
+
+ bool _muxout_to_lock = false;
+}; // class x4xx_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 regardless of environment
+// settings
+struct uhd_log_enabler
+{
+ uhd_log_enabler(uhd::log::severity_level level)
+ {
+ 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::warning) // 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::zbx::ZBX_NUM_CHANS)
+ , num_input_ports(num_channels)
+ , num_output_ports(num_channels)
+ , reg_iface(std::make_shared<x4xx_radio_mock_reg_iface_t>(num_channels))
+ , rpcs(std::make_shared<uhd::test::x4xx_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=122.88e6");
+ std::shared_ptr<x4xx_radio_mock_reg_iface_t> reg_iface;
+ std::shared_ptr<uhd::test::x4xx_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
+
+
+/******************************************************************************
+ * 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.");
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_freq_tx_test, x400_radio_fixture)
+{
+ const std::string log = "ZBX_API_TX_FREQUENCY_TEST";
+ const double ep = 10;
+ // TODO: consult step size
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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 = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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(zbx_api_freq_rx_test, x400_radio_fixture)
+{
+ const std::string log = "ZBX_API_RX_FREQUENCY_TEST";
+ const double ep = 10;
+ // TODO: consult step size
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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 = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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(zbx_frequency_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_FREQUENCY_TEST";
+ const double ep = 10;
+ // TODO: consult step size
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (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 = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX TX GAIN TEST";
+ uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1);
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+
+ const double ret_gain = test_radio->set_tx_gain(iter, chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->GET)\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+
+ test_radio->set_tx_gain(iter, chan);
+ const double ret_gain = test_radio->get_tx_gain(chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_stage_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX API TX GAIN STAGE TEST";
+
+ for (size_t chan : {0, 1}) {
+ test_radio->set_tx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan);
+
+ UHD_LOG_INFO(
+ log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n");
+ for (auto gain_stage : ZBX_TX_GAIN_STAGES) {
+ if (gain_stage == ZBX_GAIN_STAGE_AMP) {
+ for (double amp : {ZBX_TX_LOWBAND_GAIN, ZBX_TX_HIGHBAND_GAIN}) {
+ UHD_LOG_INFO(log, "Testing dsa: " << amp);
+ const double ret_gain =
+ test_radio->set_tx_gain(amp, gain_stage, chan);
+ UHD_LOG_INFO(log, "return: " << ret_gain);
+ BOOST_CHECK_EQUAL(amp, ret_gain);
+ }
+ } else {
+ for (unsigned int iter = 0; iter <= ZBX_TX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing dsa: " << iter);
+ const double ret_gain =
+ test_radio->set_tx_gain(iter, gain_stage, chan);
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_stage_test_set_get, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX API TX GAIN STAGE TEST";
+
+ for (size_t chan : {0, 1}) {
+ test_radio->set_tx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan);
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->GET)\n");
+ for (auto gain_stage : ZBX_TX_GAIN_STAGES) {
+ if (gain_stage == ZBX_GAIN_STAGE_AMP) {
+ for (double amp :
+ {/*ZBX_TX_BYPASS_GAIN, currently disabled*/ ZBX_TX_LOWBAND_GAIN,
+ ZBX_TX_HIGHBAND_GAIN}) {
+ UHD_LOG_INFO(log, "Testing amp: " << amp);
+ test_radio->set_tx_gain(amp, gain_stage, chan);
+ const double ret_gain = test_radio->get_tx_gain(gain_stage, chan);
+ BOOST_CHECK_EQUAL(amp, ret_gain);
+ }
+ } else {
+ for (unsigned int iter = 0; iter <= ZBX_TX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing dsa: " << iter);
+ test_radio->set_tx_gain(iter, gain_stage, chan);
+ const double ret_gain = test_radio->get_tx_gain(gain_stage, chan);
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_rx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX RX API GAIN TEST";
+ uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1);
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+
+ const double ret_gain = test_radio->set_rx_gain(iter, chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->GET)\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+
+ test_radio->set_rx_gain(iter, chan);
+ const double ret_gain = test_radio->get_rx_gain(chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_rx_gain_stage_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX API RX GAIN STAGE TEST";
+
+ for (size_t chan : {0, 1}) {
+ test_radio->set_rx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan);
+
+ UHD_LOG_INFO(
+ log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n");
+ for (auto gain_stage : ZBX_RX_GAIN_STAGES) {
+ for (unsigned int iter = 0; iter <= ZBX_RX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing dsa: " << gain_stage << " " << iter);
+ const double ret_gain = test_radio->set_rx_gain(iter, gain_stage, chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->GET)\n");
+ for (auto gain_stage : ZBX_RX_GAIN_STAGES) {
+ for (unsigned int iter = 0; iter <= ZBX_RX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing " << gain_stage << " " << iter);
+
+ test_radio->set_rx_gain(iter, gain_stage, chan);
+ const double ret_gain = test_radio->get_rx_gain(gain_stage, chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX GAIN TEST";
+ uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1);
+
+ for (auto fe_path :
+ {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+ const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value";
+ tree->access<double>(gain_path).set(iter);
+ const double ret_gain = tree->access<double>(gain_path).get();
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX GAIN TEST";
+ uhd::freq_range_t zbx_gain(RX_MIN_GAIN, RX_MAX_GAIN, 1);
+
+ for (auto fe_path :
+ {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+ const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value";
+ tree->access<double>(gain_path).set(iter);
+ const double ret_gain = tree->access<double>(gain_path).get();
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+}
+
+// Have to be careful about LO testing; it'll throw off the coerced frequency a bunch,
+// possibly to illegal values like negative frequencies, and could make the gain API
+// freak out. We use the center frequency to set initial mixer values, then try to test
+// all LO's in the valid zbx range.
+// TODO: expand this
+const std::map<double, std::vector<std::array<double, 2>>> valid_lo_freq_map = {
+ {1e9, {{4.5e9, 4.5e9}, {5e9, 5e9}, {5.5e9, 5.5e9}, {6e9, 6e9}}},
+ {2e9, {{4.5e9, 4.5e9}, {5e9, 5e9}, {5.5e9, 5.5e9}, {6e9, 6e9}}}};
+
+// TODO: More frequencies_are_equal issues, too much variance
+BOOST_FIXTURE_TEST_CASE(zbx_api_tx_lo_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX TX TEST";
+ const double ep = 10;
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: TX" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end();
+ iter++) {
+ for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end();
+ iter_lo++) {
+ // Just so we're clear about our value mapping
+ const double req_freq = iter->first;
+ const double req_lo1 = iter_lo->at(0);
+ const double req_lo2 = iter_lo->at(1);
+
+ UHD_LOG_INFO(log,
+ "Testing center freq " << req_freq / 1e6 << "MHz, lo1 freq "
+ << req_lo1 / 1e6 << "MHz, lo2 freq "
+ << req_lo2 / 1e6 << "MHz");
+ // Need to set center frequency first, it'll set all the mixer values
+ test_radio->set_tx_frequency(iter->first, chan);
+ const double lo1_ret =
+ test_radio->set_tx_lo_freq(iter_lo->at(0), ZBX_LO1, chan);
+ const double lo2_ret =
+ test_radio->set_tx_lo_freq(iter_lo->at(1), ZBX_LO2, chan);
+ // No use comparing set_tx_freq, we've already ran that test and
+ // get_tx_frequency would return who knows what at this point
+ BOOST_REQUIRE(abs(iter_lo->at(0) - lo1_ret) < ep);
+ BOOST_REQUIRE(abs(iter_lo->at(1) - lo2_ret) < ep);
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_rx_lo_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX RX LO TEST";
+ const double ep = 10;
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: RX" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end();
+ iter++) {
+ for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end();
+ iter_lo++) {
+ // Just so we're clear about our value mapping
+ const double req_freq = iter->first;
+ const double req_lo1 = iter_lo->at(0);
+ const double req_lo2 = iter_lo->at(1);
+
+ UHD_LOG_INFO(log,
+ "Testing center freq " << req_freq / 1e6 << "MHz, lo1 freq "
+ << req_lo1 / 1e6 << "MHz, lo2 freq "
+ << req_lo2 / 1e6 << "MHz");
+ // Need to set center frequency first, it'll set all the mixer values
+ test_radio->set_rx_frequency(iter->first, chan);
+ const double lo1_ret =
+ test_radio->set_rx_lo_freq(iter_lo->at(0), ZBX_LO1, chan);
+ const double lo2_ret =
+ test_radio->set_rx_lo_freq(iter_lo->at(1), ZBX_LO2, chan);
+ // No use comparing set_tx_freq, we've already ran that test and
+ // get_tx_frequency would return who knows what at this point
+ BOOST_REQUIRE(abs(iter_lo->at(0) - lo1_ret) < ep);
+ BOOST_REQUIRE(abs(iter_lo->at(1) - lo2_ret) < ep);
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_lo_tree_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX LO1 TEST";
+ const double ep = 10;
+
+ for (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 << " LO FREQ CHANGE (SET->RETURN)\n");
+ for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end();
+ iter++) {
+ for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end();
+ iter_lo++) {
+ // Just so we're clear about our value mapping
+ const double req_freq = iter->first;
+ const double req_lo1 = iter_lo->at(0);
+ const double req_lo2 = iter_lo->at(1);
+ UHD_LOG_INFO(log,
+ "Testing lo1 freq " << req_lo1 / 1e6 << "MHz, lo2 freq "
+ << req_lo2 / 1e6 << "MHz at center frequency "
+ << req_freq / 1e6 << "MHz");
+ tree->access<double>(fe_path / "freq").set(req_freq);
+ const double ret_lo1 =
+ tree->access<double>(fe_path / "los" / ZBX_LO1 / "freq" / "value")
+ .set(req_lo1)
+ .get();
+ const double ret_lo2 =
+ tree->access<double>(fe_path / "los" / ZBX_LO2 / "freq" / "value")
+ .set(req_lo2)
+ .get();
+ BOOST_REQUIRE(abs(req_lo1 - ret_lo1) < ep);
+ BOOST_REQUIRE(abs(req_lo2 - ret_lo2) < ep);
+ }
+ }
+ }
+}
+
+
+BOOST_FIXTURE_TEST_CASE(zbx_ant_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ std::string log = "ZBX RX ANTENNA TEST";
+
+ for (auto fe_path :
+ {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " ANTENNA CHANGE\n");
+ for (auto iter : RX_ANTENNAS) {
+ UHD_LOG_INFO(log, "Testing Antenna: " << iter);
+
+ tree->access<std::string>(fe_path / "antenna/value").set(iter);
+
+ std::string ret_ant =
+ tree->access<std::string>(fe_path / "antenna/value").get();
+ BOOST_CHECK_EQUAL(iter, ret_ant);
+ }
+ }
+ log = "ZBX TX ANTENNA TEST";
+ for (auto fe_path :
+ {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " ANTENNA CHANGE\n");
+ for (auto iter : TX_ANTENNAS) {
+ UHD_LOG_INFO(log, "Testing Antenna: " << iter);
+
+ tree->access<std::string>(fe_path / "antenna/value").set(iter);
+
+ std::string ret_ant =
+ tree->access<std::string>(fe_path / "antenna/value").get();
+ BOOST_CHECK_EQUAL(iter, ret_ant);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_freq_coercion_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_FREQUENCY_COERCION_TEST";
+ const double ep = 10;
+
+ for (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 << " FREQUENCY COERCION\n");
+ double ret_value =
+ tree->access<double>(fe_path / "freq").set(ZBX_MIN_FREQ - 1e6).get();
+
+ BOOST_REQUIRE(abs(ZBX_MIN_FREQ - ret_value) < ep);
+
+ ret_value = tree->access<double>(fe_path / "freq").set(ZBX_MAX_FREQ + 1e6).get();
+
+ BOOST_REQUIRE(abs(ZBX_MAX_FREQ - ret_value) < ep);
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_coercion_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_GAIN_COERCION_TEST";
+
+ for (auto fe_path :
+ {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) {
+ uhd::gain_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 0.1);
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " TX GAIN COERCION\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value";
+ const double ret_val = tree->access<double>(gain_path).set(iter).get();
+ BOOST_CHECK_EQUAL(ret_val, std::round(iter));
+ }
+ }
+ for (auto fe_path :
+ {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) {
+ uhd::gain_range_t zbx_gain(RX_MIN_GAIN, RX_MAX_GAIN, 0.1);
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " RX GAIN COERCION\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value";
+ const double ret_val = tree->access<double>(gain_path).set(iter).get();
+ BOOST_CHECK_EQUAL(ret_val, std::round(iter));
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_phase_sync_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_PHASE_SYNC_TEST";
+ constexpr uint32_t lo_sync_addr = 0x1024 + 0x80000;
+ constexpr uint32_t nco_sync_addr = 0x88000;
+ constexpr uint32_t gearbox_addr = 0x88004;
+ auto& regs = reg_iface->read_memory;
+ UHD_LOG_INFO("TEST", "Setting 1 GHz defaults...");
+ // Confirm default
+ test_radio->set_rx_frequency(1e9, 0);
+ test_radio->set_rx_frequency(1e9, 1);
+ test_radio->set_tx_frequency(1e9, 0);
+ test_radio->set_tx_frequency(1e9, 1);
+ // Enable time stamp
+ UHD_LOG_INFO("TEST", "Enabling time stamp chan 0...");
+ test_radio->set_command_time(uhd::time_spec_t(2.0), 0);
+ // Don't pick the ZBX default frequency here
+ UHD_LOG_INFO("TEST", "Setting RX chan 0 to 2.3 GHz...");
+ test_radio->set_rx_frequency(2.3e9, 0);
+ // Check we synced RX LOs chan 0 and RX NCO chan 0, and ADC gearboxes
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0b11 << 4);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 1);
+ // Reset strobes
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+ UHD_LOG_INFO("TEST", "Enabling time stamp chan 1...");
+ test_radio->set_command_time(uhd::time_spec_t(2.0), 1);
+ UHD_LOG_INFO("TEST", "Setting RX chan 1 to 2.3 GHz...");
+ test_radio->set_rx_frequency(2.3e9, 1);
+ // Check we synced RX LOs chan 1 and RX NCO chan 1. ADC gearbox only gets
+ // reset once, and should be left untouched.
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0b11 << 6);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 0);
+ // Reset strobes
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+ UHD_LOG_INFO("TEST", "Setting TX chan 0 to 2.3 GHz...");
+ test_radio->set_tx_frequency(2.3e9, 0);
+ // Check we synced TX LOs chan 0 and TX NCO chan 0, and DAC gearboxes
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0x3 << 0);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 1 << 1);
+ // Reset strobe
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+ UHD_LOG_INFO("TEST", "Setting TX chan 1 to 2.3 GHz...");
+ test_radio->set_tx_frequency(2.3e9, 1);
+ // Check we synced TX LOs chan 1 and TX NCO chan 1. DAC gearbox only gets
+ // reset once, and should be left untouched.
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0xC << 0);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 0);
+ // Reset strobe
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+}
+
+BOOST_FIXTURE_TEST_CASE(can_set_rfdc_test, x400_radio_fixture)
+{
+ test_radio->set_tx_lo_freq(3.141e9, "rfdc", 1);
+ test_radio->get_tx_lo_freq("rfdc", 1);
+
+ test_radio->set_rx_lo_freq(2.141e9, "rfdc", 0);
+ test_radio->get_rx_lo_freq("rfdc", 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_power_api, x400_radio_fixture)
+{
+ constexpr double tx_given_gain = 30;
+ constexpr double tx_given_power = -30;
+
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_TX_POWER_TRACKING_TEST";
+ auto tx_pwr_mgr = test_radio->get_pwr_mgr(TX_DIRECTION);
+
+ for (size_t chan = 0; chan < ZBX_NUM_CHANS; chan++) {
+ // Start in gain tracking mode
+ double gain_coerced = test_radio->set_tx_gain(tx_given_gain, chan);
+ BOOST_CHECK_EQUAL(gain_coerced, tx_given_gain);
+ for (const double freq : {6e+08, 1e+09, 2e+09, 3e+09, 4e+09, 5e+09, 6e+09}) {
+ // Setting a power reference should kick us into power tracking mode
+ test_radio->set_tx_power_reference(tx_given_power, chan);
+
+ test_radio->set_tx_frequency(freq, chan);
+ // If the tracking mode is properly set, we should not deviate much
+ // regarding power
+ const double pow_diff = std::abs<double>(
+ tx_given_power - test_radio->get_tx_power_reference(chan));
+ BOOST_CHECK_MESSAGE(pow_diff < 3.0, "power differential is too large: " << pow_diff);
+
+ // Back to gain mode
+ gain_coerced = test_radio->set_tx_gain(tx_given_gain, chan);
+ BOOST_CHECK_EQUAL(gain_coerced, tx_given_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_power_api, x400_radio_fixture)
+{
+ constexpr double rx_given_gain = 30;
+ constexpr double rx_given_power = -30;
+
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_RX_POWER_TRACKING_TEST";
+ auto rx_pwr_mgr = test_radio->get_pwr_mgr(RX_DIRECTION);
+
+ for (size_t chan = 0; chan < ZBX_NUM_CHANS; chan++) {
+ // Start in gain tracking mode
+ double gain_coerced = test_radio->set_rx_gain(rx_given_gain, chan);
+ BOOST_REQUIRE_EQUAL(gain_coerced, rx_given_gain);
+ for (const double freq : {1e+09, 2e+09, 3e+09, 4e+09, 5e+09, 6e+09}) {
+ // Setting a power reference should kick us into power tracking mode
+ test_radio->set_rx_power_reference(rx_given_power, chan);
+ // Now go tune
+ test_radio->set_rx_frequency(freq, chan);
+ // If the tracking mode is properly set, we should match our expected criteria
+ // for power reference levels
+ const double actual_power = test_radio->get_rx_power_reference(chan);
+ const double pow_diff = std::abs<double>(rx_given_power - actual_power);
+ BOOST_CHECK_MESSAGE(pow_diff < 3.0,
+ "power differential is too large ("
+ << pow_diff << "): Expected close to: " << rx_given_power
+ << " Actual: " << actual_power << " Frequency: " << (freq/1e6));
+
+ gain_coerced = test_radio->set_rx_gain(rx_given_gain, chan);
+ BOOST_REQUIRE_EQUAL(gain_coerced, rx_given_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_lo_injection_locking, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+
+ // As of right now, we don't have a way to directly get the DB prc rate, this is the
+ // value of the prc map per DEFAULT_MCR, in the mock RPC server:db_0_get_db_prc_rate()
+ constexpr double db_prc_rate = 61.44e6;
+ constexpr double lo_step_size = db_prc_rate / ZBX_RELATIVE_LO_STEP_SIZE;
+
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_freq.step()) {
+ for (const size_t chan : {0, 1}) {
+ test_radio->set_tx_frequency(iter, chan);
+
+ // The step alignment only applies to the desired LO frequency, the actual
+ // returned frequency may vary slightly
+ const double lo1_freq = std::round(test_radio->get_tx_lo_freq(ZBX_LO1, chan));
+ const double lo2_freq = std::round(test_radio->get_tx_lo_freq(ZBX_LO2, chan));
+
+ const double lo1_div = lo1_freq / lo_step_size;
+ const double lo2_div = lo2_freq / lo_step_size;
+
+ // Test whether our tuned frequencies align with the lo step size
+ BOOST_CHECK_EQUAL(std::floor(lo1_div), lo1_div);
+ BOOST_CHECK_EQUAL(std::floor(lo2_div), lo2_div);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_lo_injection_locking, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+
+ // As of right now, we don't have a way to directly get the DB prc rate, this is the
+ // value of the prc map per DEFAULT_MCR, in the mock RPC server:db_0_get_db_prc_rate()
+ constexpr double db_prc_rate = 61.44e6;
+ constexpr double lo_step_size = db_prc_rate / ZBX_RELATIVE_LO_STEP_SIZE;
+
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_freq.step()) {
+ for (const size_t chan : {0, 1}) {
+ test_radio->set_rx_frequency(iter, chan);
+
+ // The step alignment only applies to the desired LO frequency, the actual
+ // returned frequency may vary slightly
+ const double lo1_freq = std::round(test_radio->get_rx_lo_freq(ZBX_LO1, chan));
+ const double lo2_freq = std::round(test_radio->get_rx_lo_freq(ZBX_LO2, chan));
+
+ const double lo1_div = lo1_freq / lo_step_size;
+ const double lo2_div = lo2_freq / lo_step_size;
+
+ // Test whether our tuned frequencies align with the lo step size
+ BOOST_CHECK_EQUAL(std::floor(lo1_div), lo1_div);
+ BOOST_CHECK_EQUAL(std::floor(lo2_div), lo2_div);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_gain_profile_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_GAIN_PROFILE_TEST";
+ auto& regs = reg_iface->read_memory;
+ constexpr uint32_t current_config = radio_control_impl::regmap::PERIPH_BASE + 0x1000;
+ constexpr uint32_t rf_option = radio_control_impl::regmap::PERIPH_BASE + 0x1004;
+ constexpr uint32_t sw_config = radio_control_impl::regmap::PERIPH_BASE + 0x1008;
+ constexpr uint32_t rx0_dsa = radio_control_impl::regmap::PERIPH_BASE + 0x3800;
+ constexpr uint32_t rx0_table = radio_control_impl::regmap::PERIPH_BASE + 0x5800;
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(1), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(1), "default");
+ // Everything should be classic_atr
+ BOOST_CHECK_EQUAL(regs[0x81004], 0x01010101);
+ // Can't set gain stages in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "DSA1", 0), uhd::key_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "DSA1", 0), uhd::key_error);
+
+ //** manual gain profile **
+ test_radio->set_rx_gain_profile("manual", 0);
+ // Must provide valid gain name in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(23, 0), uhd::runtime_error);
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "banana", 0), uhd::key_error);
+ // Now manually set the DSAs
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA1", 0));
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA2", 0));
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA3A", 0));
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA3B", 0));
+ // Check the registers were written to correctly (gain 5 == att 10)
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0xAAAA);
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0xAAAA);
+ // Check the getters:
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA1", 0), 5);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA2", 0), 5);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA3A", 0), 5);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA3B", 0), 5);
+ // Even in 'manual', we can load from the table. Let's create a table entry:
+ regs[rx0_table + 5 * 4] = 0x7777;
+ // Now, let it be loaded into RX and XX:
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0x7777);
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0x7777);
+ // Note: If we read back the DSAs via get_rx_gain() now, they will still say
+ // 5. We might want to change that, but it will require extra peeks. The
+ // only good way to do that is to amend set_?x_gain() to do that peek when
+ // updating gains via table.
+ // Test DSA coercion
+ BOOST_CHECK_EQUAL(15, test_radio->set_rx_gain(39, "DSA1", 0));
+ BOOST_CHECK_EQUAL(0, test_radio->set_rx_gain(-17, "DSA1", 0));
+
+ // If we go back to 'default', we also reset the DSAs. That's because the
+ // desired, previously loaded default value will trigger the previous DSA
+ // values again.
+ UHD_LOG_INFO(log, "resetting to default");
+ test_radio->set_rx_gain_profile("default", 0);
+ BOOST_CHECK_EQUAL(0, test_radio->get_rx_gain("DSA1", 0));
+
+ //** table_noatr profile : **
+ UHD_LOG_INFO(log, "setting to table_noatr");
+ test_radio->set_rx_gain_profile("table_noatr", 0);
+ // This will set DSA config for chan 0 to 0 == SW_DEFINED
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01000101);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table_noatr");
+ // Yup, this will also change TX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table_noatr");
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "all", 0), uhd::key_error);
+ BOOST_CHECK_EQUAL(8.0, test_radio->set_rx_gain(8, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[sw_config], 0x80000);
+ // Returns the current config. Note the asymmetry to the previous API call.
+ // We can't, however, know which entry from the TABLE we used, so we just
+ // return the current config (which is the entry from the DSA table, not the
+ // TABLE it writes to).
+ BOOST_CHECK_EQUAL(0, test_radio->get_rx_gain("TABLE", 0));
+ // Let's pretend we're using config 7
+ regs[current_config] = 0x70000;
+ BOOST_CHECK_EQUAL(7, test_radio->get_rx_gain("TABLE", 0));
+ // And back
+ regs[current_config] = 0x00000;
+ // Now we fake an FPGA-gain-change transaction that UHD is unaware of. We
+ // keep the current config of 0, and update RX0_DSA*[0].
+ regs[rx0_dsa + 0 * 4] = 0x4444; // Turn it up to attenuation 4 == gain 11
+ BOOST_CHECK_EQUAL(11.0, test_radio->get_rx_gain("DSA1", 0));
+
+ //** table profile **
+ test_radio->set_rx_gain_profile("table", 0);
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01010101);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table");
+ // Yup, this will also change TX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table");
+ // Create another table entry
+ regs[rx0_table + 23 * 4] = 0xBBBB;
+ BOOST_CHECK_EQUAL(23.0, test_radio->set_rx_gain(23, "TABLE", 0));
+ // get_rx_gain() for "TABLE" returns the current DSA table index, not actual gain
+ BOOST_CHECK_EQUAL(0.0, test_radio->get_rx_gain("TABLE", 0));
+ // This will update RX and XX registers (that's the difference to table_noatr)
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0xBBBB); // att 0xB == gain 4.0
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0xBBBB);
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA1", 0));
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA2", 0));
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA3A", 0));
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA3B", 0));
+ // Test table coercion
+ UHD_LOG_INFO(log, "Testing TABLE coercion");
+ BOOST_CHECK_EQUAL(0.0, test_radio->set_rx_gain(-17, "TABLE", 0));
+ BOOST_CHECK_EQUAL(255.0, test_radio->set_rx_gain(1e9, "TABLE", 0));
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_profile_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_GAIN_PROFILE_TEST";
+ auto& regs = reg_iface->read_memory;
+ constexpr uint32_t current_config = radio_control_impl::regmap::PERIPH_BASE + 0x1000;
+ constexpr uint32_t rf_option = radio_control_impl::regmap::PERIPH_BASE + 0x1004;
+ constexpr uint32_t sw_config = radio_control_impl::regmap::PERIPH_BASE + 0x1008;
+ constexpr uint32_t tx0_dsa = radio_control_impl::regmap::PERIPH_BASE + 0x3000;
+ constexpr uint32_t tx0_table = radio_control_impl::regmap::PERIPH_BASE + 0x5000;
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(1), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(1), "default");
+ const double default_dsa1 = test_radio->get_tx_gain("DSA1", 0);
+ // Everything should be classic_atr
+ BOOST_CHECK_EQUAL(regs[0x81004], 0x01010101);
+ // Can't set gain stages in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "DSA1", 0), uhd::key_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "DSA1", 0), uhd::key_error);
+
+ //** manual gain profile **
+ test_radio->set_tx_gain_profile("manual", 0);
+ // Must provide valid gain name in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(23, 0), uhd::runtime_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(23, "all", 0), uhd::key_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "banana", 0), uhd::key_error);
+ // Now manually set the DSAs
+ BOOST_CHECK_EQUAL(21, test_radio->set_tx_gain(21, "DSA1", 0));
+ BOOST_CHECK_EQUAL(21, test_radio->set_tx_gain(21, "DSA2", 0));
+ // Check the registers were written to correctly (gain 5 == att 10)
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0A0A);
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0A0A);
+ // Check the getters:
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain("DSA1", 0), 21);
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain("DSA2", 0), 21);
+ // Even in 'manual', we can load from the table. Let's create a table entry:
+ regs[tx0_table + 5 * 4] = 0x0707;
+ // Now, let it be loaded into RX and XX:
+ BOOST_CHECK_EQUAL(5, test_radio->set_tx_gain(5, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0707);
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0707);
+ // Note: If we read back the DSAs via get_tx_gain() now, they will still say
+ // 5. We might want to change that, but it will require extra peeks. The
+ // only good way to do that is to amend set_?x_gain() to do that peek when
+ // updating gains via table.
+ // Test DSA coercion
+ BOOST_CHECK_EQUAL(31, test_radio->set_tx_gain(39, "DSA1", 0));
+ BOOST_CHECK_EQUAL(0, test_radio->set_tx_gain(-17, "DSA1", 0));
+
+ // If we go back to 'default', we also reset the DSAs. That's because the
+ // desired, previously loaded default value will trigger the previous DSA
+ // values again.
+ UHD_LOG_INFO(log, "resetting to default");
+ test_radio->set_tx_gain_profile("default", 0);
+ BOOST_CHECK_EQUAL(default_dsa1, test_radio->get_tx_gain("DSA1", 0));
+
+ //** table_noatr profile : **
+ UHD_LOG_INFO(log, "setting to table_noatr");
+ test_radio->set_tx_gain_profile("table_noatr", 0);
+ // This will set DSA config for chan 0 to 0 == SW_DEFINED
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01000101);
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table_noatr");
+ // Yup, this will also change RX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table_noatr");
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "all", 0), uhd::key_error);
+ BOOST_CHECK_EQUAL(8.0, test_radio->set_tx_gain(8, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[sw_config], 0x80000);
+ // Returns the current config. Note the asymmetry to the previous API call.
+ // We can't, however, know which entry from the TABLE we used, so we just
+ // return the current config (which is the entry from the DSA table, not the
+ // TABLE it writes to).
+ BOOST_CHECK_EQUAL(0, test_radio->get_tx_gain("TABLE", 0));
+ // Let's pretend we're using config 7
+ regs[current_config] = 0x70000;
+ BOOST_CHECK_EQUAL(7, test_radio->get_tx_gain("TABLE", 0));
+ // And back
+ regs[current_config] = 0x00000;
+ // Now we fake an FPGA-gain-change transaction that UHD is unaware of. We
+ // keep the current config of 0, and update TX0_DSA*[0].
+ regs[tx0_dsa + 0 * 4] = 0x0404; // Turn it up to attenuation 4 == gain 27
+ BOOST_CHECK_EQUAL(27.0, test_radio->get_tx_gain("DSA1", 0));
+
+ //** table profile **
+ test_radio->set_tx_gain_profile("table", 0);
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01010101);
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table");
+ // Yup, this will also change RX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table");
+ // Create another table entry
+ regs[tx0_table + 23 * 4] = 0x0B0B;
+ BOOST_CHECK_EQUAL(23.0, test_radio->set_tx_gain(23, "TABLE", 0));
+ // get_tx_gain() for "TABLE" returns the current DSA table index, not actual gain
+ BOOST_CHECK_EQUAL(0.0, test_radio->get_tx_gain("TABLE", 0));
+ // This will update RX and XX registers (that's the difference to table_noatr)
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0B0B); // att 0xB == gain 20.0
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0B0B);
+ BOOST_CHECK_EQUAL(20.0, test_radio->get_tx_gain("DSA1", 0));
+ BOOST_CHECK_EQUAL(20.0, test_radio->get_tx_gain("DSA2", 0));
+ // Test table coercion
+ UHD_LOG_INFO(log, "Testing TABLE coercion");
+ BOOST_CHECK_EQUAL(0.0, test_radio->set_tx_gain(-17, "TABLE", 0));
+ BOOST_CHECK_EQUAL(255.0, test_radio->set_tx_gain(1e9, "TABLE", 0));
+}
+
+// TODO:
+// - concurrent/consecutive configuration
+// - Threading tests
+// - Error cases
diff --git a/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp b/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp
new file mode 100644
index 000000000..ad47089a5
--- /dev/null
+++ b/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp
@@ -0,0 +1,337 @@
+//
+// 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/zbx/zbx_constants.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_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 = 122.88e6;
+}
+//! Mock MPM server for X410/ZBX
+//
+// This is a mock server that mimicks an X410 with a ZBX daughterboard.
+class x4xx_mock_rpc_server : public x400_rpc_iface, public mpmd_rpc_iface, public dboard_base_rpc_iface, public zbx_rpc_iface
+{
+public:
+ x4xx_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
+ *************************************************************************/
+ 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 {};
+ }
+
+ 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::zbx::ZBX_PID)}
+ // End of entries
+ }};
+ }
+
+ bool is_db_gpio_ifc_present(const size_t) override
+ {
+ return true;
+ }
+
+ void set_tick_period(const size_t, const uint64_t) override
+ {
+ // nop
+ }
+
+ 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 {};
+ }
+
+ 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 get_dboard_prc_rate() override
+ {
+ const double mcr = _device_info.cast<double>("master_clock_rate", DEFAULT_MCR);
+ static const std::map<double, double> prc_map{
+ {122.88e6, 61.44e6}, {125e6, 62.5e6}};
+ return prc_map.at(mcr);
+ }
+
+ double rfdc_set_nco_freq(const std::string& trx,
+ const size_t /*db_id*/,
+ const size_t chan,
+ const double freq)
+ {
+ BOOST_REQUIRE(trx == "rx" || trx == "tx");
+ BOOST_REQUIRE(chan < uhd::usrp::zbx::ZBX_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)
+ {
+ BOOST_REQUIRE(trx == "rx" || trx == "tx");
+ BOOST_REQUIRE(chan < uhd::usrp::zbx::ZBX_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}
+ // 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 {};
+ }
+
+ 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 /*motherboard_channel_number*/, 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
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Public attributes for easy inspection
+ //
+ // Use this in the mock functions to cache values, or expose values that get
+ // tested later
+ std::map<std::string, std::map<size_t, double>> if2_freq;
+ 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
diff --git a/host/tests/streaming_performance/run_X4xx_max_rate_tests.py b/host/tests/streaming_performance/run_X4xx_max_rate_tests.py
new file mode 100644
index 000000000..ea424f14f
--- /dev/null
+++ b/host/tests/streaming_performance/run_X4xx_max_rate_tests.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+"""
+Copyright 2021 Ettus Research, A National Instrument Brand
+
+SPDX-License-Identifier: GPL-3.0-or-later
+
+Runs streaming tests for X4xx at rates in the neighborhoood of the maximum
+rate that UHD can sustain. Each test consists of a batch of runs of the
+benchmark rate C++ example with different streaming parameters.
+
+To run all the tests, execute it with all supported options for the test_type
+parameter:
+ 1Gbe, 1x10Gbe, 2x10Gbe, 1x100Gbe, 2x100Gbe
+
+Example usage::
+run_X4xx_max_rate_tests.py --path <benchmark_rate_dir>/benchmark_rate --addr 192.168.10.2 --second_addr 192.168.20.2 --mgmt_addr 192.168.40.2 --test_type 1x100Gbe --use_dpdk 1
+"""
+import argparse
+import sys
+import time
+import datetime
+import batch_run_benchmark_rate
+
+
+def parse_args():
+ """
+ Parse command line arguments
+ """
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--path",
+ type=str,
+ required=True,
+ help="path to benchmark rate example")
+ parser.add_argument(
+ "--test_type",
+ type=str,
+ required=True,
+ choices=["1Gbe", "1x10Gbe", "2x10Gbe", "1x100Gbe", "2x100Gbe"],
+ help="test type you would like to run eg. 1Gbe, 1x10Gbe, 2x10Gbe, 1x100Gbe, 2x100Gbe")
+ parser.add_argument(
+ "--addr",
+ type=str,
+ default = "",
+ help="address of first interface")
+ parser.add_argument(
+ "--second_addr",
+ type=str,
+ default = "",
+ help="address of second interface")
+ parser.add_argument(
+ "--mgmt_addr",
+ type=str,
+ default = "",
+ help="mgmt address")
+ parser.add_argument(
+ "--use_dpdk",
+ default = False,
+ action="store_true",
+ help="enable DPDK (you must run the script as root to use this)")
+
+ return parser.parse_args()
+
+def run_test(path, params, iterations, label):
+ """
+ Runs benchmark rate for the number of iterations in the command line arguments.
+ """
+ print("-----------------------------------------------------------")
+ print(label + "\n")
+ results = batch_run_benchmark_rate.run(path, iterations, params)
+ stats = batch_run_benchmark_rate.calculate_stats(results)
+ print(batch_run_benchmark_rate.get_summary_string(stats, iterations, params))
+
+def run_tests_for_single(path, base_params, iterations, duration, rate):
+
+ base_params["duration"] = duration
+
+ rx_params = base_params.copy()
+
+ # Run 20 Msps RX with one channel
+ rx_params["rx_rate"] = str(rate)
+ rx_params["rx_channels"] = "0"
+ print(rx_params)
+ run_test(path, rx_params, iterations, "1xRX @"+ str(rate/1e6) +" Msps")
+
+ # Run 10 Msps with two channels
+ rx_params["rx_rate"] = str(rate/2)
+ rx_params["rx_channels"] = "0,1"
+ print(rx_params)
+ run_test(path, rx_params, iterations, "2xRX @"+ str(rate/2e6) +" Msps")
+
+ tx_params = base_params.copy()
+
+ # Run 20 Msps TX with one channel
+ tx_params["tx_rate"] = str(rate)
+ tx_params["tx_channels"] = "0"
+ print(tx_params)
+ run_test(path, tx_params, iterations, "1xTX @"+ str(rate/1e6) +" Msps")
+
+ # Run 10 Msps TX with two channels
+ tx_params["tx_rate"] = "100e5"
+ tx_params["tx_channels"] = "0,1"
+ print(tx_params)
+ run_test(path, tx_params, iterations, "2xTX @"+ str(rate/2e6) +" Msps")
+
+ trx_params = base_params.copy()
+
+ # Run 20 Msps TRX with one channel
+ trx_params["tx_rate"] = str(rate)
+ trx_params["rx_rate"] = str(rate)
+ trx_params["tx_channels"] = "0"
+ trx_params["rx_channels"] = "0"
+ print(trx_params)
+ run_test(path, trx_params, iterations, "1xTRX @"+ str(rate/1e6) +" Msps")
+
+ # Run 10 Msps TRX with two channels
+ trx_params["tx_rate"] = str(rate/2)
+ trx_params["rx_rate"] = str(rate/2)
+ trx_params["tx_channels"] = "0,1"
+ trx_params["rx_channels"] = "0,1"
+ print(trx_params)
+ run_test(path, trx_params, iterations, "2xTRX @"+ str(rate/2e6) +" Msps")
+
+
+def run_tests_for_dual(base_params, path, duration, iterations, rate):
+
+ base_params["duration"] = duration
+
+ rx_params = base_params.copy()
+ # Run 200 Msps with two channels
+ rx_params["rx_rate"] = str(rate)
+ rx_params["rx_channels"] = "0,1"
+ print(rx_params)
+ run_test(path, rx_params, iterations, "2xRX @"+ str(rate/1e6) +" Msps")
+
+ tx_params = base_params.copy()
+
+ # Run 200 Msps TX with two channels
+ tx_params["tx_rate"] = str(rate)
+ tx_params["tx_channels"] = "0,1"
+ print(tx_params)
+ run_test(path, tx_params, iterations, "2xTX @"+ str(rate/1e6) +" Msps")
+
+ trx_params = base_params.copy()
+
+ # Run 100 Msps TRX with two channels
+ trx_params["tx_rate"] = str(rate/2)
+ trx_params["rx_rate"] = str(rate/2)
+ trx_params["tx_channels"] = "0,1"
+ trx_params["rx_channels"] = "0,1"
+ print(trx_params)
+ run_test(path, trx_params, iterations, "2xTRX @"+ str(rate/2e6) +" Msps")
+
+
+
+def main():
+ args = parse_args()
+
+ base_params = {
+ "args" : f"addr={args.addr},second_addr={args.second_addr}",
+ }
+ if args.use_dpdk:
+ base_params["args"] += f",mgmt_addr={args.mgmt_addr},use_dpdk=1"
+
+ start_time = time.time()
+
+ rate = {
+ "1Gbe": 200e5,
+ "1x10Gbe": 200e6,
+ "2x10Gbe": 200e6,
+ "1x100Gbe": 200e7, # Rate doesn't matter here as 100Gbe has no DUC
+ "2x100Gbe": 200e7}
+
+ test_config = {
+ "1Gbe": run_tests_for_single,
+ "1x10Gbe": run_tests_for_single,
+ "2x10Gbe": run_tests_for_dual,
+ "1x100Gbe": run_tests_for_single,
+ "2x100Gbe": run_tests_for_dual}
+
+
+ # Run 10 test iterations for 60 seconds each
+ test_config[args.test_type](args.path, base_params, 10, 60, rate[args.test_type])
+ # Run 2 test iterations for 600 seconds each
+ test_config[args.test_type](args.path, base_params, 2, 600, rate[args.test_type])
+
+ end_time = time.time()
+ elapsed = end_time - start_time
+ print("Elapsed time: {}".format(datetime.timedelta(seconds=elapsed)))
+ return True
+
+if __name__ == "__main__":
+ sys.exit(not main())
diff --git a/host/tests/x400_rfdc_control_test.cpp b/host/tests/x400_rfdc_control_test.cpp
new file mode 100644
index 000000000..ec80682fa
--- /dev/null
+++ b/host/tests/x400_rfdc_control_test.cpp
@@ -0,0 +1,52 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <boost/test/unit_test.hpp>
+
+using uhd::rfnoc::x400::rfdc_control;
+
+struct x400_rfdc_fixture
+{
+ static constexpr size_t RFDC_MEM_SIZE = 1;
+
+ x400_rfdc_fixture()
+ : mem(1 << rfdc_control::regmap::NCO_RESET_DONE_MSB, RFDC_MEM_SIZE)
+ , rfdcc(
+ uhd::memmap32_iface_timed{
+ [&](const uint32_t addr, const uint32_t data, const uhd::time_spec_t&) {
+ mem[addr] = data;
+ },
+ [&](const uint32_t addr) { return mem[addr]; }},
+ "TEST::RFDC")
+ {
+ // nop
+ }
+
+ std::vector<uint32_t> mem;
+ rfdc_control rfdcc;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(test_nco_reset, x400_rfdc_fixture)
+{
+ rfdcc.reset_ncos({rfdc_control::rfdc_type::RX0}, uhd::time_spec_t::ASAP);
+ BOOST_CHECK(mem[rfdc_control::regmap::NCO_RESET] & 0x1);
+ // Fake self-clearing bit
+ mem[rfdc_control::regmap::NCO_RESET] = 1 << rfdc_control::regmap::NCO_RESET_DONE_MSB;
+ // This should print a warning:
+ rfdcc.reset_ncos({}, uhd::time_spec_t::ASAP);
+ BOOST_CHECK_EQUAL(mem[rfdc_control::regmap::NCO_RESET]
+ & (1 << rfdc_control::regmap::NCO_RESET_START_MSB),
+ 0);
+ BOOST_CHECK(rfdcc.get_nco_reset_done());
+}
+
+BOOST_FIXTURE_TEST_CASE(test_nco_freq, x400_rfdc_fixture)
+{
+ // TODO: Add checks when implemented
+ rfdcc.set_nco_freq(rfdc_control::rfdc_type::RX0, 1e9);
+}
diff --git a/host/tests/zbx_cpld_test.cpp b/host/tests/zbx_cpld_test.cpp
new file mode 100644
index 000000000..b125e8435
--- /dev/null
+++ b/host/tests/zbx_cpld_test.cpp
@@ -0,0 +1,135 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/types/time_spec.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::usrp::zbx;
+
+struct mock_reg_iface_type
+{
+ uint32_t last_addr = 0;
+ zbx_cpld_ctrl::chan_t last_chan;
+ std::map<uint32_t, uint32_t> memory;
+ uhd::time_spec_t sleep_counter = uhd::time_spec_t(0.0);
+};
+
+
+struct zbx_cpld_fixture
+{
+ zbx_cpld_fixture()
+ : cpld(
+ [&](const uint32_t addr,
+ const uint32_t data,
+ const zbx_cpld_ctrl::chan_t chan) {
+ std::cout << "[MOCKREGS] poke32(" << addr << ", " << data << ")"
+ << std::endl;
+ mock_reg_iface.last_addr = addr;
+ mock_reg_iface.last_chan = chan;
+ mock_reg_iface.memory[addr] = data;
+ },
+ [&](const uint32_t addr) -> uint32_t {
+ std::cout << "[MOCKREGS] peek32(" << addr << ") => "
+ << mock_reg_iface.memory.at(addr) << std::endl;
+ return mock_reg_iface.memory.at(addr);
+ },
+ [&](const uhd::time_spec_t& time) { mock_reg_iface.sleep_counter += time; },
+ "TEST::CPLD")
+ {
+ // nop
+ }
+
+ mock_reg_iface_type mock_reg_iface;
+ zbx_cpld_ctrl cpld;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(zbx_cpld_ctrl_test, zbx_cpld_fixture)
+{
+ cpld.set_scratch(23);
+ BOOST_CHECK_EQUAL(cpld.get_scratch(), 23);
+
+ cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1});
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 1);
+ mock_reg_iface.memory[0x1024] = 0;
+ // Make sure there are no caching issues:
+ cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1});
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 1);
+ // Now all:
+ cpld.pulse_lo_sync(0,
+ {zbx_lo_t::TX0_LO1,
+ zbx_lo_t::TX0_LO2,
+ zbx_lo_t::TX1_LO1,
+ zbx_lo_t::TX1_LO2,
+ zbx_lo_t::RX0_LO1,
+ zbx_lo_t::RX0_LO2,
+ zbx_lo_t::RX1_LO1,
+ zbx_lo_t::RX1_LO2});
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 0xFF);
+ mock_reg_iface.memory[0x1024] = 0;
+ cpld.set_lo_sync_bypass(true);
+ BOOST_CHECK_THROW(cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1}), uhd::runtime_error);
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 0x100);
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_amp_test, zbx_cpld_fixture)
+{
+ cpld.set_tx_antenna_switches(
+ 0, 0, uhd::usrp::zbx::DEFAULT_TX_ANTENNA, tx_amp::HIGHBAND);
+ BOOST_CHECK(tx_amp::HIGHBAND == cpld.get_tx_amp_settings(0, 0, false));
+ mock_reg_iface.memory[0x2000] = 0;
+ BOOST_CHECK(tx_amp::HIGHBAND == cpld.get_tx_amp_settings(0, 0, false));
+ BOOST_CHECK(tx_amp::HIGHBAND != cpld.get_tx_amp_settings(0, 0, true));
+ BOOST_CHECK(tx_amp::BYPASS == cpld.get_tx_amp_settings(0, 0, false));
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_get_set_dsa_test, zbx_cpld_fixture)
+{
+ // We only test the first table index
+ constexpr size_t dsa_table_index = 0;
+ for (const size_t chan : {0, 1}) {
+ const uint32_t tx_dsa_table_addr = 0x3000 + chan * 0x400;
+ const uint32_t rx_dsa_table_addr = 0x3800 + chan * 0x400;
+
+ auto& tx_dsa_reg = mock_reg_iface.memory[tx_dsa_table_addr];
+ auto& rx_dsa_reg = mock_reg_iface.memory[rx_dsa_table_addr];
+
+ // We'll skip DSA3A/B, because they work just like the rest and would
+ // make this test much longer and less readable without adding much test
+ // coverage.
+ for (const auto dsa :
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_ctrl::dsa_type::DSA2}) {
+ const size_t tx_shift = (dsa == zbx_cpld_ctrl::dsa_type::DSA1) ? 0 : 8;
+ const size_t rx_shift = (dsa == zbx_cpld_ctrl::dsa_type::DSA1) ? 0 : 4;
+ // 0xB and 0xC are just random attenuation values. They are valid
+ // for both TX and RX.
+ cpld.set_tx_dsa(chan, dsa_table_index, dsa, 0xB);
+ BOOST_CHECK_EQUAL((tx_dsa_reg >> tx_shift) & 0x1F, 0xB);
+ tx_dsa_reg = 0x0C0C;
+ BOOST_CHECK_EQUAL(0xB, cpld.get_tx_dsa(chan, dsa_table_index, dsa, false));
+ BOOST_CHECK_EQUAL(0xC, cpld.get_tx_dsa(chan, dsa_table_index, dsa, true));
+
+ cpld.set_rx_dsa(chan, 0, dsa, 0xB);
+ BOOST_CHECK_EQUAL((rx_dsa_reg >> rx_shift) & 0xF, 0xB);
+ rx_dsa_reg = 0xCC;
+ BOOST_CHECK_EQUAL(0xB, cpld.get_rx_dsa(chan, dsa_table_index, dsa, false));
+ BOOST_CHECK_EQUAL(0xC, cpld.get_rx_dsa(chan, dsa_table_index, dsa, true));
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_set_from_table_test, zbx_cpld_fixture)
+{
+ constexpr uint32_t tx_sel_addr = 0x4000;
+ constexpr size_t chan = 0;
+ constexpr uint8_t idx = 2;
+
+ auto& tx_table_select = mock_reg_iface.memory[tx_sel_addr + idx * 4];
+ cpld.set_tx_gain_switches(chan, idx, 23);
+ BOOST_REQUIRE_EQUAL(tx_table_select, 23);
+}
diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt
index ab7d645f4..1ac0b1c99 100644
--- a/host/utils/CMakeLists.txt
+++ b/host/utils/CMakeLists.txt
@@ -16,6 +16,7 @@ set(util_runtime_sources
uhd_cal_rx_iq_balance.cpp
uhd_cal_tx_dc_offset.cpp
uhd_cal_tx_iq_balance.cpp
+ uhd_adc_self_cal.cpp
)
#for each source: build an executable and install
diff --git a/host/utils/rfnoc_image_builder.py b/host/utils/rfnoc_image_builder.py
index 285dfe3e9..07419cb90 100755
--- a/host/utils/rfnoc_image_builder.py
+++ b/host/utils/rfnoc_image_builder.py
@@ -46,7 +46,7 @@ def setup_parser():
help="Path to grc file to generate config from")
parser.add_argument(
"-F", "--fpga-dir",
- help="Path to directory for the FPGA source tree."
+ help="Path to directory for the FPGA source tree. "
"Defaults to the FPGA source tree of the current repo.",
required=False,
default=None)
@@ -79,7 +79,7 @@ def setup_parser():
action="store_true")
parser.add_argument(
"-d", "--device",
- help="Device to be programmed [x300, x310, e310, e320, n300, n310, n320]."
+ help="Device to be programmed [x300, x310, e310, e320, n300, n310, n320, x410]. "
"Needs to be specified either here, or in the configuration file.",
default=None)
parser.add_argument(
diff --git a/host/utils/uhd_adc_self_cal.cpp b/host/utils/uhd_adc_self_cal.cpp
new file mode 100644
index 000000000..39dab3e15
--- /dev/null
+++ b/host/utils/uhd_adc_self_cal.cpp
@@ -0,0 +1,76 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/features/adc_self_calibration_iface.hpp>
+#include <uhd/rfnoc/radio_control.hpp>
+#include <uhd/rfnoc_graph.hpp>
+#include <uhd/utils/safe_main.hpp>
+#include <uhd/version.hpp>
+#include <boost/program_options.hpp>
+#include <iostream>
+#include <sstream>
+
+namespace po = boost::program_options;
+using namespace uhd;
+
+/****************************************************************************
+ * main
+ ***************************************************************************/
+int UHD_SAFE_MAIN(int argc, char* argv[])
+{
+ po::options_description desc("Allowed options");
+ // clang-format off
+ desc.add_options()
+ ("help", "help message")
+ ("version", "print the version string and exit")
+ ("args", po::value<std::string>()->default_value(""), "device address args")
+ ;
+ // clang-format on
+
+ po::variables_map vm;
+ po::store(po::parse_command_line(argc, argv, desc), vm);
+ po::notify(vm);
+
+ // print the help message
+ if (vm.count("help")) {
+ std::cout << "UHD ADC self calibration " << desc << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ if (vm.count("version")) {
+ std::cout << uhd::get_version_string() << std::endl;
+ return EXIT_SUCCESS;
+ }
+
+ rfnoc::rfnoc_graph::sptr graph =
+ rfnoc::rfnoc_graph::make(vm["args"].as<std::string>());
+
+ size_t num_calibrations = 0;
+ for (auto radio_id : graph->find_blocks("Radio")) {
+ auto radio_blk = graph->get_block<uhd::rfnoc::radio_control>(radio_id);
+ if (radio_blk->has_feature<uhd::features::adc_self_calibration_iface>()) {
+ auto& feature =
+ radio_blk->get_feature<uhd::features::adc_self_calibration_iface>();
+
+ // Run it on all channels
+ const size_t num_channels = radio_blk->get_num_output_ports();
+ for (size_t i = 0; i < num_channels; i++) {
+ std::cout << "Calibrating on channel " << i << " of " << radio_id << "..."
+ << std::endl;
+ feature.run(i);
+ std::cout << "Finished!" << std::endl;
+ num_calibrations++;
+ }
+ }
+ }
+ if (num_calibrations > 0) {
+ std::cout << "Calibrated " << num_calibrations << " channels" << std::endl;
+ } else {
+ std::cerr << "WARNING: Did not find any channels to calibrate!" << std::endl;
+ }
+
+ return EXIT_SUCCESS;
+}