From 86e18226ebaff9cba26c0ffd35b71d1159b600fd Mon Sep 17 00:00:00 2001 From: steve Date: Fri, 13 Apr 2001 03:55:18 +0000 Subject: [PATCH] More complete reap of all threads. --- vvp/README.txt | 38 ++++++++++- vvp/codes.cc | 19 +++++- vvp/codes.h | 7 +- vvp/compile.cc | 9 ++- vvp/vthread.cc | 173 +++++++++++++++++++++++++++++++++++++++++++------ vvp/vthread.h | 12 ++-- 6 files changed, 225 insertions(+), 33 deletions(-) diff --git a/vvp/README.txt b/vvp/README.txt index c95bbefdf..c87859f15 100644 --- a/vvp/README.txt +++ b/vvp/README.txt @@ -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 diff --git a/vvp/codes.cc b/vvp/codes.cc index 236394544..0e360f378 100644 --- a/vvp/codes.cc +++ b/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. * diff --git a/vvp/codes.h b/vvp/codes.h index a10a8a816..4c940fef9 100644 --- a/vvp/codes.h +++ b/vvp/codes.h @@ -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. * diff --git a/vvp/compile.cc b/vvp/compile.cc index 9fd89fe13..ec3954378 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -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. * diff --git a/vvp/vthread.cc b/vvp/vthread.cc index 39e48d995..872d37e1c 100644 --- a/vvp/vthread.cc +++ b/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 # include +/* + * 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. * diff --git a/vvp/vthread.h b/vvp/vthread.h index dbd5d657b..76d65c7db 100644 --- a/vvp/vthread.h +++ b/vvp/vthread.h @@ -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. *