More complete reap of all threads.
This commit is contained in:
parent
7fa2975c24
commit
86e18226eb
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2001 Stephen Williams (steve@icarus.com)
|
||||
*
|
||||
* $Id: README.txt,v 1.13 2001/04/05 01:34:26 steve Exp $
|
||||
* $Id: README.txt,v 1.14 2001/04/13 03:55:18 steve Exp $
|
||||
*/
|
||||
|
||||
VVP SIMULATION ENGINE
|
||||
|
|
@ -259,6 +259,42 @@ caller and %end in the callee. The %fork, then is simply a
|
|||
generalization of a function call, where the caller does not
|
||||
necessarily wait for the callee.
|
||||
|
||||
For all the behavior of threads and thread parantage to work
|
||||
correctly, all %fork statements must have a corresponding %join in the
|
||||
parent, and %end in the child. Without this proper matching, the
|
||||
hierarchical relationships can get confused. The behavior of erroneous
|
||||
code is undefined.
|
||||
|
||||
THREADS AND SCOPES:
|
||||
|
||||
The Verilog ``disable'' statement deserves some special mention
|
||||
because of how it interacts with threads. In particular, threads
|
||||
throughout the design can affect (end) other threads in the design
|
||||
using the disable statement.
|
||||
|
||||
In Verilog, the operand to the disable statement is the name of a
|
||||
scope. The behavior of the disable is to cause all threads executing
|
||||
in the scope to end. Termination of a thread includes all the children
|
||||
of the thread. In vvp, all threads are in a scope, so this is how the
|
||||
disable gains access to the desired thread.
|
||||
|
||||
It is obvious how initial/always thread join a scope. They become part
|
||||
of the scope simply by being declared after a .scope declaration. (See
|
||||
vvp.txt for .scope declarations.) The .thread statement placed in the
|
||||
assembly source after a .scope statement causes the thread to join the
|
||||
named scope.
|
||||
|
||||
Transient threads initially inherit the scope of the parent
|
||||
thread. Right after the %fork statement, the new thread is created
|
||||
within the scope of the thread that executes the %fork
|
||||
statement. Transient threads leaf the parent scope and join a new
|
||||
scope with the %scope instruction.
|
||||
|
||||
A thread normally executes a %scope instruction only once. At any
|
||||
rate, a thread is only in a single scope, and does not remember past
|
||||
scopes that it might have been a part of. This is how a new thread
|
||||
switches out of the parent scope and into its own scope.
|
||||
|
||||
TRUTH TABLES
|
||||
|
||||
The logic that a functor represents is expressed as a truth table. The
|
||||
|
|
|
|||
19
vvp/codes.cc
19
vvp/codes.cc
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#if !defined(WINNT)
|
||||
#ident "$Id: codes.cc,v 1.6 2001/04/01 06:40:44 steve Exp $"
|
||||
#ident "$Id: codes.cc,v 1.7 2001/04/13 03:55:18 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "codes.h"
|
||||
|
|
@ -40,10 +40,20 @@ struct code_index1 {
|
|||
static vvp_cpoint_t code_count = 0;
|
||||
static struct code_index1*code_table[code_index2_size] = { 0 };
|
||||
|
||||
|
||||
/*
|
||||
* This initializes the code space. It sets up a code table and places
|
||||
* at address 0 a ZOMBIE instruction.
|
||||
*/
|
||||
void codespace_init(void)
|
||||
{
|
||||
code_table[0] = 0;
|
||||
code_table[0] = new struct code_index1;
|
||||
memset(code_table[0], 0, sizeof code_table[0]);
|
||||
code_table[0]->table[0] = new struct code_index0;
|
||||
memset(code_table[0]->table[0], 0, sizeof(struct code_index0));
|
||||
|
||||
vvp_code_t cp = code_table[0]->table[0]->table + 0;
|
||||
cp->opcode = &of_ZOMBIE;
|
||||
|
||||
code_count = 1;
|
||||
}
|
||||
|
||||
|
|
@ -126,6 +136,9 @@ void codespace_dump(FILE*fd)
|
|||
|
||||
/*
|
||||
* $Log: codes.cc,v $
|
||||
* Revision 1.7 2001/04/13 03:55:18 steve
|
||||
* More complete reap of all threads.
|
||||
*
|
||||
* Revision 1.6 2001/04/01 06:40:44 steve
|
||||
* Support empty statements for hanging labels.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#if !defined(WINNT)
|
||||
#ident "$Id: codes.h,v 1.16 2001/04/05 01:12:28 steve Exp $"
|
||||
#ident "$Id: codes.h,v 1.17 2001/04/13 03:55:18 steve Exp $"
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -60,6 +60,8 @@ extern bool of_SET(vthread_t thr, vvp_code_t code);
|
|||
extern bool of_VPI_CALL(vthread_t thr, vvp_code_t code);
|
||||
extern bool of_WAIT(vthread_t thr, vvp_code_t code);
|
||||
|
||||
extern bool of_ZOMBIE(vthread_t thr, vvp_code_t code);
|
||||
|
||||
/*
|
||||
* This is the format of a machine code instruction.
|
||||
*/
|
||||
|
|
@ -106,6 +108,9 @@ extern void codespace_dump(FILE*fd);
|
|||
|
||||
/*
|
||||
* $Log: codes.h,v $
|
||||
* Revision 1.17 2001/04/13 03:55:18 steve
|
||||
* More complete reap of all threads.
|
||||
*
|
||||
* Revision 1.16 2001/04/05 01:12:28 steve
|
||||
* Get signed compares working correctly in vvp.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#if !defined(WINNT)
|
||||
#ident "$Id: compile.cc,v 1.30 2001/04/05 01:34:26 steve Exp $"
|
||||
#ident "$Id: compile.cc,v 1.31 2001/04/13 03:55:18 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "compile.h"
|
||||
|
|
@ -541,7 +541,7 @@ void compile_thread(char*start_sym)
|
|||
return;
|
||||
}
|
||||
|
||||
vthread_t thr = v_newthread(pc);
|
||||
vthread_t thr = vthread_new(pc);
|
||||
schedule_vthread(thr, 0);
|
||||
free(start_sym);
|
||||
}
|
||||
|
|
@ -703,7 +703,7 @@ void compile_dump(FILE*fd)
|
|||
functor_dump(fd);
|
||||
fprintf(fd, "UNRESOLVED PORT INPUTS:\n");
|
||||
for (struct resolv_list_s*cur = resolv_list ; cur ; cur = cur->next)
|
||||
fprintf(fd, " %p: %s\n", (void*)cur->port, cur->source);
|
||||
fprintf(fd, " %08x: %s\n", cur->port, cur->source);
|
||||
|
||||
fprintf(fd, "CODE SPACE SYMBOL TABLE:\n");
|
||||
sym_dump(sym_codespace, fd);
|
||||
|
|
@ -714,6 +714,9 @@ void compile_dump(FILE*fd)
|
|||
|
||||
/*
|
||||
* $Log: compile.cc,v $
|
||||
* Revision 1.31 2001/04/13 03:55:18 steve
|
||||
* More complete reap of all threads.
|
||||
*
|
||||
* Revision 1.30 2001/04/05 01:34:26 steve
|
||||
* Add the .var/s and .net/s statements for VPI support.
|
||||
*
|
||||
|
|
|
|||
173
vvp/vthread.cc
173
vvp/vthread.cc
|
|
@ -17,7 +17,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#if !defined(WINNT)
|
||||
#ident "$Id: vthread.cc,v 1.22 2001/04/05 01:12:28 steve Exp $"
|
||||
#ident "$Id: vthread.cc,v 1.23 2001/04/13 03:55:18 steve Exp $"
|
||||
#endif
|
||||
|
||||
# include "vthread.h"
|
||||
|
|
@ -29,15 +29,54 @@
|
|||
# include <string.h>
|
||||
# include <assert.h>
|
||||
|
||||
/*
|
||||
* This vhtread_s structure describes all there is to know about a
|
||||
* thread, including its program counter, all the private bits it
|
||||
* holds, and its place in other lists.
|
||||
*
|
||||
*
|
||||
* ** Notes On The Interactions of %fork/%join/%end:
|
||||
*
|
||||
* The %fork instruction creates a new thread and pushes that onto the
|
||||
* stack of children for the thread. This new thread, then, becomes
|
||||
* the new direct descendent of the thread. This new thread is
|
||||
* therefore also the first thread to be reaped when the parent does a
|
||||
* %join.
|
||||
*
|
||||
* It is a programming error for a thread that created threads to not
|
||||
* %join as many as it created before it %ends. The linear stack for
|
||||
* tracking thread relationships will create a mess otherwise. For
|
||||
* example, if A creates B then C, the stack is:
|
||||
*
|
||||
* A --> C --> B
|
||||
*
|
||||
* If C then %forks X, the stack is:
|
||||
*
|
||||
* A --> C --> X --> B
|
||||
*
|
||||
* If C %ends without a join, then the stack is:
|
||||
*
|
||||
* A --> C(zombie) --> X --> B
|
||||
*
|
||||
* If A then executes 2 %joins, it will read C and X (when it ends)
|
||||
* leaving B in purgatory. What's worse, A will block on the schedules
|
||||
* of X and C instead of C and B, possibly creating incorrect timing.
|
||||
*/
|
||||
|
||||
struct vthread_s {
|
||||
/* This is the program counter. */
|
||||
unsigned long pc;
|
||||
/* These hold the private thread bits. */
|
||||
unsigned char *bits;
|
||||
unsigned short nbits;
|
||||
/* This points to the top (youngest) child of the thread. */
|
||||
unsigned nbits :16;
|
||||
/* My parent sets this when it wants me to wake it up. */
|
||||
unsigned schedule_parent_on_end :1;
|
||||
unsigned i_have_ended :1;
|
||||
unsigned waiting_for_event :1;
|
||||
/* This points to the sole child of the thread. */
|
||||
struct vthread_s*child;
|
||||
/* This is set if the parent is waiting for me to end. */
|
||||
struct vthread_s*reaper;
|
||||
/* This points to my parent, if I have one. */
|
||||
struct vthread_s*parent;
|
||||
/* This is used for keeping wait queues. */
|
||||
struct vthread_s*next;
|
||||
};
|
||||
|
|
@ -76,15 +115,18 @@ static inline void thr_put_bit(struct vthread_s*thr,
|
|||
/*
|
||||
* Create a new thread with the given start address.
|
||||
*/
|
||||
vthread_t v_newthread(unsigned long pc)
|
||||
vthread_t vthread_new(unsigned long pc)
|
||||
{
|
||||
vthread_t thr = new struct vthread_s;
|
||||
thr->pc = pc;
|
||||
thr->bits = (unsigned char*)malloc(16);
|
||||
thr->nbits = 16*4;
|
||||
thr->child = 0;
|
||||
thr->reaper = 0;
|
||||
thr->next = 0;
|
||||
thr->pc = pc;
|
||||
thr->bits = (unsigned char*)malloc(16);
|
||||
thr->nbits = 16*4;
|
||||
thr->child = 0;
|
||||
thr->parent = 0;
|
||||
thr->next = 0;
|
||||
|
||||
thr->schedule_parent_on_end = 0;
|
||||
thr->i_have_ended = 0;
|
||||
|
||||
thr_put_bit(thr, 0, 0);
|
||||
thr_put_bit(thr, 1, 1);
|
||||
|
|
@ -97,7 +139,14 @@ static void vthread_reap(vthread_t thr)
|
|||
{
|
||||
assert(thr->next == 0);
|
||||
assert(thr->child == 0);
|
||||
free(thr);
|
||||
free(thr->bits);
|
||||
|
||||
if (thr->child)
|
||||
thr->child->parent = thr->parent;
|
||||
if (thr->parent)
|
||||
thr->parent->child = thr->child;
|
||||
|
||||
delete thr;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -122,15 +171,23 @@ void vthread_run(vthread_t thr)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called by an event functor to wait up all the threads on
|
||||
* its list. I in fact created that list in the %wait instruction, and
|
||||
* I also am certain that the waiting_for_event flag is set.
|
||||
*/
|
||||
void vthread_schedule_list(vthread_t thr)
|
||||
{
|
||||
while (thr) {
|
||||
vthread_t tmp = thr;
|
||||
thr = thr->next;
|
||||
assert(tmp->waiting_for_event);
|
||||
tmp->waiting_for_event = 0;
|
||||
schedule_vthread(tmp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool of_AND(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
assert(cp->bit_idx1 >= 4);
|
||||
|
|
@ -358,19 +415,71 @@ bool of_DELAY(vthread_t thr, vvp_code_t cp)
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This terminates the current thread. If there is a parent who is
|
||||
* waiting for me to die, then I schedule it. At any rate, I mark
|
||||
* myself as a zombie by setting my pc to 0.
|
||||
*/
|
||||
bool of_END(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
if (thr->reaper)
|
||||
schedule_vthread(thr->reaper, 0);
|
||||
thr->reaper = thr;
|
||||
assert(! thr->waiting_for_event);
|
||||
|
||||
thr->i_have_ended = 1;
|
||||
thr->pc = 0;
|
||||
|
||||
/* Reap direct descendents that have already ended. Do
|
||||
this in a loop until I run out of dead children. */
|
||||
|
||||
while (thr->child && (thr->child->i_have_ended)) {
|
||||
vthread_t tmp = thr->child;
|
||||
vthread_reap(tmp);
|
||||
}
|
||||
|
||||
|
||||
/* If I have a parent who is waiting for me, then mark that I
|
||||
have ended, and schedule that parent. The parent will reap
|
||||
me, so don't do it here. */
|
||||
if (thr->schedule_parent_on_end) {
|
||||
assert(thr->parent);
|
||||
schedule_vthread(thr->parent, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If I have no parents, then no one can %join me and there is
|
||||
no reason to stick around. This can happen, for example if
|
||||
I am an ``initial'' thread. */
|
||||
if (thr->parent == 0) {
|
||||
if (thr->child)
|
||||
fprintf(stderr, "vvp warning: A thread left dangling "
|
||||
"children. This is probably caused\n"
|
||||
" : a missing %%join in this thread.\n");
|
||||
vthread_reap(thr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If I make it this far, then I have a parent who may wish
|
||||
to %join me. Remain a zombie so that it can. */
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The %fork instruction causes a new child to be created and pushed
|
||||
* in front of any existing child. This causes the new child to be the
|
||||
* parent of any previous children, and for me to be the parent of the
|
||||
* new child.
|
||||
*/
|
||||
bool of_FORK(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
vthread_t child = v_newthread(cp->cptr);
|
||||
child->child = thr->child;
|
||||
vthread_t child = vthread_new(cp->cptr);
|
||||
|
||||
child->child = thr->child;
|
||||
child->parent = thr;
|
||||
thr->child = child;
|
||||
if (child->child) {
|
||||
assert(child->child->parent == thr);
|
||||
child->child->parent = child;
|
||||
}
|
||||
schedule_vthread(child, 0);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -396,6 +505,11 @@ bool of_INV(vthread_t thr, vvp_code_t cp)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The various JMP instruction work simply by pulling the new program
|
||||
* counter from the instruction and resuming. If the jump is
|
||||
* conditional, then test the bit for the expected value first.
|
||||
*/
|
||||
bool of_JMP(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
thr->pc = cp->cptr;
|
||||
|
|
@ -423,16 +537,22 @@ bool of_JMP1(vthread_t thr, vvp_code_t cp)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The %join instruction causes the thread to wait for the one and
|
||||
* only child to die. If it is already dead (and a zombie) then I
|
||||
* reap it and go on. Otherwise, I tell the child that I am ready for
|
||||
* it to die, and it will reschedule me when it does.
|
||||
*/
|
||||
bool of_JOIN(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
assert(thr->child);
|
||||
if (thr->child->reaper == thr->child) {
|
||||
assert(thr->child->parent == thr);
|
||||
if (thr->child->i_have_ended) {
|
||||
vthread_reap(thr->child);
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(thr->child->reaper == 0);
|
||||
thr->child->reaper = thr;
|
||||
thr->child->schedule_parent_on_end = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -543,6 +663,8 @@ bool of_VPI_CALL(vthread_t thr, vvp_code_t cp)
|
|||
*/
|
||||
bool of_WAIT(vthread_t thr, vvp_code_t cp)
|
||||
{
|
||||
assert(! thr->waiting_for_event);
|
||||
thr->waiting_for_event = 1;
|
||||
functor_t fp = functor_index(cp->iptr);
|
||||
assert((fp->mode == 1) || (fp->mode == 2));
|
||||
vvp_event_t ep = fp->event;
|
||||
|
|
@ -552,8 +674,17 @@ bool of_WAIT(vthread_t thr, vvp_code_t cp)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool of_ZOMBIE(vthread_t, vvp_code_t)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* $Log: vthread.cc,v $
|
||||
* Revision 1.23 2001/04/13 03:55:18 steve
|
||||
* More complete reap of all threads.
|
||||
*
|
||||
* Revision 1.22 2001/04/05 01:12:28 steve
|
||||
* Get signed compares working correctly in vvp.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
||||
*/
|
||||
#if !defined(WINNT)
|
||||
#ident "$Id: vthread.h,v 1.2 2001/03/26 04:00:39 steve Exp $"
|
||||
#ident "$Id: vthread.h,v 1.3 2001/04/13 03:55:18 steve Exp $"
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -40,7 +40,7 @@ typedef struct vthread_s*vthread_t;
|
|||
* address. The generated thread is ready to run, but is not yet
|
||||
* scheduled.
|
||||
*/
|
||||
extern vthread_t v_newthread(unsigned long sa);
|
||||
extern vthread_t vthread_new(unsigned long sa);
|
||||
|
||||
/*
|
||||
* Cause this thread to execute instructions until in is put to sleep
|
||||
|
|
@ -50,14 +50,18 @@ extern void vthread_run(vthread_t thr);
|
|||
|
||||
/*
|
||||
* This function schedules all the threads in the list to be scheduled
|
||||
* for execution with delay 0. the thr pointer is taken to be the head
|
||||
* of a list.
|
||||
* for execution with delay 0. The thr pointer is taken to be the head
|
||||
* of a list, and all the threads in the list are presumably placed in
|
||||
* the list by the %wait instruction.
|
||||
*/
|
||||
extern void vthread_schedule_list(vthread_t thr);
|
||||
|
||||
|
||||
/*
|
||||
* $Log: vthread.h,v $
|
||||
* Revision 1.3 2001/04/13 03:55:18 steve
|
||||
* More complete reap of all threads.
|
||||
*
|
||||
* Revision 1.2 2001/03/26 04:00:39 steve
|
||||
* Add the .event statement and the %wait instruction.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in New Issue