diff --git a/+adi/+AD9081/Base.m b/+adi/+AD9081/Base.m index 2e7295fe..909b35c2 100644 --- a/+adi/+AD9081/Base.m +++ b/+adi/+AD9081/Base.m @@ -129,23 +129,29 @@ idx = idx(idx2); filteredMap{end+1} = map{idx}; end - % Count unique items with XDUCX + % Count unique coarse (CDUC/CDDC) and fine (FDUC/FDDC) data + % converters. filteredMap holds one entry per unique fine + % path, so several fine paths can share a single coarse + % converter (e.g. FDUC1/2/3 all feeding CDUC0). Counting + % raw occurrences therefore overestimates the coarse count; + % track which converter indices are present instead. if isTx ss = 'U'; else ss = 'D'; end - numFDUCX = 0; numCDUCX = 0; + finePresent = false(1,8); coarsePresent = false(1,8); for DC = 0:7 for k=1:length(filteredMap) if contains(filteredMap{k},sprintf('FD%sC%d',ss,DC)) - numFDUCX = numFDUCX + 1; + finePresent(DC+1) = true; end if contains(filteredMap{k},sprintf('CD%sC%d',ss,DC)) - numCDUCX = numCDUCX + 1; + coarsePresent(DC+1) = true; end end end + numFDUCX = sum(finePresent); numCDUCX = sum(coarsePresent); num_coarse = numCDUCX; num_fine = numFDUCX; num_data = numFDUCX*2; if ~doNotCloseConnection obj.releaseImpl(); @@ -171,10 +177,8 @@ function CheckAndUpdateHW(obj, value, name, attr, phy, output) end if contains(attr,'channel_') N = obj.num_fine_attr_channels; - stride = 1; elseif contains(attr,'main_') N = obj.num_coarse_attr_channels; - stride = obj.num_fine_attr_channels/N; else error('Unknown attribute name'); end @@ -185,9 +189,9 @@ function CheckAndUpdateHW(obj, value, name, attr, phy, output) assert(c1 && c2,... sprintf('%s expected to be at most size [1x%d]',name,N)); if obj.ConnectedToDevice + ids = obj.getAttributeChannelIDs(attr, phy, output, N); for k=1:N - id = sprintf('voltage%d_i',(k-1)*stride); - obj.setAttributeLongLong(id,attr,value(k),output, tol, phy); + obj.setAttributeLongLong(ids{k},attr,value(k),output, tol, phy); end end end @@ -198,10 +202,8 @@ function CheckAndUpdateHWFloat(obj, value, name, attr, phy, output) end if contains(attr,'channel_') N = obj.num_fine_attr_channels; - stride = 1; elseif contains(attr,'main_') N = obj.num_coarse_attr_channels; - stride = obj.num_fine_attr_channels/N; else error('Unknown attribute name'); end @@ -212,9 +214,9 @@ function CheckAndUpdateHWFloat(obj, value, name, attr, phy, output) assert(c1 && c2,... sprintf('%s expected to be at most size [1x%d]',name,N)); if obj.ConnectedToDevice + ids = obj.getAttributeChannelIDs(attr, phy, output, N); for k=1:N - id = sprintf('voltage%d_i',(k-1)*stride); - obj.setAttributeDouble(id,attr,value(k),output, tol, phy); + obj.setAttributeDouble(ids{k},attr,value(k),output, tol, phy); end end end @@ -225,10 +227,8 @@ function CheckAndUpdateHWBool(obj, value, name, attr, phy, output) end if contains(attr,'channel_') || strcmpi(attr,'en') N = obj.num_fine_attr_channels; - stride = 1; elseif contains(attr,'main_') N = obj.num_coarse_attr_channels; - stride = obj.num_fine_attr_channels/N; else error('Unknown attribute name'); end @@ -238,13 +238,48 @@ function CheckAndUpdateHWBool(obj, value, name, attr, phy, output) assert(c1 && c2,... sprintf('%s expected to be at most size [1x%d]',name,N)); if obj.ConnectedToDevice + ids = obj.getAttributeChannelIDs(attr, phy, output, N); for k=1:N - id = sprintf('voltage%d_i',(k-1)*stride); - obj.setAttributeBool(id,attr,value(k),output, phy); + obj.setAttributeBool(ids{k},attr,value(k),output, phy); end end end + function ids = getAttributeChannelIDs(obj, attr, phy, output, N) + % Resolve the physical voltage channel IDs used to write an + % attribute. Fine (channel_*) attributes map directly to + % voltage0_i..voltage(N-1)_i. Coarse (main_*) attributes are + % only exposed on the subset of physical channels that own a + % coarse data converter; on some HDL datapaths several fine + % channels share a coarse converter, so the coarse-capable + % channels are not contiguous. Probe readability to select + % them robustly instead of assuming a fixed stride. + if contains(attr,'main_') + ids = obj.getReadableAttributeChannelIDs(attr, phy, output, N); + else + ids = obj.getFineAttributeChannelIDs(N); + end + end + + function ids = getFineAttributeChannelIDs(~, N) + ids = cell(1, N); + for k = 1:N + ids{k} = sprintf('voltage%d_i', k-1); + end + end + + function ids = getReadableAttributeChannelIDs(obj, attr, phy, output, N) + candidateIDs = obj.getFineAttributeChannelIDs(obj.max_num_fine_attr_channels); + readLengths = -ones(1, numel(candidateIDs)); + for k = 1:numel(candidateIDs) + chanPtr = iio_device_find_channel(obj, phy, candidateIDs{k}, output); + if cPtrCheck(obj, chanPtr) == 0 + [readLengths(k), ~] = iio_channel_attr_read(obj, chanPtr, attr, 1024); + end + end + ids = obj.selectReadableAttributeChannelIDs(candidateIDs, readLengths, N); + end + function attr = iio_channel_is_output(obj, chanPtr) % iio_channel_is_output(const struct iio_channel *chn) % @@ -256,6 +291,13 @@ function CheckAndUpdateHWBool(obj, value, name, attr, phy, output) end + methods (Static, Hidden) + function ids = selectReadableAttributeChannelIDs(candidateIDs, readLengths, N) + ids = adi.AD9081.selectReadableAttributeChannelIDs( ... + candidateIDs, readLengths, N); + end + end + %% External Dependency Methods methods (Hidden, Static) diff --git a/+adi/+AD9081/Tx.m b/+adi/+AD9081/Tx.m index 5b9e4716..5146e1ab 100644 --- a/+adi/+AD9081/Tx.m +++ b/+adi/+AD9081/Tx.m @@ -145,13 +145,31 @@ function set.DDROffloadEnable(obj, value) obj.DDROffloadEnable = value; if obj.ConnectedToDevice - obj.setDebugAttributeBool('pl_ddr_fifo_enable',value, true, obj.iioDev); + obj.setDDROffloadEnableIfSupported(value); end end end %% API Functions methods (Hidden, Access = protected) + + function setDDROffloadEnableIfSupported(obj, value) + % The pl_ddr_fifo_enable debug attribute controls the PL DDR + % transmit FIFO offload. It is only present on HDL datapaths + % that instantiate the DDR offload block; newer AD9081 + % reference designs (e.g. hdl_2026_r1 m8_l4) omit it. Probe + % for the attribute and skip the write when it is absent so + % streaming still works on both datapath variants. + if obj.hasDebugAttribute('pl_ddr_fifo_enable', obj.iioDev) + obj.setDebugAttributeBool('pl_ddr_fifo_enable', value, ... + true, obj.iioDev); + end + end + + function tf = hasDebugAttribute(obj, attr, dev) + [nBytes, ~] = obj.iio_device_debug_attr_read(dev, attr, 1024); + tf = nBytes >= 0; + end function setupImpl(obj, data) if strcmp(obj.DataSource,'DMA') @@ -206,8 +224,7 @@ function setupInit(obj) if strcmp(obj.DataSource,'DDS') obj.DDSUpdate(); else - obj.setDebugAttributeBool('pl_ddr_fifo_enable',... - obj.DDROffloadEnable, false, obj.iioDev); + obj.setDDROffloadEnableIfSupported(obj.DDROffloadEnable); end end diff --git a/+adi/+AD9081/selectReadableAttributeChannelIDs.m b/+adi/+AD9081/selectReadableAttributeChannelIDs.m new file mode 100644 index 00000000..2c8d265b --- /dev/null +++ b/+adi/+AD9081/selectReadableAttributeChannelIDs.m @@ -0,0 +1,21 @@ +function ids = selectReadableAttributeChannelIDs(candidateIDs, readLengths, N) +%selectReadableAttributeChannelIDs Choose coarse-capable AD9081 channel IDs +% IDS = selectReadableAttributeChannelIDs(CANDIDATEIDS, READLENGTHS, N) +% returns the first N channel IDs from CANDIDATEIDS whose corresponding +% READLENGTHS entry is positive (i.e. the coarse attribute read back a +% value rather than returning an error). On the AD9081 hdl_2026_r1 +% datapath, main_* (coarse) attributes are exposed on only a subset of +% the physical voltage channels, and those channels are not contiguous, +% so channel selection must be driven by attribute readability rather +% than a fixed stride. +% +% This is a plain package function (no libiio superclass dependency) so +% the selection logic can be unit tested without the libiio hardware +% support package installed. + assert(numel(candidateIDs) == numel(readLengths), ... + 'Candidate IDs and read lengths must have equal size'); + ids = candidateIDs(readLengths > 0); + assert(numel(ids) >= N, ... + 'Not enough channels expose the requested AD9081 attribute'); + ids = ids(1:N); +end diff --git a/+adi/+common b/+adi/+common index 7eb8b46e..c7dc4f63 160000 --- a/+adi/+common +++ b/+adi/+common @@ -1 +1 @@ -Subproject commit 7eb8b46e51571e7c13864e42d36fc4ad3cafa09d +Subproject commit c7dc4f633601f121ce315991c2baf23ceae7c418 diff --git a/+adi/+sim/+common/DelayLine.m b/+adi/+sim/+common/DelayLine.m new file mode 100644 index 00000000..1f712aa1 --- /dev/null +++ b/+adi/+sim/+common/DelayLine.m @@ -0,0 +1,36 @@ +classdef DelayLine < matlab.System + %DelayLine Frame-based streaming delay independent of dsp.DelayLine. + properties (Nontunable) + Length (1,1) {mustBeInteger,mustBeNonnegative} = 1 + end + + properties (DiscreteState, Hidden) + State + end + + methods + function obj = DelayLine(varargin) + setProperties(obj, nargin, varargin{:}); + end + end + + methods (Access = protected) + function setupImpl(obj, input) + obj.State = zeros(obj.Length, size(input, 2), 'like', input); + end + + function output = stepImpl(obj, input) + if obj.Length == 0 + output = input; + return; + end + buffered = [obj.State; input]; + output = buffered(1:size(input, 1), :); + obj.State = buffered(end-obj.Length+1:end, :); + end + + function resetImpl(obj) + obj.State(:) = 0; + end + end +end diff --git a/+adi/+sim/+common/PFilter.m b/+adi/+sim/+common/PFilter.m index 49a1acf5..52e4d698 100644 --- a/+adi/+sim/+common/PFilter.m +++ b/+adi/+sim/+common/PFilter.m @@ -174,7 +174,7 @@ function setupImpl(obj) end end - obj.DelayLine = dsp.DelayLine('Length',tapGroups*4); + obj.DelayLine = adi.sim.common.DelayLine('Length',tapGroups*4); end function [z1,z2] = stepImpl(obj,u1,u2) @@ -268,7 +268,10 @@ function setupImpl(obj) end function resetImpl(obj) - % Initialize / reset discrete-state properties + for filter = 1:numel(obj.Filters) + reset(obj.Filters{filter}); + end + reset(obj.DelayLine); end end end diff --git a/+adi/Version.m b/+adi/Version.m index e84668c2..c3c8decc 100644 --- a/+adi/Version.m +++ b/+adi/Version.m @@ -2,10 +2,10 @@ %Version % BSP Version information properties(Constant) - HDL = 'hdl_2022_r2'; - Vivado = '2022.2'; - MATLAB = 'R2023b'; - Release = '23.2.1'; + HDL = 'hdl_2026_r1'; + Vivado = '2025.1'; + MATLAB = 'R2025b'; + Release = '25.2.1'; AppName = 'Analog Devices, Inc. High-Speed Converter Toolbox'; ToolboxName = 'HighSpeedConverterToolbox'; ToolboxNameShort = 'hsx'; diff --git a/.github/doc/scripts/get_styles.sh b/.github/doc/scripts/get_styles.sh index 7b9b6127..799e51b9 100644 --- a/.github/doc/scripts/get_styles.sh +++ b/.github/doc/scripts/get_styles.sh @@ -1,11 +1,7 @@ get_style () { echo "Installing $2 from $1 ..." - curl -s https://api.github.com/repos/$1/$2/releases/latest \ - | grep "browser_download_url.*zip" \ - | cut -d : -f 2,3 \ - | tr -d \" \ - | wget -qi - - unzip $2.zip -d styles && rm -rf $2.zip + wget -q "https://github.com/vale-cli/$2/releases/latest/download/$2.zip" + unzip -q "$2.zip" -d styles && rm -f "$2.zip" } #styles=( Microsoft ) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47e993f5..3240fccf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,13 +8,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - - name: Set up Python 3.10 - uses: actions/setup-python@v2 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' + - name: Test release metadata helpers + run: python -m unittest CI.scripts.test_get_required_vivado_version - name: Organize Toolbox Dependencies run: | make -C ./CI/scripts build @@ -22,11 +24,19 @@ jobs: make -C CI/gen_doc doc - name: Set up MATLAB - uses: matlab-actions/setup-matlab@v1 + uses: matlab-actions/setup-matlab@v2 with: - release: R2023b + release: R2025b + products: DSP_System_Toolbox Fixed-Point_Designer + - name: Test MATLAB R2025b compatibility + uses: matlab-actions/run-command@v2 + with: + command: >- + addpath(pwd); addpath(fullfile(pwd,'test')); + results = runtests({'test/ReleaseCompatibilityTests.m', + 'test/R2025bCompatibilityTests.m'}); assertSuccess(results) - name: Compile Toolbox - uses: matlab-actions/run-command@v1 + uses: matlab-actions/run-command@v2 with: command: cd('CI/scripts');genTlbx(1);exit() diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index afee5be4..374b03b7 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -7,11 +7,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 - name: Install dependencies run: | @@ -23,7 +23,7 @@ jobs: - name: Publish master doc if: github.ref == 'refs/heads/master' - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./doc diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml index a944a964..000b1fda 100644 --- a/.github/workflows/vale.yml +++ b/.github/workflows/vale.yml @@ -4,10 +4,10 @@ jobs: Vale: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.12' architecture: x64 - name: Install dependencies run: | diff --git a/.gitignore b/.gitignore index 02778418..093698fd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,20 @@ **/slprj/** AD9361_Filter_Wizard/*TestFiltWiz*.m AD9361_Filter_Wizard/.previous_ip_addr + +__pycache__/ +*.py[cod] +CI/ports.json +hdl/vendor/AnalogDevices/+AnalogDevices/ports.json +hdl/vendor/AnalogDevices/vivado/ + +# MATLAB package and test/build outputs +/*.mltbx +/bsp.prj +/run-ad9081-hw.sh +/test/*.mat +/test/tp*/ +/test/logs/ +/test/*.log +/test/*.BIN +/test/*.rpt diff --git a/CI/__init__.py b/CI/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/CI/gen_doc/docs/_pages/targeting.md b/CI/gen_doc/docs/_pages/targeting.md index 8e3834ab..736c3492 100644 --- a/CI/gen_doc/docs/_pages/targeting.md +++ b/CI/gen_doc/docs/_pages/targeting.md @@ -11,18 +11,18 @@ High-Speed Converter Toolbox supports the IP Core generation flow from MathWorks ## Getting Started -To perform targeting will require FPGA vendor tools for the FPGA system. For Xilinx this will be Vivado and the toolbox will require specific versions for each release. For the current release this is Vivado 2022.2. Using other versions are not supported. To build the necessary BOOT.BIN files will require the Xilinx SDK as well. +To perform targeting will require FPGA vendor tools for the FPGA system. For Xilinx this will be Vivado and the toolbox will require specific versions for each release. For the current release this is Vivado 2025.1. Using other versions are not supported. To build the necessary BOOT.BIN files will require the Xilinx SDK as well. Once you have the installed the necessary 3rd party tools MATLAB needs to be told where they are installed by use of the [hdlsetuptoolpath](https://www.mathworks.com/help/hdlcoder/ref/hdlsetuptoolpath.html) command. For Windows the following MATLAB command can be used: ```matlab -hdlsetuptoolpath('ToolName', 'Xilinx Vivado', 'ToolPath', 'C:\Xilinx\Vivado\2022.2\bin\vivado.bat'); +hdlsetuptoolpath('ToolName', 'Xilinx Vivado', 'ToolPath', 'C:\Xilinx\Vivado\2025.1\bin\vivado.bat'); ``` or Linux: ```matlab -hdlsetuptoolpath('ToolName', 'Xilinx Vivado', 'ToolPath', '/opt/Xilinx/Vivado/2022.2/bin/vivado'); +hdlsetuptoolpath('ToolName', 'Xilinx Vivado', 'ToolPath', '/opt/Xilinx/Vivado/2025.1/bin/vivado'); ``` Please change the tool path if it is different on your system. diff --git a/CI/gen_doc/requirements_doc.txt b/CI/gen_doc/requirements_doc.txt index 060c0f8f..b3cc5fe0 100644 --- a/CI/gen_doc/requirements_doc.txt +++ b/CI/gen_doc/requirements_doc.txt @@ -4,3 +4,5 @@ mkdocs-material mkdocs-awesome-pages-plugin mkdocs-mermaid2-plugin mkdocs-plugin-inline-svg +# pymdown-extensions 10.4 passes filename=None; Pygments 2.20 rejects it. +Pygments<2.20 diff --git a/CI/scripts/Makefile b/CI/scripts/Makefile index 6144061b..d3e31e18 100644 --- a/CI/scripts/Makefile +++ b/CI/scripts/Makefile @@ -8,11 +8,11 @@ SHELL := /bin/bash MLFLAGS := -nodisplay -nodesktop -nosplash ifeq ($(MLRELEASE),) -MLRELEASE := R2023b +MLRELEASE := R2025b endif ifeq ($(HDLBRANCH),) -HDLBRANCH := hdl_2022_r2 +HDLBRANCH := hdl_2026_r1 endif ifeq ($(OS),Windows_NT) diff --git a/CI/scripts/__init__.py b/CI/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/CI/scripts/bsp.tmpl b/CI/scripts/bsp.tmpl index 348fd043..3465e8e1 100644 --- a/CI/scripts/bsp.tmpl +++ b/CI/scripts/bsp.tmpl @@ -48,6 +48,9 @@ slprj/* test/* itests/* mltbx/* +*.mltbx +bsp.prj +run-ad9081-hw.sh *~ .Xil/* true diff --git a/CI/scripts/build_bsp.sh b/CI/scripts/build_bsp.sh old mode 100644 new mode 100755 index 3e25a4a1..c9e7c630 --- a/CI/scripts/build_bsp.sh +++ b/CI/scripts/build_bsp.sh @@ -1,102 +1,140 @@ #!/bin/bash -set -x +set -euo pipefail -if [ -z "${HDLBRANCH}" ]; then -HDLBRANCH='hdl_2022_r2' -fi +HDLBRANCH="${HDLBRANCH:-hdl_2026_r1}" -# Script is designed to run from specific location -scriptdir=`dirname "$BASH_SOURCE"` -cd $scriptdir -cd .. +# Script is designed to run from CI/scripts. +scriptdir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +cd "$scriptdir/.." -# Get HDL -if [ -d "hdl" ]; then - rm -rf "hdl" -fi -for i in {1..5} -do - if git clone --single-branch -b $HDLBRANCH https://github.com/analogdevicesinc/hdl.git - then - break - fi - if [ -d "hdl" ]; then - break - fi +# Get HDL into a temporary directory so a failed clone cannot be mistaken for +# a usable checkout. +rm -rf hdl hdl.clone +for _ in {1..5}; do + if git clone --depth 1 --single-branch -b "$HDLBRANCH" \ + https://github.com/analogdevicesinc/hdl.git hdl.clone; then + mv hdl.clone hdl + break + fi + rm -rf hdl.clone + sleep 2 done -if [ ! -d "hdl" ]; then - echo "HDL clone failed" - exit 1 +if [ ! -d hdl/.git ]; then + echo "HDL clone failed for branch $HDLBRANCH" >&2 + exit 1 fi -# Get required vivado version needed for HDL -if [ -f "hdl/library/scripts/adi_ip.tcl" ]; then - TARGET="hdl/library/scripts/adi_ip.tcl" -else - TARGET="hdl/library/scripts/adi_ip_xilinx.tcl" +# HDL moved this declaration from library/scripts/adi_ip*.tcl to +# scripts/adi_env.tcl. Keep compatibility with older branches while preferring +# the release-level source of truth used by hdl_2026_r1. +for candidate in \ + hdl/scripts/adi_env.tcl \ + hdl/library/scripts/adi_ip.tcl \ + hdl/library/scripts/adi_ip_xilinx.tcl; do + if [ -f "$candidate" ]; then + TARGET="$candidate" + if VER=$(python3 scripts/get_required_vivado_version.py "$TARGET"); then + break + fi + fi +done +if [ -z "${VER:-}" ]; then + echo "Unable to determine required Vivado version from $HDLBRANCH" >&2 + exit 1 fi -VER=$(awk '/set required_vivado_version/ {print $3}' $TARGET | sed 's/"//g') -echo "Required Vivado version ${VER}" -VIVADOFULL=${VER} -if [ ${#VER} = 8 ] -then -VER=${VER:0:6} +echo "Required Vivado version ${VER} (from ${TARGET})" + +# Rename .prj files since MATLAB ignores them during packaging. +while IFS= read -r referring_file; do + python3 - "$referring_file" <<'PY' +from pathlib import Path +import sys +p = Path(sys.argv[1]) +s = p.read_text() +p.write_text(s.replace('.prj', '.mk')) +PY +done < <(grep -rl --exclude=Makefile --exclude-dir=.git --fixed-strings '.prj' hdl/projects/common || true) +while IFS= read -r -d '' project_file; do + mv "$project_file" "${project_file%.prj}.mk" +done < <(find hdl/projects/common -name '*.prj' -print0) + +# Remove git metadata and move the reviewed release snapshot into the BSP. +rm -rf hdl/.git* +TARGET_DIR="../hdl/vendor/AnalogDevices/vivado" +rm -rf "$TARGET_DIR" +if [ -f hdl/projects/pluto/system_constr.xdc ]; then + python3 - <<'PY' +from pathlib import Path +p = Path('hdl/projects/pluto/system_constr.xdc') +p.write_text(p.read_text().replace('16.27', '30')) +PY fi -VIVADO=${VER} +mv hdl "$TARGET_DIR" + +# Post-process ports.json. +cp scripts/ports.json . +python3 scripts/read_ports_json.py +cp ports.json ../hdl/vendor/AnalogDevices/+AnalogDevices/ -# Setup -source /opt/Xilinx/Vivado/$VIVADO/settings64.sh +# Make every generated HDL Coder plugin advertise the Vivado release required +# by the selected HDL branch. Do not key this rewrite to historical versions: +# individual projects can lag the branch default by several releases. +python3 - "../hdl/vendor/AnalogDevices" "$VER" <<'PY' +from pathlib import Path +import re +import sys -# Pre-build IP library -# cd hdl/library -# make -# cd ../.. +root = Path(sys.argv[1]) +version = sys.argv[2] +pattern = re.compile(r"(?m)^(\s*[^%\r\n]*\.SupportedToolVersion\s*=\s*\{\s*')[^']+('\s*\}\s*;?\s*)$") +replacements = 0 +for path in root.rglob('*.m'): + text = path.read_text() + updated, count = pattern.subn(rf"\g<1>{version}\g<2>", text) + if count: + path.write_text(updated) + replacements += count +if replacements == 0: + raise SystemExit('No SupportedToolVersion declarations found in generated BSP') +print(f'Updated {replacements} SupportedToolVersion declarations to {version}') +PY -# Rename .prj files since MATLAB ignores then during packaging -FILES=$(grep -lrn hdl/projects/common -e '.prj' | grep -v Makefile | grep -v .git) -for f in $FILES -do - echo "Updating prj reference in: $f" - sed -i "s/\.prj/\.mk/g" "$f" -done -FILES=$(find hdl/projects/common -name "*.prj") -for f in $FILES -do - DEST="${f::-3}mk" - echo "Renaming: $f to $DEST" - mv "$f" "$DEST" +# Toolbox-specific HDL Coder integration scripts. +# +# NOTE: adi_project_xilinx.tcl is intentionally NOT overwritten. It ships with +# the HDL branch and, since hdl_2026_r1, defines procs the reference-design +# system_project.tcl files depend on (e.g. adi_xcvr_project, used by the +# transceiver-based designs like daq2/ad9434/ad9265/ad9783/ad9208). The older +# toolbox fork lacked that proc and broke "Create Project" for every non-AD9081 +# design. The HDL branch version already supports the MATLAB HDL Coder flow +# natively via the ADI_MATLAB env var (see system_project_rxtx.tcl), so we keep +# the branch copy and only layer the genuinely toolbox-only scripts on top. +for script in \ + matlab_processors.tcl system_project_rxtx.tcl \ + adi_build.tcl adi_build_win.tcl fsbl_build_zynq.tcl \ + fsbl_build_zynqmp.tcl pmufw_zynqmp.tcl fixmake.sh; do + cp "scripts/$script" "../hdl/vendor/AnalogDevices/vivado/projects/scripts/$script" done -# Remove git directory move to bsp folder -rm -fr hdl/.git* -TARGET="../hdl/vendor/AnalogDevices/vivado" -if [ -d "$TARGET" ]; then - rm -rf "$TARGET" +# Guard: the HDL branch must provide adi_xcvr_project. If a future branch drops +# or renames it, fail loudly here rather than deep inside a Vivado create-project +# run that only surfaces after ~15 minutes of IP packaging. +XCVR_TCL="../hdl/vendor/AnalogDevices/vivado/projects/scripts/adi_project_xilinx.tcl" +if ! grep -q 'proc adi_xcvr_project' "$XCVR_TCL"; then + echo "adi_project_xilinx.tcl is missing 'proc adi_xcvr_project' (HDL branch $HDLBRANCH)" >&2 + exit 1 fi -# Increase rx_clk period to fix timing failures for Pluto designs in R2021b -sed -i 's/16.27/30/' hdl/projects/pluto/system_constr.xdc -mv hdl $TARGET - -# Post-process ports.json -cp ./scripts/ports.json ./ -python3 ./scripts/read_ports_json.py -cp ports.json ../hdl/vendor/AnalogDevices/+AnalogDevices/ - -# Updates -cp scripts/matlab_processors.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/matlab_processors.tcl -cp scripts/adi_project_xilinx.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/adi_project_xilinx.tcl -cp scripts/system_project_rxtx.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/system_project_rxtx.tcl -cp scripts/adi_build.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/adi_build.tcl -cp scripts/adi_build_win.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/adi_build_win.tcl -# Copy fsbl files -cp scripts/fsbl_build_zynq.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/fsbl_build_zynq.tcl -cp scripts/fsbl_build_zynqmp.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/fsbl_build_zynqmp.tcl -cp scripts/pmufw_zynqmp.tcl ../hdl/vendor/AnalogDevices/vivado/projects/scripts/pmufw_zynqmp.tcl -cp scripts/fixmake.sh ../hdl/vendor/AnalogDevices/vivado/projects/scripts/fixmake.sh +# adi_xcvr_project shells out to build a standalone xcvr_wizard Vivado project. +# The toolbox runs the top-level design in HDL Coder in-memory mode (ADI_MATLAB), +# but that mode must NOT leak into the nested build or it skips create_project. +# Patch the sub-make to run with ADI_MATLAB/MATLAB unset. Idempotent. +python3 scripts/patch_xcvr_matlab_env.py "$XCVR_TCL" -# Copy boot files -mkdir ../hdl/vendor/AnalogDevices/vivado/projects/common/boot/ -cp -r scripts/boot/* ../hdl/vendor/AnalogDevices/vivado/projects/common/boot/ +mkdir -p ../hdl/vendor/AnalogDevices/vivado/projects/common/boot +cp -r scripts/boot/. ../hdl/vendor/AnalogDevices/vivado/projects/common/boot/ -echo 'puts "Skipping"' > ../hdl/vendor/AnalogDevices/vivado/library/axi_ad9361/axi_ad9361_delay.tcl +DELAY_TCL=../hdl/vendor/AnalogDevices/vivado/library/axi_ad9361/axi_ad9361_delay.tcl +if [ -f "$DELAY_TCL" ]; then + printf '%s\n' 'puts "Skipping"' > "$DELAY_TCL" +fi diff --git a/CI/scripts/get_required_vivado_version.py b/CI/scripts/get_required_vivado_version.py new file mode 100755 index 00000000..9cda7d1c --- /dev/null +++ b/CI/scripts/get_required_vivado_version.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Read required_vivado_version from an ADI HDL Tcl source file.""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +_PATTERN = re.compile( + r'^\s*set\s+required_vivado_version\s+["{]?([^"}\s]+)["}]?\s*(?:#.*)?$' +) + + +def get_required_vivado_version(path: Path) -> str: + for line in path.read_text(encoding="utf-8").splitlines(): + match = _PATTERN.match(line) + if match: + return match.group(1) + raise ValueError(f"required_vivado_version not found in {path}") + + +def main(argv: list[str]) -> int: + if len(argv) != 2: + print(f"usage: {argv[0]} TCL_FILE", file=sys.stderr) + return 2 + try: + print(get_required_vivado_version(Path(argv[1]))) + except (OSError, ValueError) as exc: + print(exc, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/CI/scripts/matlab_processors.tcl b/CI/scripts/matlab_processors.tcl index b7bb86e9..d87ee9bf 100644 --- a/CI/scripts/matlab_processors.tcl +++ b/CI/scripts/matlab_processors.tcl @@ -1,6 +1,45 @@ - proc preprocess_bd {project carrier rxtx} { + proc adi_cpu_interconnect_cell {} { + # hdl_2026_r1 renamed the control/CPU AXI interconnect from the fixed + # "axi_cpu_interconnect" to a carrier-specific SmartConnect created by + # ad_hpmx_interconnect (e.g. axi_hpm0_lpd_interconnect on ZynqMP/zcu102, + # axi_gp0_interconnect on Zynq-7000, axi_fpd_interconnect on Versal). + # Resolve the actual cell so the MATLAB post-build preprocessing keeps + # working across HDL branches instead of matching zero cells and failing + # "'set_property' expects at least one object". + set candidates [list \ + axi_hpm0_lpd_interconnect \ + axi_gp0_interconnect \ + axi_fpd_interconnect \ + axi_dp_interconnect \ + axi_axi_interconnect \ + axi_cpu_interconnect] + foreach name $candidates { + if {[llength [get_bd_cells -quiet $name]] == 1} { + return $name + } + } + # Last resort: any single *_interconnect whose S00_AXI is driven by the + # processing system's control master. + foreach cell [get_bd_cells -quiet *_interconnect] { + if {[llength [get_bd_intf_pins -quiet $cell/S00_AXI]] == 1} { + set src [get_bd_intf_nets -quiet -of_objects [get_bd_intf_pins $cell/S00_AXI]] + if {[string match *M_AXI_HPM0_LPD* $src] || \ + [string match *M_AXI_FPD* $src] || \ + [string match *M_AXI_GP0* $src] || \ + [string match *M_AXI_HPM0_FPD* $src]} { + return $cell + } + } + } + error "adi_cpu_interconnect_cell: could not resolve CPU AXI interconnect cell" +} + +proc preprocess_bd {project carrier rxtx} { puts "Preprocessing $project $carrier $rxtx" + set cpu_ic [adi_cpu_interconnect_cell] + puts "Using CPU interconnect cell: $cpu_ic" + switch $project { daq2 { if {$rxtx == "rx" || $rxtx == "rxtx"} { @@ -46,14 +85,14 @@ } switch $carrier { zcu102 { - set_property -dict [list CONFIG.NUM_CLKS {2}] [get_bd_cells axi_cpu_interconnect] + set_property -dict [list CONFIG.NUM_CLKS {2}] [get_bd_cells $cpu_ic] if {$rxtx == "rx" || $rxtx == "rxtx"} { - set_property -dict [list CONFIG.NUM_MI {12}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/aclk1] [get_bd_pins util_daq2_xcvr/rx_out_clk_0] + set_property -dict [list CONFIG.NUM_MI {12}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/aclk1] [get_bd_pins util_daq2_xcvr/rx_out_clk_0] } if {$rxtx == "tx"} { - set_property -dict [list CONFIG.NUM_MI {12}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/aclk1] [get_bd_pins util_daq2_xcvr/tx_out_clk_0] + set_property -dict [list CONFIG.NUM_MI {12}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/aclk1] [get_bd_pins util_daq2_xcvr/tx_out_clk_0] } } } @@ -104,9 +143,9 @@ switch $carrier { zc706 { if {$rxtx == "rx"} { - set_property -dict [list CONFIG.NUM_MI {9}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/M08_ACLK] [get_bd_pins axi_ad9434/adc_clk] - connect_bd_net [get_bd_pins sys_rstgen/peripheral_aresetn] [get_bd_pins axi_cpu_interconnect/M08_ARESETN] + set_property -dict [list CONFIG.NUM_MI {9}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/M08_ACLK] [get_bd_pins axi_ad9434/adc_clk] + connect_bd_net [get_bd_pins sys_rstgen/peripheral_aresetn] [get_bd_pins ${cpu_ic}/M08_ARESETN] } } } @@ -147,8 +186,8 @@ switch $carrier { zc706 { if {$rxtx == "tx"} { - set_property -dict [list CONFIG.NUM_MI {9}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/M08_ACLK] [get_bd_pins sys_ps7/FCLK_CLK0] + set_property -dict [list CONFIG.NUM_MI {9}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/M08_ACLK] [get_bd_pins sys_ps7/FCLK_CLK0] } } } @@ -218,14 +257,19 @@ } switch $carrier { zcu102 { - set_property -dict [list CONFIG.NUM_CLKS {2}] [get_bd_cells axi_cpu_interconnect] - if {$rxtx == "rx" || $rxtx == "rxtx"} { - set_property -dict [list CONFIG.NUM_MI {12}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/aclk1] [get_bd_pins util_mxfe_xcvr/rx_out_clk_0] + set cpu_interconnect axi_hpm0_lpd_interconnect + set_property -dict [list \ + CONFIG.NUM_CLKS {2} \ + CONFIG.NUM_MI {12}] [get_bd_cells $cpu_interconnect] + if {$rxtx == "rx" || $rxtx == "rxtx"} { + connect_bd_net \ + [get_bd_pins $cpu_interconnect/aclk1] \ + [get_bd_pins util_mxfe_xcvr/rx_out_clk_0] } if {$rxtx == "tx"} { - set_property -dict [list CONFIG.NUM_MI {12}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/aclk1] [get_bd_pins util_mxfe_xcvr/tx_out_clk_0] + connect_bd_net \ + [get_bd_pins $cpu_interconnect/aclk1] \ + [get_bd_pins util_mxfe_xcvr/tx_out_clk_0] } } } @@ -241,8 +285,8 @@ switch $carrier { zc706 { if {$rxtx == "rx" } { - set_property -dict [list CONFIG.NUM_MI {9}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/M08_ACLK] [get_bd_pins axi_ad9265/adc_clk] + set_property -dict [list CONFIG.NUM_MI {9}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/M08_ACLK] [get_bd_pins axi_ad9265/adc_clk] } } } @@ -266,8 +310,8 @@ switch $carrier { zc706 { if {$rxtx == "rx" } { - set_property -dict [list CONFIG.NUM_MI {11}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/M10_ACLK] [get_bd_pins util_fmcjesdadc1_xcvr/rx_clk_0] + set_property -dict [list CONFIG.NUM_MI {11}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/M10_ACLK] [get_bd_pins util_fmcjesdadc1_xcvr/rx_clk_0] } } } @@ -281,11 +325,11 @@ } switch $carrier { zcu102 { - set_property -dict [list CONFIG.NUM_CLKS {2}] [get_bd_cells axi_cpu_interconnect] + set_property -dict [list CONFIG.NUM_CLKS {2}] [get_bd_cells $cpu_ic] if {$rxtx == "tx"} { - set_property -dict [list CONFIG.NUM_MI {4}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/aclk1] [get_bd_pins axi_ad9783/dac_div_clk] + set_property -dict [list CONFIG.NUM_MI {4}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/aclk1] [get_bd_pins axi_ad9783/dac_div_clk] } } } @@ -304,10 +348,10 @@ } switch $carrier { vcu118 { - set_property -dict [list CONFIG.NUM_CLKS {3}] [get_bd_cells axi_cpu_interconnect] + set_property -dict [list CONFIG.NUM_CLKS {3}] [get_bd_cells $cpu_ic] if {$rxtx == "rx"} { - set_property -dict [list CONFIG.NUM_MI {18}] [get_bd_cells axi_cpu_interconnect] - connect_bd_net [get_bd_pins axi_cpu_interconnect/aclk2] [get_bd_pins glbl_clk_0] + set_property -dict [list CONFIG.NUM_MI {18}] [get_bd_cells $cpu_ic] + connect_bd_net [get_bd_pins ${cpu_ic}/aclk2] [get_bd_pins glbl_clk_0] } } } diff --git a/CI/scripts/patch_xcvr_matlab_env.py b/CI/scripts/patch_xcvr_matlab_env.py new file mode 100644 index 00000000..5e6ea51c --- /dev/null +++ b/CI/scripts/patch_xcvr_matlab_env.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Scrub HDL-Coder MATLAB-mode env vars around the nested xcvr_wizard sub-make. + +Since hdl_2026_r1, transceiver-based reference designs call adi_xcvr_project, +which shells out (`eval exec $make_command`) to build a *standalone* +xcvr_wizard Vivado project. The toolbox sets ADI_MATLAB=1 (and legacy MATLAB=1) +to make the top-level reference design reuse HDL Coder's in-memory project. +Tcl `exec` inherits ::env, so those vars leak into the nested Vivado, which then +skips create_project and dies with "No projects are currently open." + +This patch wraps the exec so the nested make runs with ADI_MATLAB/MATLAB unset, +restoring them afterward. Idempotent: safe to run on every BSP stage. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +TARGET_LINE = " eval exec $make_command\n" +SENTINEL = "_adi_saved_matlab_env" +REPLACEMENT = ( + " # The nested xcvr_wizard build is a standalone Vivado project and must not\n" + " # inherit the HDL Coder in-memory-project mode (ADI_MATLAB/MATLAB); otherwise\n" + " # adi_project skips create_project and the sub-build fails with\n" + ' # "No projects are currently open".\n' + " set _adi_saved_matlab_env {}\n" + " foreach _adi_ev {ADI_MATLAB MATLAB} {\n" + " if {[info exists ::env($_adi_ev)]} {\n" + " dict set _adi_saved_matlab_env $_adi_ev $::env($_adi_ev)\n" + " unset ::env($_adi_ev)\n" + " }\n" + " }\n" + " eval exec $make_command\n" + " dict for {_adi_ev _adi_val} $_adi_saved_matlab_env {\n" + " set ::env($_adi_ev) $_adi_val\n" + " }\n" +) + +# The cfng path adi_xcvr_project reconstructs depends on a fragile +# token-ordering (linsert/tac/MAKELEVEL) that does not match the directory the +# Makefile actually creates when ADI_PROJECT_DIR is unset (the MATLAB/BSP flow). +# Rather than predict the order, glob for the single generated cfng file under +# the xcvr_wizard project dir and use it when the reconstructed path is missing. +RETURN_LINE = ( + ' return [dict create "cfng_file_path" $adi_project_dir_path ' + '"param_file_path" $file_local_param_path]\n' +) +GLOB_SENTINEL = "_adi_xcvr_cfng_glob" +GLOB_FIX = ( + " # Robust fallback: if the reconstructed cfng path does not exist (the\n" + " # xcvr_wizard output directory is named by a Makefile token order that can\n" + " # differ from this reconstruction when ADI_PROJECT_DIR is unset), locate\n" + " # the actual generated cfng file by globbing. Exactly one is produced per\n" + " # xcvr_wizard sub-build.\n" + " if {![file exists $adi_project_dir_path]} {\n" + " set _adi_xcvr_cfng_glob [glob -nocomplain -directory \\\n" + " [file join $ad_hdl_dir/projects $project_name $carrier_name] \\\n" + " -- \"*/${project_name}_${carrier_name}.gen/sources_1/ip/${xcvr_type}_cfng.txt\"]\n" + " if {[llength $_adi_xcvr_cfng_glob] >= 1} {\n" + " set adi_project_dir_path [lindex $_adi_xcvr_cfng_glob 0]\n" + " set config_dir_path [file dirname $adi_project_dir_path]\n" + " if {$xcvr_type == \"GTXE2\"} {\n" + " set file_local_param_path [file join $config_dir_path $config_parser_dir_name $file_local_param]\n" + " }\n" + " }\n" + " }\n" + " return [dict create \"cfng_file_path\" $adi_project_dir_path " + "\"param_file_path\" $file_local_param_path]\n" +) + + +def main() -> int: + path = Path(sys.argv[1]) + text = path.read_text() + changed = False + + if SENTINEL not in text: + count = text.count(TARGET_LINE) + if count != 1: + print(f"ERROR: expected exactly 1 '{TARGET_LINE.strip()}' in {path}, found {count}", + file=sys.stderr) + return 1 + text = text.replace(TARGET_LINE, REPLACEMENT) + changed = True + else: + print("env-scrub already present") + + if GLOB_SENTINEL not in text: + count = text.count(RETURN_LINE) + if count != 1: + print(f"ERROR: expected exactly 1 xcvr return line in {path}, found {count}", + file=sys.stderr) + return 1 + text = text.replace(RETURN_LINE, GLOB_FIX) + changed = True + else: + print("cfng-glob fallback already present") + + if changed: + path.write_text(text) + print(f"patched xcvr sub-make env scrub + cfng-glob fallback into {path}") + else: + print(f"already fully patched: {path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/CI/scripts/synth_designs.sh b/CI/scripts/synth_designs.sh index cea5816f..69700da1 100644 --- a/CI/scripts/synth_designs.sh +++ b/CI/scripts/synth_designs.sh @@ -6,7 +6,7 @@ MLFLAGS="-nodisplay -nodesktop -nosplash" if [ -z "$MLRELEASE" ] then - MLRELEASE=R2023b + MLRELEASE=R2025b fi MLPATH=/opt/MATLAB @@ -14,15 +14,15 @@ MLPATH=/opt/MATLAB cd ../.. cp hdl/vendor/AnalogDevices/hdlcoder_board_customization.m test/hdlcoder_board_customization_local.m sed -i "s/hdlcoder_board_customization/hdlcoder_board_customization_local/g" test/hdlcoder_board_customization_local.m -source /opt/Xilinx/Vivado/2022.2/settings64.sh +source "${VIVADO_SETTINGS:-/tools/Xilinx/2025.1/Vivado/settings64.sh}" # Randomize DISPLAY number to avoid conflicts export DISPLAY_ID=:$(shuf -i 10-1000 -n 1) Xvfb $DISPLAY_ID & XVFB_PID=$! export DISPLAY=$DISPLAY_ID export SWT_GTK3=0 -source /opt/Xilinx/Vivado/2022.2/settings64.sh -$MLPATH/$MLRELEASE/bin/matlab $MLFLAGS -r "cd('test');runSynthTests('$BOARD');" +source "${VIVADO_SETTINGS:-/tools/Xilinx/2025.1/Vivado/settings64.sh}" +"${MATLAB_BIN:-$MLPATH/$MLRELEASE/bin/matlab}" $MLFLAGS -r "cd('test');runSynthTests('$BOARD');" EC=$? kill -9 $XVFB_PID || true exit $EC diff --git a/CI/scripts/system_project_rxtx.tcl b/CI/scripts/system_project_rxtx.tcl index c13eb85f..5a8a4f7f 100644 --- a/CI/scripts/system_project_rxtx.tcl +++ b/CI/scripts/system_project_rxtx.tcl @@ -1,4 +1,10 @@ set start_dir [pwd] +# adi_make::lib sources library Makefiles which may reuse these global Tcl +# variable names. Preserve the HDL Coder reference-design parameters so the +# post-build block-design preprocessing targets the requested carrier. +set matlab_project $project +set matlab_carrier $carrier +set matlab_ref_design $ref_design puts "Starting High-Speed Converter Toolbox HDL build" if {$preprocess == "on"} { @@ -11,11 +17,20 @@ adi_make::lib all set ::env(SKIP_SYNTHESIS) 1 set ::env(MATLAB) 1 +# hdl_2026_r1 renamed the HDL Coder in-memory-project contract from the legacy +# MATLAB env var to ADI_MATLAB inside adi_project_xilinx.tcl. Set both so the +# reference design reuses HDL Coder's project (instead of calling create_project) +# regardless of which HDL branch supplies adi_project_xilinx.tcl. +set ::env(ADI_MATLAB) 1 set ::env(ADI_USE_OOC_SYNTHESYS) 1 source ./system_project.tcl -# Update block design to make room for new IP +# Update block design to make room for new IP. Restore the HDL Coder +# parameters because the library build can overwrite generic Tcl variables. +set project $matlab_project +set carrier $matlab_carrier +set ref_design $matlab_ref_design source ../../scripts/matlab_processors.tcl preprocess_bd $project $carrier $ref_design diff --git a/CI/scripts/test_get_required_vivado_version.py b/CI/scripts/test_get_required_vivado_version.py new file mode 100644 index 00000000..d234d2a2 --- /dev/null +++ b/CI/scripts/test_get_required_vivado_version.py @@ -0,0 +1,29 @@ +from pathlib import Path +from tempfile import TemporaryDirectory +import unittest + +from CI.scripts.get_required_vivado_version import get_required_vivado_version + + +class GetRequiredVivadoVersionTests(unittest.TestCase): + def parse(self, content: str) -> str: + with TemporaryDirectory() as tmp: + path = Path(tmp, "version.tcl") + path.write_text(content, encoding="utf-8") + return get_required_vivado_version(path) + + def test_parses_current_adi_env_format(self): + self.assertEqual( + self.parse('set required_vivado_version "2025.1"\n'), "2025.1" + ) + + def test_parses_legacy_unquoted_format(self): + self.assertEqual(self.parse("set required_vivado_version 2022.2\n"), "2022.2") + + def test_rejects_missing_declaration(self): + with self.assertRaises(ValueError): + self.parse("set other_version 2025.1\n") + + +if __name__ == "__main__": + unittest.main() diff --git a/Jenkinsfile b/Jenkinsfile index 82e25317..59195d51 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,12 +3,12 @@ flags = gitParseFlags() dockerConfig = getDockerConfig(['MATLAB','Vivado','Internal'], matlabHSPro=false) -dockerConfig.add("-e MLRELEASE=R2023b") +dockerConfig.add("-e MLRELEASE=R2025b") dockerHost = 'docker' //////////////////////////// -hdlBranches = ['main','hdl_2022_r2'] +hdlBranches = ['main','hdl_2026_r1'] stage("Build Toolbox") { dockerParallelBuild(hdlBranches, dockerHost, dockerConfig) { @@ -21,14 +21,14 @@ stage("Build Toolbox") { sh 'make -C ./CI/scripts gen_tlbx' } } catch(Exception ex) { - if (branchName == 'hdl_2022_r2') { + if (branchName == 'hdl_2026_r1') { error('Production Toolbox Build Failed') } else { unstable('Development Build Failed') } } - if (branchName == 'hdl_2022_r2') { + if (branchName == 'hdl_2026_r1') { local_stash('builtSources') sh 'ls -lR' archiveArtifacts artifacts: 'hdl/*', followSymlinks: false, allowEmptyArchive: true @@ -38,8 +38,8 @@ stage("Build Toolbox") { ///////////////////////////////////////////////////// -boardNames = ['daq2','ad9081','ad9434','ad9739a','ad9265', 'fmcjesdadc1','ad9783'] -dockerConfig.add("-e HDLBRANCH=hdl_2022_r2") +boardNames = ['daq2','ad9081','ad9434','ad9265','ad9783'] +dockerConfig.add("-e HDLBRANCH=hdl_2026_r1") cstage("HDL Tests", "", flags) { dockerParallelBuild(boardNames, dockerHost, dockerConfig) { @@ -77,7 +77,7 @@ def board = 'ad9208'; def nodeLabel = 'baremetal && high_memory'; deployments[board] = { node(nodeLabel) { cstage("Baremetal HDL Test", "", flags) { - withEnv(['BOARD='+board,'MLRELEASE=R2023b','HDLBRANCH=hdl_2022_r2','LC_ALL=C.UTF-8','LANG=C.UTF-8']) { + withEnv(['BOARD='+board,'MLRELEASE=R2025b','HDLBRANCH=hdl_2026_r1','LC_ALL=C.UTF-8','LANG=C.UTF-8']) { try { cstage("AD9208 HDL Test", "", flags) { echo "Node: ${env.NODE_NAME}" diff --git a/JenkinsfileCron b/JenkinsfileCron index 53ff392e..ac6f16db 100644 --- a/JenkinsfileCron +++ b/JenkinsfileCron @@ -1,12 +1,12 @@ @Library('tfc-lib@adef-ci') _ dockerConfig = getDockerConfig(['MATLAB','Vivado','Internal'], matlabHSPro=false) -dockerConfig.add("-e MLRELEASE=R2023b") +dockerConfig.add("-e MLRELEASE=R2025b") dockerHost = 'docker' //////////////////////////// -hdlBranches = ['hdl_2022_r2'] +hdlBranches = ['hdl_2026_r1'] stage("Build Toolbox") { dockerParallelBuild(hdlBranches, dockerHost, dockerConfig) { @@ -19,14 +19,14 @@ stage("Build Toolbox") { sh 'make -C ./CI/scripts gen_tlbx' } } catch(Exception ex) { - if (branchName == 'hdl_2022_r2') { + if (branchName == 'hdl_2026_r1') { error('Production Toolbox Build Failed') } else { unstable('Development Build Failed') } } - if (branchName == 'hdl_2022_r2') { + if (branchName == 'hdl_2026_r1') { local_stash('builtSources') archiveArtifacts artifacts: 'hdl/*', followSymlinks: false, allowEmptyArchive: true } @@ -36,7 +36,7 @@ stage("Build Toolbox") { ///////////////////////////////////////////////////// boardNames = ['daq2_zcu102','ad9081_fmca_ebz_zcu102','ad9434_fmc_zc706', - 'ad9739a_fmc_zc706','ad9265_fmc_zc706', 'fmcjesdadc1_zc706','ad9783_ebz_zcu102', + 'ad9265_fmc_zc706','ad9783_ebz_zcu102', 'ad9208_dual_ebz_vcu118'] // Create unique closure for each board and run in parallel @@ -52,7 +52,7 @@ for (int i=0; i < boardNames.size(); i++) { def cworkspace = env.WORKSPACE + '/' + workspaceLabel ws(cworkspace) { stage("Synthesis Tests") { - withEnv(['BOARD='+board,'MLRELEASE=R2023b','HDLBRANCH=hdl_2022_r2','LC_ALL=C.UTF-8','LANG=C.UTF-8']) { + withEnv(['BOARD='+board,'MLRELEASE=R2025b','HDLBRANCH=hdl_2026_r1','LC_ALL=C.UTF-8','LANG=C.UTF-8']) { try { stage("Synth") { echo "Node: ${env.NODE_NAME}" diff --git a/JenkinsfileHW b/JenkinsfileHW index 259e8037..f99b697b 100644 --- a/JenkinsfileHW +++ b/JenkinsfileHW @@ -3,7 +3,7 @@ lock(label: 'adgt_test_harness_boards', quantity: 1){ @Library('sdgtt-lib@adgt-test-harness') _ // Not necessary when we turn on global libraries :) def hdlBranch = "NA" def linuxBranch = "NA" - def bootPartitionBranch = "2022_r2" + def bootPartitionBranch = "2026_r1" def firmwareVersion = 'v0.34' def bootfile_source = 'artifactory' // options: sftp, artifactory, http, local def harness = getGauntlet(hdlBranch, linuxBranch, bootPartitionBranch, firmwareVersion, bootfile_source) @@ -17,7 +17,7 @@ lock(label: 'adgt_test_harness_boards', quantity: 1){ // harness.set_env('telemetry_repo', 'http://gateway.englab:3000/mirrors/telemetry.git') // harness.set_env('telemetry_branch', 'master') harness.set_env('matlab_repo', 'https://github.com/analogdevicesinc/HighSpeedConverterToolbox.git') // Not necessary when using checkout scm - harness.set_env('matlab_release','R2023b') + harness.set_env('matlab_release','R2025b') harness.set_env('matlab_license','network') harness.set_matlab_timeout('8m') diff --git a/README.md b/README.md index e101a632..71c10c5c 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,16 @@ As with many open source packages, we use [GitHub](https://github.com/analogdevi | HDL Branch | MATLAB Release | Installer Package | |:------------------:|:--------------:|:-------------------:| -| 2022_R2 | R2023b | | +| 2026_R1 | R2025b | Development build from the `master` branch | If you use it, and like it - please let us know. If you use it, and hate it - please let us know that too. ## Supported Tools and Releases We provide support for certain releases of MATLAB. This does not mean older releases will not work but they are not maintained. Currently supported tools are: -- Bug fixes: MATLAB R2023b with Vivado 2021.2 -- Bug fixes and new features: MATLAB R2023b with Vivado 2022.2 +- MATLAB R2025b +- Analog Devices HDL branch `hdl_2026_r1` +- AMD Vivado 2025.1 ## Support and Documentation diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m b/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m index 2e3363de..ee1cc7a6 100644 --- a/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m @@ -5,7 +5,7 @@ case 'daq2' switch fpga case {'ZCU102'} - InterfaceConnection = 'axi_cpu_interconnect/M11_AXI'; + InterfaceConnection = 'axi_hpm0_lpd_interconnect/M11_AXI'; BaseAddress = '0x9D000000'; MasterAddressSpace = 'sys_ps8/Data'; otherwise @@ -15,7 +15,7 @@ case 'ad9081' switch fpga case {'ZCU102'} - InterfaceConnection = 'axi_cpu_interconnect/M11_AXI'; + InterfaceConnection = 'axi_hpm0_lpd_interconnect/M11_AXI'; BaseAddress = '0x9D000000'; MasterAddressSpace = 'sys_ps8/Data'; otherwise @@ -24,7 +24,7 @@ case 'ad9434' switch fpga case {'ZC706'} - InterfaceConnection = 'axi_cpu_interconnect/M08_AXI'; + InterfaceConnection = 'axi_gp0_interconnect/M08_AXI'; BaseAddress = '0x50000000'; MasterAddressSpace = 'sys_ps7/Data'; otherwise @@ -33,7 +33,7 @@ case 'fmcjesdadc1' switch fpga case {'ZC706'} - InterfaceConnection = 'axi_cpu_interconnect/M10_AXI'; + InterfaceConnection = 'axi_gp0_interconnect/M10_AXI'; BaseAddress = '0x50000000'; MasterAddressSpace = 'sys_ps7/Data'; otherwise @@ -42,7 +42,7 @@ case 'ad9265' switch fpga case {'ZC706'} - InterfaceConnection = 'axi_cpu_interconnect/M08_AXI'; + InterfaceConnection = 'axi_gp0_interconnect/M08_AXI'; BaseAddress = '0x50000000'; MasterAddressSpace = 'sys_ps7/Data'; otherwise @@ -51,7 +51,7 @@ case 'ad9739a' switch fpga case {'ZC706'} - InterfaceConnection = 'axi_cpu_interconnect/M08_AXI'; + InterfaceConnection = 'axi_gp0_interconnect/M08_AXI'; BaseAddress = '0x50000000'; MasterAddressSpace = 'sys_ps7/Data'; otherwise @@ -60,7 +60,7 @@ case 'ad9783' switch fpga case {'ZCU102'} - InterfaceConnection = 'axi_cpu_interconnect/M03_AXI'; + InterfaceConnection = 'axi_hpm0_lpd_interconnect/M03_AXI'; BaseAddress = '0x9D000000'; MasterAddressSpace = 'sys_ps8/Data'; otherwise @@ -69,7 +69,7 @@ case 'ad9208' switch fpga case {'VCU118'} - InterfaceConnection = 'axi_cpu_interconnect/M17_AXI'; + InterfaceConnection = 'axi_axi_interconnect/M17_AXI'; BaseAddress = '0xFF0000'; MasterAddressSpace = 'sys_mb/Data'; otherwise diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m b/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m index 5a418bde..ac24faeb 100644 --- a/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m @@ -38,7 +38,7 @@ % Tool information %hRD.SupportedToolVersion = {adi.Version.Vivado}; % FIXME -hRD.SupportedToolVersion = {'2022.2'}; +hRD.SupportedToolVersion = {'2025.1'}; % Get the root directory rootDir = fileparts(strtok(mfilename('fullpath'), '+')); @@ -74,10 +74,19 @@ }; % custom source files +% NOTE: quiet.mk and Makefile are HDL-repo-root files (siblings of the +% projects/library/scripts folders). Since hdl_2026_r1, transceiver-based +% reference designs call adi_xcvr_project, which shells out to `make` in +% projects/xcvr_wizard/; that Makefile chain includes ../../../quiet.mk. +% HDL Coder only copies the folders listed here into the generated work area, +% so quiet.mk must be listed explicitly or the xcvr sub-build fails with +% "../../../quiet.mk: No such file or directory". hRD.CustomFiles = {... fullfile('projects')... fullfile('library')... fullfile('scripts')... + fullfile('quiet.mk')... + fullfile('Makefile')... }; hRD.addParameter( ... diff --git a/test/AD9081Tests.m b/test/AD9081Tests.m index 8bb9bd7c..0e4872b7 100644 --- a/test/AD9081Tests.m +++ b/test/AD9081Tests.m @@ -15,6 +15,19 @@ freq = freqRangeRx(ind); end + function nsd = measureADCNSD(data, sampleRate, outputBits) + fullScale = 2^(outputBits-1); + nsd = 20*log10(rms(double(data(:)))/fullScale) ... + - 10*log10(sampleRate/2); + end + + function nsd = measureDACNSD(data, outputBits) + % DACGeneric defines RMS noise as ConverterNSD + 30 dBFS. + fullScale = 2^(outputBits-1); + noise = double(data(:)) - mean(double(data(:))); + nsd = 20*log10(rms(noise)/fullScale) - 30; + end + function [IRR, out_level] = measureIIR(input_data, Freq_bin, plot_enable ) % Assume a complex data input that is coherent. Measure the fund_level, % image_level, and IRR @@ -46,149 +59,75 @@ end + methods (TestMethodSetup) + function seedRandomStream(testCase) + previousState = rng; + testCase.addTeardown(@() rng(previousState)); + rng(0, "twister"); + end + end + methods (Test) function testAD9081Converter(testCase) - adc = adi.sim.common.ADC9081; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = adc.SampleRate; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power density'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(adc.Bits-1); - sa.CursorMeasurements.Enable = true; - - %% Test ADC - logs = []; - for k=1:10 - data = zeros(1e5,1); - o = adc(data); - sa(o); - if sa.isNewDataReady - m = getMeasurementsData(sa); - logs = [logs;m.CursorMeasurements.Power]; %#ok - end + logs = zeros(1, 10); + for k = 1:numel(logs) + output = adc(zeros(1e5, 1)); + logs(k) = testCase.measureADCNSD( ... + output, adc.SampleRate, adc.Bits); end - - %% Verify - % Reduce target by 3dB since we are measuring complex spectrum - testCase.verifyEqual(mean(logs),adc.ConverterNSD-3,'AbsTol',1,... - 'Incorrect noise floor') + testCase.verifyEqual(mean(logs), adc.ConverterNSD, ... + 'AbsTol', 1, 'Incorrect noise floor') end function testAD9081NyquistMode(testCase) - rx = adi.sim.AD9081.Rx; - ConverterNSD = -150; - ADCOutputBits = 12; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = rx.SampleRate; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power density'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(ADCOutputBits-1); - sa.CursorMeasurements.Enable = true; - - %% Test MxFE - logs = []; - for k=1:10 - data = zeros(1e5,1); - o = rx(data,data,data,data); - sa(o); - if sa.isNewDataReady - m = getMeasurementsData(sa); - logs = [logs;m.CursorMeasurements.Power]; %#ok - end + converterNSD = -150; + outputBits = 12; + logs = zeros(1, 10); + for k = 1:numel(logs) + data = zeros(1e5, 1); + output = rx(data, data, data, data); + logs(k) = testCase.measureADCNSD( ... + output, rx.SampleRate, outputBits); end - - %% Verify - % Reduce target by 3dB since we are measuring complex spectrum - testCase.verifyEqual(mean(logs),ConverterNSD-3,'AbsTol',1,... - 'Incorrect noise floor') + testCase.verifyEqual(mean(logs), converterNSD, ... + 'AbsTol', 1, 'Incorrect noise floor') end function testAD9081CDDCDec(testCase) - rx = adi.sim.AD9081.Rx; rx.MainDataPathDecimation = 4; - ConverterNSD = -150; - ADCOutputBits = 12; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = rx.SampleRate/rx.MainDataPathDecimation; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power density'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(ADCOutputBits-1); - sa.CursorMeasurements.Enable = true; - - %% Test MxFE - logs = []; - for k=1:10 - data = zeros(1e5,1); - o = rx(data,data,data,data); - sa(o); - if sa.isNewDataReady - m = getMeasurementsData(sa); - logs = [logs;m.CursorMeasurements.Power]; %#ok - end + converterNSD = -150; + outputBits = 12; + outputRate = rx.SampleRate/rx.MainDataPathDecimation; + logs = zeros(1, 10); + for k = 1:numel(logs) + data = zeros(1e5, 1); + output = rx(data, data, data, data); + logs(k) = testCase.measureADCNSD( ... + output, outputRate, outputBits); end - - %% Verify - % Reduce target by 3dB since we are measuring complex spectrum - testCase.verifyEqual(mean(logs),ConverterNSD-3,'AbsTol',1,... - 'Incorrect noise floor') + testCase.verifyEqual(mean(logs), converterNSD, ... + 'AbsTol', 1, 'Incorrect noise floor') end function testAD9081FDDCDec(testCase) - rx = adi.sim.AD9081.Rx; rx.ChannelizerPathDecimation = 4; - ConverterNSD = -150; - ADCOutputBits = 12; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = rx.SampleRate/rx.ChannelizerPathDecimation; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power density'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(ADCOutputBits-1); - sa.CursorMeasurements.Enable = true; - - %% Test MxFE - logs = []; - for k=1:10 - data = zeros(1e4,1); - o = rx(data,data,data,data); - sa(o); - if sa.isNewDataReady - m = getMeasurementsData(sa); - logs = [logs;m.CursorMeasurements.Power]; %#ok - end + converterNSD = -150; + outputBits = 12; + outputRate = rx.SampleRate/rx.ChannelizerPathDecimation; + logs = zeros(1, 10); + for k = 1:numel(logs) + data = zeros(1e4, 1); + output = rx(data, data, data, data); + logs(k) = testCase.measureADCNSD( ... + output, outputRate, outputBits); end - - %% Verify - testCase.verifyEqual(mean(logs),ConverterNSD-6,'AbsTol',3,... - 'Incorrect noise floor') + testCase.verifyEqual(mean(logs), converterNSD, ... + 'AbsTol', 3, 'Incorrect noise floor') end function testAD9081RxTonesWithCDDCNCO(testCase) @@ -204,7 +143,7 @@ function testAD9081RxTonesWithCDDCNCO(testCase) sw.SamplesPerFrame = 1e4; if testCase.EnableVisuals - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = sw.SampleRate; sa.YLimits = [-150 10]; sa.SpectralAverages = 100; @@ -225,7 +164,7 @@ function testAD9081RxTonesWithCDDCNCO(testCase) freqEst = testCase.estFrequency(double(o1),rx.SampleRate); truePos = rx.CDDCNCOFrequencies(1) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... @@ -246,7 +185,7 @@ function testAD9081RxTonesWithFDDCNCO(testCase) sw.SamplesPerFrame = 1e4; if testCase.EnableVisuals - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = sw.SampleRate; sa.YLimits = [-150 10]; sa.SpectralAverages = 100; @@ -265,7 +204,7 @@ function testAD9081RxTonesWithFDDCNCO(testCase) freqEst = testCase.estFrequency(double(o1),rx.SampleRate); truePos = rx.FDDCNCOFrequencies(1) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... @@ -321,7 +260,7 @@ function testAD9081PFiltAloneMatrix(testCase) rx.Gains = [12,12,12,12]; %% Measurement - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = fs; sa.SpectralAverages = 100; sa.YLimits = [-150 10]; @@ -459,7 +398,7 @@ function testAD9081PFiltMatrix(testCase) fs = rx.SampleRate; %% Measurement - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = fs; sa.SpectralAverages = 100; sa.YLimits = [-150 10]; @@ -600,7 +539,7 @@ function testAD9081PFiltEQMatrix(testCase) fs = rx.SampleRate; %% Measurement - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = fs; sa.SpectralAverages = 100; sa.YLimits = [-150 10]; @@ -739,7 +678,7 @@ function testAD9081PFiltIRRMatrix(testCase) fs = rx.SampleRate; %% Measurement - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = fs; sa.SpectralAverages = 100; sa.YLimits = [-150 10]; @@ -827,111 +766,45 @@ function testAD9081PFiltIRRMatrix(testCase) %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function testAD9081NyquistDAC(testCase) - dac = adi.sim.common.DAC9081; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = dac.SampleRate; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(dac.Bits-1); - sa.CursorMeasurements.Enable = true; - - %% Test MxFE - logs = []; - data = fi(2^15.*ones(1e4,1),1,16,0); % Full scale - for k=1:100 - o = dac(data); - sa(o); - if sa.isNewDataReady - sa.CursorMeasurements.XLocation = [0 2e9]; - m = getMeasurementsData(sa); - logs = [logs;diff(m.CursorMeasurements.Power)]; %#ok - end + logs = zeros(1, 20); + data = fi(2^15.*ones(1e4, 1), 1, 16, 0); + for k = 1:numel(logs) + output = dac(data); + logs(k) = testCase.measureDACNSD(output, dac.Bits); end - logs(1) = []; - %% Verify - testCase.verifyEqual(mean(logs),dac.ConverterNSD,'AbsTol',2,... - 'Incorrect noise floor') + testCase.verifyEqual(mean(logs), dac.ConverterNSD, ... + 'AbsTol', 2, 'Incorrect noise floor') end - function testAD9081NyquistTX(testCase) - tx = adi.sim.AD9081.Tx; dac = adi.sim.common.DAC9081; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = tx.SampleRate; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(dac.Bits-1); - sa.CursorMeasurements.Enable = true; - - %% Test MxFE - logs = []; - data = fi(2^15.*ones(1e4,1),1,16,0);% Full scale - for k=1:100 - o = tx(data,data,data,data,data,data,data,data); - sa(o); - if sa.isNewDataReady - sa.CursorMeasurements.XLocation = [0 2e9]; - m = getMeasurementsData(sa); - logs = [logs;diff(m.CursorMeasurements.Power)]; %#ok - end + logs = zeros(1, 20); + data = fi(2^15.*ones(1e4, 1), 1, 16, 0); + for k = 1:numel(logs) + output = tx(data, data, data, data, data, data, data, data); + logs(k) = testCase.measureDACNSD(output, dac.Bits); end - logs(1) = []; - %% Verify - testCase.verifyEqual(mean(logs),dac.ConverterNSD,'AbsTol',2,... - 'Incorrect noise floor') - end + testCase.verifyEqual(mean(logs), dac.ConverterNSD, ... + 'AbsTol', 2, 'Incorrect noise floor') + end function testAD9081TXFDUCInt(testCase) - tx = adi.sim.AD9081.Tx; tx.ChannelizerPathInterpolation = 2; dac = adi.sim.common.DAC9081; - - %% Measurement - sa = dsp.SpectrumAnalyzer; - sa.SampleRate = tx.SampleRate; - sa.YLimits = [-180 10]; - sa.SpectralAverages = 100; - sa.SpectrumType = 'Power'; - sa.SpectrumUnits = 'dBFS'; - sa.NumInputPorts = 1; - sa.FullScaleSource = 'Property'; - sa.FullScale = 2^(dac.Bits-1); - sa.CursorMeasurements.Enable = true; - - %% Test MxFE - logs = []; - for k=1:30 - data = complex(fi(2^15.*ones(1e4,1),1,16,0)); - o = tx(data,data,data,data,data,data,data,data); - sa(o); - if sa.isNewDataReady - sa.CursorMeasurements.XLocation = [0 2e9]; - m = getMeasurementsData(sa); - logs = [logs;diff(m.CursorMeasurements.Power)]; %#ok - end + logs = zeros(1, 20); + data = complex(fi(2^15.*ones(1e4, 1), 1, 16, 0)); + for k = 1:numel(logs) + output = tx(data, data, data, data, data, data, data, data); + logs(k) = testCase.measureDACNSD(output, dac.Bits); end - logs(1:10) = []; - %% Verify - testCase.verifyEqual(mean(logs),dac.ConverterNSD,'AbsTol',2,... - 'Incorrect noise floor') - end - + expectedNSD = dac.ConverterNSD + ... + 20*log10(tx.ChannelizerPathInterpolation); + testCase.verifyEqual(mean(logs), expectedNSD, ... + 'AbsTol', 2, 'Incorrect noise floor') + end function testAD9081TXCDUCNCO(testCase) @@ -948,7 +821,7 @@ function testAD9081TXCDUCNCO(testCase) sw.SamplesPerFrame = 1e4; if testCase.EnableVisuals - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = tx.SampleRate; sa.YLimits = [-180 10]; sa.SpectralAverages = 100; @@ -960,7 +833,7 @@ function testAD9081TXCDUCNCO(testCase) sa.CursorMeasurements.Enable = true; end - sa2 = dsp.SpectrumAnalyzer; + sa2 = spectrumAnalyzer; sa2.SampleRate = tx.SampleRate; sa2.YLimits = [-180 10]; sa2.SpectralAverages = 100; @@ -985,14 +858,14 @@ function testAD9081TXCDUCNCO(testCase) freqEst = testCase.estFrequency(double(o1),tx.SampleRate); truePos = tx.CDUCNCOFrequencies(1) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... 'Frequency of DDS tone unexpected') freqEst = testCase.estFrequency(double(o2),tx.SampleRate); truePos = tx.CDUCNCOFrequencies(2) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... 'Frequency of DDS tone unexpected') @@ -1014,7 +887,7 @@ function testAD9081TXFDUCNCO(testCase) sw.SamplesPerFrame = 1e4; if testCase.EnableVisuals - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = tx.SampleRate; sa.YLimits = [-180 10]; sa.SpectralAverages = 100; @@ -1026,7 +899,7 @@ function testAD9081TXFDUCNCO(testCase) sa.CursorMeasurements.Enable = true; end - sa2 = dsp.SpectrumAnalyzer; + sa2 = spectrumAnalyzer; sa2.SampleRate = tx.SampleRate; sa2.YLimits = [-180 10]; sa2.SpectralAverages = 100; @@ -1051,14 +924,14 @@ function testAD9081TXFDUCNCO(testCase) freqEst = testCase.estFrequency(double(o1),tx.SampleRate); truePos = tx.FDUCNCOFrequencies(1) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... 'Frequency of DDS tone unexpected') freqEst = testCase.estFrequency(double(o2),tx.SampleRate); truePos = tx.FDUCNCOFrequencies(2) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... 'Frequency of DDS tone unexpected') @@ -1086,7 +959,7 @@ function testAD9081TXCDUCFDUCNCO(testCase) sw.ComplexOutput = true; if testCase.EnableVisuals - sa = dsp.SpectrumAnalyzer; + sa = spectrumAnalyzer; sa.SampleRate = tx.SampleRate; sa.YLimits = [-180 10]; sa.SpectralAverages = 100; @@ -1098,7 +971,7 @@ function testAD9081TXCDUCFDUCNCO(testCase) sa.CursorMeasurements.Enable = true; end - sa2 = dsp.SpectrumAnalyzer; + sa2 = spectrumAnalyzer; sa2.SampleRate = tx.SampleRate; sa2.YLimits = [-180 10]; sa2.SpectralAverages = 100; @@ -1123,14 +996,14 @@ function testAD9081TXCDUCFDUCNCO(testCase) freqEst = testCase.estFrequency(double(o1),tx.SampleRate); truePos = tx.CDUCNCOFrequencies(1) + tx.FDUCNCOFrequencies(1) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... 'Frequency of DDS tone unexpected') freqEst = testCase.estFrequency(double(o2),tx.SampleRate); truePos = tx.CDUCNCOFrequencies(2) + tx.FDUCNCOFrequencies(2) + sw.Frequency; - [~,loc] = min(truePos - freqEst); + [~,loc] = min(abs(truePos - freqEst)); freqEst = freqEst(loc); testCase.verifyEqual(freqEst,truePos,'RelTol',0.01,... 'Frequency of DDS tone unexpected') @@ -1172,7 +1045,7 @@ function testAD9081TXCDUCFDUCNCOPDP(testCase) sw.ComplexOutput = true; if testCase.EnableVisuals - scope = dsp.TimeScope; + scope = timescope; scope.NumInputPorts = 4; scope.SampleRate = tx.SampleRate*[1, 1, 1, 1]; scope.TimeSpan = sw.SamplesPerFrame/sw.SampleRate; diff --git a/test/BSPTestsBase.m b/test/BSPTestsBase.m index d9f38726..05bb0ff5 100644 --- a/test/BSPTestsBase.m +++ b/test/BSPTestsBase.m @@ -164,9 +164,20 @@ function setVivadoPath(~,vivado) if ispc pathname = ['C:\Xilinx\Vivado\',vivado,'\bin\vivado.bat']; elseif isunix - pathname = ['/opt/Xilinx/Vivado/',vivado,'/bin/vivado']; + candidates = {... + ['/tools/Xilinx/',vivado,'/Vivado/bin/vivado'], ... + ['/opt/Xilinx/',vivado,'/Vivado/bin/vivado'], ... + ['/opt/Xilinx/Vivado/',vivado,'/bin/vivado']}; + pathname = ''; + for candidate = candidates + if isfile(candidate{1}) + pathname = candidate{1}; + break; + end + end end - assert(exist(pathname,'file')>0,'Correct version of Vivado is unavailable or in a non-standard location'); + assert(~isempty(pathname) && exist(pathname,'file')>0, ... + 'Correct version of Vivado is unavailable or in a supported location'); hdlsetuptoolpath('ToolName', 'Xilinx Vivado', ... 'ToolPath', pathname); pause(4); diff --git a/test/R2025bCompatibilityTests.m b/test/R2025bCompatibilityTests.m new file mode 100644 index 00000000..2aacbbe7 --- /dev/null +++ b/test/R2025bCompatibilityTests.m @@ -0,0 +1,84 @@ +classdef R2025bCompatibilityTests < matlab.unittest.TestCase + methods (Test) + function delayLinePreservesStateAcrossFrames(testCase) + delay = adi.sim.common.DelayLine('Length', 3); + first = delay((1:2).'); + second = delay((3:5).'); + testCase.verifyEqual(first, zeros(2, 1)); + testCase.verifyEqual(second, [0; 1; 2]); + end + + function delayLineSupportsZeroDelay(testCase) + delay = adi.sim.common.DelayLine('Length', 0); + input = (1:4).'; + testCase.verifyEqual(delay(input), input); + end + + + function delayLinePreservesComplexMultichannelTypeAndResets(testCase) + delay = adi.sim.common.DelayLine('Length', 2); + input = complex(fi([1 2; 3 4], 1, 16, 0), ... + fi([5 6; 7 8], 1, 16, 0)); + first = delay(input); + second = delay(input); + testCase.verifyClass(first, class(input)); + testCase.verifySize(first, size(input)); + testCase.verifyEqual(first, zeros(size(input), 'like', input)); + testCase.verifyEqual(second, input); + reset(delay); + testCase.verifyEqual(delay(input), zeros(size(input), 'like', input)); + end + + function ad9081MainNCOUsesReadablePhysicalChannels(testCase) + % Physical AD9081 m8_l4 datapath exposes main_* attributes on + % only two of the four fine channels; the coarse-capable + % channels are non-contiguous. Selection must pick exactly the + % readable channels, preserving their physical order. + candidateIDs = {'voltage0_i', 'voltage1_i', ... + 'voltage2_i', 'voltage3_i'}; + readLengths = [2, -22, -22, 2]; + ids = adi.AD9081.selectReadableAttributeChannelIDs( ... + candidateIDs, readLengths, 2); + testCase.verifyEqual(ids, {'voltage0_i', 'voltage3_i'}); + end + + function ad9081CoarseSelectionErrorsWhenTooFewReadable(testCase) + candidateIDs = {'voltage0_i', 'voltage1_i', ... + 'voltage2_i', 'voltage3_i'}; + readLengths = [2, -22, -22, -22]; + testCase.verifyError(@() ... + adi.AD9081.selectReadableAttributeChannelIDs( ... + candidateIDs, readLengths, 2), ?MException); + end + + function ad9081FineSelectionUsesAllContiguousChannels(testCase) + % When every channel is readable (e.g. RX or a fully populated + % datapath) selection returns the first N in order. + candidateIDs = {'voltage0_i', 'voltage1_i', ... + 'voltage2_i', 'voltage3_i'}; + readLengths = [2, 2, 2, 2]; + ids = adi.AD9081.selectReadableAttributeChannelIDs( ... + candidateIDs, readLengths, 4); + testCase.verifyEqual(ids, candidateIDs); + end + + function pFilterHalfComplexUsesStreamingDelay(testCase) + taps = zeros(2, 96); + widths = 16 .* ones(2, 24); + filter = adi.sim.common.PFilter( ... + 'Mode', 'HalfComplexSumInphase', ... + 'Taps', taps, ... + 'TapsWidthsPerQuad', widths); + firstInput = int16((1:64).'); + secondInput = int16((65:128).'); + [~, firstDelayed] = filter(firstInput, firstInput); + [~, secondDelayed] = filter(secondInput, secondInput); + testCase.verifyEqual(int16(firstDelayed), zeros(64, 1, 'int16')); + testCase.verifyEqual(int16(secondDelayed), ... + [zeros(32, 1, 'int16'); int16((1:32).')]); + reset(filter); + [~, resetDelayed] = filter(firstInput, firstInput); + testCase.verifyEqual(int16(resetDelayed), zeros(64, 1, 'int16')); + end + end +end diff --git a/test/ReleaseCompatibilityTests.m b/test/ReleaseCompatibilityTests.m new file mode 100644 index 00000000..5a662750 --- /dev/null +++ b/test/ReleaseCompatibilityTests.m @@ -0,0 +1,43 @@ +classdef ReleaseCompatibilityTests < matlab.unittest.TestCase + methods (Test) + function versionMetadataTargetsSupportedRelease(testCase) + v = adi.Version; + testCase.verifyEqual(v.MATLAB, 'R2025b'); + testCase.verifyEqual(v.HDL, 'hdl_2026_r1'); + testCase.verifyEqual(v.Vivado, '2025.1'); + testCase.verifyEqual(v.VivadoShort, '2025.1'); + end + + function packagedHdlMatchesReleaseMetadata(testCase) + root = fileparts(fileparts(mfilename('fullpath'))); + envFile = fullfile(root, 'hdl', 'vendor', 'AnalogDevices', ... + 'vivado', 'scripts', 'adi_env.tcl'); + testCase.assertTrue(isfile(envFile), ... + 'Packaged HDL release metadata is missing.'); + text = fileread(envFile); + expected = sprintf('set required_vivado_version "%s"', ... + adi.Version.Vivado); + testCase.verifySubstring(text, expected); + + pluginRoot = fullfile(root, 'hdl', 'vendor', ... + 'AnalogDevices', '+AnalogDevices'); + pluginFiles = dir(fullfile(pluginRoot, '**', 'plugin_*.m')); + versions = {}; + expression = ... + ['(?m)^\s*[^%\r\n]*\.SupportedToolVersion\s*=\s*' ... + '\{\s*''([^'']+)''\s*\}\s*;?\s*$']; + for file = pluginFiles' + pluginText = fileread(fullfile(file.folder, file.name)); + matches = regexp(pluginText, expression, 'tokens'); + for match = matches + versions{end + 1} = match{1}{1}; %#ok + end + end + testCase.assertNotEmpty(versions, ... + 'No generated HDL Coder tool-version declarations found.'); + testCase.verifyTrue(all(strcmp(versions, adi.Version.Vivado)), ... + sprintf('Generated plugins must all target Vivado %s.', ... + adi.Version.Vivado)); + end + end +end diff --git a/test/board_variants.m b/test/board_variants.m index 252973e6..bc1c85fc 100644 --- a/test/board_variants.m +++ b/test/board_variants.m @@ -16,9 +16,7 @@ 'AnalogDevices.ad9081_fmca_ebz.zcu102.plugin_rd_tx', ... 'AnalogDevices.ad9081_fmca_ebz.zcu102.plugin_rd_rxtx' ... 'AnalogDevices.ad9434_fmc.zc706.plugin_rd_rx' ... - 'AnalogDevices.ad9739a_fmc.zc706.plugin_rd_tx' ... 'AnalogDevices.ad9265_fmc.zc706.plugin_rd_rx', ... - 'AnalogDevices.fmcjesdadc1.zc706.plugin_rd_rx', ... 'AnalogDevices.ad9783_ebz.zcu102.plugin_rd_tx', ... 'AnalogDevices.ad9208_dual_ebz.vcu118.plugin_rd_rx'... };