mirror of
https://github.com/buserror/mii_emu.git
synced 2024-11-25 05:32:20 +00:00
e231ecb762
Tons of changes, see changelog Signed-off-by: Michel Pollet <buserror@gmail.com>
547 lines
11 KiB
C
547 lines
11 KiB
C
/*
|
|
* mii_vcd.c
|
|
*
|
|
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <inttypes.h>
|
|
#include "mii.h"
|
|
#include "mii_vcd.h"
|
|
|
|
DEFINE_FIFO(mii_vcd_log_t, mii_vcd_fifo);
|
|
|
|
#define strdupa(__s) strcpy(alloca(strlen(__s)+1), __s)
|
|
|
|
static void
|
|
_mii_vcd_notify(
|
|
struct mii_signal_t * sig,
|
|
uint32_t value,
|
|
void * param);
|
|
|
|
int
|
|
mii_vcd_init(
|
|
struct mii_t * mii,
|
|
const char * filename,
|
|
mii_vcd_t * vcd,
|
|
uint32_t cycle_to_nsec)
|
|
{
|
|
memset(vcd, 0, sizeof(mii_vcd_t));
|
|
vcd->mii = mii;
|
|
vcd->filename = strdup(filename);
|
|
vcd->cycle_to_nsec = cycle_to_nsec; // mii_usec_to_cycles(vcd->mii, period);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
mii_vcd_close(
|
|
mii_vcd_t * vcd)
|
|
{
|
|
mii_vcd_stop(vcd);
|
|
|
|
/* dispose of any link and hooks */
|
|
for (int i = 0; i < vcd->signal_count; i++) {
|
|
mii_vcd_signal_t * s = &vcd->signal[i];
|
|
|
|
mii_free_signal(&s->sig, 1);
|
|
}
|
|
if (vcd->filename) {
|
|
free(vcd->filename);
|
|
vcd->filename = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static char *
|
|
_mii_vcd_get_float_signal_text(
|
|
mii_vcd_signal_t * s,
|
|
char * out)
|
|
{
|
|
char * dst = out;
|
|
|
|
if (s->size > 1)
|
|
*dst++ = 'b';
|
|
|
|
for (int i = s->size; i > 0; i--)
|
|
*dst++ = 'x';
|
|
if (s->size > 1)
|
|
*dst++ = ' ';
|
|
*dst++ = s->alias;
|
|
*dst = 0;
|
|
return out;
|
|
}
|
|
|
|
static char *
|
|
_mii_vcd_get_signal_text(
|
|
mii_vcd_signal_t * s,
|
|
char * out,
|
|
uint32_t value)
|
|
{
|
|
char * dst = out;
|
|
|
|
if (s->size > 1)
|
|
*dst++ = 'b';
|
|
|
|
for (int i = s->size; i > 0; i--)
|
|
*dst++ = value & (1 << (i-1)) ? '1' : '0';
|
|
if (s->size > 1)
|
|
*dst++ = ' ';
|
|
*dst++ = s->alias;
|
|
*dst = 0;
|
|
return out;
|
|
}
|
|
|
|
#define mii_cycles_to_nsec(mii, c) ((c) * vcd->cycle_to_nsec)
|
|
|
|
/* Write queued output to the VCD file. */
|
|
|
|
static void
|
|
mii_vcd_flush_log(
|
|
mii_vcd_t * vcd)
|
|
{
|
|
uint64_t seen = 0;
|
|
uint64_t oldbase = 0; // make sure it's different
|
|
char out[48];
|
|
|
|
if (mii_vcd_fifo_isempty(&vcd->log) || !vcd->output)
|
|
return;
|
|
|
|
while (!mii_vcd_fifo_isempty(&vcd->log)) {
|
|
mii_vcd_log_t l = mii_vcd_fifo_read(&vcd->log);
|
|
// 10ns base -- 100MHz should be enough
|
|
uint64_t base = mii_cycles_to_nsec(vcd->mii, l.when - vcd->start) / 10;
|
|
|
|
/*
|
|
* 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 || !seen) {
|
|
seen = 0;
|
|
fprintf(vcd->output, "#%" PRIu64 "\n", base);
|
|
oldbase = base;
|
|
}
|
|
// mark this trace as seen for this timestamp
|
|
seen |= (1 << l.sigindex);
|
|
fprintf(vcd->output, "%s\n",
|
|
l.floating ?
|
|
_mii_vcd_get_float_signal_text(
|
|
&vcd->signal[l.sigindex],
|
|
out) :
|
|
_mii_vcd_get_signal_text(
|
|
&vcd->signal[l.sigindex],
|
|
out, l.value));
|
|
}
|
|
}
|
|
|
|
|
|
/* Called for an IRQ that is being recorded. */
|
|
|
|
static void
|
|
_mii_vcd_notify(
|
|
struct mii_signal_t * sig,
|
|
uint32_t value,
|
|
void * param)
|
|
{
|
|
mii_vcd_t * vcd = (mii_vcd_t *)param;
|
|
|
|
if (!vcd->output) {
|
|
printf("%s: no output\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
mii_vcd_signal_t * s = (mii_vcd_signal_t*)sig;
|
|
mii_vcd_log_t l = {
|
|
.sigindex = s->sig.sig,
|
|
.when = vcd->cycle,
|
|
.value = value,
|
|
.floating = !!(mii_signal_get_flags(sig) & SIG_FLAG_FLOATING),
|
|
};
|
|
if (mii_vcd_fifo_isfull(&vcd->log)) {
|
|
// printf("%s FIFO Overload, flushing!\n", __func__);
|
|
/* Decrease period by a quarter, for next time */
|
|
// vcd->period -= vcd->period >> 2;
|
|
mii_vcd_flush_log(vcd);
|
|
}
|
|
mii_vcd_fifo_write(&vcd->log, l);
|
|
}
|
|
|
|
/* Register an IRQ whose value is to be logged. */
|
|
|
|
int
|
|
mii_vcd_add_signal(
|
|
mii_vcd_t * vcd,
|
|
mii_signal_t * signal_sig,
|
|
uint signal_bit_size,
|
|
const char * name )
|
|
{
|
|
if (vcd->signal_count == MII_VCD_MAX_SIGNALS) {
|
|
printf(" %s: unable add signal '%s'\n", __func__, name);
|
|
return -1;
|
|
}
|
|
int index = vcd->signal_count++;
|
|
mii_vcd_signal_t * s = &vcd->signal[index];
|
|
strncpy(s->name, name, sizeof(s->name));
|
|
s->size = signal_bit_size;
|
|
s->alias = ' ' + vcd->signal_count ;
|
|
|
|
/* manufacture a nice IRQ name */
|
|
int l = strlen(name);
|
|
char iname[10 + l + 1];
|
|
if (signal_bit_size > 1)
|
|
sprintf(iname, "%d>vcd.%s", signal_bit_size, name);
|
|
else
|
|
sprintf(iname, ">vcd.%s", name);
|
|
|
|
const char * names[1] = { iname };
|
|
mii_init_signal(&vcd->mii->sig_pool, &s->sig, index, 1, names);
|
|
mii_signal_register_notify(&s->sig, _mii_vcd_notify, vcd);
|
|
|
|
mii_connect_signal(signal_sig, &s->sig);
|
|
return 0;
|
|
}
|
|
|
|
/* Open the VCD output file and write header. Does nothing for input. */
|
|
|
|
int
|
|
mii_vcd_start(
|
|
mii_vcd_t * vcd)
|
|
{
|
|
time_t now;
|
|
|
|
vcd->start = 0;
|
|
mii_vcd_fifo_reset(&vcd->log);
|
|
|
|
if (vcd->output)
|
|
mii_vcd_stop(vcd);
|
|
vcd->output = fopen(vcd->filename, "w");
|
|
if (vcd->output == NULL) {
|
|
perror(vcd->filename);
|
|
return -1;
|
|
}
|
|
|
|
time(&now);
|
|
fprintf(vcd->output, "$date %s$end\n", ctime(&now));
|
|
fprintf(vcd->output,
|
|
"$version Simmii " "1.0.0" " $end\n");
|
|
fprintf(vcd->output, "$timescale 10ns $end\n"); // 10ns base, aka 100MHz
|
|
fprintf(vcd->output, "$scope module logic $end\n");
|
|
|
|
for (int i = 0; i < vcd->signal_count; i++) {
|
|
fprintf(vcd->output, "$var wire %d %c %s $end\n",
|
|
vcd->signal[i].size, vcd->signal[i].alias, vcd->signal[i].name);
|
|
}
|
|
|
|
fprintf(vcd->output, "$upscope $end\n");
|
|
fprintf(vcd->output, "$enddefinitions $end\n");
|
|
|
|
fprintf(vcd->output, "$dumpvars\n");
|
|
for (int i = 0; i < vcd->signal_count; i++) {
|
|
mii_vcd_signal_t * s = &vcd->signal[i];
|
|
char out[48];
|
|
fprintf(vcd->output, "%s\n",
|
|
_mii_vcd_get_float_signal_text(s, out));
|
|
}
|
|
fprintf(vcd->output, "$end\n");
|
|
// mii_cycle_timer_register(vcd->mii, vcd->period, _mii_vcd_timer, vcd);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
mii_vcd_stop(
|
|
mii_vcd_t * vcd)
|
|
{
|
|
// mii_cycle_timer_cancel(vcd->mii, _mii_vcd_timer, vcd);
|
|
|
|
mii_vcd_flush_log(vcd);
|
|
|
|
if (vcd->output)
|
|
fclose(vcd->output);
|
|
vcd->output = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// internal structure for a hook, never seen by the notify procs
|
|
typedef struct mii_signal_hook_t {
|
|
struct mii_signal_hook_t * next;
|
|
int busy; // prevent reentrance of callbacks
|
|
|
|
struct mii_signal_t * chain; // raise the IRQ on this too - optional if "notify" is on
|
|
mii_signal_notify_t notify; // called when IRQ is raised - optional if "chain" is on
|
|
void * param; // "notify" parameter
|
|
} mii_signal_hook_t;
|
|
|
|
static void
|
|
_mii_signal_pool_add(
|
|
mii_signal_pool_t * pool,
|
|
mii_signal_t * sig)
|
|
{
|
|
uint insert = 0;
|
|
/* lookup a slot */
|
|
for (; insert < pool->count && pool->sig[insert]; insert++)
|
|
;
|
|
if (insert == pool->count) {
|
|
if ((pool->count & 0xf) == 0) {
|
|
pool->sig = (mii_signal_t**)realloc(pool->sig,
|
|
(pool->count + 16) * sizeof(mii_signal_t *));
|
|
}
|
|
pool->count++;
|
|
}
|
|
pool->sig[insert] = sig;
|
|
sig->pool = pool;
|
|
}
|
|
|
|
static void
|
|
_mii_signal_pool_remove(
|
|
mii_signal_pool_t * pool,
|
|
mii_signal_t * sig)
|
|
{
|
|
for (uint i = 0; i < pool->count; i++)
|
|
if (pool->sig[i] == sig) {
|
|
pool->sig[i] = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
mii_init_signal(
|
|
mii_signal_pool_t * pool,
|
|
mii_signal_t * sig,
|
|
uint32_t base,
|
|
uint32_t count,
|
|
const char ** names /* optional */)
|
|
{
|
|
memset(sig, 0, sizeof(mii_signal_t) * count);
|
|
|
|
for (uint i = 0; i < count; i++) {
|
|
sig[i].sig = base + i;
|
|
sig[i].flags = SIG_FLAG_INIT;
|
|
if (pool)
|
|
_mii_signal_pool_add(pool, &sig[i]);
|
|
if (names && names[i])
|
|
sig[i].name = strdup(names[i]);
|
|
else {
|
|
printf("WARNING %s() with NULL name for sig %d.\n",
|
|
__func__, sig[i].sig);
|
|
}
|
|
}
|
|
}
|
|
|
|
mii_signal_t *
|
|
mii_alloc_signal(
|
|
mii_signal_pool_t * pool,
|
|
uint32_t base,
|
|
uint32_t count,
|
|
const char ** names /* optional */)
|
|
{
|
|
mii_signal_t * sig = (mii_signal_t*)malloc(sizeof(mii_signal_t) * count);
|
|
mii_init_signal(pool, sig, base, count, names);
|
|
for (uint i = 0; i < count; i++)
|
|
sig[i].flags |= SIG_FLAG_ALLOC;
|
|
return sig;
|
|
}
|
|
|
|
static mii_signal_hook_t *
|
|
_mii_alloc_signal_hook(
|
|
mii_signal_t * sig)
|
|
{
|
|
mii_signal_hook_t *hook = malloc(sizeof(mii_signal_hook_t));
|
|
memset(hook, 0, sizeof(mii_signal_hook_t));
|
|
hook->next = sig->hook;
|
|
sig->hook = hook;
|
|
return hook;
|
|
}
|
|
|
|
void
|
|
mii_free_signal(
|
|
mii_signal_t * sig,
|
|
uint32_t count)
|
|
{
|
|
if (!sig || !count)
|
|
return;
|
|
for (uint i = 0; i < count; i++) {
|
|
mii_signal_t * iq = sig + i;
|
|
if (iq->pool)
|
|
_mii_signal_pool_remove(iq->pool, iq);
|
|
if (iq->name)
|
|
free((char*)iq->name);
|
|
iq->name = NULL;
|
|
// purge hooks
|
|
mii_signal_hook_t *hook = iq->hook;
|
|
while (hook) {
|
|
mii_signal_hook_t * next = hook->next;
|
|
free(hook);
|
|
hook = next;
|
|
}
|
|
iq->hook = NULL;
|
|
}
|
|
// if that sig list was allocated by us, free it
|
|
if (sig->flags & SIG_FLAG_ALLOC)
|
|
free(sig);
|
|
}
|
|
|
|
void
|
|
mii_signal_register_notify(
|
|
mii_signal_t * sig,
|
|
mii_signal_notify_t notify,
|
|
void * param)
|
|
{
|
|
if (!sig || !notify)
|
|
return;
|
|
|
|
mii_signal_hook_t *hook = sig->hook;
|
|
while (hook) {
|
|
if (hook->notify == notify && hook->param == param)
|
|
return; // already there
|
|
hook = hook->next;
|
|
}
|
|
hook = _mii_alloc_signal_hook(sig);
|
|
hook->notify = notify;
|
|
hook->param = param;
|
|
}
|
|
|
|
void
|
|
mii_signal_unregister_notify(
|
|
mii_signal_t * sig,
|
|
mii_signal_notify_t notify,
|
|
void * param)
|
|
{
|
|
mii_signal_hook_t *hook, *prev;
|
|
if (!sig || !notify)
|
|
return;
|
|
|
|
hook = sig->hook;
|
|
prev = NULL;
|
|
while (hook) {
|
|
if (hook->notify == notify && hook->param == param) {
|
|
if ( prev )
|
|
prev->next = hook->next;
|
|
else
|
|
sig->hook = hook->next;
|
|
free(hook);
|
|
return;
|
|
}
|
|
prev = hook;
|
|
hook = hook->next;
|
|
}
|
|
}
|
|
|
|
void
|
|
mii_raise_signal_float(
|
|
mii_signal_t * sig,
|
|
uint32_t value,
|
|
int floating)
|
|
{
|
|
if (!sig)
|
|
return ;
|
|
uint32_t output = (sig->flags & SIG_FLAG_NOT) ? !value : value;
|
|
// if value is the same but it's the first time, raise it anyway
|
|
if (sig->value == output &&
|
|
(sig->flags & SIG_FLAG_FILTERED) && !(sig->flags & SIG_FLAG_INIT))
|
|
return;
|
|
sig->flags &= ~(SIG_FLAG_INIT | SIG_FLAG_FLOATING);
|
|
if (floating)
|
|
sig->flags |= SIG_FLAG_FLOATING;
|
|
mii_signal_hook_t *hook = sig->hook;
|
|
while (hook) {
|
|
mii_signal_hook_t * next = hook->next;
|
|
// prevents reentrance / endless calling loops
|
|
if (hook->busy == 0) {
|
|
hook->busy++;
|
|
if (hook->notify)
|
|
hook->notify(sig, output, hook->param);
|
|
if (hook->chain)
|
|
mii_raise_signal_float(hook->chain, output, floating);
|
|
hook->busy--;
|
|
}
|
|
hook = next;
|
|
}
|
|
// the value is set after the callbacks are called, so the callbacks
|
|
// can themselves compare for old/new values between their parameter
|
|
// they are passed (new value) and the previous sig->value
|
|
sig->value = output;
|
|
}
|
|
|
|
void
|
|
mii_raise_signal(
|
|
mii_signal_t * sig,
|
|
uint32_t value)
|
|
{
|
|
mii_raise_signal_float(sig, value, !!(sig->flags & SIG_FLAG_FLOATING));
|
|
}
|
|
|
|
void
|
|
mii_connect_signal(
|
|
mii_signal_t * src,
|
|
mii_signal_t * dst)
|
|
{
|
|
if (!src || !dst || src == dst) {
|
|
fprintf(stderr, "error: %s invalid sig %p/%p", __func__, src, dst);
|
|
return;
|
|
}
|
|
mii_signal_hook_t *hook = src->hook;
|
|
while (hook) {
|
|
if (hook->chain == dst)
|
|
return; // already there
|
|
hook = hook->next;
|
|
}
|
|
hook = _mii_alloc_signal_hook(src);
|
|
hook->chain = dst;
|
|
}
|
|
|
|
void
|
|
mii_unconnect_signal(
|
|
mii_signal_t * src,
|
|
mii_signal_t * dst)
|
|
{
|
|
mii_signal_hook_t *hook, *prev;
|
|
|
|
if (!src || !dst || src == dst) {
|
|
fprintf(stderr, "error: %s invalid sig %p/%p", __func__, src, dst);
|
|
return;
|
|
}
|
|
hook = src->hook;
|
|
prev = NULL;
|
|
while (hook) {
|
|
if (hook->chain == dst) {
|
|
if ( prev )
|
|
prev->next = hook->next;
|
|
else
|
|
src->hook = hook->next;
|
|
free(hook);
|
|
return;
|
|
}
|
|
prev = hook;
|
|
hook = hook->next;
|
|
}
|
|
}
|
|
|
|
uint8_t
|
|
mii_signal_get_flags(
|
|
mii_signal_t * sig )
|
|
{
|
|
return sig->flags;
|
|
}
|
|
|
|
void
|
|
mii_signal_set_flags(
|
|
mii_signal_t * sig,
|
|
uint8_t flags )
|
|
{
|
|
sig->flags = flags;
|
|
}
|