From 4b8b599fa8544cb5a9d0adfc235e256e6b8548fb Mon Sep 17 00:00:00 2001 From: Michel Pollet Date: Thu, 23 Feb 2017 10:53:55 +0000 Subject: [PATCH] vcd: Extensive update; added support for VCD Input Massive update here. Now allow reading from a VCD file, as long as the traces are named in a way that allows us to connect an IRQ. Typically, you can send pin values to for example PORTB5 by naming the trace 'iogB_5' -- you can use sigrock for example to capture traces from real hardware, and 'replay' it in simavr. Also since I was there, reworked the whole module to use a 'proper' FIFO and simplified the output generation too. There are a few 'TODO' left, but it's functional. Signed-off-by: Michel Pollet --- simavr/sim/sim_vcd_file.c | 457 ++++++++++++++++++++++++++++++++------ simavr/sim/sim_vcd_file.h | 93 +++++--- 2 files changed, 451 insertions(+), 99 deletions(-) diff --git a/simavr/sim/sim_vcd_file.c b/simavr/sim/sim_vcd_file.c index d710967..18ae4b1 100644 --- a/simavr/sim/sim_vcd_file.c +++ b/simavr/sim/sim_vcd_file.c @@ -21,70 +21,326 @@ You should have received a copy of the GNU General Public License along with simavr. If not, see . */ - +#define _GNU_SOURCE /* for strdupa */ #include #include #include -#include +#include +#include #include "sim_vcd_file.h" #include "sim_avr.h" #include "sim_time.h" - -void _avr_vcd_notify(struct avr_irq_t * irq, uint32_t value, void * param); - -int avr_vcd_init(struct avr_t * avr, const char * filename, avr_vcd_t * vcd, uint32_t period) +#include "sim_utils.h" + +DEFINE_FIFO(avr_vcd_log_t, avr_vcd_fifo); + +static void +_avr_vcd_notify( + struct avr_irq_t * irq, + uint32_t value, + void * param); + +int +avr_vcd_init( + struct avr_t * avr, + const char * filename, + avr_vcd_t * vcd, + uint32_t period) { memset(vcd, 0, sizeof(avr_vcd_t)); vcd->avr = avr; - strncpy(vcd->filename, filename, sizeof(vcd->filename)); + vcd->filename = strdup(filename); vcd->period = avr_usec_to_cycles(vcd->avr, period); - // No longer initializes the IRQs here, they can be initialized on the - // fly when traces are added - return 0; } -void avr_vcd_close(avr_vcd_t * vcd) +/* + * Parse a VCD 'timing' line. The lines are assumed to be: + * #[\n][| + * b[x/0/1]?argc == 0) + return res; + + if (v->argv[0][0] == '#') { + res = atoll(v->argv[0] + 1) * vcd->vcd_to_us; + vcd->start = vcd->period; + vcd->period = res; + vi++; + } + for (int i = vi; i < v->argc; i++) { + char * a = v->argv[i]; + uint32_t val = 0; + int floating = 0; + char name = 0; + int sigindex = -1; + + if (*a == 'b') + a++; + while (*a) { + if (*a == 'x') { + val <<= 1; + floating |= (floating << 1) | 1; + } else if (*a == '0' || *a == '1') { + val = (val << 1) | (*a - '0'); + floating <<= 1; + } else { + name = *a; + break; + } + a++; + } + if (!name && (i < v->argc - 1)) { + const char *n = v->argv[i+1]; + if (strlen(n) == 1) { + // we've got a name, it was not attached + name = *n; + i++; // skip that one + } + } + if (name) { + for (int si = 0; + si < vcd->signal_count && + sigindex == -1; si++) { + if (vcd->signal[si].alias == name) + sigindex = si; + } + } + if (sigindex == -1) { + printf("Signal name '%c' value %x not found\n", + name? name : '?', val); + continue; + } + avr_vcd_log_t e = { + .when = vcd->period, + .sigindex = sigindex, + .value = val, + }; + avr_vcd_fifo_write(&vcd->log, e); + } + return res; } -void _avr_vcd_notify(struct avr_irq_t * irq, uint32_t value, void * param) +/* + * Read some signals from the file and fill the FIFO with it, we read + * a completely arbitrary amount of stuff to fill the FIFO reasonably well + */ +static int +avr_vcd_input_read( + avr_vcd_t * vcd ) { - avr_vcd_t * vcd = (avr_vcd_t *)param; - if (!vcd->output) - return; + char line[1024]; + + while (fgets(line, sizeof(line), vcd->input) && + avr_vcd_fifo_get_read_size(&vcd->log) < 128) { + if (!line[0]) // technically can't happen, but make sure next line works + continue; + vcd->input_line = argv_parse(vcd->input_line, line); + /* stop once we found a new timestamp */ + avr_vcd_input_parse_line(vcd, vcd->input_line); + } + return avr_vcd_fifo_isempty(&vcd->log); +} + +/* + * This is called when we need to change the state of one or more IRQ, + * so look in the FIFO to know 'our' stamp time, read as much as we can + * that is still on that same timestamp. + * When when the FIFO content has too far in the future, re-schedule the + * timer for that time and shoot of. + * Also try to top up the FIFO with new read stuff when it's drained + */ +static avr_cycle_count_t +_avr_vcd_input_timer( + struct avr_t * avr, + avr_cycle_count_t when, + void * param) +{ + avr_vcd_t * vcd = param; + + // get some more if needed + if (avr_vcd_fifo_get_read_size(&vcd->log) < (vcd->signal_count * 16)) + avr_vcd_input_read(vcd); + + if (avr_vcd_fifo_isempty(&vcd->log)) { + printf("%s DONE but why are we here?\n", __func__); + return 0; + } + + avr_vcd_log_t log = avr_vcd_fifo_read_at(&vcd->log, 0); + uint64_t stamp = log.when; + while (!avr_vcd_fifo_isempty(&vcd->log)) { + log = avr_vcd_fifo_read_at(&vcd->log, 0); + if (log.when != stamp) // leave those in the FIFO + break; + // we already have it + avr_vcd_fifo_read_offset(&vcd->log, 1); + avr_vcd_signal_p signal = &vcd->signal[log.sigindex]; + avr_raise_irq(&signal->irq, log.value); + } + + if (avr_vcd_fifo_isempty(&vcd->log)) { + AVR_LOG(vcd->avr, LOG_TRACE, + "%s Finished reading, ending simavr\n", + vcd->filename); + avr->state = cpu_Done; + return 0; + } + log = avr_vcd_fifo_read_at(&vcd->log, 0); + + when += avr_usec_to_cycles(avr, log.when - stamp); + + return when; +} - /* - * buffer starts empty, the first trace will resize it to AVR_VCD_LOG_CHUNK_SIZE, - * further growth will resize it accordingly. - */ - if (vcd->logindex >= vcd->logsize) { - vcd->logsize += AVR_VCD_LOG_CHUNK_SIZE; - vcd->log = (avr_vcd_log_p)realloc(vcd->log, vcd->logsize * sizeof(vcd->log[0])); - AVR_LOG(vcd->avr, LOG_TRACE, "%s trace buffer resized to %d\n", - __func__, (int)vcd->logsize); - if ((vcd->logsize / AVR_VCD_LOG_CHUNK_SIZE) == 8) { - AVR_LOG(vcd->avr, LOG_WARNING, "%s log size runnaway (%d) flush problem?\n", - __func__, (int)vcd->logsize); +int +avr_vcd_init_input( + struct avr_t * avr, + const char * filename, // filename to read + avr_vcd_t * vcd ) // vcd struct to initialize +{ + memset(vcd, 0, sizeof(avr_vcd_t)); + vcd->avr = avr; + vcd->filename = strdup(filename); + + vcd->input = fopen(vcd->filename, "r"); + if (!vcd->input) { + perror(filename); + return -1; + } + char line[1024]; + argv_p v = NULL; + + while (fgets(line, sizeof(line), vcd->input)) { + if (!line[0]) // technically can't happen, but make sure next line works + continue; + v = argv_parse(v, line); + + // we are done reading headers, got our first timestamp + if (v->line[0] == '#') { + vcd->start = 0; + avr_vcd_input_parse_line(vcd, v); + avr_cycle_timer_register_usec(vcd->avr, + vcd->period, _avr_vcd_input_timer, vcd); + break; } - if (!vcd->log) { - AVR_LOG(vcd->avr, LOG_ERROR, "%s log resizing, out of memory (%d)!\n", - __func__, (int)vcd->logsize); - vcd->logsize = 0; - return; + // ignore multiline stuff + if (v->line[0] != '$') + continue; + + const char * end = !strcmp(v->argv[v->argc - 1], "$end") ? + v->argv[v->argc - 1] : NULL; + const char *keyword = v->argv[0]; + + if (keyword == end) + keyword = NULL; + if (!keyword) + continue; + + if (!strcmp(keyword, "$timescale")) { + double cnt = 0; + vcd->vcd_to_us = 1; + char *si = v->argv[1]; + while (si && *si && isdigit(*si)) + cnt = (cnt * 10) + (*si++ - '0'); + while (*si == ' ') + si++; + if (!*si) + si = v->argv[2]; + // if (!strcmp(si, "ns")) // TODO: Check that, + // vcd->vcd_to_us = cnt; + // printf("cnt %dus; unit %s\n", (int)cnt, si); + } else if (!strcmp(keyword, "$var")) { + const char *name = v->argv[4]; + + vcd->signal[vcd->signal_count].alias = v->argv[3][0]; + vcd->signal[vcd->signal_count].size = atoi(v->argv[2]); + strncpy(vcd->signal[vcd->signal_count].name, name, + sizeof(vcd->signal[0].name)); + + vcd->signal_count++; } } - avr_vcd_signal_t * s = (avr_vcd_signal_t*)irq; - avr_vcd_log_t *l = &vcd->log[vcd->logindex++]; - l->signal = s; - l->when = vcd->avr->cycle; - l->value = value; + // reuse this one + vcd->input_line = v; + + for (int i = 0; i < vcd->signal_count; i++) { + AVR_LOG(vcd->avr, LOG_TRACE, "%s %2d '%c' %s : size %d\n", + __func__, i, + vcd->signal[i].alias, vcd->signal[i].name, + vcd->signal[i].size); + /* format is [_] */ + if (strlen(vcd->signal[i].name) >= 4) { + char *dup = strdupa(vcd->signal[i].name); + char *ioctl = strsep(&dup, "_"); + int index = 0; + if (dup) + index = atoi(dup); + if (strlen(ioctl) == 4) { + uint32_t ioc = AVR_IOCTL_DEF( + ioctl[0], ioctl[1], ioctl[2], ioctl[3]); + avr_irq_t * irq = avr_io_getirq(vcd->avr, ioc, index); + printf(" irq %p\n", irq); + if (irq) { + vcd->signal[i].irq.flags = IRQ_FLAG_INIT; + avr_connect_irq(&vcd->signal[i].irq, irq); + } else + AVR_LOG(vcd->avr, LOG_WARNING, + "%s IRQ was not found\n", + vcd->signal[i].name); + continue; + } + AVR_LOG(vcd->avr, LOG_WARNING, + "%s is an invalid IRQ format\n", + vcd->signal[i].name); + } + } + return 0; } +void +avr_vcd_close( + avr_vcd_t * vcd) +{ + avr_vcd_stop(vcd); + + /* dispose of any link and hooks */ + for (int i = 0; i < vcd->signal_count; i++) { + avr_vcd_signal_t * s = &vcd->signal[i]; + + avr_free_irq(&s->irq, 1); + } -static char * _avr_vcd_get_float_signal_text(avr_vcd_signal_t * s, char * out) + if (vcd->filename) { + free(vcd->filename); + vcd->filename = NULL; + } +} + +static char * +_avr_vcd_get_float_signal_text( + avr_vcd_signal_t * s, + char * out) { char * dst = out; @@ -100,7 +356,11 @@ static char * _avr_vcd_get_float_signal_text(avr_vcd_signal_t * s, char * out) return out; } -static char * _avr_vcd_get_signal_text(avr_vcd_signal_t * s, char * out, uint32_t value) +static char * +_avr_vcd_get_signal_text( + avr_vcd_signal_t * s, + char * out, + uint32_t value) { char * dst = out; @@ -116,7 +376,9 @@ static char * _avr_vcd_get_signal_text(avr_vcd_signal_t * s, char * out, uint32_ return out; } -static void avr_vcd_flush_log(avr_vcd_t * vcd) +static void +avr_vcd_flush_log( + avr_vcd_t * vcd) { #if AVR_VCD_MAX_SIGNALS > 32 uint64_t seen = 0; @@ -126,45 +388,84 @@ static void avr_vcd_flush_log(avr_vcd_t * vcd) uint64_t oldbase = 0; // make sure it's different char out[48]; - if (!vcd->logindex || !vcd->output) + if (avr_vcd_fifo_isempty(&vcd->log) || !vcd->output) return; -// printf("avr_vcd_flush_log %d\n", vcd->logindex); - - - for (uint32_t li = 0; li < vcd->logindex; li++) { - avr_vcd_log_t *l = &vcd->log[li]; - uint64_t base = avr_cycles_to_nsec(vcd->avr, l->when - vcd->start); // 1ns base - // if that trace was seen in this nsec already, we fudge the base time - // to make sure the new value is offset by one nsec, to make sure we get - // at least a small pulse on the waveform - // This is a bit of a fudge, but it is the only way to represent very - // short"pulses" that are still visible on the waveform. - if (base == oldbase && seen & (1 << l->signal->irq.irq)) + while (!avr_vcd_fifo_isempty(&vcd->log)) { + avr_vcd_log_t l = avr_vcd_fifo_read(&vcd->log); + // 1ns base + uint64_t base = avr_cycles_to_nsec(vcd->avr, l.when - vcd->start); + + /* + * if that trace was seen in this nsec already, we fudge the + * base time to make sure the new value is offset by one nsec, + * to make sure we get at least a small pulse on the waveform. + * + * This is a bit of a fudge, but it is the only way to represent + * very short "pulses" that are still visible on the waveform. + */ + if (base == oldbase && + (seen & (1 << l.sigindex))) base++; // this forces a new timestamp - if (base > oldbase || li == 0) { + if (base > oldbase || !seen) { seen = 0; fprintf(vcd->output, "#%" PRIu64 "\n", base); oldbase = base; } - seen |= (1 << l->signal->irq.irq); // mark this trace as seen for this timestamp - fprintf(vcd->output, "%s\n", _avr_vcd_get_signal_text(l->signal, out, l->value)); + // mark this trace as seen for this timestamp + seen |= (1 << l.sigindex); + fprintf(vcd->output, "%s\n", + _avr_vcd_get_signal_text(&vcd->signal[l.sigindex], + out, l.value)); } - vcd->logindex = 0; } -static avr_cycle_count_t _avr_vcd_timer(struct avr_t * avr, avr_cycle_count_t when, void * param) +static avr_cycle_count_t +_avr_vcd_timer( + struct avr_t * avr, + avr_cycle_count_t when, + void * param) { avr_vcd_t * vcd = param; avr_vcd_flush_log(vcd); return when + vcd->period; } -int avr_vcd_add_signal(avr_vcd_t * vcd, - avr_irq_t * signal_irq, - int signal_bit_size, - const char * name ) +static void +_avr_vcd_notify( + struct avr_irq_t * irq, + uint32_t value, + void * param) +{ + avr_vcd_t * vcd = (avr_vcd_t *)param; + + if (!vcd->output) + return; + + avr_vcd_signal_t * s = (avr_vcd_signal_t*)irq; + avr_vcd_log_t l = { + .sigindex = s->irq.irq, + .when = vcd->avr->cycle, + .value = value, + }; + if (avr_vcd_fifo_isfull(&vcd->log)) { + AVR_LOG(vcd->avr, LOG_WARNING, + "%s FIFO Overload, flushing!\n", + __func__); + /* Decrease period by a quarter, for next time */ + vcd->period -= vcd->period >> 2; + avr_vcd_flush_log(vcd); + } + avr_vcd_fifo_write(&vcd->log, l); +} + +int +avr_vcd_add_signal( + avr_vcd_t * vcd, + avr_irq_t * signal_irq, + int signal_bit_size, + const char * name ) { if (vcd->signal_count == AVR_VCD_MAX_SIGNALS) return -1; @@ -191,8 +492,20 @@ int avr_vcd_add_signal(avr_vcd_t * vcd, } -int avr_vcd_start(avr_vcd_t * vcd) +int +avr_vcd_start( + avr_vcd_t * vcd) { + vcd->start = vcd->avr->cycle; + avr_vcd_fifo_reset(&vcd->log); + + if (vcd->input) { + /* + * nothing to do here, the first cycle timer will take care + * if it. + */ + return 0; + } if (vcd->output) avr_vcd_stop(vcd); vcd->output = fopen(vcd->filename, "w"); @@ -216,21 +529,29 @@ int avr_vcd_start(avr_vcd_t * vcd) for (int i = 0; i < vcd->signal_count; i++) { avr_vcd_signal_t * s = &vcd->signal[i]; char out[48]; - fprintf(vcd->output, "%s\n", _avr_vcd_get_float_signal_text(s, out)); + fprintf(vcd->output, "%s\n", + _avr_vcd_get_float_signal_text(s, out)); } fprintf(vcd->output, "$end\n"); - vcd->logindex = 0; - vcd->start = vcd->avr->cycle; avr_cycle_timer_register(vcd->avr, vcd->period, _avr_vcd_timer, vcd); return 0; } -int avr_vcd_stop(avr_vcd_t * vcd) +int +avr_vcd_stop( + avr_vcd_t * vcd) { avr_cycle_timer_cancel(vcd->avr, _avr_vcd_timer, vcd); + avr_cycle_timer_cancel(vcd->avr, _avr_vcd_input_timer, vcd); avr_vcd_flush_log(vcd); + if (vcd->input_line) + free(vcd->input_line); + vcd->input_line = NULL; + if (vcd->input) + fclose(vcd->input); + vcd->input = NULL; if (vcd->output) fclose(vcd->output); vcd->output = NULL; diff --git a/simavr/sim/sim_vcd_file.h b/simavr/sim/sim_vcd_file.h index 0c6b77e..221081d 100644 --- a/simavr/sim/sim_vcd_file.h +++ b/simavr/sim/sim_vcd_file.h @@ -27,6 +27,7 @@ #include #include "sim_irq.h" +#include "fifo_declare.h" #ifdef __cplusplus extern "C" { @@ -34,62 +35,92 @@ extern "C" { /* * Value Change dump module for simavr. - * + * * This structure registers IRQ change hooks to various "source" IRQs - * and dumps their values (if changed) at certain intervals into the VCD file + * and dumps their values (if changed) at certain intervals into the VCD + * file. + * + * It can also do the reverse, load a VCD file generated by for example + * sigrock signal analyzer, and 'replay' digital input with the proper + * timing. + * + * TODO: Add support for 'looping' a VCD input. */ #define AVR_VCD_MAX_SIGNALS 64 typedef struct avr_vcd_signal_t { - avr_irq_t irq; // receiving IRQ - char alias; // vcd one character alias - int size; // in bits - char name[32]; // full human name -} avr_vcd_signal_t; + /* + * For VCD output this is the IRQ we receive new values from. + * For VCD input, this is the IRQ we broadcast the values to + */ + avr_irq_t irq; + char alias; // vcd one character alias + uint8_t size; // in bits + char name[32]; // full human name +} avr_vcd_signal_t, *avr_vcd_signal_p; typedef struct avr_vcd_log_t { - uint64_t when; - avr_vcd_signal_t * signal; - uint32_t value; + uint64_t when; + uint64_t sigindex : 8, // index in signal table + value : 32; } avr_vcd_log_t, *avr_vcd_log_p; -#define AVR_VCD_LOG_CHUNK_SIZE (4096 / sizeof(avr_vcd_signal_t)) +DECLARE_FIFO(avr_vcd_log_t, avr_vcd_fifo, 256); + +struct argv_t; typedef struct avr_vcd_t { struct avr_t * avr; // AVR we are attaching timers to.. - - char filename[74]; // output filename - FILE * output; - int signal_count; + char * filename; // .vcd filename + /* can be input OR output, not both */ + FILE * output; + FILE * input; + struct argv_t * input_line; + + int signal_count; avr_vcd_signal_t signal[AVR_VCD_MAX_SIGNALS]; - uint64_t period; - uint64_t start; + uint64_t start; + uint64_t period; // for output cycles + uint64_t vcd_to_us; // for input unit mapping - size_t logsize; - uint32_t logindex; - avr_vcd_log_p log; + avr_vcd_fifo_t log; } avr_vcd_t; // initializes a new VCD trace file, and returns zero if all is well -int avr_vcd_init(struct avr_t * avr, - const char * filename, // filename to write - avr_vcd_t * vcd, // vcd struct to initialize - uint32_t period ); // file flushing period is in usec -void avr_vcd_close(avr_vcd_t * vcd); +int +avr_vcd_init( + struct avr_t * avr, + const char * filename, // filename to write + avr_vcd_t * vcd, // vcd struct to initialize + uint32_t period ); // file flushing period is in usec +int +avr_vcd_init_input( + struct avr_t * avr, + const char * filename, // filename to read + avr_vcd_t * vcd ); // vcd struct to initialize +void +avr_vcd_close( + avr_vcd_t * vcd ); // Add a trace signal to the vcd file. Must be called before avr_vcd_start() -int avr_vcd_add_signal(avr_vcd_t * vcd, - avr_irq_t * signal_irq, - int signal_bit_size, - const char * name ); +int +avr_vcd_add_signal( + avr_vcd_t * vcd, + avr_irq_t * signal_irq, + int signal_bit_size, + const char * name ); // Starts recording the signal value into the file -int avr_vcd_start(avr_vcd_t * vcd); +int +avr_vcd_start( + avr_vcd_t * vcd); // stops recording signal values into the file -int avr_vcd_stop(avr_vcd_t * vcd); +int +avr_vcd_stop( + avr_vcd_t * vcd); #ifdef __cplusplus }; -- 2.39.5