From: rs <> Date: Mon, 12 Jan 2026 19:11:56 +0000 (-0600) Subject: Add STM audio streaming program X-Git-Url: https://git.the-white-hart.net/?a=commitdiff_plain;h=8f3eee975c27305821b616a5e46edd1eee492c2d;p=vhdl Add STM audio streaming program --- diff --git a/.gitignore b/.gitignore index 889539a..e01d0b5 100644 --- a/.gitignore +++ b/.gitignore @@ -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 index 0000000..74cc75c --- /dev/null +++ b/projects/nexys2_host_controller/host/stm_test.c @@ -0,0 +1,470 @@ +#include +#include +#include +#include +#include +#include +#include + +// 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 Specify device (default: \"Nexys2\")\n" + " -P, --port STM port number (default: 0)\n" + "\nOperation options:\n" + " -f, --format Input/output file format (default: \"bin\")\n" + " \"bin\" - raw binary bytes\n" + " \"wav\" - WAV audio file\n" + " -b, --blocksize Make transfers in blocks of \n" + " (default: \"2M\")\n" + " -n, --nopad Don't pad data to a multiple of 512B\n" + " -i, --infile Input filename\n" + " -l, --inlen Use length of from the input\n" + " (default: use all input bytes)\n" + " -o, --outfile Output filename\n" + " -x, --outlen Expect 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 +} +