More complete reap of all threads.

This commit is contained in:
steve 2001-04-13 03:55:18 +00:00
parent 7fa2975c24
commit 86e18226eb
6 changed files with 225 additions and 33 deletions

View File

@ -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

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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.
*