4343#define BUFFER_SIZE_128 128 /* 128 complex samples */
4444#define BUFFER_SIZE_256 256 /* 256 complex samples */
4545#define BYTES_PER_COMPLEX_SAMPLE 4 /* I and Q are int16_t each */
46+ #define DEFAULT_PIPELINE_DEPTH 4 /* Number of in-flight blocks in the RX stream */
47+ #define MAX_LATENCY_SAMPLES (16 * 1024 * 1024) /* Safety cap, ~128 MiB of uint64_t */
4648
4749/* Test modes */
4850enum test_mode {
@@ -51,24 +53,35 @@ enum test_mode {
5153 MODE_RXTX_128 ,
5254};
5355
56+ /* Per-block RX latency statistics, computed from a bounded sample buffer */
57+ struct block_latency_stats {
58+ uint64_t * samples_us ; /* Array of per-block latency deltas, in microseconds */
59+ uint64_t count ; /* Number of latency samples actually recorded */
60+ uint64_t capacity ; /* Allocated capacity of samples_us */
61+ };
62+
5463static const struct option options [] = {
5564 { "ip" , required_argument , 0 , 'i' },
5665 { "mode" , required_argument , 0 , 'm' },
5766 { "samples" , required_argument , 0 , 's' },
5867 { "output" , required_argument , 0 , 'o' },
5968 { "rate" , required_argument , 0 , 'r' },
6069 { "loopback" , no_argument , 0 , 'l' },
70+ { "num-blocks" , required_argument , 0 , 'n' },
71+ { "quiet" , no_argument , 0 , 'q' },
6172 { 0 , 0 , 0 , 0 },
6273};
6374
6475static const char * options_descriptions [] = {
65- "[-i <ip>] [-m <mode>] [-s <samples>] [-o <file>] [-r <rate>] [-l]" ,
76+ "[-i <ip>] [-m <mode>] [-s <samples>] [-o <file>] [-r <rate>] [-l] [-n <blocks>] [-q] " ,
6677 "IP address of the remote board (required)." ,
6778 "Test mode: rx128, rx256, rxtx128 (default: rx128)." ,
6879 "Number of samples to capture (default: 8M)." ,
6980 "Output file for captured data (default: capture_<mode>.bin)." ,
7081 "Sample rate in Hz (default: 8000000)." ,
7182 "Enable digital loopback mode (default: disabled)." ,
83+ "RX stream pipeline depth / number of in-flight blocks, RX modes only (default: 4)." ,
84+ "Suppress progress output (recommended for timing-sensitive runs, e.g. under 'time')." ,
7285};
7386
7487/* Global state */
@@ -212,15 +225,67 @@ static int configure_sample_rate(uint64_t sample_rate)
212225 return 0 ;
213226}
214227
228+ /* Comparator for qsort() over an array of uint64_t microsecond latencies */
229+ static int compare_u64 (const void * a , const void * b )
230+ {
231+ uint64_t va = * (const uint64_t * )a ;
232+ uint64_t vb = * (const uint64_t * )b ;
233+
234+ if (va < vb )
235+ return -1 ;
236+ if (va > vb )
237+ return 1 ;
238+ return 0 ;
239+ }
240+
241+ /* Compute and print min/max/mean/percentile latency stats from a bounded sample set.
242+ * Sorts the array in place; only ever called once, after capture has completed. */
243+ static void print_latency_stats (struct block_latency_stats * stats )
244+ {
245+ uint64_t i , sum = 0 ;
246+ uint64_t p50_idx , p95_idx , p99_idx ;
247+
248+ if (stats -> count == 0 ) {
249+ printf ("Block latency: no samples recorded\n" );
250+ return ;
251+ }
252+
253+ qsort (stats -> samples_us , stats -> count , sizeof (* stats -> samples_us ), compare_u64 );
254+
255+ for (i = 0 ; i < stats -> count ; i ++ )
256+ sum += stats -> samples_us [i ];
257+
258+ /* Nearest-rank percentile method, clamped to the last valid index */
259+ p50_idx = (stats -> count * 50 ) / 100 ;
260+ p95_idx = (stats -> count * 95 ) / 100 ;
261+ p99_idx = (stats -> count * 99 ) / 100 ;
262+ if (p50_idx >= stats -> count )
263+ p50_idx = stats -> count - 1 ;
264+ if (p95_idx >= stats -> count )
265+ p95_idx = stats -> count - 1 ;
266+ if (p99_idx >= stats -> count )
267+ p99_idx = stats -> count - 1 ;
268+
269+ printf ("Block latency (get-next-block wait time, %" PRIu64 " samples):\n" , stats -> count );
270+ printf (" Min: %" PRIu64 " us\n" , stats -> samples_us [0 ]);
271+ printf (" Max: %" PRIu64 " us\n" , stats -> samples_us [stats -> count - 1 ]);
272+ printf (" Mean: %.2f us\n" , (double )sum / (double )stats -> count );
273+ printf (" p50: %" PRIu64 " us\n" , stats -> samples_us [p50_idx ]);
274+ printf (" p95: %" PRIu64 " us\n" , stats -> samples_us [p95_idx ]);
275+ printf (" p99: %" PRIu64 " us\n" , stats -> samples_us [p99_idx ]);
276+ }
277+
215278/* RX-only capture using stream API */
216279static int capture_rx_only (size_t block_size , uint64_t total_samples ,
217- FILE * out_file , uint64_t sample_rate , bool loopback_enabled )
280+ FILE * out_file , uint64_t sample_rate , bool loopback_enabled ,
281+ size_t pipeline_depth , bool quiet )
218282{
219283 struct iio_stream * rx_stream = NULL ;
220284 struct iio_buffer * rxbuf = NULL ;
221285 struct iio_channels_mask * rxmask = NULL ;
222286 struct iio_channel * rx0_i , * rx0_q ;
223287 const struct iio_block * rx_block = NULL ;
288+ struct block_latency_stats lat_stats = {0 };
224289 uint64_t samples_captured = 0 ;
225290 uint64_t blocks_processed = 0 ;
226291 uint64_t start_time_us , end_time_us ;
@@ -266,8 +331,21 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
266331 goto cleanup ;
267332 }
268333
334+ /* Allocate per-block latency sample buffer, sized to the expected block count */
335+ lat_stats .capacity = (total_samples + block_size - 1 ) / block_size ;
336+ if (lat_stats .capacity > MAX_LATENCY_SAMPLES )
337+ lat_stats .capacity = MAX_LATENCY_SAMPLES ;
338+
339+ lat_stats .samples_us = malloc (lat_stats .capacity * sizeof (* lat_stats .samples_us ));
340+ if (!lat_stats .samples_us ) {
341+ fprintf (stderr , "Error: Failed to allocate latency sample buffer (%" PRIu64 " entries)\n" ,
342+ lat_stats .capacity );
343+ ret = - ENOMEM ;
344+ goto cleanup ;
345+ }
346+
269347 /* Create RX stream */
270- rx_stream = iio_buffer_create_stream (rxbuf , 4 , block_size , rxmask );
348+ rx_stream = iio_buffer_create_stream (rxbuf , pipeline_depth , block_size , rxmask );
271349 ret = iio_err (rx_stream );
272350 if (ret ) {
273351 fprintf (stderr , "Error: Failed to create RX stream: %d\n" , ret );
@@ -283,15 +361,22 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
283361
284362 /* Capture loop */
285363 while (app_running && samples_captured < total_samples ) {
364+ uint64_t block_wait_start_us , block_wait_end_us ;
365+
286366 /* Get next RX block */
367+ block_wait_start_us = get_time_us ();
287368 rx_block = iio_stream_get_next_block (rx_stream );
369+ block_wait_end_us = get_time_us ();
288370 ret = iio_err (rx_block );
289371 if (ret ) {
290372 if (app_running )
291373 fprintf (stderr , "Error: Failed to get next RX block: %d\n" , ret );
292374 break ;
293375 }
294376
377+ if (lat_stats .count < lat_stats .capacity )
378+ lat_stats .samples_us [lat_stats .count ++ ] = block_wait_end_us - block_wait_start_us ;
379+
295380 const void * start = iio_block_start (rx_block );
296381 const void * end = iio_block_end (rx_block );
297382 ptrdiff_t len = (uintptr_t )end - (uintptr_t )start ;
@@ -317,7 +402,7 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
317402 blocks_processed ++ ;
318403
319404 /* Progress indicator */
320- if (blocks_processed % 100 == 0 ) {
405+ if (! quiet && blocks_processed % 100 == 0 ) {
321406 double progress = (double )samples_captured / total_samples * 100.0 ;
322407 printf ("\rProgress: %.1f%% (%" PRIu64 " / %" PRIu64 " samples, %"
323408 PRIu64 " blocks)" ,
@@ -327,14 +412,16 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
327412 }
328413
329414 end_time_us = get_time_us ();
330- printf ("\n" );
415+ if (!quiet )
416+ printf ("\n" );
331417
332418 /* Print statistics */
333419 printf ("\n--- Capture Statistics ---\n" );
334420 printf ("Samples captured: %" PRIu64 "\n" , samples_captured );
335421 printf ("Blocks processed: %" PRIu64 "\n" , blocks_processed );
336422 printf ("Block size: %zu complex samples\n" , block_size );
337423 printf ("Sample size: %zd bytes\n" , sample_size );
424+ printf ("Pipeline depth: %zu blocks\n" , pipeline_depth );
338425
339426 if (end_time_us > start_time_us ) {
340427 uint64_t duration_us = end_time_us - start_time_us ;
@@ -349,18 +436,22 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
349436 printf ("Rate error: %.2f%%\n" , rate_error );
350437 }
351438
439+ print_latency_stats (& lat_stats );
440+
352441cleanup :
353- if (rx_stream )
442+ if (rx_stream && ! iio_err ( rx_stream ) )
354443 iio_stream_destroy (rx_stream );
355444 if (rxmask )
356445 iio_channels_mask_destroy (rxmask );
446+ free (lat_stats .samples_us );
357447
358448 return ret ;
359449}
360450
361451/* Simultaneous RX/TX capture using block API */
362452static int capture_rxtx_simultaneous (size_t block_size , uint64_t total_samples ,
363- FILE * rx_file , FILE * tx_file , uint64_t sample_rate )
453+ FILE * rx_file , FILE * tx_file , uint64_t sample_rate ,
454+ bool quiet )
364455{
365456 struct iio_buffer_stream * rx_buf_stream = NULL ;
366457 struct iio_buffer_stream * tx_buf_stream = NULL ;
@@ -538,7 +629,7 @@ static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
538629 }
539630
540631 /* Progress indicator */
541- if (blocks_processed % 100 == 0 ) {
632+ if (! quiet && blocks_processed % 100 == 0 ) {
542633 double progress = (double )samples_captured / total_samples * 100.0 ;
543634 printf ("\rProgress: %.1f%% (%" PRIu64 " / %" PRIu64 " samples, %"
544635 PRIu64 " blocks)" ,
@@ -548,7 +639,8 @@ static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
548639 }
549640
550641 end_time_us = get_time_us ();
551- printf ("\n" );
642+ if (!quiet )
643+ printf ("\n" );
552644
553645 /* Print statistics */
554646 printf ("\n--- Capture Statistics ---\n" );
@@ -571,13 +663,13 @@ static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
571663 }
572664
573665cleanup :
574- if (rx_block )
666+ if (rx_block && ! iio_err ( rx_block ) )
575667 iio_block_destroy (rx_block );
576- if (tx_block )
668+ if (tx_block && ! iio_err ( tx_block ) )
577669 iio_block_destroy (tx_block );
578- if (rx_buf_stream )
670+ if (rx_buf_stream && ! iio_err ( rx_buf_stream ) )
579671 iio_buffer_close (rx_buf_stream );
580- if (tx_buf_stream )
672+ if (tx_buf_stream && ! iio_err ( tx_buf_stream ) )
581673 iio_buffer_close (tx_buf_stream );
582674 if (rxmask )
583675 iio_channels_mask_destroy (rxmask );
@@ -598,11 +690,13 @@ int main(int argc, char **argv)
598690 uint64_t total_samples = DEFAULT_DURATION_SAMPLES ;
599691 uint64_t sample_rate = DEFAULT_SAMPLE_RATE_HZ ;
600692 bool enable_loopback = false;
693+ bool quiet = false;
601694 struct iio_context_params params = {0 };
602695 struct option * opts ;
603696 FILE * out_file = NULL ;
604697 FILE * tx_ref_file = NULL ;
605698 size_t block_size ;
699+ size_t pipeline_depth = DEFAULT_PIPELINE_DEPTH ;
606700 int c , ret = EXIT_FAILURE ;
607701
608702 /* Parse arguments */
@@ -612,7 +706,7 @@ int main(int argc, char **argv)
612706 return EXIT_FAILURE ;
613707 }
614708
615- while ((c = getopt_long (argc , argv , "i:m:s:o:r:l " COMMON_OPTIONS , opts , NULL )) != -1 ) {
709+ while ((c = getopt_long (argc , argv , "i:m:s:o:r:ln:q " COMMON_OPTIONS , opts , NULL )) != -1 ) {
616710 switch (c ) {
617711 case 'i' :
618712 ip_addr = optarg ;
@@ -653,6 +747,12 @@ int main(int argc, char **argv)
653747 case 'l' :
654748 enable_loopback = true;
655749 break ;
750+ case 'n' :
751+ pipeline_depth = sanitize_clamp ("pipeline depth" , optarg , 1 , 4096 );
752+ break ;
753+ case 'q' :
754+ quiet = true;
755+ break ;
656756 case 'h' :
657757 usage (MY_NAME , options , options_descriptions );
658758 free (opts );
@@ -672,7 +772,7 @@ int main(int argc, char **argv)
672772 if (!ip_addr ) {
673773 fprintf (stderr , "Error: IP address is required\n" );
674774 fprintf (stderr , "Usage: %s -i <ip_address> [-m <mode>] [-s <samples>] "
675- "[-o <output_file>] [-r <sample_rate>]\n" , MY_NAME );
775+ "[-o <output_file>] [-r <sample_rate>] [-n <pipeline_depth>] [-q] \n" , MY_NAME );
676776 return EXIT_FAILURE ;
677777 }
678778
@@ -768,15 +868,22 @@ int main(int argc, char **argv)
768868 sample_rate , (double )sample_rate / 1000000.0 );
769869 printf (" Total samples: %" PRIu64 " (%.3f seconds)\n" ,
770870 total_samples , (double )total_samples / sample_rate );
871+ if (mode == MODE_RXTX_128 ) {
872+ if (pipeline_depth != DEFAULT_PIPELINE_DEPTH )
873+ printf (" Note: -n/--num-blocks has no effect in rxtx128 mode "
874+ "(single RX/TX block, no pipelining).\n" );
875+ } else {
876+ printf (" RX pipeline depth: %zu blocks\n" , pipeline_depth );
877+ }
771878 printf ("========================================\n" );
772879
773880 /* Run capture */
774881 if (mode == MODE_RXTX_128 ) {
775882 ret = capture_rxtx_simultaneous (block_size , total_samples ,
776- out_file , tx_ref_file , sample_rate );
883+ out_file , tx_ref_file , sample_rate , quiet );
777884 } else {
778885 ret = capture_rx_only (block_size , total_samples , out_file ,
779- sample_rate , enable_loopback );
886+ sample_rate , enable_loopback , pipeline_depth , quiet );
780887 }
781888
782889 if (ret < 0 ) {
0 commit comments