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'...
};