+
+ /*
+ * Format of $var or $reg header specs:
+ * $var type size identifier reference [opt-index] $end
+ */
+ parts = g_strsplit_set(contents, " \r\n\t", 0);
+ remove_empty_parts(parts);
+ length = g_strv_length(parts);
+ if (length != 4 && length != 5) {
+ sr_warn("$var section should have 4 or 5 items");
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+
+ type = parts[0];
+ size_txt = parts[1];
+ id = parts[2];
+ ref = parts[3];
+ idx = parts[4];
+ if (idx && !*idx)
+ idx = NULL;
+ is_reg = g_strcmp0(type, "reg") == 0;
+ is_wire = g_strcmp0(type, "wire") == 0;
+ is_real = g_strcmp0(type, "real") == 0;
+ is_int = g_strcmp0(type, "integer") == 0;
+ is_str = g_strcmp0(type, "string") == 0;
+
+ if (is_reg || is_wire) {
+ ch_type = SR_CHANNEL_LOGIC;
+ } else if (is_real || is_int) {
+ ch_type = SR_CHANNEL_ANALOG;
+ } else if (is_str) {
+ sr_warn("Skipping id %s, name '%s%s', unsupported type '%s'.",
+ id, ref, idx ? idx : "", type);
+ inc->ignored_signals = g_slist_append(inc->ignored_signals,
+ g_strdup(id));
+ g_strfreev(parts);
+ return SR_OK;
+ } else {
+ sr_err("Unsupported signal type: '%s'", type);
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+
+ size = strtol(size_txt, NULL, 10);
+ if (ch_type == SR_CHANNEL_ANALOG) {
+ if (is_real && size != 32 && size != 64) {
+ /*
+ * The VCD input module does not depend on the
+ * specific width of the floating point value.
+ * This is just for information. Upon value
+ * changes, a mere string gets converted to
+ * float, so we may not care at all.
+ *
+ * Strictly speaking we might warn for 64bit
+ * (double precision) declarations, because
+ * sigrok internally uses single precision
+ * (32bit) only.
+ */
+ sr_info("Unexpected real width: '%s'", size_txt);
+ }
+ /* Simplify code paths below, by assuming size 1. */
+ size = 1;
+ }
+ if (!size) {
+ sr_warn("Unsupported signal size: '%s'", size_txt);
+ g_strfreev(parts);
+ return SR_ERR_DATA;
+ }
+ if (inc->conv_bits.max_bits < size)
+ inc->conv_bits.max_bits = size;
+ next_size = inc->logic_count + inc->analog_count + size;
+ if (inc->options.maxchannels && next_size > inc->options.maxchannels) {
+ sr_warn("Skipping '%s%s', exceeds requested channel count %zu.",
+ ref, idx ? idx : "", inc->options.maxchannels);
+ inc->ignored_signals = g_slist_append(inc->ignored_signals,
+ g_strdup(id));
+ g_strfreev(parts);
+ return SR_OK;
+ }
+
+ vcd_ch = g_malloc0(sizeof(*vcd_ch));
+ vcd_ch->identifier = g_strdup(id);
+ vcd_ch->name = g_strconcat(inc->scope_prefix->str, ref, idx, NULL);
+ vcd_ch->size = size;
+ vcd_ch->type = ch_type;
+ switch (ch_type) {
+ case SR_CHANNEL_LOGIC:
+ vcd_ch->array_index = inc->logic_count;
+ vcd_ch->byte_idx = vcd_ch->array_index / 8;
+ vcd_ch->bit_mask = 1 << (vcd_ch->array_index % 8);
+ inc->logic_count += size;
+ break;
+ case SR_CHANNEL_ANALOG:
+ vcd_ch->array_index = inc->analog_count++;
+ /* TODO: Use proper 'digits' value for this input module. */
+ vcd_ch->submit_digits = is_real ? 2 : 0;
+ break;
+ }
+ inc->vcdsignals++;
+ sr_spew("VCD signal %zu '%s' ID '%s' (size %zu), sr type %s, idx %zu.",
+ inc->vcdsignals, vcd_ch->name,
+ vcd_ch->identifier, vcd_ch->size,
+ vcd_ch->type == SR_CHANNEL_ANALOG ? "A" : "L",
+ vcd_ch->array_index);
+ inc->channels = g_slist_append(inc->channels, vcd_ch);
+ g_strfreev(parts);
+
+ return SR_OK;
+}
+
+/**
+ * Construct the name of the nth sigrok channel for a VCD signal.
+ *
+ * Uses the VCD signal name for scalar types and single-bit signals.
+ * Uses "signal.idx" for multi-bit VCD signals without a range spec in
+ * their declaration. Uses "signal[idx]" when a range is known and was
+ * verified.
+ *
+ * @param[in] vcd_ch The VCD signal's description.
+ * @param[in] idx The sigrok channel's index within the VCD signal's group.
+ *
+ * @return An allocated text buffer which callers need to release, #NULL
+ * upon failure to create a sigrok channel name.
+ */
+static char *get_channel_name(struct vcd_channel *vcd_ch, size_t idx)
+{
+ char *open_pos, *close_pos, *check_pos, *endptr;
+ gboolean has_brackets, has_range;
+ size_t upper, lower, tmp;
+ char *ch_name;
+
+ /* Handle simple scalar types, and single-bit logic first. */
+ if (vcd_ch->size <= 1)
+ return g_strdup(vcd_ch->name);
+
+ /*
+ * If not done before: Search for a matching pair of brackets in
+ * the right-most position at the very end of the string. Get the
+ * two colon separated numbers between the brackets, which are
+ * the range limits for array indices into the multi-bit signal.
+ * Grab the "base name" of the VCD signal.
+ *
+ * Notice that arrays can get nested. Earlier path components can
+ * be indexed as well, that's why we need the right-most range.
+ * This implementation does not handle bit vectors of size 1 here
+ * by explicit logic. The check for a [0:0] range would even fail.
+ * But the case of size 1 is handled above, and "happens to" give
+ * the expected result (just the VCD signal name).
+ *
+ * This implementation also deals with range limits in the reverse
+ * order, as well as ranges which are not 0-based (like "[4:7]").
+ */
+ if (!vcd_ch->base_name) {
+ has_range = TRUE;
+ open_pos = strrchr(vcd_ch->name, '[');
+ close_pos = strrchr(vcd_ch->name, ']');
+ if (close_pos && close_pos[1])
+ close_pos = NULL;
+ has_brackets = open_pos && close_pos && close_pos > open_pos;
+ if (!has_brackets)
+ has_range = FALSE;
+ if (has_range) {
+ check_pos = &open_pos[1];
+ endptr = NULL;
+ upper = strtoul(check_pos, &endptr, 10);
+ if (!endptr || *endptr != ':')
+ has_range = FALSE;
+ }
+ if (has_range) {
+ check_pos = &endptr[1];
+ endptr = NULL;
+ lower = strtoul(check_pos, &endptr, 10);
+ if (!endptr || endptr != close_pos)
+ has_range = FALSE;
+ }
+ if (has_range && lower > upper) {
+ tmp = lower;
+ lower = upper;
+ upper = tmp;
+ }
+ if (has_range) {
+ if (lower >= upper)
+ has_range = FALSE;
+ if (upper + 1 - lower != vcd_ch->size)
+ has_range = FALSE;
+ }
+ if (has_range) {
+ /* Temporarily patch the VCD channel's name. */
+ *open_pos = '\0';
+ vcd_ch->base_name = g_strdup(vcd_ch->name);
+ *open_pos = '[';
+ vcd_ch->range_lower = lower;
+ vcd_ch->range_upper = upper;
+ }
+ }
+ has_range = vcd_ch->range_lower + vcd_ch->range_upper;
+ if (has_range && idx >= vcd_ch->size)
+ has_range = FALSE;
+ if (!has_range)
+ return g_strdup_printf("%s.%zu", vcd_ch->name, idx);
+
+ /*
+ * Create a sigrok channel name with just the bit's index in
+ * brackets. This avoids "name[7:0].3" results, instead results
+ * in "name[3]".
+ */
+ ch_name = g_strdup_printf("%s[%zu]",
+ vcd_ch->base_name, vcd_ch->range_lower + idx);
+ return ch_name;
+}
+
+/*
+ * Create (analog or logic) sigrok channels for the VCD signals. Create
+ * multiple sigrok channels for vector input since sigrok has no concept
+ * of multi-bit signals. Create a channel group for the vector's bits
+ * though to reflect that they form a unit. This is beneficial when UIs
+ * support optional "collapsed" displays of channel groups (like
+ * "parallel bus, hex output").
+ *
+ * Defer channel creation until after completion of parsing the input
+ * file header. Make sure to create all logic channels first before the
+ * analog channels get created. This avoids issues with the mapping of
+ * channel indices to bitmap positions in the sample buffer.
+ */
+static void create_channels(const struct sr_input *in,
+ struct sr_dev_inst *sdi, enum sr_channeltype ch_type)
+{
+ struct context *inc;
+ size_t ch_idx;
+ GSList *l;
+ struct vcd_channel *vcd_ch;
+ size_t size_idx;
+ char *ch_name;
+ struct sr_channel_group *cg;
+ struct sr_channel *ch;
+
+ inc = in->priv;
+
+ ch_idx = 0;
+ if (ch_type > SR_CHANNEL_LOGIC)
+ ch_idx += inc->logic_count;
+ if (ch_type > SR_CHANNEL_ANALOG)
+ ch_idx += inc->analog_count;
+ for (l = inc->channels; l; l = l->next) {
+ vcd_ch = l->data;
+ if (vcd_ch->type != ch_type)
+ continue;
+ cg = NULL;
+ if (vcd_ch->size != 1)
+ cg = sr_channel_group_new(sdi, vcd_ch->name, NULL);
+ for (size_idx = 0; size_idx < vcd_ch->size; size_idx++) {
+ ch_name = get_channel_name(vcd_ch, size_idx);
+ sr_dbg("sigrok channel idx %zu, name %s, type %s, en %d.",
+ ch_idx, ch_name,
+ ch_type == SR_CHANNEL_ANALOG ? "A" : "L", TRUE);
+ ch = sr_channel_new(sdi, ch_idx, ch_type, TRUE, ch_name);
+ g_free(ch_name);
+ ch_idx++;
+ if (cg)
+ cg->channels = g_slist_append(cg->channels, ch);
+ }
+ }
+}
+
+static void create_feeds(const struct sr_input *in)
+{
+ struct context *inc;
+ GSList *l;
+ struct vcd_channel *vcd_ch;
+ size_t ch_idx;
+ struct sr_channel *ch;
+
+ inc = in->priv;
+
+ /* Create one feed for logic data. */
+ if (inc->logic_count) {
+ inc->unit_size = (inc->logic_count + 7) / 8;
+ inc->feed_logic = feed_queue_logic_alloc(in->sdi,
+ CHUNK_SIZE / inc->unit_size, inc->unit_size);
+ }
+
+ /* Create one feed per analog channel. */
+ for (l = inc->channels; l; l = l->next) {
+ vcd_ch = l->data;
+ if (vcd_ch->type != SR_CHANNEL_ANALOG)
+ continue;
+ ch_idx = vcd_ch->array_index;
+ ch_idx += inc->logic_count;
+ ch = g_slist_nth_data(in->sdi->channels, ch_idx);
+ vcd_ch->feed_analog = feed_queue_analog_alloc(in->sdi,
+ CHUNK_SIZE / sizeof(float),
+ vcd_ch->submit_digits, ch);
+ }
+}
+
+/*
+ * Keep track of a previously created channel list, in preparation of
+ * re-reading the input file. Gets called from reset()/cleanup() paths.
+ */
+static void keep_header_for_reread(const struct sr_input *in)
+{
+ struct context *inc;
+
+ inc = in->priv;
+
+ g_slist_free_full(inc->prev.sr_groups, sr_channel_group_free_cb);
+ inc->prev.sr_groups = in->sdi->channel_groups;
+ in->sdi->channel_groups = NULL;
+
+ g_slist_free_full(inc->prev.sr_channels, sr_channel_free_cb);
+ inc->prev.sr_channels = in->sdi->channels;
+ in->sdi->channels = NULL;
+}
+
+/*
+ * Check whether the input file is being re-read, and refuse operation
+ * when essential parameters of the acquisition have changed in ways
+ * that are unexpected to calling applications. Gets called after the
+ * file header got parsed (again).
+ *
+ * Changing the channel list across re-imports of the same file is not
+ * supported, by design and for valid reasons, see bug #1215 for details.
+ * Users are expected to start new sessions when they change these
+ * essential parameters in the acquisition's setup. When we accept the
+ * re-read file, then make sure to keep using the previous channel list,
+ * applications may still reference them.
+ */
+static gboolean check_header_in_reread(const struct sr_input *in)
+{
+ struct context *inc;
+
+ if (!in)
+ return FALSE;
+ inc = in->priv;
+ if (!inc)
+ return FALSE;
+ if (!inc->prev.sr_channels)
+ return TRUE;
+
+ if (sr_channel_lists_differ(inc->prev.sr_channels, in->sdi->channels)) {
+ sr_err("Channel list change not supported for file re-read.");
+ return FALSE;
+ }
+
+ g_slist_free_full(in->sdi->channel_groups, sr_channel_group_free_cb);
+ in->sdi->channel_groups = inc->prev.sr_groups;
+ inc->prev.sr_groups = NULL;
+
+ g_slist_free_full(in->sdi->channels, sr_channel_free_cb);
+ in->sdi->channels = inc->prev.sr_channels;
+ inc->prev.sr_channels = NULL;
+
+ return TRUE;
+}
+
+/* Parse VCD file header sections (rate and variables declarations). */
+static int parse_header(const struct sr_input *in, GString *buf)
+{