]> git.the-white-hart.net Git - vhdl/commitdiff
Add STM audio streaming program
authorrs <>
Mon, 12 Jan 2026 19:11:56 +0000 (13:11 -0600)
committerrs <>
Mon, 12 Jan 2026 19:11:56 +0000 (13:11 -0600)
.gitignore
projects/nexys2_host_controller/host/stm_test.c [new file with mode: 0644]

index 889539a656534322d0042502999d7a738cab7fd4..e01d0b5addab8b1276e0869bccb48f44d85902f6 100644 (file)
@@ -19,5 +19,9 @@ projects/nexys2_host_controller/host/epp_write
 projects/nexys2_host_controller/host/digdude
 projects/nexys2_host_controller/host/jtag
 projects/nexys2_host_controller/host/stm
+projects/nexys2_host_controller/host/stm_test
+
+# Test audio files
+*.wav
 
 .idea/
diff --git a/projects/nexys2_host_controller/host/stm_test.c b/projects/nexys2_host_controller/host/stm_test.c
new file mode 100644 (file)
index 0000000..74cc75c
--- /dev/null
@@ -0,0 +1,470 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+
+// Digilent SDK
+#include "dpcdecl.h"
+#include "dstm.h"
+#include "dmgr.h"
+
+#include "ec.h"
+
+
+// -----------------------------------------------------------------------------
+
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+
+typedef struct
+{
+       uint32_t file_size;
+       uint32_t block_size;
+       uint16_t audio_format;
+       uint16_t num_channels;
+       uint32_t sample_rate;
+       uint32_t bytes_per_sec;
+       uint16_t bytes_per_block;
+       uint16_t bits_per_sample;
+       uint32_t data_block_size;
+} wav_t;
+
+
+// -----------------------------------------------------------------------------
+
+
+bool file_expect(FILE *f, const char *data, size_t len)
+{
+       assert(f != NULL);
+       assert(data != NULL);
+
+       size_t read_len;
+       uint8_t value[len];
+
+       EC_NE(read_len = fread(value, sizeof(value[0]), len, f), len);
+
+       return !memcmp(data, value, len);
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_read_u32(FILE *f, uint32_t *value_out)
+{
+       assert(f != NULL);
+       assert(value_out != NULL);
+
+       size_t read_len;
+       uint8_t bytes[4];
+
+       EC_NE(read_len = fread(bytes, sizeof(bytes[0]), 4, f), 4);
+       *value_out = (uint32_t)bytes[0] << 0  |
+                    (uint32_t)bytes[1] << 8  |
+                    (uint32_t)bytes[2] << 16 |
+                    (uint32_t)bytes[3] << 24;
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_read_u16(FILE *f, uint16_t *value_out)
+{
+       assert(f != NULL);
+       assert(value_out != NULL);
+
+       size_t read_len;
+       uint8_t bytes[2];
+
+       EC_NE(read_len = fread(bytes, sizeof(bytes[0]), 2, f), 2);
+       *value_out = (uint32_t)bytes[0] << 0 |
+                    (uint32_t)bytes[1] << 8;
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_str(FILE *f, const char *data, size_t len)
+{
+       assert(f != NULL);
+       assert(data != NULL);
+
+       EC_NE(fwrite(data, 1, len, f), len);
+
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_u32(FILE *f, uint32_t data)
+{
+       assert(f != NULL);
+
+       uint8_t bytes[4];
+
+       bytes[0] = (data >> 0)  & 0xff;
+       bytes[1] = (data >> 8)  & 0xff;
+       bytes[2] = (data >> 16) & 0xff;
+       bytes[3] = (data >> 24) & 0xff;
+
+       EC_NE(fwrite(bytes, 1, 4, f), 4);
+
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_u16(FILE *f, uint16_t data)
+{
+       assert(f != NULL);
+
+       uint8_t bytes[2];
+
+       bytes[0] = (data >> 0)  & 0xff;
+       bytes[1] = (data >> 8)  & 0xff;
+
+       EC_NE(fwrite(bytes, 1, 2, f), 2);
+
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_read_wav_header(FILE *f, wav_t *info)
+{
+       EC_FALSE(file_expect(f, "RIFF", 4));
+       EC_FALSE(file_read_u32(f, &info->file_size));
+       EC_FALSE(file_expect(f, "WAVE", 4));
+       EC_FALSE(file_expect(f, "fmt ", 4));
+       EC_FALSE(file_read_u32(f, &info->block_size));
+       EC_FALSE(file_read_u16(f, &info->audio_format));
+       EC_FALSE(file_read_u16(f, &info->num_channels));
+       EC_FALSE(file_read_u32(f, &info->sample_rate));
+       EC_FALSE(file_read_u32(f, &info->bytes_per_sec));
+       EC_FALSE(file_read_u16(f, &info->bytes_per_block));
+       EC_FALSE(file_read_u16(f, &info->bits_per_sample));
+       EC_FALSE(file_expect(f, "data", 4));
+       EC_FALSE(file_read_u32(f, &info->data_block_size));
+
+       printf("File size:       %" PRIi32 "\n", info->file_size);
+       printf("Block size:      %" PRIi32 "\n", info->block_size);
+       printf("Audio format:    %" PRIi16 "\n", info->audio_format);
+       printf("Num channels:    %" PRIi16 "\n", info->num_channels);
+       printf("Sample rate:     %" PRIi32 "\n", info->sample_rate);
+       printf("Bytes per sec:   %" PRIi32 "\n", info->bytes_per_sec);
+       printf("Bytes per block: %" PRIi16 "\n", info->bytes_per_block);
+       printf("Bits per sample: %" PRIi16 "\n", info->bits_per_sample);
+       printf("Data block size: %" PRIi32 "\n", info->data_block_size);
+
+       // Check for supported settings
+       EC_NE(info->block_size,      16);
+       EC_NE(info->audio_format,    1);
+       EC_NE(info->num_channels,    2);
+       EC_NE(info->bytes_per_block, 4);
+       EC_NE(info->bits_per_sample, 16);
+
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+bool file_emit_wav_header(FILE *f, wav_t *info)
+{
+       EC_FALSE(file_emit_str(f, "RIFF", 4));
+       EC_FALSE(file_emit_u32(f, info->file_size));
+       EC_FALSE(file_emit_str(f, "WAVE", 4));
+       EC_FALSE(file_emit_str(f, "fmt ", 4));
+       EC_FALSE(file_emit_u32(f, info->block_size));
+       EC_FALSE(file_emit_u16(f, info->audio_format));
+       EC_FALSE(file_emit_u16(f, info->num_channels));
+       EC_FALSE(file_emit_u32(f, info->sample_rate));
+       EC_FALSE(file_emit_u32(f, info->bytes_per_sec));
+       EC_FALSE(file_emit_u16(f, info->bytes_per_block));
+       EC_FALSE(file_emit_u16(f, info->bits_per_sample));
+       EC_FALSE(file_emit_str(f, "data", 4));
+       EC_FALSE(file_emit_u32(f, info->data_block_size));
+
+       return true;
+
+EC_CLEANUP_BEGIN
+       return false;
+EC_CLEANUP_END
+}
+
+
+int32_t parse_length_arg(const char *optarg)
+{
+       int32_t value;
+       char *endptr;
+       value = strtol(optarg, &endptr, 0);
+       switch (*endptr)
+       {
+       case 'k': case 'K': value *= 1024;      break;
+       case 'm': case 'M': value *= 1024*1024; break;
+       default:            return -2;
+       }
+
+       return value;
+}
+
+
+void usage(void)
+{
+       fprintf(stderr,
+               "STM test program for Digilent Nexys2\n"
+               "\n"
+               "Usage: stm [options]\n"
+               "\nConnection options:\n"
+               "  -p, --part <name>        Specify device (default: \"Nexys2\")\n"
+               "  -P, --port <portnum>     STM port number (default: 0)\n"
+               "\nOperation options:\n"
+               "  -f, --format <format>    Input/output file format (default: \"bin\")\n"
+               "                           \"bin\" - raw binary bytes\n"
+               "                           \"wav\" - WAV audio file\n"
+               "  -b, --blocksize <bytes>  Make transfers in blocks of <bytes>\n"
+               "                           (default: \"2M\")\n"
+               "  -n, --nopad              Don't pad data to a multiple of 512B\n"
+               "  -i, --infile <filename>  Input filename\n"
+               "  -l, --inlen <bytes>      Use length of <bytes> from the input\n"
+               "                           (default: use all input bytes)\n"
+               "  -o, --outfile <filename> Output filename\n"
+               "  -x, --outlen <bytes>     Expect <bytes> length uploaded\n"
+               "                           (default: same length as input)\n"
+               "\nOutput options:\n"
+               "  -v, --verbose            Verbose output; -v -v for more\n"
+               "  -q, --quell              Quell progress output; -q -q for less\n"
+               "  -?, --help               Display this message\n"
+               "\n"
+               );
+}
+
+
+int main(int argc, char **argv)
+{
+       // Arguments with defaults
+       char   *part      = "Nexys2";
+       int32_t port      = 0;
+       char   *format    = "bin";
+       int32_t block_len = 1024 * 1024;  // Empirically, this is where diminishing returns begin as block size is made larger
+       bool    nopad     = false;
+       char   *in_name   = NULL;
+       int32_t in_len    = -1;
+       char   *out_name  = NULL;
+       int32_t out_len   = -1;
+       int     verbose   = 0;
+       int     quell     = 0;
+
+       // Options
+       int opt;
+       int option_idx = 0;
+       struct option longopts[] = {
+               // Output options
+               {"help",      no_argument,       NULL, '?'},
+               {"quell",     no_argument,       NULL, 'q'},
+               {"verbose",   no_argument,       NULL, 'v'},
+
+               // Operation options
+               {"format",    required_argument, NULL, 'f'},
+               {"blocksize", required_argument, NULL, 'b'},
+               {"nopad",     no_argument,       NULL, 'n'},
+               {"infile",    required_argument, NULL, 'i'},
+               {"inlen",     required_argument, NULL, 'l'},
+               {"outfile",   required_argument, NULL, 'o'},
+               {"outlen",    required_argument, NULL, 'x'},
+
+               // Connection options
+               {"part",      required_argument, NULL, 'p'},
+               {"port",      required_argument, NULL, 'P'},
+       };
+
+       // Port handle and properties
+       HIF hif = hifInvalid;
+       int32_t port_count;
+       char version[cchVersionMax];
+
+       // -------------------------------------------------------------
+       // Parse arguments
+
+       while ((opt = getopt_long(argc, argv, "?qvf:b:ni:l:o:x:p:P:", longopts, &option_idx)) != -1)
+       {
+               switch (opt)
+               {
+               case '?': usage(); exit(0); break;
+               case 'q': quell++; break;
+               case 'v': verbose++; break;
+
+               case 'f': format = optarg; break;
+               case 'b': block_len = parse_length_arg(optarg); break;
+               case 'n': nopad = true; break;
+               case 'i': in_name = optarg; break;
+               case 'l': in_len = parse_length_arg(optarg); break;
+               case 'o': out_name = optarg; break;
+               case 'x': out_len = parse_length_arg(optarg); break;
+
+               case 'p': part = optarg; break;
+               case 'P': port = strtol(optarg, NULL, 0); break;
+
+               default:
+                       fprintf(stderr, "Invalid option -%c\n\n", opt);
+                       usage();
+                       exit(1);
+                       break;
+               }
+       }
+
+       if (block_len <= 0)
+       {
+               fprintf(stderr, "Invalid block length\n");
+               usage();
+               exit(1);
+       }
+
+       if (in_len < -1)
+       {
+               fprintf(stderr, "Invalid input length\n");
+               usage();
+               exit(1);
+       }
+
+       if (out_len < -1)
+       {
+               fprintf(stderr, "Invalid output length\n");
+               usage();
+               exit(1);
+       }
+
+       // -------------------------------------------------------------
+       // Open files
+
+       FILE *in_file;
+       FILE *out_file;
+       uint8_t dl_block[block_len];
+       uint8_t ul_block[block_len];
+
+       EC_NULL(in_file = fopen(in_name, "r"));
+       EC_NULL(out_file = fopen(out_name, "w"));
+
+       if (!strcmp(format, "wav"))
+       {
+               // WAV format, parse headers, emit new header, read data length
+               wav_t info;
+
+               EC_FALSE(file_read_wav_header(in_file,  &info));
+               EC_FALSE(file_emit_wav_header(out_file, &info));
+               in_len = info.data_block_size;  // Make sure we don't download metadata
+       }
+       else if (!strcmp(format, "bin"))
+       {
+               // Binary format, nothing to do
+       }
+       else
+       {
+               fprintf(stderr, "Invalid output format\n");
+               usage();
+               exit(1);
+       }
+
+       // -------------------------------------------------------------
+       // Open session
+
+       EC_FALSE(DstmGetVersion(version));
+       printf("DSTM version: %s\n", version);
+
+       EC_FALSE(DmgrOpen(&hif, part));
+       printf("Opened device \"%s\"\n", part);
+
+       EC_FALSE(DstmGetPortCount(hif, &port_count));
+       printf("Port count: %" PRIi32 "\n", port_count);
+       for (int32_t i = 0; i < port_count; i++)
+       {
+               uint32_t port_properties = 0;
+
+               EC_FALSE(DstmGetPortProperties(hif, i, &port_properties));
+               printf("Port %" PRIi32 " properties: 0x%08" PRIx32 "\n", i, port_properties);
+       }
+
+       EC_FALSE(DstmEnableEx(hif, port));
+       printf("Opened STM port %" PRIi32 "\n", port);
+
+       // -------------------------------------------------------------
+       // Download input file to device and upload to output file
+
+       (void)nopad;  // TODO
+       int32_t in_left  = in_len;
+       int32_t out_left = out_len;
+       while (true)
+       {
+               // Compute number of download bytes for this block
+               int32_t request_len;
+               if (in_left < 0)
+                       request_len = block_len;
+               else
+                       request_len = MIN(block_len, in_left);
+
+               // Load the download block from the input file
+               int32_t dl_len = fread(dl_block, sizeof(dl_block[0]), request_len, in_file);
+
+               // Compute the number of upload bytes for this block
+               size_t ul_len;
+               if (out_left < 0)
+                       ul_len = dl_len;  // Upload size same as download size
+               else
+                       ul_len = MIN(block_len, out_left);  // Separate upload size
+
+               // Perform the transfer
+               if (dl_len == 0 && ul_len == 0) break;
+               EC_FALSE(DstmIO(hif, dl_block, dl_len, ul_block, ul_len, false));
+               if (in_left  > 0) in_left  -= dl_len;
+               if (out_left > 0) out_left -= ul_len;
+
+               // Write the upload portion to the output file
+               fwrite(ul_block, sizeof(ul_block[0]), ul_len, out_file);
+       }
+
+       // -------------------------------------------------------------
+       // Close session
+
+       if (hif != hifInvalid)
+       {
+               DstmDisable(hif);
+               DmgrClose(hif);
+               printf("Closed session\n");
+       }
+
+       return 0;
+
+EC_CLEANUP_BEGIN
+       if (hif != hifInvalid)
+       {
+               DstmDisable(hif);
+               DmgrClose(hif);
+       }
+
+       ec_print();
+       return 1;
+EC_CLEANUP_END
+}
+