]> sigrok.org Git - libsigrok.git/blob - src/input/vcd.c
input: Free instance-private storage at instance free.
[libsigrok.git] / src / input / vcd.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2012 Petteri Aimonen <jpa@sr.mail.kapsi.fi>
5  * Copyright (C) 2014 Bert Vermeulen <bert@biot.com>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 /* The VCD input module has the following options:
22  *
23  * numchannels: Maximum number of channels to use. The channels are
24  *              detected in the same order as they are listed
25  *              in the $var sections of the VCD file.
26  *
27  * skip:        Allows skipping until given timestamp in the file.
28  *              This can speed up analyzing of long captures.
29  *            
30  *              Value < 0: Skip until first timestamp listed in
31  *              the file. (default)
32  *
33  *              Value = 0: Do not skip, instead generate samples
34  *              beginning from timestamp 0.
35  *
36  *              Value > 0: Start at the given timestamp.
37  *
38  * downsample:  Divide the samplerate by the given factor.
39  *              This can speed up analyzing of long captures.
40  *
41  * compress:    Compress idle periods longer than this value.
42  *              This can speed up analyzing of long captures.
43  *              Default 0 = don't compress.
44  *
45  * Based on Verilog standard IEEE Std 1364-2001 Version C
46  *
47  * Supported features:
48  * - $var with 'wire' and 'reg' types of scalar variables
49  * - $timescale definition for samplerate
50  * - multiple character variable identifiers
51  *
52  * Most important unsupported features:
53  * - vector variables (bit vectors etc.)
54  * - analog, integer and real number variables
55  * - $dumpvars initial value declaration
56  * - $scope namespaces
57  * - more than 64 channels
58  */
59
60 #include <stdlib.h>
61 #include <glib.h>
62 #include <stdio.h>
63 #include <string.h>
64 #include "libsigrok.h"
65 #include "libsigrok-internal.h"
66
67 #define LOG_PREFIX "input/vcd"
68
69 #define DEFAULT_NUM_CHANNELS 8
70 #define CHUNKSIZE 1024
71
72 struct context {
73         gboolean started;
74         gboolean got_header;
75         uint64_t samplerate;
76         unsigned int maxchannels;
77         unsigned int channelcount;
78         int downsample;
79         unsigned compress;
80         int64_t skip;
81         gboolean skip_until_end;
82         GSList *channels;
83 };
84
85 struct vcd_channel {
86         gchar *name;
87         gchar *identifier;
88 };
89
90
91 /*
92  * Reads a single VCD section from input file and parses it to name/contents.
93  * e.g. $timescale 1ps $end  => "timescale" "1ps"
94  */
95 static gboolean parse_section(GString *buf, gchar **name, gchar **contents)
96 {
97         GString *sname, *scontent;
98         gboolean status;
99         unsigned int pos;
100
101         *name = *contents = NULL;
102         status = FALSE;
103         pos = 0;
104
105         /* Skip any initial white-space. */
106         while (pos < buf->len && g_ascii_isspace(buf->str[pos]))
107                 pos++;
108
109         /* Section tag should start with $. */
110         if (buf->str[pos++] != '$')
111                 return FALSE;
112
113         sname = g_string_sized_new(32);
114         scontent = g_string_sized_new(128);
115
116         /* Read the section tag. */
117         while (pos < buf->len && !g_ascii_isspace(buf->str[pos]))
118                 g_string_append_c(sname, buf->str[pos++]);
119
120         /* Skip whitespace before content. */
121         while (pos < buf->len && g_ascii_isspace(buf->str[pos]))
122                 pos++;
123
124         /* Read the content. */
125         while (pos < buf->len - 4 && strncmp(buf->str + pos, "$end", 4))
126                 g_string_append_c(scontent, buf->str[pos++]);
127
128         if (sname->len && pos < buf->len - 4 && !strncmp(buf->str + pos, "$end", 4)) {
129                 status = TRUE;
130                 pos += 4;
131                 while (pos < buf->len && g_ascii_isspace(buf->str[pos]))
132                         pos++;
133                 g_string_erase(buf, 0, pos);
134         }
135
136         *name = g_string_free(sname, !status);
137         *contents = g_string_free(scontent, !status);
138         if (*contents)
139                 g_strchomp(*contents);
140
141         return status;
142 }
143
144 static void free_channel(void *data)
145 {
146         struct vcd_channel *vcd_ch = data;
147         g_free(vcd_ch->name);
148         g_free(vcd_ch->identifier);
149         g_free(vcd_ch);
150 }
151
152 /* Remove empty parts from an array returned by g_strsplit. */
153 static void remove_empty_parts(gchar **parts)
154 {
155         gchar **src = parts;
156         gchar **dest = parts;
157         while (*src != NULL) {
158                 if (**src != '\0')
159                         *dest++ = *src;
160                 src++;
161         }
162
163         *dest = NULL;
164 }
165
166 /*
167  * Parse VCD header to get values for context structure.
168  * The context structure should be zeroed before calling this.
169  */
170 static gboolean parse_header(const struct sr_input *in, GString *buf)
171 {
172         struct vcd_channel *vcd_ch;
173         uint64_t p, q;
174         struct context *inc;
175         gboolean status;
176         gchar *name, *contents, **parts;
177
178         inc = in->priv;
179         name = contents = NULL;
180         status = FALSE;
181         while (parse_section(buf, &name, &contents)) {
182                 sr_dbg("Section '%s', contents '%s'.", name, contents);
183
184                 if (g_strcmp0(name, "enddefinitions") == 0) {
185                         status = TRUE;
186                         break;
187                 } else if (g_strcmp0(name, "timescale") == 0) {
188                         /*
189                          * The standard allows for values 1, 10 or 100
190                          * and units s, ms, us, ns, ps and fs.
191                          */
192                         if (sr_parse_period(contents, &p, &q) == SR_OK) {
193                                 inc->samplerate = q / p;
194                                 if (q % p != 0) {
195                                         /* Does not happen unless time value is non-standard */
196                                         sr_warn("Inexact rounding of samplerate, %" PRIu64 " / %" PRIu64 " to %" PRIu64 " Hz.",
197                                                 q, p, inc->samplerate);
198                                 }
199
200                                 sr_dbg("Samplerate: %" PRIu64, inc->samplerate);
201                         } else {
202                                 sr_err("Parsing timescale failed.");
203                         }
204                 } else if (g_strcmp0(name, "var") == 0) {
205                         /* Format: $var type size identifier reference $end */
206                         parts = g_strsplit_set(contents, " \r\n\t", 0);
207                         remove_empty_parts(parts);
208
209                         if (g_strv_length(parts) != 4)
210                                 sr_warn("$var section should have 4 items");
211                         else if (g_strcmp0(parts[0], "reg") != 0 && g_strcmp0(parts[0], "wire") != 0)
212                                 sr_info("Unsupported signal type: '%s'", parts[0]);
213                         else if (strtol(parts[1], NULL, 10) != 1)
214                                 sr_info("Unsupported signal size: '%s'", parts[1]);
215                         else if (inc->channelcount >= inc->maxchannels)
216                                 sr_warn("Skipping '%s' because only %d channels requested.",
217                                                 parts[3], inc->maxchannels);
218                         else {
219                                 sr_info("Channel %d is '%s' identified by '%s'.",
220                                                 inc->channelcount, parts[3], parts[2]);
221                                 vcd_ch = g_malloc(sizeof(struct vcd_channel));
222                                 vcd_ch->identifier = g_strdup(parts[2]);
223                                 vcd_ch->name = g_strdup(parts[3]);
224                                 inc->channels = g_slist_append(inc->channels, vcd_ch);
225                                 inc->channelcount++;
226                         }
227
228                         g_strfreev(parts);
229                 }
230
231                 g_free(name); name = NULL;
232                 g_free(contents); contents = NULL;
233         }
234         g_free(name);
235         g_free(contents);
236
237         inc->got_header = status;
238
239         return status;
240 }
241
242 static int format_match(GHashTable *metadata)
243 {
244         GString *buf, *tmpbuf;
245         gboolean status;
246         gchar *name, *contents;
247
248         buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
249         tmpbuf = g_string_new_len(buf->str, buf->len);
250
251         /*
252          * If we can parse the first section correctly,
253          * then it is assumed to be a VCD file.
254          */
255         status = parse_section(tmpbuf, &name, &contents);
256         g_string_free(tmpbuf, TRUE);
257         g_free(name);
258         g_free(contents);
259
260         return status ? SR_OK : SR_ERR;
261 }
262
263 /* Send N samples of the given value. */
264 static void send_samples(const struct sr_dev_inst *sdi, uint64_t sample, uint64_t count)
265 {
266         struct sr_datafeed_packet packet;
267         struct sr_datafeed_logic logic;
268         uint64_t buffer[CHUNKSIZE];
269         uint64_t i;
270         unsigned chunksize = CHUNKSIZE;
271
272         if (count < chunksize)
273                 chunksize = count;
274
275         for (i = 0; i < chunksize; i++)
276                 buffer[i] = sample;
277
278         packet.type = SR_DF_LOGIC;
279         packet.payload = &logic;
280         logic.unitsize = sizeof(uint64_t);
281         logic.data = buffer;
282
283         while (count) {
284                 if (count < chunksize)
285                         chunksize = count;
286
287                 logic.length = sizeof(uint64_t) * chunksize;
288
289                 sr_session_send(sdi, &packet);
290                 count -= chunksize;
291         }
292 }
293
294 /* Parse a set of lines from the data section. */
295 static void parse_contents(const struct sr_input *in, char *data)
296 {
297         struct context *inc;
298         struct vcd_channel *vcd_ch;
299         GSList *l;
300         uint64_t timestamp, prev_timestamp, prev_values;
301         unsigned int bit, i, j;
302         char **tokens;
303
304         inc = in->priv;
305         prev_timestamp = prev_values = 0;
306
307         /* Read one space-delimited token at a time. */
308         tokens = g_strsplit_set(data, " \t\r\n", 0);
309         remove_empty_parts(tokens);
310         for (i = 0; tokens[i]; i++) {
311                 if (inc->skip_until_end) {
312                         if (!strcmp(tokens[i], "$end")) {
313                                 /* Done with unhandled/unknown section. */
314                                 inc->skip_until_end = FALSE;
315                                 break;
316                         }
317                 }
318                 if (tokens[i][0] == '#' && g_ascii_isdigit(tokens[i][1])) {
319                         /* Numeric value beginning with # is a new timestamp value */
320                         timestamp = strtoull(tokens[i] + 1, NULL, 10);
321
322                         if (inc->downsample > 1)
323                                 timestamp /= inc->downsample;
324
325                         /*
326                          * Skip < 0 => skip until first timestamp.
327                          * Skip = 0 => don't skip
328                          * Skip > 0 => skip until timestamp >= skip.
329                          */
330                         if (inc->skip < 0) {
331                                 inc->skip = timestamp;
332                                 prev_timestamp = timestamp;
333                         } else if (inc->skip > 0 && timestamp < (uint64_t)inc->skip) {
334                                 prev_timestamp = inc->skip;
335                         } else if (timestamp == prev_timestamp) {
336                                 /* Ignore repeated timestamps (e.g. sigrok outputs these) */
337                         } else {
338                                 if (inc->compress != 0 && timestamp - prev_timestamp > inc->compress) {
339                                         /* Compress long idle periods */
340                                         prev_timestamp = timestamp - inc->compress;
341                                 }
342
343                                 sr_dbg("New timestamp: %" PRIu64, timestamp);
344
345                                 /* Generate samples from prev_timestamp up to timestamp - 1. */
346                                 send_samples(in->sdi, prev_values, timestamp - prev_timestamp);
347                                 prev_timestamp = timestamp;
348                         }
349                 } else if (tokens[i][0] == '$' && tokens[i][1] != '\0') {
350                         /*
351                          * This is probably a $dumpvars, $comment or similar.
352                          * $dump* contain useful data.
353                          */
354                         if (g_strcmp0(tokens[i], "$dumpvars") == 0
355                                         || g_strcmp0(tokens[i], "$dumpon") == 0
356                                         || g_strcmp0(tokens[i], "$dumpoff") == 0
357                                         || g_strcmp0(tokens[i], "$end") == 0) {
358                                 /* Ignore, parse contents as normally. */
359                         } else {
360                                 /* Ignore this and future lines until $end. */
361                                 inc->skip_until_end = TRUE;
362                                 break;
363                         }
364                 } else if (strchr("bBrR", tokens[i][0]) != NULL) {
365                         /* A vector value, not supported yet. */
366                         break;
367                 } else if (strchr("01xXzZ", tokens[i][0]) != NULL) {
368                         /* A new 1-bit sample value */
369                         bit = (tokens[i][0] == '1');
370
371                         /*
372                          * The identifier is either the next character, or, if
373                          * there was whitespace after the bit, the next token.
374                          */
375                         if (tokens[i][1] == '\0') {
376                                 if (!tokens[++i])
377                                         /* Missing identifier */
378                                         continue;
379                         } else {
380                                 for (j = 1; tokens[i][j]; j++)
381                                         tokens[i][j - 1] = tokens[i][j];
382                                 tokens[i][j - 1] = '\0';
383                         }
384
385                         for (j = 0, l = inc->channels; j < inc->channelcount && l; j++, l = l->next) {
386                                 vcd_ch = l->data;
387                                 if (g_strcmp0(tokens[i], vcd_ch->identifier) == 0) {
388                                         /* Found our channel */
389                                         if (bit)
390                                                 prev_values |= (uint64_t)1 << j;
391                                         else
392                                                 prev_values &= ~((uint64_t)1 << j);
393                                         break;
394                                 }
395                         }
396                         if (j == inc->channelcount)
397                                 sr_dbg("Did not find channel for identifier '%s'.", tokens[i]);
398                 } else {
399                         sr_warn("Skipping unknown token '%s'.", tokens[i]);
400                 }
401         }
402         g_strfreev(tokens);
403 }
404
405 static int init(struct sr_input *in, GHashTable *options)
406 {
407         struct sr_channel *ch;
408         int num_channels, i;
409         char name[16];
410         struct context *inc;
411
412         num_channels = g_variant_get_int32(g_hash_table_lookup(options, "numchannels"));
413         if (num_channels < 1) {
414                 sr_err("Invalid value for numchannels: must be at least 1.");
415                 return SR_ERR_ARG;
416         }
417         if (num_channels > 64) {
418                 sr_err("No more than 64 channels supported.");
419                 return SR_ERR_ARG;
420         }
421         inc = in->priv = g_malloc0(sizeof(struct context));
422         inc->maxchannels = num_channels;
423
424         inc->downsample = g_variant_get_int32(g_hash_table_lookup(options, "downsample"));
425         if (inc->downsample < 1)
426                 inc->downsample = 1;
427
428         inc->compress = g_variant_get_int32(g_hash_table_lookup(options, "compress"));
429         inc->skip = g_variant_get_int32(g_hash_table_lookup(options, "skip"));
430         inc->skip /= inc->downsample;
431
432         in->sdi = sr_dev_inst_new(SR_ST_ACTIVE, NULL, NULL, NULL);
433         in->priv = inc;
434
435         for (i = 0; i < num_channels; i++) {
436                 snprintf(name, 16, "%d", i);
437                 ch = sr_channel_new(i, SR_CHANNEL_LOGIC, TRUE, name);
438                 in->sdi->channels = g_slist_append(in->sdi->channels, ch);
439         }
440
441         return SR_OK;
442 }
443
444 static gboolean have_header(GString *buf)
445 {
446         unsigned int pos;
447         char *p;
448
449         if (!(p = g_strstr_len(buf->str, buf->len, "$enddefinitions")))
450                 return FALSE;
451         pos = p - buf->str + 15;
452         while (pos < buf->len - 4 && g_ascii_isspace(buf->str[pos]))
453                 pos++;
454         if (!strncmp(buf->str + pos, "$end", 4))
455                 return TRUE;
456
457         return FALSE;
458 }
459
460 static int receive(struct sr_input *in, GString *buf)
461 {
462         struct sr_datafeed_packet packet;
463         struct sr_datafeed_meta meta;
464         struct sr_config *src;
465         struct context *inc;
466         uint64_t samplerate;
467         char *p;
468
469         g_string_append_len(in->buf, buf->str, buf->len);
470
471         inc = in->priv;
472         if (!inc->got_header) {
473                 if (!have_header(in->buf))
474                         return SR_OK;
475                 if (!parse_header(in, in->buf) != SR_OK)
476                         /* There was a header in there, but it was malformed. */
477                         return SR_ERR;
478
479                 in->sdi_ready = TRUE;
480                 /* sdi is ready, notify frontend. */
481                 return SR_OK;
482         }
483
484         if (!inc->started) {
485                 std_session_send_df_header(in->sdi, LOG_PREFIX);
486
487                 packet.type = SR_DF_META;
488                 packet.payload = &meta;
489                 samplerate = inc->samplerate / inc->downsample;
490                 src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(samplerate));
491                 meta.config = g_slist_append(NULL, src);
492                 sr_session_send(in->sdi, &packet);
493                 sr_config_free(src);
494
495                 inc->started = TRUE;
496         }
497
498         while ((p = g_strrstr_len(in->buf->str, in->buf->len, "\n"))) {
499                 *p = '\0';
500                 g_strstrip(in->buf->str);
501                 if (in->buf->str[0] != '\0')
502                         parse_contents(in, in->buf->str);
503                 g_string_erase(in->buf, 0, p - in->buf->str + 1);
504         }
505
506         return SR_OK;
507 }
508
509 static int cleanup(struct sr_input *in)
510 {
511         struct sr_datafeed_packet packet;
512         struct context *inc;
513
514         inc = in->priv;
515         if (inc->started) {
516                 /* End of stream. */
517                 packet.type = SR_DF_END;
518                 sr_session_send(in->sdi, &packet);
519         }
520
521         g_slist_free_full(inc->channels, free_channel);
522
523         return SR_OK;
524 }
525
526 static struct sr_option options[] = {
527         { "numchannels", "Number of channels", "Number of channels", NULL, NULL },
528         { "skip", "Skip", "Skip until timestamp", NULL, NULL },
529         { "downsample", "Downsample", "Divide samplerate by factor", NULL, NULL },
530         { "compress", "Compress", "Compress idle periods longer than this value", NULL, NULL },
531         ALL_ZERO
532 };
533
534 static struct sr_option *get_options(void)
535 {
536         if (!options[0].def) {
537                 options[0].def = g_variant_ref_sink(g_variant_new_int32(DEFAULT_NUM_CHANNELS));
538                 options[1].def = g_variant_ref_sink(g_variant_new_int32(-1));
539                 options[2].def = g_variant_ref_sink(g_variant_new_int32(1));
540                 options[3].def = g_variant_ref_sink(g_variant_new_int32(0));
541         }
542
543         return options;
544 }
545
546 SR_PRIV struct sr_input_module input_vcd = {
547         .id = "vcd",
548         .name = "VCD",
549         .desc = "Value Change Dump",
550         .metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
551         .options = get_options,
552         .format_match = format_match,
553         .init = init,
554         .receive = receive,
555         .cleanup = cleanup,
556 };