/* * Copyright (c) 2001-2014 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU * General Public License as published by the Free Software * Foundation; either version 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ # include "config.h" # include "vthread.h" # include "codes.h" # include "schedule.h" # include "ufunc.h" # include "event.h" # include "vpi_priv.h" # include "vvp_net_sig.h" # include "vvp_cobject.h" # include "vvp_darray.h" # include "class_type.h" #ifdef CHECK_WITH_VALGRIND # include "vvp_cleanup.h" #endif # include # include # include # include # include # include # include # include # include # include using namespace std; /* This is the size of an unsigned long in bits. This is just a convenience macro. */ # define CPU_WORD_BITS (8*sizeof(unsigned long)) # define TOP_BIT (1UL << (CPU_WORD_BITS-1)) /* * This vthread_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 into a * set of children for the thread. This new thread, then, becomes a * child of the current thread, and the current thread a parent of the * new thread. Any child can be reaped by a %join. * * Children that are detached with %join/detach need to have a different * parent/child relationship since the parent can still effect them if * it uses the %disable/fork or %wait/fork opcodes. The i_am_detached * flag and detached_children set are used for this relationship. * * Children placed into a task or function scope are given special * treatment, which is required to make task/function calls that they * represent work correctly. These task/function children are copied * into a task_func_children set to mark them for this handling. %join * operations will guarantee that task/function threads are joined first, * before any non-task/function threads. * * It is a programming error for a thread that created threads to not * %join (or %join/detach) as many as it created before it %ends. The * children set will get messed up otherwise. * * the i_am_joining flag is a clue to children that the parent is * blocked in a %join and may need to be scheduled. The %end * instruction will check this flag in the parent to see if it should * notify the parent that something is interesting. * * The i_have_ended flag, on the other hand, is used by threads to * tell their parents that they are already dead. A thread that * executes %end will set its own i_have_ended flag and let its parent * reap it when the parent does the %join. If a thread has its * schedule_parent_on_end flag set already when it %ends, then it * reaps itself and simply schedules its parent. If a child has its * i_have_ended flag set when a thread executes %join, then it is free * to reap the child immediately. */ struct vthread_s { vthread_s(); void debug_dump(ostream&fd, const char*label_text); /* This is the program counter. */ vvp_code_t pc; /* These hold the private thread bits. */ enum { FLAGS_COUNT = 256, WORDS_COUNT = 16 }; vvp_bit4_t flags[FLAGS_COUNT]; /* These are the word registers. */ union { int64_t w_int; uint64_t w_uint; } words[WORDS_COUNT]; private: vectorstack_vec4_; public: inline vvp_vector4_t pop_vec4(void) { assert(! stack_vec4_.empty()); vvp_vector4_t val = stack_vec4_.back(); stack_vec4_.pop_back(); return val; } inline void push_vec4(const vvp_vector4_t&val) { stack_vec4_.push_back(val); } inline const vvp_vector4_t& peek_vec4(unsigned depth) { assert(depth < stack_vec4_.size()); unsigned use_index = stack_vec4_.size()-1-depth; return stack_vec4_[use_index]; } inline vvp_vector4_t& peek_vec4(void) { assert(! stack_vec4_.empty()); unsigned use_index = stack_vec4_.size()-1; return stack_vec4_[use_index]; } inline void pop_vec4(unsigned cnt) { while (cnt > 0) { stack_vec4_.pop_back(); cnt -= 1; } } private: vector stack_real_; public: inline double pop_real(void) { assert(! stack_real_.empty()); double val = stack_real_.back(); stack_real_.pop_back(); return val; } inline void push_real(double val) { stack_real_.push_back(val); } inline double peek_real(unsigned depth) { assert(depth < stack_real_.size()); unsigned use_index = stack_real_.size()-1-depth; return stack_real_[use_index]; } inline void pop_real(unsigned cnt) { while (cnt > 0) { stack_real_.pop_back(); cnt -= 1; } } /* Strings are operated on using a forth-like operator set. Items at the top of the stack (back()) are the objects operated on except for special cases. New objects are pushed onto the top (back()) and pulled from the top (back()) only. */ private: vector stack_str_; public: inline string pop_str(void) { assert(! stack_str_.empty()); string val = stack_str_.back(); stack_str_.pop_back(); return val; } inline void push_str(const string&val) { stack_str_.push_back(val); } inline string&peek_str(unsigned depth) { assert(depth 0) { stack_str_.pop_back(); cnt -= 1; } } /* Objects are also operated on in a stack. */ private: enum { STACK_OBJ_MAX_SIZE = 32 }; vvp_object_t stack_obj_[STACK_OBJ_MAX_SIZE]; unsigned stack_obj_size_; public: inline vvp_object_t& peek_object(void) { assert(stack_obj_size_ > 0); return stack_obj_[stack_obj_size_-1]; } inline void pop_object(vvp_object_t&obj) { assert(stack_obj_size_ > 0); stack_obj_size_ -= 1; obj = stack_obj_[stack_obj_size_]; stack_obj_[stack_obj_size_].reset(0); } inline void pop_object(unsigned cnt, unsigned skip =0) { assert((cnt+skip) <= stack_obj_size_); for (size_t idx = stack_obj_size_-skip-cnt ; idx < stack_obj_size_-skip ; idx += 1) stack_obj_[idx].reset(0); stack_obj_size_ -= cnt; for (size_t idx = stack_obj_size_-skip ; idx < stack_obj_size_ ; idx += 1) stack_obj_[idx] = stack_obj_[idx+skip]; for (size_t idx = stack_obj_size_ ; idx < stack_obj_size_+skip ; idx += 1) stack_obj_[idx].reset(0); } inline void push_object(const vvp_object_t&obj) { assert(stack_obj_size_ < STACK_OBJ_MAX_SIZE); stack_obj_[stack_obj_size_] = obj; stack_obj_size_ += 1; } /* My parent sets this when it wants me to wake it up. */ unsigned i_am_joining :1; unsigned i_am_detached :1; unsigned i_am_waiting :1; unsigned i_have_ended :1; unsigned waiting_for_event :1; unsigned is_scheduled :1; unsigned delay_delete :1; /* This points to the children of the thread. */ setchildren; /* This points to the detached children of the thread. */ setdetached_children; /* No more than 1 of the children are tasks or functions. */ settask_func_children; /* This points to my parent, if I have one. */ struct vthread_s*parent; /* This points to the containing scope. */ struct __vpiScope*parent_scope; /* This is used for keeping wait queues. */ struct vthread_s*wait_next; /* These are used to access automatically allocated items. */ vvp_context_t wt_context, rd_context; /* These are used to pass non-blocking event control information. */ vvp_net_t*event; uint64_t ecount; inline void cleanup() { assert(stack_vec4_.empty()); assert(stack_real_.empty()); assert(stack_str_.empty()); assert(stack_obj_size_ == 0); } }; inline vthread_s::vthread_s() { stack_obj_size_ = 0; } void vthread_s::debug_dump(ostream&fd, const char*label) { fd << "**** " << label << endl; fd << "**** Flags: "; for (int idx = 0 ; idx < FLAGS_COUNT ; idx += 1) fd << flags[idx]; fd << endl; fd << "**** vec4 stack..." << endl; for (size_t idx = stack_vec4_.size() ; idx > 0 ; idx -= 1) fd << " " << (stack_vec4_.size()-idx) << ": " << stack_vec4_[idx-1] << endl; fd << "**** str stack (" << stack_str_.size() << ")..." << endl; fd << "**** obj stack (" << stack_obj_size_ << ")..." << endl; fd << "**** Done ****" << endl; } static bool test_joinable(vthread_t thr, vthread_t child); static void do_join(vthread_t thr, vthread_t child); struct __vpiScope* vthread_scope(struct vthread_s*thr) { return thr->parent_scope; } struct vthread_s*running_thread = 0; void vthread_push_vec4(struct vthread_s*thr, const vvp_vector4_t&val) { thr->push_vec4(val); } void vthread_push_real(struct vthread_s*thr, double val) { thr->push_real(val); } void vthread_pop_vec4(struct vthread_s*thr, unsigned depth) { thr->pop_vec4(depth); } void vthread_pop_real(struct vthread_s*thr, unsigned depth) { thr->pop_real(depth); } void vthread_pop_str(struct vthread_s*thr, unsigned depth) { thr->pop_str(depth); } const string&vthread_get_str_stack(struct vthread_s*thr, unsigned depth) { return thr->peek_str(depth); } double vthread_get_real_stack(struct vthread_s*thr, unsigned depth) { return thr->peek_real(depth); } const vvp_vector4_t& vthread_get_vec4_stack(struct vthread_s*thr, unsigned depth) { return thr->peek_vec4(depth); } /* * This is a function to get a vvp_queue handle from the variable * referenced by "net". If the queue is nil, then allocated it and * assign the value to the net. Note that this function is * parameterized by the queue type so that we can create the right * derived type of queue object. */ template static vvp_queue*get_queue_object(vthread_t thr, vvp_net_t*net) { vvp_fun_signal_object*obj = dynamic_cast (net->fun); assert(obj); vvp_queue*dqueue = obj->get_object().peek(); if (dqueue == 0) { assert(obj->get_object().test_nil()); dqueue = new VVP_QUEUE; vvp_object_t val (dqueue); vvp_net_ptr_t ptr (net, 0); vvp_send_object(ptr, val, thr->wt_context); } return dqueue; } template T coerce_to_width(const T&that, unsigned width) { if (that.size() == width) return that; assert(that.size() > width); T res (width); for (unsigned idx = 0 ; idx < width ; idx += 1) res.set_bit(idx, that.value(idx)); return res; } /* Explicitly define the vvp_vector4_t version of coerce_to_width(). */ template vvp_vector4_t coerce_to_width(const vvp_vector4_t&that, unsigned width); static void multiply_array_imm(unsigned long*res, unsigned long*val, unsigned words, unsigned long imm) { for (unsigned idx = 0 ; idx < words ; idx += 1) res[idx] = 0; for (unsigned mul_idx = 0 ; mul_idx < words ; mul_idx += 1) { unsigned long sum; unsigned long tmp = multiply_with_carry(val[mul_idx], imm, sum); unsigned long carry = 0; res[mul_idx] = add_with_carry(res[mul_idx], tmp, carry); for (unsigned add_idx = mul_idx+1 ; add_idx < words ; add_idx += 1) { res[add_idx] = add_with_carry(res[add_idx], sum, carry); sum = 0; } } } /* * Allocate a context for use by a child thread. By preference, use * the last freed context. If none available, create a new one. Add * it to the list of live contexts in that scope. */ static vvp_context_t vthread_alloc_context(struct __vpiScope*scope) { assert(scope->is_automatic); vvp_context_t context = scope->free_contexts; if (context) { scope->free_contexts = vvp_get_next_context(context); for (unsigned idx = 0 ; idx < scope->nitem ; idx += 1) { scope->item[idx]->reset_instance(context); } } else { context = vvp_allocate_context(scope->nitem); for (unsigned idx = 0 ; idx < scope->nitem ; idx += 1) { scope->item[idx]->alloc_instance(context); } } vvp_set_next_context(context, scope->live_contexts); scope->live_contexts = context; return context; } /* * Free a context previously allocated to a child thread by pushing it * onto the freed context stack. Remove it from the list of live contexts * in that scope. */ static void vthread_free_context(vvp_context_t context, struct __vpiScope*scope) { assert(scope->is_automatic); assert(context); if (context == scope->live_contexts) { scope->live_contexts = vvp_get_next_context(context); } else { vvp_context_t tmp = scope->live_contexts; while (context != vvp_get_next_context(tmp)) { assert(tmp); tmp = vvp_get_next_context(tmp); } vvp_set_next_context(tmp, vvp_get_next_context(context)); } vvp_set_next_context(context, scope->free_contexts); scope->free_contexts = context; } #ifdef CHECK_WITH_VALGRIND void contexts_delete(struct __vpiScope*scope) { vvp_context_t context = scope->free_contexts; while (context) { scope->free_contexts = vvp_get_next_context(context); for (unsigned idx = 0; idx < scope->nitem; idx += 1) { scope->item[idx]->free_instance(context); } free(context); context = scope->free_contexts; } free(scope->item); } #endif /* * Create a new thread with the given start address. */ vthread_t vthread_new(vvp_code_t pc, struct __vpiScope*scope) { vthread_t thr = new struct vthread_s; thr->pc = pc; //thr->bits4 = vvp_vector4_t(32); thr->parent = 0; thr->parent_scope = scope; thr->wait_next = 0; thr->wt_context = 0; thr->rd_context = 0; thr->i_am_joining = 0; thr->i_am_detached = 0; thr->i_am_waiting = 0; thr->is_scheduled = 0; thr->i_have_ended = 0; thr->delay_delete = 0; thr->waiting_for_event = 0; thr->event = 0; thr->ecount = 0; thr->flags[0] = BIT4_0; thr->flags[1] = BIT4_1; thr->flags[2] = BIT4_X; thr->flags[3] = BIT4_Z; for (int idx = 4 ; idx < 8 ; idx += 1) thr->flags[idx] = BIT4_X; scope->threads .insert(thr); return thr; } #ifdef CHECK_WITH_VALGRIND #if 0 /* * These are not currently correct. If you use them you will get * double delete messages. There is still a leak related to a * waiting event that needs to be investigated. */ static void wait_next_delete(vthread_t base) { while (base) { vthread_t tmp = base->wait_next; delete base; base = tmp; if (base->waiting_for_event == 0) break; } } static void child_delete(vthread_t base) { while (base) { vthread_t tmp = base->child; delete base; base = tmp; } } #endif void vthreads_delete(struct __vpiScope*scope) { for (std::set::iterator cur = scope->threads.begin() ; cur != scope->threads.end() ; ++ cur ) { delete *cur; } scope->threads.clear(); } #endif /* * Reaping pulls the thread out of the stack of threads. If I have a * child, then hand it over to my parent or fully detach it. */ static void vthread_reap(vthread_t thr) { if (! thr->children.empty()) { for (set::iterator cur = thr->children.begin() ; cur != thr->children.end() ; ++cur) { vthread_t child = *cur; assert(child); assert(child->parent == thr); child->parent = thr->parent; } } if (! thr->detached_children.empty()) { for (set::iterator cur = thr->detached_children.begin() ; cur != thr->detached_children.end() ; ++cur) { vthread_t child = *cur; assert(child); assert(child->parent == thr); assert(child->i_am_detached); child->parent = 0; child->i_am_detached = 0; } } if (thr->parent) { /* assert that the given element was removed. */ if (thr->i_am_detached) { size_t res = thr->parent->detached_children.erase(thr); assert(res == 1); } else { size_t res = thr->parent->children.erase(thr); assert(res == 1); } } thr->parent = 0; // Remove myself from the containing scope if needed. thr->parent_scope->threads.erase(thr); thr->pc = codespace_null(); /* If this thread is not scheduled, then is it safe to delete it now. Otherwise, let the schedule event (which will execute the thread at of_ZOMBIE) delete the object. */ if ((thr->is_scheduled == 0) && (thr->waiting_for_event == 0)) { assert(thr->children.empty()); assert(thr->wait_next == 0); if (thr->delay_delete) schedule_del_thr(thr); else vthread_delete(thr); } } void vthread_delete(vthread_t thr) { thr->cleanup(); delete thr; } void vthread_mark_scheduled(vthread_t thr) { while (thr != 0) { assert(thr->is_scheduled == 0); thr->is_scheduled = 1; thr = thr->wait_next; } } void vthread_delay_delete() { if (running_thread) running_thread->delay_delete = 1; } /* * This function runs each thread by fetching an instruction, * incrementing the PC, and executing the instruction. The thread may * be the head of a list, so each thread is run so far as possible. */ void vthread_run(vthread_t thr) { while (thr != 0) { vthread_t tmp = thr->wait_next; thr->wait_next = 0; assert(thr->is_scheduled); thr->is_scheduled = 0; running_thread = thr; for (;;) { vvp_code_t cp = thr->pc; thr->pc += 1; /* Run the opcode implementation. If the execution of the opcode returns false, then the thread is meant to be paused, so break out of the loop. */ bool rc = (cp->opcode)(thr, cp); if (rc == false) break; } thr = tmp; } running_thread = 0; } /* * The CHUNK_LINK instruction is a special next pointer for linking * chunks of code space. It's like a simplified %jmp. */ bool of_CHUNK_LINK(vthread_t thr, vvp_code_t code) { assert(code->cptr); thr->pc = code->cptr; return true; } /* * This is called by an event functor to wake 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) { for (vthread_t cur = thr ; cur ; cur = cur->wait_next) { assert(cur->waiting_for_event); cur->waiting_for_event = 0; } schedule_vthread(thr, 0); } vvp_context_t vthread_get_wt_context() { if (running_thread) return running_thread->wt_context; else return 0; } vvp_context_t vthread_get_rd_context() { if (running_thread) return running_thread->rd_context; else return 0; } vvp_context_item_t vthread_get_wt_context_item(unsigned context_idx) { assert(running_thread && running_thread->wt_context); return vvp_get_context_item(running_thread->wt_context, context_idx); } vvp_context_item_t vthread_get_rd_context_item(unsigned context_idx) { assert(running_thread && running_thread->rd_context); return vvp_get_context_item(running_thread->rd_context, context_idx); } bool of_ABS_WR(vthread_t thr, vvp_code_t) { thr->push_real( fabs(thr->pop_real()) ); return true; } bool of_ALLOC(vthread_t thr, vvp_code_t cp) { /* Allocate a context. */ vvp_context_t child_context = vthread_alloc_context(cp->scope); /* Push the allocated context onto the write context stack. */ vvp_set_stacked_context(child_context, thr->wt_context); thr->wt_context = child_context; return true; } bool of_AND(vthread_t thr, vvp_code_t) { vvp_vector4_t valb = thr->pop_vec4(); vvp_vector4_t&vala = thr->peek_vec4(); assert(vala.size() == valb.size()); vala &= valb; return true; } /* * This function must ALWAYS be called with the val set to the right * size, and initialized with BIT4_0 bits. Certain optimizations rely * on that. */ static void get_immediate_rval(vvp_code_t cp, vvp_vector4_t&val) { uint32_t vala = cp->bit_idx[0]; uint32_t valb = cp->bit_idx[1]; unsigned wid = cp->number; if (valb == 0) { // Special case: if the value is zero, we are done // before we start. if (vala == 0) return; // Special case: The value has no X/Z bits, so we can // use the setarray method to write the value all at once. unsigned use_wid = 8*sizeof(unsigned long); if (wid < use_wid) use_wid = wid; unsigned long tmp[1]; tmp[0] = vala; val.setarray(0, use_wid, tmp); return; } // The immediate value can be values bigger then 32 bits, but // only if the high bits are zero. So at most we need to run // through the loop below 32 times. Maybe less, if the target // width is less. We don't have to do anything special on that // because vala/valb bits will shift away so (vala|valb) will // turn to zero at or before 32 shifts. for (unsigned idx = 0 ; idx < wid && (vala|valb) ; idx += 1) { uint32_t ba = 0; // Convert the vala/valb bits to a ba number that // matches the encoding of the vvp_bit4_t enumeration. ba = (valb & 1) << 1; ba |= vala & 1; // Note that the val is already pre-filled with BIT4_0 // bits, os we only need to set non-zero bit values. if (ba) val.set_bit(idx, (vvp_bit4_t)ba); vala >>= 1; valb >>= 1; } } /* * %add * * Pop r, * Pop l, * Push l+r * * Pop 2 and push 1 is the same as pop 1 and replace the remaining top * of the stack with a new value. That is what we will do. */ bool of_ADD(vthread_t thr, vvp_code_t) { vvp_vector4_t r = thr->pop_vec4(); // Rather then pop l, use it directly from the stack. When we // assign to 'l', that will edit the top of the stack, which // replaces a pop and a pull. vvp_vector4_t&l = thr->peek_vec4(); l.add(r); return true; } /* * %addi , , * * Pop1 operand, get the other operand from the arguments, and push * the result. */ bool of_ADDI(vthread_t thr, vvp_code_t cp) { unsigned wid = cp->number; vvp_vector4_t&l = thr->peek_vec4(); // I expect that most of the bits of an immediate value are // going to be zero, so start the result vector with all zero // bits. Then we only need to replace the bits that are different. vvp_vector4_t r (wid, BIT4_0); get_immediate_rval (cp, r); l.add(r); return true; } bool of_ADD_WR(vthread_t thr, vvp_code_t) { double r = thr->pop_real(); double l = thr->pop_real(); thr->push_real(l + r); return true; } /* %assign/ar , * Generate an assignment event to a real array. Index register 3 * contains the canonical address of the word in the memory. * is the delay in simulation time. is the index register * containing the real value. */ bool of_ASSIGN_AR(vthread_t thr, vvp_code_t cp) { long adr = thr->words[3].w_int; unsigned delay = cp->bit_idx[0]; double value = thr->pop_real(); if (adr >= 0) { schedule_assign_array_word(cp->array, adr, value, delay); } return true; } /* %assign/ar/d , * Generate an assignment event to a real array. Index register 3 * contains the canonical address of the word in the memory. * is the integer register that contains the delay value. */ bool of_ASSIGN_ARD(vthread_t thr, vvp_code_t cp) { long adr = thr->words[3].w_int; vvp_time64_t delay = thr->words[cp->bit_idx[0]].w_uint; double value = thr->pop_real(); if (adr >= 0) { schedule_assign_array_word(cp->array, adr, value, delay); } return true; } /* %assign/ar/e * Generate an assignment event to a real array. Index register 3 * contains the canonical address of the word in the memory. * is the index register containing the real value. The event * information is contained in the thread event control registers * and is set with %evctl. */ bool of_ASSIGN_ARE(vthread_t thr, vvp_code_t cp) { long adr = thr->words[3].w_int; double value = thr->pop_real(); if (adr >= 0) { if (thr->ecount == 0) { schedule_assign_array_word(cp->array, adr, value, 0); } else { schedule_evctl(cp->array, adr, value, thr->event, thr->ecount); } } return true; } /* * %assign/vec4 , */ bool of_ASSIGN_VEC4(vthread_t thr, vvp_code_t cp) { vvp_net_ptr_t ptr (cp->net, 0); unsigned delay = cp->bit_idx[0]; vvp_vector4_t&val = thr->peek_vec4(); schedule_assign_vector(ptr, 0, 0, val, delay); thr->pop_vec4(1); return true; } /* * %assign/vec4/a/d , , */ bool of_ASSIGN_VEC4_A_D(vthread_t thr, vvp_code_t cp) { int off_idx = cp->bit_idx[0]; int del_idx = cp->bit_idx[1]; int adr_idx = 3; long off = off_idx? thr->words[off_idx].w_int : 0; vvp_time64_t del = del_idx? thr->words[del_idx].w_uint : 0; long adr = thr->words[adr_idx].w_int; vvp_vector4_t val = thr->pop_vec4(); unsigned wid = val.size(); const unsigned array_wid = cp->array->get_word_size(); // Abort if flags[4] is set. This can happen if the calulation // into an index register failed. if (thr->flags[4] == BIT4_1) return true; if (off >= (long)array_wid) return true; if (off < 0) { if ((unsigned)-off >= array_wid) return true; int use_off = -off; assert(wid > (unsigned)use_off); unsigned use_wid = wid - use_off; val = val.subvalue(use_off, use_wid); off = 0; wid = use_wid; } if (off+wid > array_wid) { val = val.subvalue(0, array_wid-off); wid = val.size(); } schedule_assign_array_word(cp->array, adr, off, val, del); return true; } /* * %assign/vec4/a/e , */ bool of_ASSIGN_VEC4_A_E(vthread_t thr, vvp_code_t cp) { int off_idx = cp->bit_idx[0]; int adr_idx = 3; long off = off_idx? thr->words[off_idx].w_int : 0; long adr = thr->words[adr_idx].w_int; vvp_vector4_t val = thr->pop_vec4(); unsigned wid = val.size(); const unsigned array_wid = cp->array->get_word_size(); // Abort if flags[4] is set. This can happen if the calulation // into an index register failed. if (thr->flags[4] == BIT4_1) return true; if (off >= (long)array_wid) return true; if (off < 0) { if ((unsigned)-off >= array_wid) return true; int use_off = -off; assert(wid > (unsigned)use_off); unsigned use_wid = wid - use_off; val = val.subvalue(use_off, use_wid); off = 0; wid = use_wid; } if (off+wid > array_wid) { val = val.subvalue(0, array_wid-off); wid = val.size(); } if (thr->ecount == 0) { schedule_assign_array_word(cp->array, adr, off, val, 0); } else { schedule_evctl(cp->array, adr, val, off, thr->event, thr->ecount); } return true; } /* * %assign/vec4/off/d , , */ bool of_ASSIGN_VEC4_OFF_D(vthread_t thr, vvp_code_t cp) { vvp_net_ptr_t ptr (cp->net, 0); unsigned off_index = cp->bit_idx[0]; unsigned del_index = cp->bit_idx[1]; vvp_vector4_t val = thr->pop_vec4(); unsigned wid = val.size(); int off = thr->words[off_index].w_int; vvp_time64_t del = thr->words[del_index].w_uint; // Abort if flags[4] is set. This can happen if the calulation // into an index register failed. if (thr->flags[4] == BIT4_1) return true; vvp_signal_value*sig = dynamic_cast (cp->net->fil); assert(sig); if (off >= (long)sig->value_size()) return true; if (off < 0) { if ((unsigned)-off >= wid) return true; int use_off = -off; assert(wid > (unsigned)use_off); unsigned use_wid = wid - use_off; val = val.subvalue(use_off, use_wid); off = 0; wid = use_wid; } if (off+wid > sig->value_size()) { val = val.subvalue(0, sig->value_size()-off); wid = val.size(); } schedule_assign_vector(ptr, off, sig->value_size(), val, del); return true; } /* * %assign/vec4/off/e , */ bool of_ASSIGN_VEC4_OFF_E(vthread_t thr, vvp_code_t cp) { vvp_net_ptr_t ptr (cp->net, 0); unsigned off_index = cp->bit_idx[0]; vvp_vector4_t val = thr->pop_vec4(); unsigned wid = val.size(); int off = thr->words[off_index].w_int; // Abort if flags[4] is set. This can happen if the calulation // into an index register failed. if (thr->flags[4] == BIT4_1) return true; vvp_signal_value*sig = dynamic_cast (cp->net->fil); assert(sig); if (off >= (long)sig->value_size()) return true; if (off < 0) { if ((unsigned)-off >= wid) return true; int use_off = -off; assert((int)wid > use_off); unsigned use_wid = wid - use_off; val = val.subvalue(use_off, use_wid); off = 0; wid = use_wid; } if (off+wid > sig->value_size()) { val = val.subvalue(0, sig->value_size()-off); wid = val.size(); } if (thr->ecount == 0) { schedule_assign_vector(ptr, off, sig->value_size(), val, 0); } else { schedule_evctl(ptr, val, off, sig->value_size(), thr->event, thr->ecount); } return true; } /* * %assign/vec4/d */ bool of_ASSIGN_VEC4D(vthread_t thr, vvp_code_t cp) { vvp_net_ptr_t ptr (cp->net, 0); unsigned del_index = cp->bit_idx[0]; vvp_time64_t del = thr->words[del_index].w_int; vvp_vector4_t value = thr->pop_vec4(); vvp_signal_value*sig = dynamic_cast (cp->net->fil); assert(sig); schedule_assign_vector(ptr, 0, sig->value_size(), value, del); return true; } /* * %assign/vec4/e */ bool of_ASSIGN_VEC4E(vthread_t thr, vvp_code_t cp) { vvp_net_ptr_t ptr (cp->net, 0); vvp_vector4_t value = thr->pop_vec4(); vvp_signal_value*sig = dynamic_cast (cp->net->fil); assert(sig); if (thr->ecount == 0) { schedule_assign_vector(ptr, 0, sig->value_size(), value, 0); } else { schedule_evctl(ptr, value, 0, sig->value_size(), thr->event, thr->ecount); } thr->event = 0; thr->ecount = 0; return true; } /* * This is %assign/wr , * * This assigns (after a delay) a value to a real variable. Use the * vpi_put_value function to do the assign, with the delay written * into the vpiInertialDelay carrying the desired delay. */ bool of_ASSIGN_WR(vthread_t thr, vvp_code_t cp) { unsigned delay = cp->bit_idx[0]; double value = thr->pop_real(); s_vpi_time del; del.type = vpiSimTime; vpip_time_to_timestruct(&del, delay); __vpiHandle*tmp = cp->handle; t_vpi_value val; val.format = vpiRealVal; val.value.real = value; vpi_put_value(tmp, &val, &del, vpiTransportDelay); return true; } bool of_ASSIGN_WRD(vthread_t thr, vvp_code_t cp) { vvp_time64_t delay = thr->words[cp->bit_idx[0]].w_uint; double value = thr->pop_real(); s_vpi_time del; del.type = vpiSimTime; vpip_time_to_timestruct(&del, delay); __vpiHandle*tmp = cp->handle; t_vpi_value val; val.format = vpiRealVal; val.value.real = value; vpi_put_value(tmp, &val, &del, vpiTransportDelay); return true; } bool of_ASSIGN_WRE(vthread_t thr, vvp_code_t cp) { assert(thr->event != 0); double value = thr->pop_real(); __vpiHandle*tmp = cp->handle; // If the count is zero then just put the value. if (thr->ecount == 0) { t_vpi_value val; val.format = vpiRealVal; val.value.real = value; vpi_put_value(tmp, &val, 0, vpiNoDelay); } else { schedule_evctl(tmp, value, thr->event, thr->ecount); } thr->event = 0; thr->ecount = 0; return true; } bool of_BLEND(vthread_t thr, vvp_code_t) { vvp_vector4_t vala = thr->pop_vec4(); vvp_vector4_t valb = thr->pop_vec4(); assert(vala.size() == valb.size()); for (unsigned idx = 0 ; idx < vala.size() ; idx += 1) { if (vala.value(idx) == valb.value(idx)) continue; vala.set_bit(idx, BIT4_X); } thr->push_vec4(vala); return true; } bool of_BLEND_WR(vthread_t thr, vvp_code_t) { double f = thr->pop_real(); double t = thr->pop_real(); thr->push_real((t == f) ? t : 0.0); return true; } bool of_BREAKPOINT(vthread_t, vvp_code_t) { return true; } /* * The %cassign/link instruction connects a source node to a * destination node. The destination node must be a signal, as it is * marked with the source of the cassign so that it may later be * unlinked without specifically knowing the source that this * instruction used. */ bool of_CASSIGN_LINK(vthread_t, vvp_code_t cp) { vvp_net_t*dst = cp->net; vvp_net_t*src = cp->net2; vvp_fun_signal_base*sig = dynamic_cast(dst->fun); assert(sig); /* Any previous continuous assign should have been removed already. */ assert(sig->cassign_link == 0); sig->cassign_link = src; /* Link the output of the src to the port[1] (the cassign port) of the destination. */ vvp_net_ptr_t dst_ptr (dst, 1); src->link(dst_ptr); return true; } /* * If there is an existing continuous assign linked to the destination * node, unlink it. This must be done before applying a new continuous * assign, otherwise the initial assigned value will be propagated to * any other nodes driven by the old continuous assign source. */ static void cassign_unlink(vvp_net_t*dst) { vvp_fun_signal_base*sig = dynamic_cast(dst->fun); assert(sig); if (sig->cassign_link == 0) return; vvp_net_ptr_t tmp (dst, 1); sig->cassign_link->unlink(tmp); sig->cassign_link = 0; } /* * The %cassign/v instruction invokes a continuous assign of a * constant value to a signal. The instruction arguments are: * * %cassign/vec4 ; * * Where the is the net label assembled into a vvp_net pointer, * and the and are stashed in the bit_idx array. * * This instruction writes vvp_vector4_t values to port-1 of the * target signal. */ bool of_CASSIGN_VEC4(vthread_t thr, vvp_code_t cp) { vvp_net_t*net = cp->net; vvp_vector4_t value = thr->pop_vec4(); /* Remove any previous continuous assign to this net. */ cassign_unlink(net); /* Set the value into port 1 of the destination. */ vvp_net_ptr_t ptr (net, 1); vvp_send_vec4(ptr, value, 0); return true; } /* * %cassign/vec4/off , */ bool of_CASSIGN_VEC4_OFF(vthread_t thr, vvp_code_t cp) { vvp_net_t*net = cp->net; unsigned base_idx = cp->bit_idx[0]; long base = thr->words[base_idx].w_int; vvp_vector4_t value = thr->pop_vec4(); unsigned wid = value.size(); if (thr->flags[4] == BIT4_1) return true; /* Remove any previous continuous assign to this net. */ cassign_unlink(net); vvp_signal_value*sig = dynamic_cast (net->fil); assert(sig); if (base < 0 && (wid <= (unsigned)-base)) return true; if (base >= (long)sig->value_size()) return true; if (base < 0) { wid -= (unsigned) -base; base = 0; value.resize(wid); } if (base+wid > sig->value_size()) { wid = sig->value_size() - base; value.resize(wid); } vvp_net_ptr_t ptr (net, 1); vvp_send_vec4_pv(ptr, value, base, wid, sig->value_size(), 0); return true; } bool of_CASSIGN_WR(vthread_t thr, vvp_code_t cp) { vvp_net_t*net = cp->net; double value = thr->pop_real(); /* Remove any previous continuous assign to this net. */ cassign_unlink(net); /* Set the value into port 1 of the destination. */ vvp_net_ptr_t ptr (net, 1); vvp_send_real(ptr, value, 0); return true; } /* * %cast2 */ bool of_CAST2(vthread_t thr, vvp_code_t) { vvp_vector4_t&val = thr->peek_vec4(); unsigned wid = val.size(); for (unsigned idx = 0 ; idx < wid ; idx += 1) { switch (val.value(idx)) { case BIT4_0: case BIT4_1: break; default: val.set_bit(idx, BIT4_0); break; } } return true; } static void do_CMPE(vthread_t thr, const vvp_vector4_t&lval, const vvp_vector4_t&rval) { assert(rval.size() == lval.size()); if (lval.has_xz() || rval.has_xz()) { unsigned wid = lval.size(); vvp_bit4_t eq = BIT4_1; vvp_bit4_t eeq = BIT4_1; for (unsigned idx = 0 ; idx < wid ; idx += 1) { vvp_bit4_t lv = lval.value(idx); vvp_bit4_t rv = rval.value(idx); if (lv != rv) eeq = BIT4_0; if (eq==BIT4_1 && (bit4_is_xz(lv) || bit4_is_xz(rv))) eq = BIT4_X; if ((lv == BIT4_0) && (rv==BIT4_1)) eq = BIT4_0; if ((lv == BIT4_1) && (rv==BIT4_0)) eq = BIT4_0; if (eq == BIT4_0) break; } thr->flags[4] = eq; thr->flags[6] = eeq; } else { // If there are no XZ bits anywhere, then the results of // == match the === test. thr->flags[4] = thr->flags[6] = (lval.eeq(rval)? BIT4_1 : BIT4_0); } } /* * %cmp/e * * Pop the operands from the stack, and do not replace them. The * results are written to flag bits: * * 4: eq (equal) * * 6: eeq (case equal) */ bool of_CMPE(vthread_t thr, vvp_code_t) { // We are going to pop these and push nothing in their // place, but for now it is more efficient to use a constant // reference. When we finish, pop the stack without copies. const vvp_vector4_t&rval = thr->peek_vec4(0); const vvp_vector4_t&lval = thr->peek_vec4(1); do_CMPE(thr, lval, rval); thr->pop_vec4(2); return true; } bool of_CMPNE(vthread_t thr, vvp_code_t) { // We are going to pop these and push nothing in their // place, but for now it is more efficient to use a constant // reference. When we finish, pop the stack without copies. const vvp_vector4_t&rval = thr->peek_vec4(0); const vvp_vector4_t&lval = thr->peek_vec4(1); do_CMPE(thr, lval, rval); thr->flags[4] = ~thr->flags[4]; thr->flags[6] = ~thr->flags[6]; thr->pop_vec4(2); return true; } /* * %cmpi/e , , * * Pop1 operand, get the other operand from the arguments. */ bool of_CMPIE(vthread_t thr, vvp_code_t cp) { unsigned wid = cp->number; vvp_vector4_t&lval = thr->peek_vec4(); // I expect that most of the bits of an immediate value are // going to be zero, so start the result vector with all zero // bits. Then we only need to replace the bits that are different. vvp_vector4_t rval (wid, BIT4_0); get_immediate_rval (cp, rval); do_CMPE(thr, lval, rval); thr->pop_vec4(1); return true; } bool of_CMPINE(vthread_t thr, vvp_code_t cp) { unsigned wid = cp->number; vvp_vector4_t&lval = thr->peek_vec4(); // I expect that most of the bits of an immediate value are // going to be zero, so start the result vector with all zero // bits. Then we only need to replace the bits that are different. vvp_vector4_t rval (wid, BIT4_0); get_immediate_rval (cp, rval); do_CMPE(thr, lval, rval); thr->flags[4] = ~thr->flags[4]; thr->flags[6] = ~thr->flags[6]; thr->pop_vec4(1); return true; } static void do_CMPS(vthread_t thr, const vvp_vector4_t&lval, const vvp_vector4_t&rval) { assert(rval.size() == lval.size()); // If either value has XZ bits, then the eq and lt values are // known already to be X. Just calculate the eeq result as a // special case and short circuit the rest of the compare. if (lval.has_xz() || rval.has_xz()) { thr->flags[4] = BIT4_X; // eq thr->flags[5] = BIT4_X; // lt thr->flags[6] = lval.eeq(rval)? BIT4_1 : BIT4_0; return; } // Past this point, we know we are dealing only with fully // defined values. unsigned wid = lval.size(); const vvp_bit4_t sig1 = lval.value(wid-1); const vvp_bit4_t sig2 = rval.value(wid-1); // If the lval is <0 and the rval is >=0, then we know the result. if ((sig1 == BIT4_1) && (sig2 == BIT4_0)) { thr->flags[4] = BIT4_0; // eq; thr->flags[5] = BIT4_1; // lt; thr->flags[6] = BIT4_0; // eeq return; } // If the lval is >=0 and the rval is <0, then we know the result. if ((sig1 == BIT4_0) && (sig2 == BIT4_1)) { thr->flags[4] = BIT4_0; // eq; thr->flags[5] = BIT4_0; // lt; thr->flags[6] = BIT4_0; // eeq return; } // The values have the same sign, so we have to look at the // actual value. Scan from the MSB down. As soon as we find a // bit that differs, we know the result. for (unsigned idx = 1 ; idx < wid ; idx += 1) { vvp_bit4_t lv = lval.value(wid-1-idx); vvp_bit4_t rv = rval.value(wid-1-idx); if (lv == rv) continue; thr->flags[4] = BIT4_0; // eq thr->flags[6] = BIT4_0; // eeq if (lv==BIT4_0) { thr->flags[5] = BIT4_1; // lt } else { thr->flags[5] = BIT4_0; // lt } return; } // If we survive the loop above, then the values must be equal. thr->flags[4] = BIT4_1; thr->flags[5] = BIT4_0; thr->flags[6] = BIT4_1; } /* * %cmp/s * * Pop the operands from the stack, and do not replace them. The * results are written to flag bits: * * 4: eq (equal) * 5: lt (less than) * 6: eeq (case equal) */ bool of_CMPS(vthread_t thr, vvp_code_t) { // We are going to pop these and push nothing in their // place, but for now it is more efficient to use a constant // reference. When we finish, pop the stack without copies. const vvp_vector4_t&rval = thr->peek_vec4(0); const vvp_vector4_t&lval = thr->peek_vec4(1); do_CMPS(thr, lval, rval); thr->pop_vec4(2); return true; } /* * %cmpi/s , , * * Pop1 operand, get the other operand from the arguments. */ bool of_CMPIS(vthread_t thr, vvp_code_t cp) { unsigned wid = cp->number; vvp_vector4_t&lval = thr->peek_vec4(); // I expect that most of the bits of an immediate value are // going to be zero, so start the result vector with all zero // bits. Then we only need to replace the bits that are different. vvp_vector4_t rval (wid, BIT4_0); get_immediate_rval (cp, rval); do_CMPS(thr, lval, rval); thr->pop_vec4(1); return true; } bool of_CMPSTR(vthread_t thr, vvp_code_t) { string re = thr->pop_str(); string le = thr->pop_str(); int rc = strcmp(le.c_str(), re.c_str()); vvp_bit4_t eq; vvp_bit4_t lt; if (rc == 0) { eq = BIT4_1; lt = BIT4_0; } else if (rc < 0) { eq = BIT4_0; lt = BIT4_1; } else { eq = BIT4_0; lt = BIT4_0; } thr->flags[4] = eq; thr->flags[5] = lt; return true; } static void of_CMPU_the_hard_way(vthread_t thr, unsigned wid, const vvp_vector4_t&lval, const vvp_vector4_t&rval) { vvp_bit4_t eq = BIT4_1; vvp_bit4_t eeq = BIT4_1; for (unsigned idx = 0 ; idx < wid ; idx += 1) { vvp_bit4_t lv = lval.value(idx); vvp_bit4_t rv = rval.value(idx); if (lv != rv) eeq = BIT4_0; if (eq==BIT4_1 && (bit4_is_xz(lv) || bit4_is_xz(rv))) eq = BIT4_X; if ((lv == BIT4_0) && (rv==BIT4_1)) eq = BIT4_0; if ((lv == BIT4_1) && (rv==BIT4_0)) eq = BIT4_0; if (eq == BIT4_0) break; } thr->flags[4] = eq; thr->flags[5] = BIT4_X; thr->flags[6] = eeq; } static void do_CMPU(vthread_t thr, const vvp_vector4_t&lval, const vvp_vector4_t&rval) { vvp_bit4_t eq = BIT4_1; vvp_bit4_t lt = BIT4_0; if (rval.size() != lval.size()) { cerr << "VVP ERROR: %cmp/u operand width mismatch: lval=" << lval << ", rval=" << rval << endl; } assert(rval.size() == lval.size()); unsigned wid = lval.size(); unsigned long*larray = lval.subarray(0,wid); if (larray == 0) return of_CMPU_the_hard_way(thr, wid, lval, rval); unsigned long*rarray = rval.subarray(0,wid); if (rarray == 0) { delete[]larray; return of_CMPU_the_hard_way(thr, wid, lval, rval); } unsigned words = (wid+CPU_WORD_BITS-1) / CPU_WORD_BITS; for (unsigned wdx = 0 ; wdx < words ; wdx += 1) { if (larray[wdx] == rarray[wdx]) continue; eq = BIT4_0; if (larray[wdx] < rarray[wdx]) lt = BIT4_1; else lt = BIT4_0; } delete[]larray; delete[]rarray; thr->flags[4] = eq; thr->flags[5] = lt; thr->flags[6] = eq; } bool of_CMPU(vthread_t thr, vvp_code_t) { const vvp_vector4_t&rval = thr->peek_vec4(0); const vvp_vector4_t&lval = thr->peek_vec4(1); do_CMPU(thr, lval, rval); thr->pop_vec4(2); return true; } /* * %cmpi/u , , * * Pop1 operand, get the other operand from the arguments. */ bool of_CMPIU(vthread_t thr, vvp_code_t cp) { unsigned wid = cp->number; vvp_vector4_t&lval = thr->peek_vec4(); // I expect that most of the bits of an immediate value are // going to be zero, so start the result vector with all zero // bits. Then we only need to replace the bits that are different. vvp_vector4_t rval (wid, BIT4_0); get_immediate_rval (cp, rval); do_CMPU(thr, lval, rval); thr->pop_vec4(1); return true; } /* * %cmp/x */ bool of_CMPX(vthread_t thr, vvp_code_t) { vvp_bit4_t eq = BIT4_1; vvp_vector4_t rval = thr->pop_vec4(); vvp_vector4_t lval = thr->pop_vec4(); assert(rval.size() == lval.size()); unsigned wid = lval.size(); for (unsigned idx = 0 ; idx < wid ; idx += 1) { vvp_bit4_t lv = lval.value(idx); vvp_bit4_t rv = rval.value(idx); if ((lv != rv) && !bit4_is_xz(lv) && !bit4_is_xz(rv)) { eq = BIT4_0; break; } } thr->flags[4] = eq; return true; } bool of_CMPWR(vthread_t thr, vvp_code_t) { double r = thr->pop_real(); double l = thr->pop_real(); vvp_bit4_t eq = (l == r)? BIT4_1 : BIT4_0; vvp_bit4_t lt = (l < r)? BIT4_1 : BIT4_0; thr->flags[4] = eq; thr->flags[5] = lt; return true; } bool of_CMPWS(vthread_t thr, vvp_code_t cp) { int64_t l = thr->words[cp->bit_idx[0]].w_int; int64_t r = thr->words[cp->bit_idx[1]].w_int; vvp_bit4_t eq = (l == r)? BIT4_1 : BIT4_0; vvp_bit4_t lt = (l < r)? BIT4_1 : BIT4_0; thr->flags[4] = eq; thr->flags[5] = lt; return true; } bool of_CMPWU(vthread_t thr, vvp_code_t cp) { uint64_t l = thr->words[cp->bit_idx[0]].w_uint; uint64_t r = thr->words[cp->bit_idx[1]].w_uint; vvp_bit4_t eq = (l == r)? BIT4_1 : BIT4_0; vvp_bit4_t lt = (l < r)? BIT4_1 : BIT4_0; thr->flags[4] = eq; thr->flags[5] = lt; return true; } /* * %cmp/z */ bool of_CMPZ(vthread_t thr, vvp_code_t) { vvp_bit4_t eq = BIT4_1; vvp_vector4_t rval = thr->pop_vec4(); vvp_vector4_t lval = thr->pop_vec4(); assert(rval.size() == lval.size()); unsigned wid = lval.size(); for (unsigned idx = 0 ; idx < wid ; idx += 1) { vvp_bit4_t lv = lval.value(idx); vvp_bit4_t rv = rval.value(idx); if ((lv != rv) && (rv != BIT4_Z) && (lv != BIT4_Z)) { eq = BIT4_0; break; } } thr->flags[4] = eq; return true; } /* * %concat/str; */ bool of_CONCAT_STR(vthread_t thr, vvp_code_t) { string text = thr->pop_str(); thr->peek_str(0).append(text); return true; } /* * %concati/str ; */ bool of_CONCATI_STR(vthread_t thr, vvp_code_t cp) { const char*text = cp->text; thr->peek_str(0).append(text); return true; } /* * %concat/vec4 */ bool of_CONCAT_VEC4(vthread_t thr, vvp_code_t) { const vvp_vector4_t&lsb = thr->peek_vec4(0); const vvp_vector4_t&msb = thr->peek_vec4(1); // The result is the size of the top two vectors in the stack. vvp_vector4_t res (msb.size() + lsb.size(), BIT4_X); // Build up the result. res.set_vec(0, lsb); res.set_vec(lsb.size(), msb); // Rearrange the stack to pop the inputs and push the // result. Do that by actually popping only 1 stack position // and replacing the new top with the new value. thr->pop_vec4(1); thr->peek_vec4() = res; return true; } /* * %concati/vec4 , , * * Concat the immediate value to the LOW bits of the concatenation. * Get the HIGH bits from the top of the vec4 stack. */ bool of_CONCATI_VEC4(vthread_t thr, vvp_code_t cp) { uint32_t vala = cp->bit_idx[0]; uint32_t valb = cp->bit_idx[1]; unsigned wid = cp->number; vvp_vector4_t&msb = thr->peek_vec4(); // I expect that most of the bits of an immediate value are // going to be zero, so start the result vector with all zero // bits. Then we only need to replace the bits that are different. vvp_vector4_t lsb (wid, BIT4_0); // The %concati/vec4 can create values bigger then 32 bits, but // only if the high bits are zero. So at most we need to run // through the loop below 32 times. Maybe less, if the target // width is less. We don't have to do anything special on that // because vala/valb bits will shift away so (vala|valb) will // turn to zero at or before 32 shifts. for (unsigned idx = 0 ; idx < wid && (vala|valb) ; idx += 1) { uint32_t ba = 0; // Convert the vala/valb bits to a ba number that can be // used to select what goes into the value. ba = (valb & 1) << 1; ba |= vala & 1; switch (ba) { case 1: lsb.set_bit(idx, BIT4_1); break; case 2: lsb.set_bit(idx, BIT4_Z); break; case 3: lsb.set_bit(idx, BIT4_X); break; default: break; } vala >>= 1; valb >>= 1; } vvp_vector4_t res (msb.size()+lsb.size(), BIT4_X); res.set_vec(0, lsb); res.set_vec(lsb.size(), msb); msb = res; return true; } bool of_CVT_RS(vthread_t thr, vvp_code_t cp) { int64_t r = thr->words[cp->bit_idx[0]].w_int; thr->push_real( (double)(r) ); return true; } bool of_CVT_RU(vthread_t thr, vvp_code_t cp) { uint64_t r = thr->words[cp->bit_idx[0]].w_uint; thr->push_real( (double)(r) ); return true; } /* * %cvt/rv */ bool of_CVT_RV(vthread_t thr, vvp_code_t) { double val; vvp_vector4_t val4 = thr->pop_vec4(); vector4_to_value(val4, val, false); thr->push_real(val); return true; } /* * %cvt/rv/s */ bool of_CVT_RV_S(vthread_t thr, vvp_code_t) { double val; vvp_vector4_t val4 = thr->pop_vec4(); vector4_to_value(val4, val, true); thr->push_real(val); return true; } /* * %cvt/sr * Pop the top value from the real stack, convert it to a 64bit signed * and save it to the indexed register. */ bool of_CVT_SR(vthread_t thr, vvp_code_t cp) { double r = thr->pop_real(); thr->words[cp->bit_idx[0]].w_int = i64round(r); return true; } bool of_CVT_UR(vthread_t thr, vvp_code_t cp) { double r = thr->pop_real(); if (r >= 0.0) thr->words[cp->bit_idx[0]].w_uint = (uint64_t)floor(r+0.5); else thr->words[cp->bit_idx[0]].w_uint = (uint64_t)ceil(r-0.5); return true; } /* * %cvt/vr */ bool of_CVT_VR(vthread_t thr, vvp_code_t cp) { double r = thr->pop_real(); unsigned wid = cp->number; vvp_vector4_t tmp(wid, r); thr->push_vec4(tmp); return true; } /* * This implements the %deassign instruction. All we do is write a * long(1) to port-3 of the addressed net. This turns off an active * continuous assign activated by %cassign/v */ bool of_DEASSIGN(vthread_t, vvp_code_t cp) { vvp_net_t*net = cp->net; unsigned base = cp->bit_idx[0]; unsigned width = cp->bit_idx[1]; vvp_signal_value*fil = dynamic_cast (net->fil); assert(fil); vvp_fun_signal_vec*sig = dynamic_cast(net->fun); assert(sig); if (base >= fil->value_size()) return true; if (base+width > fil->value_size()) width = fil->value_size() - base; bool full_sig = base == 0 && width == fil->value_size(); // This is the net that is forcing me... if (vvp_net_t*src = sig->cassign_link) { if (! full_sig) { fprintf(stderr, "Sorry: when a signal is assigning a " "register, I cannot deassign part of it.\n"); exit(1); } // And this is the pointer to be removed. vvp_net_ptr_t dst_ptr (net, 1); src->unlink(dst_ptr); sig->cassign_link = 0; } /* Do we release all or part of the net? */ if (full_sig) { sig->deassign(); } else { sig->deassign_pv(base, width); } return true; } bool of_DEASSIGN_WR(vthread_t, vvp_code_t cp) { vvp_net_t*net = cp->net; vvp_fun_signal_real*sig = dynamic_cast(net->fun); assert(sig); // This is the net that is forcing me... if (vvp_net_t*src = sig->cassign_link) { // And this is the pointer to be removed. vvp_net_ptr_t dst_ptr (net, 1); src->unlink(dst_ptr); sig->cassign_link = 0; } sig->deassign(); return true; } /* * %debug/thr */ bool of_DEBUG_THR(vthread_t thr, vvp_code_t cp) { const char*text = cp->text; thr->debug_dump(cerr, text); return true; } /* * The delay takes two 32bit numbers to make up a 64bit time. * * %delay , */ bool of_DELAY(vthread_t thr, vvp_code_t cp) { vvp_time64_t low = cp->bit_idx[0]; vvp_time64_t hig = cp->bit_idx[1]; vvp_time64_t res = 32; res = hig << res; res += low; schedule_vthread(thr, res); return false; } bool of_DELAYX(vthread_t thr, vvp_code_t cp) { vvp_time64_t delay; assert(cp->number < vthread_s::WORDS_COUNT); delay = thr->words[cp->number].w_uint; schedule_vthread(thr, delay); return false; } /* %delete/obj