Skip to content

Commit ba6e86f

Browse files
committed
cli: Fix cross-to-parallel data corruption in LA captures
Three bugs caused CLI logic-analyzer captures to corrupt after ~10ms while the DSView GUI captured correctly: 1. cross_to_parallel() dropped partial groups when USB bulk transfers or receive_transfer() truncation delivered data not aligned to nch*8 byte boundaries. Add a static leftover buffer that accumulates partial groups across callbacks, matching the GUI's append_cross_payload() behavior in logicsnapshot.cpp. 2. The CLI only enabled user-requested channels, but the FPGA packs cross-format data for ALL enabled channels. When the hardware mode provides more channels than requested, the FPGA sends data for the full mode width. Enable all channels in the selected mode range (matching GUI behavior) and use g_hw_nch for unitsize and header. 3. Mode selection used only the count of enabled channels, ignoring their indices. For non-contiguous channels (e.g. 0,3,7 = 3 channels but max index 7), the mode must cover the highest index. Use max(channel_count, max_channel_index+1) for mode selection. Tested with DSLogic U3Pro16 across all four channel modes (3, 6, 12, 16 channels) with static signals and triggered JTAG captures. No spurious inversions observed. Assisted-By: Claude Opus <noreply@anthropic.com> Signed-off-by: Nishanth Menon <nm@ti.com>
1 parent f69b6f3 commit ba6e86f

1 file changed

Lines changed: 132 additions & 52 deletions

File tree

‎cli/dsview_cli.c‎

Lines changed: 132 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ static uint64_t g_sample_bytes = 0;
4747
static int g_unitsize = 2; /* bytes per sample, set from channel count */
4848
static int g_dev_mode = 0; /* LOGIC=0, DSO=1, ANALOG=2 */
4949
static uint64_t g_limit_samples = 0; /* requested sample count for DSO stop */
50+
static int g_hw_nch = 0; /* hw mode channel count (FPGA data packing) */
51+
52+
/* Leftover buffer for cross-to-parallel conversion.
53+
* USB transfers and receive_transfer() truncation can deliver data
54+
* that is not a whole multiple of nch*8 bytes (one cross-format group).
55+
* We buffer partial groups here and prepend them to the next callback. */
56+
static uint8_t g_cross_leftover[MAX_CH * 8]; /* max group = 16 * 8 = 128 */
57+
static size_t g_cross_leftover_len = 0;
5058

