Rodrigo Arias
cddda71cfb
These checks allows the emulator to abort at the moment in which two consecutive states are going to be emitted by the channel, showing the culpirit in the backtrace. Waiting for the check in chan_emit() hides the source of the problem.
431 lines
7.9 KiB
C
431 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2021 Barcelona Supercomputing Center (BSC)
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "chan.h"
|
|
|
|
#include <assert.h>
|
|
|
|
#include "emu.h"
|
|
#include "prv.h"
|
|
#include "utlist.h"
|
|
|
|
static void
|
|
chan_init(struct ovni_chan *chan, enum chan_track track, int row, int type, FILE *prv, int64_t *clock)
|
|
{
|
|
chan->n = 0;
|
|
chan->type = type;
|
|
chan->enabled = 0;
|
|
chan->badst = ST_NULL;
|
|
chan->ev = -1;
|
|
chan->prv = prv;
|
|
chan->clock = clock;
|
|
chan->t = *clock;
|
|
chan->row = row;
|
|
chan->dirty = 0;
|
|
chan->track = track;
|
|
chan->lastst = -1;
|
|
}
|
|
|
|
static void
|
|
mark_dirty(struct ovni_chan *chan, enum chan_dirty dirty)
|
|
{
|
|
assert(chan->dirty == CHAN_CLEAN);
|
|
assert(dirty != CHAN_CLEAN);
|
|
|
|
dbg("adding dirty chan %d to list\n", chan->id);
|
|
chan->dirty = dirty;
|
|
DL_APPEND(*chan->update_list, chan);
|
|
}
|
|
|
|
void
|
|
chan_th_init(struct ovni_ethread *th,
|
|
struct ovni_chan **update_list,
|
|
enum chan id,
|
|
enum chan_track track,
|
|
int init_st,
|
|
int enabled,
|
|
int dirty,
|
|
int row,
|
|
FILE *prv,
|
|
int64_t *clock)
|
|
{
|
|
struct ovni_chan *chan;
|
|
int prvth;
|
|
|
|
chan = &th->chan[id];
|
|
prvth = CHAN_PRV_TH(id);
|
|
|
|
chan_init(chan, track, row, prvth, prv, clock);
|
|
|
|
chan->id = id;
|
|
chan->thread = th;
|
|
chan->update_list = update_list;
|
|
chan->enabled = enabled;
|
|
chan->stack[chan->n++] = init_st;
|
|
if(dirty)
|
|
mark_dirty(chan, CHAN_DIRTY_ACTIVE);
|
|
}
|
|
|
|
void
|
|
chan_cpu_init(struct ovni_cpu *cpu,
|
|
struct ovni_chan **update_list,
|
|
enum chan id,
|
|
enum chan_track track,
|
|
int init_st,
|
|
int enabled,
|
|
int dirty,
|
|
int row,
|
|
FILE *prv,
|
|
int64_t *clock)
|
|
{
|
|
struct ovni_chan *chan;
|
|
int prvcpu;
|
|
|
|
chan = &cpu->chan[id];
|
|
prvcpu = CHAN_PRV_CPU(id);
|
|
|
|
chan_init(chan, track, row, prvcpu, prv, clock);
|
|
|
|
chan->id = id;
|
|
chan->cpu = cpu;
|
|
chan->update_list = update_list;
|
|
chan->enabled = enabled;
|
|
chan->stack[chan->n++] = init_st;
|
|
if(dirty)
|
|
mark_dirty(chan, CHAN_DIRTY_ACTIVE);
|
|
}
|
|
|
|
static void
|
|
chan_dump_update_list(struct ovni_chan *chan)
|
|
{
|
|
struct ovni_chan *c;
|
|
|
|
dbg("update list for chan %d at %p:\n",
|
|
chan->id, (void *) chan);
|
|
for(c = *chan->update_list; c; c = c->next)
|
|
{
|
|
dbg(" chan %d at %p\n", c->id, (void *) c);
|
|
}
|
|
}
|
|
|
|
void
|
|
chan_enable(struct ovni_chan *chan, int enabled)
|
|
{
|
|
/* Can be dirty */
|
|
|
|
dbg("chan_enable chan=%d enabled=%d\n", chan->id, enabled);
|
|
|
|
if(chan->enabled == enabled)
|
|
{
|
|
err("chan already in enabled=%d\n", enabled);
|
|
abort();
|
|
}
|
|
|
|
chan->enabled = enabled;
|
|
chan->t = *chan->clock;
|
|
|
|
/* Only append if not dirty */
|
|
if(!chan->dirty)
|
|
{
|
|
mark_dirty(chan, CHAN_DIRTY_ACTIVE);
|
|
}
|
|
else
|
|
{
|
|
dbg("already dirty chan %d: skip update list\n",
|
|
chan->id);
|
|
chan_dump_update_list(chan);
|
|
}
|
|
}
|
|
|
|
void
|
|
chan_disable(struct ovni_chan *chan)
|
|
{
|
|
chan_enable(chan, 0);
|
|
}
|
|
|
|
int
|
|
chan_is_enabled(struct ovni_chan *chan)
|
|
{
|
|
return chan->enabled;
|
|
}
|
|
|
|
void
|
|
chan_set(struct ovni_chan *chan, int st)
|
|
{
|
|
/* Can be dirty */
|
|
|
|
dbg("chan_set chan %d st=%d\n", chan->id, st);
|
|
|
|
assert(chan->enabled);
|
|
|
|
/* Only chan_set can set the 0 state */
|
|
if(st < 0)
|
|
{
|
|
err("chan_set: cannot set a negative state %d\n", st);
|
|
abort();
|
|
}
|
|
|
|
/* Don't enforce this check if we are dirty because the channel was
|
|
* just enabled; it may collide with a new state 0 set via chan_set()
|
|
* used by the tracking channels */
|
|
if(chan->dirty != CHAN_DIRTY_ACTIVE
|
|
&& chan->lastst >= 0
|
|
&& chan->lastst == st)
|
|
{
|
|
err("chan_set id=%d cannot emit the state %d twice\n",
|
|
chan->id, st);
|
|
abort();
|
|
}
|
|
|
|
if(chan->n == 0)
|
|
chan->n = 1;
|
|
|
|
chan->stack[chan->n - 1] = st;
|
|
chan->t = *chan->clock;
|
|
|
|
/* Only append if not dirty */
|
|
if(!chan->dirty)
|
|
{
|
|
mark_dirty(chan, CHAN_DIRTY_VALUE);
|
|
}
|
|
else
|
|
{
|
|
dbg("already dirty chan %d: skip update list\n",
|
|
chan->id);
|
|
chan_dump_update_list(chan);
|
|
}
|
|
}
|
|
|
|
void
|
|
chan_enable_and_set(struct ovni_chan *chan, int st)
|
|
{
|
|
chan_enable(chan, 1);
|
|
chan_set(chan, st);
|
|
}
|
|
|
|
void
|
|
chan_push(struct ovni_chan *chan, int st)
|
|
{
|
|
dbg("chan_push chan %d st=%d\n", chan->id, st);
|
|
|
|
assert(chan->enabled);
|
|
|
|
if(st <= 0)
|
|
{
|
|
err("chan_push: cannot push a non-positive state %d\n", st);
|
|
abort();
|
|
}
|
|
|
|
/* Cannot be dirty */
|
|
assert(chan->dirty == 0);
|
|
|
|
if(chan->lastst >= 0 && chan->lastst == st)
|
|
{
|
|
err("chan_push id=%d cannot emit the state %d twice\n",
|
|
chan->id, st);
|
|
abort();
|
|
}
|
|
|
|
if(chan->n >= MAX_CHAN_STACK)
|
|
{
|
|
err("channel stack full\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
chan->stack[chan->n++] = st;
|
|
chan->t = *chan->clock;
|
|
|
|
mark_dirty(chan, CHAN_DIRTY_VALUE);
|
|
}
|
|
|
|
int
|
|
chan_pop(struct ovni_chan *chan, int expected_st)
|
|
{
|
|
int st;
|
|
|
|
dbg("chan_pop chan %d expected_st=%d\n", chan->id, expected_st);
|
|
|
|
assert(chan->enabled);
|
|
|
|
/* Cannot be dirty */
|
|
assert(chan->dirty == 0);
|
|
|
|
if(chan->n <= 0)
|
|
{
|
|
err("channel empty\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
st = chan->stack[chan->n - 1];
|
|
|
|
if(expected_st >= 0 && st != expected_st)
|
|
{
|
|
err("unexpected channel state %d (expected %d)\n",
|
|
st, expected_st);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
chan->n--;
|
|
|
|
/* Take the current stack value */
|
|
st = chan_get_st(chan);
|
|
|
|
/* A st == 0 can be obtained when returning to the initial state */
|
|
if(st < 0)
|
|
{
|
|
err("chan_pop: obtained negative state %d from the stack\n", st);
|
|
abort();
|
|
}
|
|
|
|
if(chan->lastst >= 0 && chan->lastst == st)
|
|
{
|
|
err("chan_pop id=%d cannot emit the state %d twice\n",
|
|
chan->id, st);
|
|
abort();
|
|
}
|
|
|
|
chan->t = *chan->clock;
|
|
|
|
mark_dirty(chan, CHAN_DIRTY_VALUE);
|
|
|
|
return st;
|
|
}
|
|
|
|
void
|
|
chan_ev(struct ovni_chan *chan, int ev)
|
|
{
|
|
dbg("chan_ev chan %d ev=%d\n", chan->id, ev);
|
|
|
|
assert(chan->enabled);
|
|
|
|
/* Cannot be dirty */
|
|
assert(chan->dirty == 0);
|
|
|
|
if(ev <= 0)
|
|
{
|
|
err("chan_ev: cannot emit non-positive state %d\n", ev);
|
|
abort();
|
|
}
|
|
|
|
if(chan->lastst >= 0 && chan->lastst == ev)
|
|
{
|
|
err("chan_ev id=%d cannot emit the state %d twice\n",
|
|
chan->id, ev);
|
|
abort();
|
|
}
|
|
|
|
chan->ev = ev;
|
|
chan->t = *chan->clock;
|
|
|
|
mark_dirty(chan, CHAN_DIRTY_VALUE);
|
|
}
|
|
|
|
int
|
|
chan_get_st(struct ovni_chan *chan)
|
|
{
|
|
if(chan->enabled == 0)
|
|
return chan->badst;
|
|
|
|
if(chan->n == 0)
|
|
return 0;
|
|
|
|
assert(chan->n > 0);
|
|
return chan->stack[chan->n-1];
|
|
}
|
|
|
|
static void
|
|
emit(struct ovni_chan *chan, int64_t t, int state)
|
|
{
|
|
assert(chan->dirty != CHAN_CLEAN);
|
|
|
|
/* A channel can only emit the same state as lastst if is dirty because
|
|
* it has been enabled or disabled. Otherwise is a bug (ie. you have two
|
|
* consecutive ovni events?) */
|
|
if(chan->lastst != -1
|
|
&& chan->dirty == CHAN_DIRTY_VALUE
|
|
&& chan->lastst == state)
|
|
{
|
|
/* TODO: Print the raw clock of the offending event */
|
|
err("chan: id=%d cannot emit the same state %d twice\n",
|
|
chan->id, state);
|
|
abort();
|
|
}
|
|
|
|
if(chan->lastst != state)
|
|
{
|
|
prv_ev(chan->prv, chan->row, t, chan->type, state);
|
|
|
|
chan->lastst = state;
|
|
}
|
|
}
|
|
|
|
static void
|
|
emit_ev(struct ovni_chan *chan)
|
|
{
|
|
int new, last;
|
|
|
|
assert(chan->enabled);
|
|
assert(chan->ev != -1);
|
|
|
|
new = chan->ev;
|
|
last = chan_get_st(chan);
|
|
|
|
emit(chan, chan->t-1, new);
|
|
emit(chan, chan->t, last);
|
|
|
|
chan->ev = -1;
|
|
}
|
|
|
|
static void
|
|
emit_st(struct ovni_chan *chan)
|
|
{
|
|
int st;
|
|
|
|
st = chan_get_st(chan);
|
|
|
|
emit(chan, chan->t, st);
|
|
}
|
|
|
|
/* Emits either the current state or punctual event in the PRV file */
|
|
void
|
|
chan_emit(struct ovni_chan *chan)
|
|
{
|
|
if(likely(chan->dirty == 0))
|
|
return;
|
|
|
|
dbg("chan_emit chan %d\n", chan->id);
|
|
|
|
/* Emit badst if disabled */
|
|
if(chan->enabled == 0)
|
|
{
|
|
/* No punctual events allowed when disabled */
|
|
assert(chan->ev == -1);
|
|
|
|
emit_st(chan);
|
|
goto shower;
|
|
}
|
|
|
|
/* Otherwise, emit punctual event if any or the state */
|
|
if(chan->ev != -1)
|
|
emit_ev(chan);
|
|
else
|
|
emit_st(chan);
|
|
|
|
shower:
|
|
chan->dirty = 0;
|
|
}
|