Skip to content

Commit 00981d1

Browse files
committed
Add V15/V16 AXI-based diagnostic builds for per-bit visibility
V15 (per-bit-position counters, 120 of them) and V16 (1024-bit raw capture buffer) substitute for Vivado JTAG-over-XVC, which is not available on this Jupiter rootfs (no hw_server / xvc_server, no remote JTAG cable). Both expose internal Rx-output bit-stream data through AXI4-Lite x"10C" (selector) / x"11C" (data). V15 first-run on hardware reveals a clear packet-position error ramp: byte 0 ('A'): per-bit BER ~1.9% (packet start, lowest) bytes 1-3: 2.7-3.6% bytes 5-7: 3.8-6.6% bytes 8-12: 5.7-7.7% (peak) byte 14 ('d'): 6.0% Hot individual bits: 12:2 11.7%, 11:1 11.1%, 14:6 11.0%. Pattern suggests packet-position-dependent impairment (timing or carrier drift between preamble re-locks), not white noise. Symbol sync / carrier sync within-packet wander is the leading hypothesis. Includes: - HardwarePerBitPositionTest.m (sweeps iq_debug_mux 0..119) - HardwareCaptureBufferTest.m (decodes 32x32-bit buffer) - tools/variant_pre_perbit.m (V15 kit logic) - tools/variant_pre_capture_buf.m (V16 kit logic) - buildfile.m updated to include both - test-results/perBitHistogram.json (first measurement) Related: see memory qpsk-carrier-sync-loop-bw-immutable for prior investigation that ruled out CS dedup as cause.
1 parent 7306c80 commit 00981d1

6 files changed

