You should have received a copy of the GNU General Public License
along with simavr. If not, see <http://www.gnu.org/licenses/>.
*/
-
+#define _GNU_SOURCE /* for strdupa */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
-#include<inttypes.h>
+#include <inttypes.h>
+#include <ctype.h>
#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:
+ * #<absolute timestamp>[\n][<value x/0/1><signal alias character>|
+ * b[x/0/1]?<space><signal alias character]+
+ * For example:
+ * #1234 1' 0$
+ * Or:
+ * #1234
+ * b1101x1 '
+ * 0$
+ *
+ * This function tries to handle this transparently, and pushes the
+ * signal/values into the FIFO for processing by the timer when
+ * convenient.
+ * NOTE: Add 'floating' support here. Also, FIX THE TIMING.
+ */
+static avr_cycle_count_t
+avr_vcd_input_parse_line(
+ avr_vcd_t * vcd,
+ argv_p v )
{
- avr_vcd_stop(vcd);
+ avr_cycle_count_t res = 0;
+ int vi = 0;
+
+ if (v->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 <four-character ioctl>[_<IRQ index>] */
+ 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;
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;
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;
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;
}
-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");
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;
#include <stdio.h>
#include "sim_irq.h"
+#include "fifo_declare.h"
#ifdef __cplusplus
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
};