diff options
author | Lars Amsel <lars.amsel@ni.com> | 2021-06-04 08:27:50 +0200 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 12:01:53 -0500 |
commit | 2a575bf9b5a4942f60e979161764b9e942699e1e (patch) | |
tree | 2f0535625c30025559ebd7494a4b9e7122550a73 /host | |
parent | e17916220cc955fa219ae37f607626ba88c4afe3 (diff) | |
download | uhd-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')
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 Binary files differnew file mode 100644 index 000000000..215527d22 --- /dev/null +++ b/host/docs/res/x410.png diff --git a/host/docs/res/x410_back_panel.png b/host/docs/res/x410_back_panel.png Binary files differnew file mode 100644 index 000000000..782305c04 --- /dev/null +++ b/host/docs/res/x410_back_panel.png diff --git a/host/docs/res/x410_front_panel.png b/host/docs/res/x410_front_panel.png Binary files differnew file mode 100644 index 000000000..9df5ce9b4 --- /dev/null +++ b/host/docs/res/x410_front_panel.png 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 Binary files differnew file mode 100644 index 000000000..e89563b3f --- /dev/null +++ b/host/docs/res/x4xx_rearpanel_status_leds.png 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 Binary files differnew file mode 100644 index 000000000..cc25f6ded --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal 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 Binary files differnew file mode 100644 index 000000000..6da01d48b --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal 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 Binary files differnew file mode 100644 index 000000000..d56c740c2 --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal 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 Binary files differnew file mode 100644 index 000000000..99f4eec4d --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal 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 Binary files differnew file mode 100644 index 000000000..349557bae --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal 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 Binary files differnew file mode 100644 index 000000000..4ecf1ba00 --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal diff --git a/host/lib/rc/cal/zbx_dsa_rx.cal b/host/lib/rc/cal/zbx_dsa_rx.cal Binary files differnew file mode 100644 index 000000000..197a85b70 --- /dev/null +++ b/host/lib/rc/cal/zbx_dsa_rx.cal 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 Binary files differnew file mode 100644 index 000000000..16c17ac2c --- /dev/null +++ b/host/lib/rc/cal/zbx_dsa_tx.cal 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; +} |