Lines changed: 522 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
classdef HardwareCaptureBufferTest < matlab.unittest.TestCase
2+
%HARDWARECAPTUREBUFFERTEST Read the V16_capture_buf 1024-bit ring buffer
3+
% via AXI4-Lite, reconstruct the captured Rx bit stream, and diff against
4+
% the expected ADI Hello World packet stream to localize errors.
5+
%
6+
% Selector: iq_debug_mux @ x"10C" (5 LSBs, 0..31)
7+
% Output: capture_word_out @ x"11C" (32-bit word)
8+
%
9+
% Buffer captures 1024 bits starting from first dstart after boot, then
10+
% freezes. A board reboot is required to re-arm capture.
11+
%
12+
% Prerequisite: V16_capture_buf BOOT.BIN deployed to root@10.0.0.146.
13+
%
14+
% Run: runtests('HardwareCaptureBufferTest')
15+
16+
properties (Constant)
17+
URI = 'ip:10.0.0.146'
18+
REG_IQ_DEBUG_MUX = uint32(hex2dec('10C'))
19+
REG_CAPTURE_WORD = uint32(hex2dec('11C'))
20+
REG_PACKETS = uint32(hex2dec('104'))
21+
end
22+
23+
methods (TestClassSetup)
24+
function setupPath(testCase)
25+
here = fileparts(mfilename('fullpath'));
26+
addpath(here);
27+
tbxRoot = fileparts(fileparts(fileparts(here)));
28+
setupM = fullfile(tbxRoot, 'setup.m');
29+
if exist(setupM, 'file') == 2
30+
run(setupM);
31+
end
32+
end
33+
end
34+
35+
methods (Test)
36+
function decodeCaptureBuffer(testCase)
37+
ctx = iio.Context(testCase.URI);
38+
cleanup = onCleanup(@() ctx.release());
39+
dev = ctx.find_device('axi_hdlcoder');
40+
assert(~isempty(dev), 'axi_hdlcoder device not found');
41+
42+
% Read all 32 words
43+
words = zeros(1,32,'uint32');
44+
for i = 0:31
45+
dev.reg_write(testCase.REG_IQ_DEBUG_MUX, uint32(i));
46+
pause(0.05);
47+
words(i+1) = dev.reg_read(testCase.REG_CAPTURE_WORD);
48+
end
49+
50+
% Reconstruct bit stream (LSB-first per word, matching the
51+
% bitor(buf, bitshift(1, bitInWord)) write order in V16)
52+
bits = false(1,1024);
53+
for i = 0:1023
54+
w = floor(i/32) + 1;
55+
b = mod(i,32);
56+
bits(i+1) = bitand(words(w), bitshift(uint32(1), b)) ~= 0;
57+
end
58+
59+
% Build expected reference: 120-bit ADI Hello World repeated.
60+
refMsg = dec2bin('ADI Hello World', 8);
61+
refBits = logical(reshape((refMsg - '0').', 1, [])); % 120 bits
62+
assert(numel(refBits)==120, 'reference must be 120 bits');
63+
64+
% Align: brute-force search the best start offset within the
65+
% first 120 bits of the capture by trying each cyclic shift
66+
% and counting matches against ~8 repetitions of refBits.
67+
captured = bits;
68+
bestShift = 0; bestMatch = -inf;
69+
% We have 1024 bits captured. Each packet boundary creates
70+
% a 120-bit alignment. After dstart, the receiver delivers
71+
% the 120 BIST bits then idle/inter-frame data until the next
72+
% start. Conservatively, we assume the first 120 bits ARE
73+
% the start of the first packet's window.
74+
shifts = 0:119;
75+
tgt = repmat(refBits, 1, ceil(1024/120));
76+
tgt = tgt(1:1024);
77+
for s = shifts
78+
rotated = [captured(s+1:end) captured(1:s)];
79+
m = sum(rotated == tgt);
80+
if m > bestMatch
81+
bestMatch = m;
82+
bestShift = s;
83+
end
84+
end
85+
86+
aligned = [captured(bestShift+1:end) captured(1:bestShift)];
87+
88+
% Per-position error: across the 8 packets in the capture,
89+
% count how many times each bit-position-within-packet
90+
% differs from the reference.
91+
nPackets = floor(1024/120); % = 8
92+
errMatrix = zeros(nPackets, 120, 'uint8');
93+
for p = 0:nPackets-1
94+
window = aligned(p*120+1 : (p+1)*120);
95+
errMatrix(p+1,:) = uint8(window ~= refBits);
96+
end
97+
98+
% Reporting
99+
fprintf('\n=== V16 capture-buffer decode ===\n');
100+
fprintf('Best alignment shift: %d bits\n', bestShift);
101+
fprintf('Total matches: %d / 1024 (%.2f%%)\n', bestMatch, 100*bestMatch/1024);
102+
fprintf('\nPer-bit-position error count (across %d packets):\n', nPackets);
103+
fprintf('bit | byte:bit | errors / %d\n', nPackets);
104+
colSums = sum(errMatrix, 1);
105+
for b = 0:119
106+
fprintf('%3d | %2d:%d | %d\n', b, floor(b/8), mod(b,8), colSums(b+1));
107+
end
108+
fprintf('\nPer-packet error count:\n');
109+
for p = 0:nPackets-1
110+
fprintf(' pkt%d: %d / 120\n', p, sum(errMatrix(p+1,:)));
111+
end
112+
113+
% Show the actual captured bytes (LSB-first within byte)
114+
fprintf('\nCaptured packet 0 as ASCII:\n');
115+
pkt0 = aligned(1:120);
116+
bytes = zeros(1,15,'uint8');
117+
for k = 0:14
118+
v = uint8(0);
119+
for bit = 0:7
120+
if pkt0(k*8 + bit + 1)
121+
v = bitor(v, bitshift(uint8(1), 7-bit)); % MSB-first inside byte (matches dec2bin default)
122+
end
123+
end
124+
bytes(k+1) = v;
125+
end
126+
fprintf(' %s\n', char(bytes));
127+
128+
% Persist output for offline analysis
129+
outDir = fullfile(fileparts(mfilename('fullpath')),'test-results');
130+
if ~exist(outDir,'dir'), mkdir(outDir); end
131+
s.rawWords = double(words);
132+
s.bestShift = bestShift;
133+
s.bestMatch = bestMatch;
134+
s.errMatrix = double(errMatrix);
135+
s.colSums = double(colSums);
136+
s.capturedASCII = char(bytes);
137+
fid = fopen(fullfile(outDir,'captureBuffer.json'),'w');
138+
fprintf(fid, '%s', jsonencode(s));
139+
fclose(fid);
140+
141+
testCase.verifyGreaterThan(bestMatch, 0, 'No bits matched — buffer never armed?');
142+
end
143+
end
144+
end
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
classdef HardwarePerBitPositionTest < matlab.unittest.TestCase
2+
%HARDWAREPERBITPOSITIONTEST Sweep the V15_perbit_diag BOOT.BIN's per-bit
3+
% error counters (120 counters, one per bit-position in the BIST window).
4+
% Successor to V7's HardwarePerBytePositionTest, which gave 15 bytes worth
5+
% of resolution; V15 gives 120 -- enough to see which BIT within each
6+
% byte is flipping the most.
7+
%
8+
% Selector: iq_debug_mux @ 0x9D00010C (8 LSBs, 0..119)
9+
% Output: per_bit_errors_out @ 0x9D00011C (cumulative since boot)
10+
11+
properties (Constant)
12+
AxiPerBit = '0x9D00011C';
13+
AxiMuxSelect = '0x9D00010C';
14+
AxiBitErrors = '0x9D000108';
15+
AxiPackets = '0x9D000104';
16+
AxiCount = '0x9D000100';
17+
SshTimeoutSec = 5;
18+
SettleSec = 8;
19+
DwellSec = 1; % seconds between mux write and counter read
20+
end
21+
22+
methods (TestClassSetup)
23+
function setupPath(testCase)
24+
here = fileparts(mfilename('fullpath'));
25+
addpath(here);
26+
tbxRoot = fileparts(fileparts(fileparts(here)));
27+
setupM = fullfile(tbxRoot, 'setup.m');
28+
if exist(setupM, 'file') == 2
29+
run(setupM);
30+
end
31+
end
32+
end
33+
34+
methods (Test, TestTags = {'Hardware'})
35+
function testPerBitHistogram(testCase)
36+
% Skip if board not reachable
37+
[rc,~] = BistRegisters.sshExec('true', testCase.SshTimeoutSec);
38+
testCase.assumeEqual(rc, 0, ...
39+
'Jupiter at 10.0.0.146 not reachable -- skipping V15 per-bit test');
40+
41+
% V15 detection: pick a middle bit, read the per-bit reg.
42+
BistRegisters.write(testCase.AxiMuxSelect, 0, testCase.SshTimeoutSec);
43+
pause(testCase.SettleSec);
44+
probe = BistRegisters.read(testCase.AxiPerBit, testCase.SshTimeoutSec);
45+
testCase.assumeGreaterThan(probe, 0, ...
46+
sprintf(['per-bit counter at %s reads 0; deployed BOOT.BIN ' ...
47+
'does not appear to be V15_perbit_diag'], testCase.AxiPerBit));
48+
49+
% Snapshot global counters at the start
50+
startErr = BistRegisters.read(testCase.AxiBitErrors, testCase.SshTimeoutSec);
51+
startPkts = BistRegisters.read(testCase.AxiPackets, testCase.SshTimeoutSec);
52+
startCnt = BistRegisters.read(testCase.AxiCount, testCase.SshTimeoutSec);
53+
54+
% Sweep all 120 bit-positions
55+
errsPerBit = zeros(120,1);
56+
for b = 0:119
57+
BistRegisters.write(testCase.AxiMuxSelect, b, testCase.SshTimeoutSec);
58+
pause(testCase.DwellSec);
59+
errsPerBit(b+1) = BistRegisters.read(testCase.AxiPerBit, testCase.SshTimeoutSec);
60+
end
61+
62+
endErr = BistRegisters.read(testCase.AxiBitErrors, testCase.SshTimeoutSec);
63+
endPkts = BistRegisters.read(testCase.AxiPackets, testCase.SshTimeoutSec);
64+
endCnt = BistRegisters.read(testCase.AxiCount, testCase.SshTimeoutSec);
65+
66+
dErr = endErr - startErr;
67+
dPkts = endPkts - startPkts;
68+
dCnt = endCnt - startCnt;
69+
globalBer = dErr / max(1,dCnt);
70+
71+
% Per-bit normalised by total packets seen since boot (each
72+
% packet contributes at most one increment per bit-position)
73+
perBitRate = errsPerBit / max(1, endPkts);
74+
75+
chars = 'ADI Hello World';
76+
fprintf('\n=== Per-bit-position error histogram (V15) ===\n');
77+
fprintf('Sweep window: dPkts=%d dCnt=%d dErr=%d global_BER=%.4f%%\n', ...
78+
dPkts, dCnt, dErr, 100*globalBer);
79+
fprintf(' bit | byte:bit (chr) | errs | rate/pkt\n');
80+
for b = 0:119
81+
byteI = floor(b/8);
82+
bitI = mod(b,8);
83+
fprintf(' %3d | %2d:%d (''%c'') | %-10d | %.4f%%\n', ...
84+
b, byteI, bitI, chars(byteI+1), errsPerBit(b+1), 100*perBitRate(b+1));
85+
end
86+
87+
% Per-byte aggregation for sanity vs V7
88+
perByte = zeros(15,1);
89+
for B = 0:14
90+
perByte(B+1) = sum(errsPerBit(B*8+1 : B*8+8));
91+
end
92+
byteRate = perByte / max(1, endPkts);
93+
fprintf('\nByte aggregate (compare to V7):\n');
94+
for B = 0:14
95+
fprintf(' byte %2d (''%c''): errs=%d rate=%.4f%%\n', ...
96+
B, chars(B+1), perByte(B+1), 100*byteRate(B+1));
97+
end
98+
99+
% Within-byte hottest bit
100+
fprintf('\nWithin-byte hottest bit (bitI is MSB-first within byte):\n');
101+
for B = 0:14
102+
seg = errsPerBit(B*8+1 : B*8+8);
103+
[~,bi] = max(seg);
104+
fprintf(' byte %2d (''%c''): hottest bitI=%d (errs=%d)\n', ...
105+
B, chars(B+1), bi-1, seg(bi));
106+
end
107+
108+
% Persist for offline analysis
109+
outDir = fullfile(fileparts(mfilename('fullpath')),'test-results');
110+
if ~exist(outDir,'dir'), mkdir(outDir); end
111+
s.errsPerBit = errsPerBit;
112+
s.perBitRate = perBitRate;
113+
s.perByte = perByte;
114+
s.endPackets = endPkts;
115+
s.bitsChecked = dCnt;
116+
s.errorsInWindow = dErr;
117+
s.globalBER = globalBer;
118+
fid = fopen(fullfile(outDir,'perBitHistogram.json'),'w');
119+
fprintf(fid, '%s', jsonencode(s));
120+
fclose(fid);
121+
122+
% Assertions
123+
testCase.verifyTrue(sum(errsPerBit) > 0, ...
124+
'no errors at any bit position -- diagnostic block may be disconnected');
125+
testCase.verifyGreaterThan(std(errsPerBit)/mean(errsPerBit), 0.05, ...
126+
'errors exactly uniform across bits -- looks like instrumentation bug');
127+
end
128+
end
129+
end

trx_examples/targeting/QPSKTxRxHDLExample/buildfile.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"HardwareRFLOCfoTest.m"; ...
3333
"HardwareEnduranceTest.m"; ...
3434
"HardwarePerBytePositionTest.m"; ...
35+
"HardwarePerBitPositionTest.m"; ...
36+
"HardwareCaptureBufferTest.m"; ...
3537
"OfflineFECAnalysisTest.m"];
3638

3739
plan("test") = matlab.buildtool.tasks.TestTask(testFiles, ...
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"errsPerBit":[143873,39399,64349,60844,62614,76379,69084,137273,22376,46233,170302,75163,137198,160329,85623,222476,79012,221295,191523,193103,174204,75547,77562,170924,85946,86028,254614,216559,216258,91347,172981,86448,106878,24698,96316,26380,75001,241387,101797,93038,95700,215492,233429,106703,30306,82968,265597,244843,211750,258960,238089,341632,258354,347112,290859,290666,122577,282045,282796,142978,279573,281479,36169,317596,315886,290723,176510,124709,281460,35530,304405,408512,341757,341055,313772,131069,134427,166906,146515,148498,42029,114713,366175,336689,290334,354230,324848,40955,158913,469825,124314,391438,359731,378002,378054,344795,167264,373516,498625,130588,416505,175140,231869,163440,200845,46378,396637,49007,140408,145852,185749,169138,173113,387830,417991,190164,53751,146459,466795,196198],"perBitRate":[0.0338704258987176,0.0092752699254451832,0.015148971913816901,0.014323828608436425,0.014740520092180631,0.017981061489773284,0.016263680487561993,0.032316661044078189,0.0052677336950623472,0.010884122806749084,0.040092312465878972,0.01769479208625184,0.03229900462527547,0.037744479602951873,0.020157273961937939,0.052375059060720895,0.018600919499207461,0.052097029319307389,0.045088137311379423,0.0454600992008234,0.041010917081455184,0.017785192950521771,0.018259562069021532,0.040238743032482864,0.020233314272248323,0.020252618623472631,0.059940952227145357,0.050982085326644923,0.050911224232516671,0.021504811844961575,0.040722999745512148,0.020351494568767867,0.025161103050628957,0.0058143764211945771,0.022674608445371159,0.0062103510402102572,0.017656654221637966,0.05682706620709755,0.02396493953147398,0.021902905234233584,0.022529590392271478,0.05073089334181155,0.054953602462670212,0.025119904740089277,0.0071346057098033381,0.01953223673632163,0.062526558196615761,0.057640673985526913,0.049849955753014479,0.060964082842033668,0.056050654617612584,0.0804266355788139,0.060821418978107687,0.081716731245999347,0.0684737108872068,0.068428275036154465,0.028856944634414433,0.066398728549511066,0.066575528156455649,0.033659725967671805,0.0658167729857734,0.066265481442279867,0.0085148668223413489,0.074768106480918,0.074365540132215968,0.068441693914444532,0.041553793104909496,0.029358857766246436,0.066261008482849854,0.00836443413414217,0.0716626955418955,0.096171452772493282,0.0804560629434851,0.080290798863491636,0.073867864540896624,0.030856122080717141,0.031646658805244289,0.039292829822491784,0.034492402678408106,0.034959238391552037,0.0098944216781272527,0.027005610268219835,0.086204522067816189,0.079262959870255931,0.068350115955587751,0.083392443099836219,0.0764753644696824,0.0096415817608722938,0.037411126415956508,0.11060569285317605,0.029265867293885441,0.092151910177324589,0.084687482564286432,0.088988821603546542,0.089001063387249765,0.081171265614453966,0.0393771097949101,0.087932732340226472,0.11738575767342076,0.030742885581462362,0.098053156179028558,0.041231269188113137,0.054586348951573625,0.03847686785488872,0.047282712459098909,0.010918258549767678,0.093375853128729189,0.011537174883532376,0.033054699350031913,0.034336319936192058,0.043728828482487304,0.039818284846060745,0.040754075042604941,0.091302518723455045,0.098402988690239793,0.044768203002674124,0.012654002227533797,0.034479219219035406,0.10989237353354614,0.046188720750082339],"perByte":[653815,919700,1.18317E+6,1.210181E+6,765495,1.275038E+6,2.237422E+6,1.745213E+6,1.937735E+6,1.723999E+6,1.869973E+6,2.605072E+6,2.156947E+6,1.334014E+6,2.032301E+6],"endPackets":4.247747E+6,"bitsChecked":0,"errorsInWindow":2.6653989E+7,"globalBER":2.6653989E+7}

0 commit comments

Comments
 (0)