Skip to content

Commit d4418b8

Browse files
committed
Cleanup AD9084 test common functions
Signed-off-by: Travis Collins <travis.collins@analog.com>
1 parent 218878f commit d4418b8

File tree

2 files changed

+317
-281
lines changed

2 files changed

+317
-281
lines changed

test/ad9084/test_ad9084_hw.py

Lines changed: 4 additions & 281 deletions
Original file line numberDiff line numberDiff line change
@@ -28,137 +28,18 @@
2828
import iio
2929
import adijif
3030
import adi
31+
from test.dts_utils import download_and_cache_toolchain, compile_dts_to_dtb, verify_dts_match, TOOLCHAIN_2025_R1_ARCH_ARM64
3132

3233
TFTP_BOOT_FOLDER = "/var/lib/tftpboot/"
3334

34-
# ARM GNU Toolchain configuration for cross-compiling ARM64 device trees
35-
# Vivado/Vitis 2023.2
36-
TOOLCHAIN_2023_R2_URL_ARM64 = "https://developer.arm.com/-/media/Files/downloads/gnu/12.2.rel1/binrel/arm-gnu-toolchain-12.2.rel1-x86_64-aarch64-none-elf.tar.xz"
37-
TOOLCHAIN_2023_R2_VERSION = "12.2.rel1"
38-
TOOLCHAIN_2023_R2_ARCH_ARM64 = "aarch64-none-elf"
39-
40-
# ARM GNU Toolchain configuration for cross-compiling ARM32 device trees
41-
# Vivado/Vitis 2023.2
42-
TOOLCHAIN_2023_R2_URL_ARM32 = "https://developer.arm.com/-/media/Files/downloads/gnu/12.2.rel1/binrel/arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi.tar.xz"
43-
TOOLCHAIN_2023_R2_ARCH_ARM32 = "arm-none-eabi"
44-
45-
# ARM GNU Toolchain configuration for cross-compiling ARM64 device trees
46-
# Vivado/Vitis 2025.1
47-
TOOLCHAIN_2025_R1_URL_ARM64 = "https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-elf.tar.xz"
48-
TOOLCHAIN_2025_R1_VERSION = "13.3.rel1"
49-
TOOLCHAIN_2025_R1_ARCH_ARM64 = "aarch64-none-elf"
50-
51-
52-
def download_and_cache_toolchain(arch: str = "arm64", version: str = "2023.2", cache_dir: Path = None) -> Path:
53-
"""Download and cache ARM GNU toolchain for cross-compilation.
54-
55-
Downloads the ARM GNU toolchain for arm or arm64 from ARM's official site
56-
and extracts it to a cache directory. If already cached, skips download.
57-
58-
Args:
59-
arch: Target architecture ('arm' or 'arm64')
60-
version: Target version ('2023.2' or '2025.1')
61-
cache_dir: Directory to cache toolchain. Defaults to ~/.cache/pyadi-dt/
62-
63-
Returns:
64-
Path to toolchain bin directory containing cross-compiler
65-
66-
Raises:
67-
RuntimeError: If download or extraction fails or arch is invalid
68-
"""
69-
# Select toolchain based on architecture
70-
if version == "2023.2":
71-
if arch == "arm64":
72-
toolchain_url = TOOLCHAIN_2023_R2_URL_ARM64
73-
toolchain_version = TOOLCHAIN_2023_R2_VERSION
74-
toolchain_arch = TOOLCHAIN_2023_R2_ARCH_ARM64
75-
elif arch == "arm":
76-
toolchain_url = TOOLCHAIN_2023_R2_URL_ARM32
77-
toolchain_version = TOOLCHAIN_2023_R2_VERSION
78-
toolchain_arch = TOOLCHAIN_2023_R2_ARCH_ARM32
79-
else:
80-
raise ValueError(f"Unsupported architecture: {arch}. Must be 'arm' or 'arm64'")
81-
elif version == "2025.1":
82-
if arch == "arm64":
83-
toolchain_url = TOOLCHAIN_2025_R1_URL_ARM64
84-
toolchain_version = TOOLCHAIN_2025_R1_VERSION
85-
toolchain_arch = TOOLCHAIN_2025_R1_ARCH_ARM64
86-
else:
87-
raise ValueError(f"Unsupported architecture: {arch}. Must be 'arm64'")
88-
else:
89-
raise ValueError(f"Unsupported version: {version}. Must be '2023.2' or '2025.1'")
90-
91-
if toolchain_url == "":
92-
raise ValueError(f"Toolchain URL not found for version: {version}")
93-
94-
# Determine cache directory
95-
if cache_dir is None:
96-
cache_dir = Path.home() / ".cache" / "pyadi-dt" / "toolchains"
97-
cache_dir = Path(cache_dir)
98-
cache_dir.mkdir(parents=True, exist_ok=True)
99-
100-
# Expected toolchain directory after extraction
101-
toolchain_name = f"arm-gnu-toolchain-{toolchain_version}-x86_64-{toolchain_arch}"
102-
toolchain_dir = cache_dir / toolchain_name
103-
toolchain_bin = toolchain_dir / "bin"
104-
105-
# Check if already cached
106-
if toolchain_bin.exists():
107-
print(f" Using cached toolchain: {toolchain_dir}")
108-
return toolchain_bin
109-
110-
# Download toolchain
111-
print(f" Downloading ARM GNU toolchain {toolchain_version} ({arch})...")
112-
tarball_path = cache_dir / f"{toolchain_name}.tar.xz"
113-
114-
try:
115-
with urllib.request.urlopen(toolchain_url) as response:
116-
total_size = int(response.headers.get('content-length', 0))
117-
downloaded = 0
118-
chunk_size = 1024 * 1024 # 1MB chunks
119-
120-
with open(tarball_path, 'wb') as f:
121-
while True:
122-
chunk = response.read(chunk_size)
123-
if not chunk:
124-
break
125-
f.write(chunk)
126-
downloaded += len(chunk)
127-
if total_size > 0:
128-
percent = (downloaded / total_size) * 100
129-
print(f" Progress: {percent:.1f}% ({downloaded // (1024*1024)}MB / {total_size // (1024*1024)}MB)", end='\r')
130-
131-
print(f"\n Download complete: {tarball_path}")
132-
133-
# Extract toolchain
134-
print(f" Extracting toolchain...")
135-
with tarfile.open(tarball_path, 'r:xz') as tar:
136-
tar.extractall(cache_dir)
137-
138-
# Verify extraction
139-
if not toolchain_bin.exists():
140-
raise RuntimeError(f"Toolchain extraction failed: {toolchain_bin} not found")
141-
142-
# Clean up tarball to save space
143-
tarball_path.unlink()
144-
print(f" Toolchain ready: {toolchain_bin}")
145-
146-
return toolchain_bin
147-
148-
except Exception as e:
149-
# Clean up on failure
150-
if tarball_path.exists():
151-
tarball_path.unlink()
152-
raise RuntimeError(f"Failed to download/extract toolchain: {e}")
153-
15435