5159
/* -------------------------------------------------------------------------
5260
* Channel / trigger config (filled by argument parsing)
@@ -95,50 +103,91 @@ static void event_callback(int event)
95103
}
96104

97105
/*
98-
* Convert cross-format logic data to parallel format.
106+
* Convert one complete cross-format group to parallel format and write it.
99107
*
108+
* One group = nch * 8 input bytes -> 64 parallel samples of unitsize bytes.
100109
* The DSLogic FPGA sends LA_CROSS_DATA: data interleaved at 8-byte (64-bit)
101-
* boundaries per enabled channel. With N enabled channels, the byte stream
102-
* is: [ch0 uint64][ch1 uint64]...[chN-1 uint64] repeated.
110+
* boundaries per enabled channel. With N enabled channels, each group is:
111+
* [ch0 uint64][ch1 uint64]...[chN-1 uint64]
103112
* Each uint64 holds 64 consecutive sample bits for that one channel.
104113
*
105114
* Parallel format (what sigrok / our Python layer expects):
106-
* Each sample is g_unitsize bytes, with bit K = channel K value.
107-
* So 64 cross-format samples (N*8 input bytes) become 64 parallel samples
108-
* of g_unitsize bytes each (64 * g_unitsize output bytes).
115+
* Each sample is unitsize bytes, with bit K = channel K value.
116+
*/
117+
static void convert_one_group(const uint8_t *gp, int nch, int unitsize,
118+
FILE *fp, uint64_t *written)
119+
{
120+
uint8_t out[64 * 2]; /* max unitsize=2, 64 samples per group */
121+
122+
memset(out, 0, (size_t)(64 * unitsize));
123+
124+
for (int ch = 0; ch < nch; ch++) {
125+
/* 8 bytes (64 bits) for this channel in this group */
126+
const uint8_t *ch_bytes = gp + ch * 8;
127+
int bit = ch; /* bit position in the output sample */
128+
129+
for (int b = 0; b < 64; b++) {
130+
int byte_idx = b / 8;
131+
int bit_idx = b % 8;
132+
if (ch_bytes[byte_idx] & (1 << bit_idx)) {
133+
out[b * unitsize + bit / 8]
134+
|= (uint8_t)(1 << (bit % 8));
135+
}
136+
}
137+
}
138+
fwrite(out, (size_t)unitsize, 64, fp);
139+
*written += (uint64_t)(64 * unitsize);
140+
}
141+
142+
/*
143+
* Convert cross-format logic data to parallel format, handling partial
144+
* groups across callbacks.
109145
*
110-
* We convert in-place in the callback and write parallel-format data to
111-
* the output file.
146+
* USB transfers and receive_transfer() truncation (dsl.c:2403) can
147+
* deliver data that is not a whole multiple of nch*8 bytes. We buffer
148+
* leftover bytes in g_cross_leftover[] and prepend them to the next
149+
* callback's data before processing complete groups.
112150
*/
113151
static void cross_to_parallel(const uint8_t *src, size_t src_len,
114152
int nch, int unitsize, FILE *fp,
115153
uint64_t *written)
116154
{
117-
/* Each group: nch * 8 input bytes -> 64 parallel samples */
118155
size_t grp_in = (size_t)nch * 8;
119-
size_t n_groups = src_len / grp_in;
120-
uint8_t out[64 * 2]; /* max unitsize=2, 64 samples per group */
121-
122-
for (size_t g = 0; g < n_groups; g++) {
123-
const uint8_t *gp = src + g * grp_in;
124-
memset(out, 0, (size_t)(64 * unitsize));
125-
126-
for (int ch = 0; ch < nch; ch++) {
127-
/* 8 bytes (64 bits) for this channel in this group */
128-
const uint8_t *ch_bytes = gp + ch * 8;
129-
int bit = ch; /* bit position in the output sample */
130-
131-
for (int b = 0; b < 64; b++) {
132-
int byte_idx = b / 8;
133-
int bit_idx = b % 8;
134-
if (ch_bytes[byte_idx] & (1 << bit_idx)) {
135-
out[b * unitsize + bit / 8]
136-
|= (uint8_t)(1 << (bit % 8));
137-
}
138-
}
156+
const uint8_t *p = src;
157+
size_t remain = src_len;
158+
159+
/* If we have leftover bytes from the previous callback, try to
160+
* complete the partial group by appending from the new data. */
161+
if (g_cross_leftover_len > 0) {
162+
size_t need = grp_in - g_cross_leftover_len;
163+
if (remain >= need) {
164+
memcpy(g_cross_leftover + g_cross_leftover_len,
165+
p, need);
166+
convert_one_group(g_cross_leftover, nch, unitsize,
167+
fp, written);
168+
p += need;
169+
remain -= need;
170+
g_cross_leftover_len = 0;
171+
} else {
172+
/* Still not enough for a full group -- accumulate */
173+
memcpy(g_cross_leftover + g_cross_leftover_len,
174+
p, remain);
175+
g_cross_leftover_len += remain;
176+
return;
139177
}
140-
fwrite(out, (size_t)unitsize, 64, fp);
141-
*written += (uint64_t)(64 * unitsize);
178+
}
179+
180+
/* Process all complete groups in the current buffer */
181+
while (remain >= grp_in) {
182+
convert_one_group(p, nch, unitsize, fp, written);
183+
p += grp_in;
184+
remain -= grp_in;
185+
}
186+
187+
/* Save any remaining bytes for the next callback */
188+
if (remain > 0) {
189+
memcpy(g_cross_leftover, p, remain);
190+
g_cross_leftover_len = remain;
142191
}
143192
}
144193

@@ -151,11 +200,11 @@ static void datafeed_callback(const struct sr_dev_inst *sdi,
151200
(const struct sr_datafeed_logic *)packet->payload;
152201
if (logic && logic->data && logic->length > 0) {
153202
if (logic->format == LA_CROSS_DATA &&
154-
g_n_enabled_chs > 0) {
203+
g_hw_nch > 0) {
155204
cross_to_parallel(
156205
(const uint8_t *)logic->data,
157206
(size_t)logic->length,
158-
g_n_enabled_chs,
207+
g_hw_nch,
159208
g_unitsize,
160209
g_capture_file,
161210
&g_sample_bytes);
@@ -444,19 +493,27 @@ static void apply_trigger(void)
444493

445494
static int setup_channels(int total_ch)
446495
{
447-
/* Enable only the requested physical channels; name them if provided */
496+
/* Enable ALL channels within the hardware mode range, matching
497+
* the DSView GUI behaviour. The FPGA packs cross-format data
498+
* for every enabled channel. Disabling a subset within the mode
499+
* causes the FPGA packing to differ from what cross_to_parallel()
500+
* expects (nch = g_hw_nch = mode channel count).
501+
*
502+
* Channels beyond the mode range are disabled.
503+
* User-requested channels get their names set. */
448504
for (int ch = 0; ch < total_ch; ch++) {
449-
gboolean en = FALSE;
505+
gboolean en = (ch < g_hw_nch) ? TRUE : FALSE;
506+
ds_enable_device_channel_index(ch, en);
507+
508+
/* Set name for user-requested channels */
450509
int name_idx = -1;
451510
for (int j = 0; j < g_n_enabled_chs; j++) {
452511
if (g_enabled_chs[j] == ch) {
453-
en = TRUE;
454512
name_idx = j;
455513
break;
456514
}
457515
}
458-
ds_enable_device_channel_index(ch, en);
459-
if (en && name_idx >= 0 && g_ch_names[name_idx][0])
516+
if (name_idx >= 0 && g_ch_names[name_idx][0])
460517
ds_set_device_channel_name(ch, g_ch_names[name_idx]);
461518
}
462519
return SR_OK;
@@ -667,6 +724,8 @@ static int cmd_capture(int dev_index, uint64_t samplerate, uint64_t limit_sample
667724
{
668725
g_capture_done = g_capture_error = 0;
669726
g_sample_bytes = 0;
727+
g_cross_leftover_len = 0;
728+
g_hw_nch = 0;
670729
memset(g_ch_vdiv, 0, sizeof(g_ch_vdiv));
671730
memset(g_ch_vfactor, 0, sizeof(g_ch_vfactor));
672731
memset(g_ch_coupling, 0, sizeof(g_ch_coupling));
@@ -740,6 +799,21 @@ static int cmd_capture(int dev_index, uint64_t samplerate, uint64_t limit_sample
740799
(struct sr_list_item *)(uintptr_t) g_variant_get_uint64(cm_data);
741800
g_variant_unref(cm_data);
742801

802+
/* The hw mode must cover ALL requested channel indices.
803+
* For contiguous channels 0-6 (count=7), max index is 6,
804+
* so min_hw_channels = 7. For non-contiguous channels
805+
* like 0,3,7 (count=3), max index is 7, so
806+
* min_hw_channels = 8. The mode must have >= this many
807+
* channels because the FPGA addresses channels by index. */
808+
int max_ch_idx = 0;
809+
for (int i = 0; i < g_n_enabled_chs; i++) {
810+
if (g_enabled_chs[i] > max_ch_idx)
811+
max_ch_idx = g_enabled_chs[i];
812+
}
813+
int min_hw_chs = max_ch_idx + 1;
814+
if (min_hw_chs < g_n_enabled_chs)
815+
min_hw_chs = g_n_enabled_chs;
816+
743817
/* Find the mode with the smallest channel count
744818
* that can still accommodate all requested channels.
745819
* Parse channel count from desc: "Use N Channels ..." */
@@ -760,7 +834,7 @@ static int cmd_capture(int dev_index, uint64_t samplerate, uint64_t limit_sample
760834
nch = atoi(t + 2) + 1;
761835
}
762836
}
763-
if (nch >= g_n_enabled_chs && nch < best_nch) {
837+
if (nch >= min_hw_chs && nch < best_nch) {
764838
best_nch = nch;
765839
best_id = modes[i].id;
766840
}
@@ -778,17 +852,18 @@ static int cmd_capture(int dev_index, uint64_t samplerate, uint64_t limit_sample
778852
}
779853
}
780854

855+
/* Store the hw channel mode count globally. The FPGA packs cross-
856+
* format data for ALL enabled channels. Like the DSView GUI, we
857+
* keep all channels in the mode enabled (not just user-requested
858+
* ones) so the FPGA packing matches our expectation exactly. */
859+
g_hw_nch = ch_mode_num > 0 ? ch_mode_num : g_n_enabled_chs;
860+
781861
/* bytes-per-sample: DSO = 1 byte per channel (8-bit ADC),
782862
* LOGIC = use the hw channel mode's channel count for unitsize */
783863
if (is_dso)
784864
g_unitsize = 1; /* 8-bit ADC, 1 byte per channel per sample */
785-
else {
786-
/* The FPGA always packs data based on the hardware channel
787-
* mode, not the number of user-enabled channels. Use the
788-
* actual hw mode channel count for unitsize calculation. */
789-
int hw_nch = ch_mode_num > 0 ? ch_mode_num : g_n_enabled_chs;
790-
g_unitsize = (hw_nch <= 8) ? 1 : 2;
791-
}
865+
else
866+
g_unitsize = (g_hw_nch <= 8) ? 1 : 2;
792867

793868
/* Samplerate -- sr_config_set() takes ownership of the GVariant
794869
* (ref_sink + unref), so we must NOT unref after the call. */
@@ -847,11 +922,13 @@ static int cmd_capture(int dev_index, uint64_t samplerate, uint64_t limit_sample
847922
/* Channel enable / name.
848923
*
849924
* After the channel mode change, dsl_adjust_probes() may have
850-
* resized the probe list (e.g. 16 -> 8 for 3-channel mode).
851-
* Re-query the channel count and enable only the user-requested
852-
* channels. The FPGA packs data in "cross" format: cycling
853-
* through enabled channels, 8 sample-bits per byte per channel.
854-
* dsl_en_ch_num() must match what the FPGA was told via ch_en. */
925+
* resized the probe list (e.g. 16 -> 8 for "Use 8 Channels").
926+
* Re-query the channel count and enable ALL channels within the
927+
* hw mode range (matching DSView GUI behaviour). The FPGA packs
928+
* cross-format data for every enabled channel -- selectively
929+
* disabling channels within the mode causes the data packing to
930+
* differ from what cross_to_parallel() expects.
931+
* dsl_en_ch_num() will return g_hw_nch after setup_channels(). */
855932
struct ds_device_full_info finfo;
856933
memset(&finfo, 0, sizeof(finfo));
857934
ds_get_actived_device_info(&finfo);
@@ -941,7 +1018,10 @@ static int cmd_capture(int dev_index, uint64_t samplerate, uint64_t limit_sample
9411018
return 1;
9421019
}
9431020
uint64_t hdr_sr = samplerate;
944-
uint32_t hdr_ch = (uint32_t) g_n_enabled_chs;
1021+
/* Write the hw mode channel count so the Python layer computes
1022+
* the correct unitsize for the parallel-format data we produce. */
1023+
uint32_t hdr_ch = is_dso ? (uint32_t)g_n_enabled_chs
1024+
: (uint32_t)g_hw_nch;
9451025
fwrite(&hdr_sr, sizeof(hdr_sr), 1, g_capture_file);
9461026
fwrite(&hdr_ch, sizeof(hdr_ch), 1, g_capture_file);
9471027

0 commit comments

Comments
 (0)