15536
def get_ad9084_config() -> dict:
15637
"""Get AD9084 configuration.
15738
15839
Returns:
15940
Complete configuration dict for ad9084_fmc board
16041
"""
161-
vcxo = 122880000
42+
vcxo = 125000000
16243
sys = adijif.system("ad9084", "hmc7044", "xilinx", vcxo)
16344
sys.fpga.setup_by_dev_kit_name("vpk180")
16445

@@ -184,107 +65,7 @@ def get_ad9084_config() -> dict:
18465
return cfg
18566

18667

187-
def compile_dts_to_dtb(dts_path: Path, dtb_path: Path, kernel_path: str, arch: str = "arm64", version: str = "2023.2", cross_compile: str = None) -> None:
188-
"""Compile DTS to DTB using kernel build system with cross-compiler.
189-
"""
190-
# Validate architecture
191-
if arch not in ["arm", "arm64"]:
192-
raise ValueError(f"Unsupported architecture: {arch}. Must be 'arm' or 'arm64'")
193-
194-
# Download and cache cross-compiler if not provided
195-
if cross_compile is None:
196-
print(f" Setting up {arch.upper()} cross-compiler...")
197-
toolchain_bin = download_and_cache_toolchain(arch=arch, version=version)
198-
199-
if version == "2023.2":
200-
if arch == "arm64":
201-
cross_compile = f"{toolchain_bin}/{TOOLCHAIN_2023_R2_ARCH_ARM64}-"
202-
else: # arch == "arm"
203-
cross_compile = f"{toolchain_bin}/{TOOLCHAIN_2023_R2_ARCH_ARM32}-"
204-
elif version == "2025.1":
205-
if arch == "arm64":
206-
cross_compile = f"{toolchain_bin}/{TOOLCHAIN_2025_R1_ARCH_ARM64}-"
207-
else: # arch == "arm"
208-
# Assuming 2025.1 arm32 follows same pattern or is not supported yet?
209-
# The file didn't define TOOLCHAIN_2025_R1_ARCH_ARM32, so we might fail here if used.
210-
# Lines 81-87 in download_and_cache_toolchain suggest 2025.1 only supports arm64 for now.
211-
raise ValueError("ARM32 not supported for version 2025.1")
212-
else:
213-
raise ValueError(f"Unsupported version: {version}. Must be '2023.2' or '2025.1'")
214-
215-
print(f" ✓ Cross-compiler ready: {cross_compile}")
216-
217-
# Set up environment for kernel compilation
218-
env = os.environ.copy()
219-
env['ARCH'] = arch
220-
env['CROSS_COMPILE'] = cross_compile
221-
222-
# Determine platform-specific paths
223-
dts_filename = dts_path.name
224-
if arch == "arm64":
225-
kernel_dts_dir = Path(kernel_path) / "arch" / arch / "boot" / "dts" / "xilinx"
226-
else:
227-
kernel_dts_dir = Path(kernel_path) / "arch" / arch / "boot" / "dts"
228-
kernel_dts_path = kernel_dts_dir / dts_filename
229-
230-
# DTB will be compiled to same location with .dtb extension
231-
kernel_dtb_path = kernel_dts_path.with_suffix('.dtb')
232-
233-
# Step 1: Copy DTS file into kernel tree
234-
shutil.copy2(dts_path, kernel_dts_path)
235-
236-
# Step 2: Ensure kernel is configured
237-
print(" Configuring kernel...")
238-
239-
if arch == "arm64":
240-
defconfig = "adi_zynqmp_defconfig"
241-
else:
242-
defconfig = "zynq_xcomm_adv7511_defconfig"
243-
244-
config_cmd = ["make", defconfig]
245-
print(f" Running: {' '.join(config_cmd)}")
246-
config_result = subprocess.run(
247-
config_cmd,
248-
cwd=kernel_path,
249-
capture_output=True,
250-
text=True,
251-
env=env
252-
)
253-
if config_result.returncode != 0:
254-
raise RuntimeError(f"Kernel configuration failed: {config_result.stderr}")
255-
print(" Kernel configured.")
256-
257-
# Step 3: Compile DTB using kernel make system
258-
print(" Compiling DTB...")
259-
if arch == "arm64":
260-
make_target = f"xilinx/{dts_filename.replace('.dts', '.dtb')}"
261-
else:
262-
make_target = f"{dts_filename.replace('.dts', '.dtb')}"
263-
make_cmd = ["make", make_target]
264-
print(f" Running: {' '.join(make_cmd)}")
265-
266-
make_result = subprocess.run(
267-
make_cmd,
268-
cwd=kernel_path,
269-
capture_output=True,
270-
text=True,
271-
env=env
272-
)
273-
274-
if make_result.returncode != 0:
275-
raise RuntimeError(
276-
f"DTB compilation failed:\n"
277-
f"Command: {' '.join(make_cmd)}\n"
278-
f"ARCH={env['ARCH']} CROSS_COMPILE={env['CROSS_COMPILE']}\n"
279-
f"Error: {make_result.stderr}"
280-
)
28168

282-
# Step 4: Verify DTB was created
283-
if not kernel_dtb_path.exists():
284-
raise RuntimeError(f"DTB file not created at {kernel_dtb_path}")
285-
286-
# Step 5: Copy compiled DTB to desired output location
287-
shutil.copy2(kernel_dtb_path, dtb_path)
28869

28970

29071
class ad9084_fmc_no_plugin(ad9084_fmc):
@@ -395,7 +176,7 @@ def test_vpk180_rev10_ad9084(
395176

396177
# 3a. Compile Generated
397178
gen_dtb = dtb_output_dir / "generated.dtb"
398-
compile_dts_to_dtb(Path(generated_dts), gen_dtb, kernel_path, arch="arm64", version="2025.1")
179+
compile_dts_to_dtb(Path(generated_dts), gen_dtb, kernel_path, arch="arm64", version="2025.1", platform="vpk180")
399180

400181
# 3b. Compile Reference
401182
ref_dts_name = "versal-vpk180-reva-ad9084.dts"
@@ -438,65 +219,7 @@ def test_vpk180_rev10_ad9084(
438219
print(f" Reference DTB Size: {ref_dtb.stat().st_size}")
439220

440221
print(f"[3/4] Verifying content match...")
441-
442-
dtc_path = Path(kernel_path) / "scripts/dtc/dtc"
443-
if not dtc_path.exists():
444-
dtc_path = "dtc"
445-
446-
def dtb_to_dts(dtb, output):
447-
cmd = [str(dtc_path), "-I", "dtb", "-O", "dts", "-o", str(output), "-s", str(dtb)]
448-
subprocess.run(cmd, check=True, capture_output=True)
449-
450-
gen_dts_flat = dtb_output_dir / "generated_flat.dts"
451-
ref_dts_flat = dtb_output_dir / "reference_flat.dts"
452-
453-
try:
454-
dtb_to_dts(gen_dtb, gen_dts_flat)
455-
dtb_to_dts(ref_dtb, ref_dts_flat)
456-
457-
# Read and Compare text
458-
with open(gen_dts_flat) as f: gen_text = f.readlines()
459-
with open(ref_dts_flat) as f: ref_text = f.readlines()
460-
461-
print(" Comparing flattened DTS content...")
462-
463-
mismatches = []
464-
465-
ref_set = set([l.strip() for l in ref_text])
466-
467-
for line in gen_text:
468-
l = line.strip()
469-
if not l: continue
470-
# Skip phandle noise if it varies
471-
if "phandle =" in l: continue
472-
if "linux,phandle" in l: continue
473-
474-
if l not in ref_set:
475-
mismatches.append(f"Excess in GEN: {l}")
476-
477-
# Also check if REF has lines missing in GEN (Critical!)
478-
gen_set = set([l.strip() for l in gen_text])
479-
for line in ref_text:
480-
l = line.strip()
481-
if not l: continue
482-
if "phandle =" in l: continue
483-
if "linux,phandle" in l: continue
484-
485-
if l not in gen_set:
486-
mismatches.append(f"Missing in GEN: {l}")
487-
488-
if mismatches:
489-
# Dump first few
490-
print(" Mismatch details:")
491-
for m in mismatches[:50]:
492-
print(f" {m}")
493-
raise Exception(f"Found {len(mismatches)} mismatches in generated DTS content")
494-
495-
print(" ✓ DTS Content Matches Reference")
496-
497-
except Exception as e:
498-
print(f" Warning: DTC comparison failed: {e}")
499-
raise e
222+
verify_dts_match(gen_dtb, ref_dtb, kernel_path, dtb_output_dir)
500223

501224
# Step 4: Deploy (Run)
502225
print(f"[3/4] Deploying to board...")

0 commit comments

Comments
 (0)