/* * Copyright (c) 1998 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #if !defined(WINNT) #ident "$Id: t-vvm.cc,v 1.6 1998/11/23 00:20:23 steve Exp $" #endif # include # include # include # include # include # include "netlist.h" # include "target.h" static string make_temp() { static unsigned counter = 0; ostrstream str; str << "TMP" << counter << ends; counter += 1; return str.str(); } class target_vvm : public target_t { public: virtual void start_design(ostream&os, const Design*); virtual void signal(ostream&os, const NetNet*); virtual void logic(ostream&os, const NetLogic*); virtual void bufz(ostream&os, const NetBUFZ*); virtual void net_const(ostream&os, const NetConst*); virtual void net_pevent(ostream&os, const NetPEvent*); virtual void start_process(ostream&os, const NetProcTop*); virtual void proc_assign(ostream&os, const NetAssign*); virtual void proc_block(ostream&os, const NetBlock*); virtual void proc_condit(ostream&os, const NetCondit*); virtual void proc_task(ostream&os, const NetTask*); virtual void proc_while(ostream&os, const NetWhile*); virtual void proc_event(ostream&os, const NetPEvent*); virtual void proc_delay(ostream&os, const NetPDelay*); virtual void end_process(ostream&os, const NetProcTop*); virtual void end_design(ostream&os, const Design*); private: void emit_gate_outputfun_(const NetNode*); ostrstream delayed; ostrstream init_code; unsigned process_counter; unsigned thread_step_; unsigned indent_; }; /* * This class emits code for the rvalue of a procedural * assignment. The expression is evaluated to fit the width * specified. */ class vvm_proc_rval : public expr_scan_t { public: explicit vvm_proc_rval(ostream&os, unsigned i) : result(""), os_(os), indent_(i) { } string result; private: ostream&os_; unsigned indent_; private: virtual void expr_const(const NetEConst*); virtual void expr_ident(const NetEIdent*); virtual void expr_signal(const NetESignal*); virtual void expr_unary(const NetEUnary*); virtual void expr_binary(const NetEBinary*); }; void vvm_proc_rval::expr_const(const NetEConst*expr) { string tname = make_temp(); os_ << setw(indent_) << "" << "vvm_bitset_t<" << expr->expr_width() << "> " << tname << ";" << endl; for (unsigned idx = 0 ; idx < expr->expr_width() ; idx += 1) { os_ << setw(indent_) << "" << tname << "[" << idx << "] = "; switch (expr->value().get(idx)) { case verinum::V0: os_ << "V0"; break; case verinum::V1: os_ << "V1"; break; case verinum::Vx: os_ << "Vx"; break; case verinum::Vz: os_ << "Vz"; break; } os_ << ";" << endl; } result = tname; } void vvm_proc_rval::expr_ident(const NetEIdent*expr) { result = mangle(expr->name()); } void vvm_proc_rval::expr_signal(const NetESignal*expr) { result = mangle(expr->name()); } void vvm_proc_rval::expr_unary(const NetEUnary*expr) { expr->expr()->expr_scan(this); string tname = make_temp(); os_ << " vvm_bitset_t<" << expr->expr_width() << "> " << tname << " = "; switch (expr->op()) { case '~': os_ << "vvm_unop_not(" << result << ");" << endl; break; default: cerr << "vvm: Unhandled unary op `" << expr->op() << "'" << endl; os_ << result << ";" << endl; break; } result = tname; } void vvm_proc_rval::expr_binary(const NetEBinary*expr) { expr->left()->expr_scan(this); string lres = result; expr->right()->expr_scan(this); string rres = result; result = make_temp(); os_ << setw(indent_) << "" << "vvm_bitset_t<" << expr->expr_width() << ">" << result << ";" << endl; switch (expr->op()) { case 'e': os_ << setw(indent_) << "" << result << " = vvm_binop_eq(" << lres << "," << rres << ");" << endl; break; case 'n': os_ << setw(indent_) << "" << result << " = vvm_binop_ne(" << lres << "," << rres << ");" << endl; break; case '+': os_ << setw(indent_) << "" << result << " = vvm_binop_plus(" << lres << "," << rres << ");" << endl; break; case '-': os_ << setw(indent_) << "" << result << " = vvm_binop_minus(" << lres << "," << rres << ");" << endl; break; default: cerr << "vvm: Unhandled binary op `" << expr->op() << "': " << *expr << endl; os_ << lres << ";" << endl; result = lres; break; } } static string emit_proc_rval(ostream&os, unsigned indent, const NetExpr*expr) { vvm_proc_rval scan (os, indent); expr->expr_scan(&scan); return scan.result; } class vvm_parm_rval : public expr_scan_t { public: explicit vvm_parm_rval(ostream&os) : result(""), os_(os) { } string result; private: virtual void expr_const(const NetEConst*); virtual void expr_ident(const NetEIdent*); virtual void expr_signal(const NetESignal*); private: ostream&os_; }; void vvm_parm_rval::expr_const(const NetEConst*expr) { if (expr->value().is_string()) { result = "\""; result = result + expr->value().as_string() + "\""; return; } } void vvm_parm_rval::expr_ident(const NetEIdent*expr) { if (expr->name() == "$time") { string res = make_temp(); os_ << " vvm_calltf_parm " << res << "(vvm_calltf_parm::TIME);" << endl; result = res; } else { cerr << "Unhandled identifier: " << expr->name() << endl; } } void vvm_parm_rval::expr_signal(const NetESignal*expr) { string res = make_temp(); os_ << " vvm_calltf_parm::SIG " << res << ";" << endl; os_ << " " << res << ".bits = &" << mangle(expr->name()) << ";" << endl; os_ << " " << res << ".mon = &" << mangle(expr->name()) << "_mon;" << endl; result = res; } static string emit_parm_rval(ostream&os, const NetExpr*expr) { vvm_parm_rval scan (os); expr->expr_scan(&scan); return scan.result; } void target_vvm::start_design(ostream&os, const Design*mod) { os << "# include \"vvm.h\"" << endl; os << "# include \"vvm_gates.h\"" << endl; os << "# include \"vvm_func.h\"" << endl; os << "# include \"vvm_calltf.h\"" << endl; os << "# include \"vvm_thread.h\"" << endl; process_counter = 0; } void target_vvm::end_design(ostream&os, const Design*mod) { delayed << ends; os << delayed.str(); os << "main()" << endl << "{" << endl; os << " vvm_simulation sim;" << endl; init_code << ends; os << init_code.str(); for (unsigned idx = 0 ; idx < process_counter ; idx += 1) os << " thread" << (idx+1) << "_t thread_" << (idx+1) << "(&sim);" << endl; os << " sim.run();" << endl; os << "}" << endl; } void target_vvm::signal(ostream&os, const NetNet*sig) { os << "static vvm_bitset_t<" << sig->pin_count() << "> " << mangle(sig->name()) << "; /* " << sig->name() << " */" << endl; os << "static vvm_monitor_t " << mangle(sig->name()) << "_mon(\"" << sig->name() << "\");" << endl; } /* * This method handles writing output functions for gates that have a * single output (at pin 0). This writes the output_fun method into * the delayed stream to be emitted to the output file later. */ void target_vvm::emit_gate_outputfun_(const NetNode*gate) { delayed << "static void " << mangle(gate->name()) << "_output_fun(vvm_simulation*sim, vvm_bit_t val)" << endl << "{" << endl; /* The output function connects to pin 0 of the netlist part and causes the inputs that it is connected to to be set with the new value. */ const NetObj*cur; unsigned pin; gate->pin(0).next_link(cur, pin); while (cur != gate) { // Skip signals if (const NetNet*sig = dynamic_cast(cur)) { delayed << " " << mangle(sig->name()) << "[" << pin << "] = val;" << endl; delayed << " " << mangle(sig->name()) << "_mon.trigger(sim);" << endl; } else { delayed << " " << mangle(cur->name()) << ".set(sim, " << pin << ", val);" << endl; } cur->pin(pin).next_link(cur, pin); } delayed << "}" << endl; } void target_vvm::logic(ostream&os, const NetLogic*gate) { os << "static void " << mangle(gate->name()) << "_output_fun(vvm_simulation*, vvm_bit_t);" << endl; switch (gate->type()) { case NetLogic::AND: os << "static vvm_and" << "<" << gate->pin_count()-1 << "," << gate->delay1() << "> "; break; case NetLogic::NAND: os << "static vvm_nand" << "<" << gate->pin_count()-1 << "," << gate->delay1() << "> "; break; case NetLogic::NOR: os << "static vvm_nor" << "<" << gate->pin_count()-1 << "," << gate->delay1() << "> "; break; case NetLogic::NOT: os << "static vvm_not" << "<" << gate->delay1() << "> "; break; case NetLogic::OR: os << "static vvm_or" << "<" << gate->pin_count()-1 << "," << gate->delay1() << "> "; break; case NetLogic::XNOR: os << "static vvm_xnor" << "<" << gate->pin_count()-1 << "," << gate->delay1() << "> "; break; case NetLogic::XOR: os << "static vvm_xor" << "<" << gate->pin_count()-1 << "," << gate->delay1() << "> "; break; } os << mangle(gate->name()) << "(&" << mangle(gate->name()) << "_output_fun);" << endl; emit_gate_outputfun_(gate); } void target_vvm::bufz(ostream&os, const NetBUFZ*gate) { os << "static void " << mangle(gate->name()) << "_output_fun(vvm_simulation*, vvm_bit_t);" << endl; os << "static vvm_bufz " << mangle(gate->name()) << "(&" << mangle(gate->name()) << "_output_fun);" << endl; emit_gate_outputfun_(gate); } /* * The NetConst is a synthetic device created to represent constant * values. I represent them in the output as a vvm_bufz object that * has its input connected to nothing but is initialized to the * desired constant value. */ void target_vvm::net_const(ostream&os, const NetConst*gate) { os << "static void " << mangle(gate->name()) << "_output_fun(vvm_simulation*, vvm_bit_t);" << endl; os << "static vvm_bufz " << mangle(gate->name()) << "(&" << mangle(gate->name()) << "_output_fun);" << endl; init_code << " " << mangle(gate->name()) << ".set(&sim, 1, "; switch (gate->value()) { case verinum::V0: init_code << "V0"; break; case verinum::V1: init_code << "V1"; break; case verinum::Vx: init_code << "Vx"; break; case verinum::Vz: init_code << "Vz"; break; } init_code << ");" << endl; emit_gate_outputfun_(gate); } /* * The net_pevent device is a synthetic device type--a fabrication of * the elaboration phase. An event device receives value changes from * the attached signal. It is an input only device, its only value * being the side-effects that threads waiting on events can be * awakened. * * The proc_event method handles the other half of this, the process * that blocks on the event. */ void target_vvm::net_pevent(ostream&os, const NetPEvent*gate) { os << "static vvm_pevent " << mangle(gate->name()) << ";" " /* " << gate->name() << " */" << endl; } void target_vvm::start_process(ostream&os, const NetProcTop*proc) { process_counter += 1; indent_ = 8; thread_step_ = 0; os << "class thread" << process_counter << "_t : public vvm_thread {" << endl; os << " public:" << endl; os << " thread" << process_counter << "_t(vvm_simulation*sim)" << endl; os << " : vvm_thread(sim), step_(&step_0_)" << endl; os << " { }" << endl; os << " ~thread" << process_counter << "_t() { }" << endl; os << endl; os << " bool go() { return (this->*step_)(); }" << endl; os << " private:" << endl; os << " bool (thread" << process_counter << "_t::*step_)();" << endl; os << " bool step_0_() {" << endl; } /* * This method generates code for a procedural assignment. The lval is * a signal, but the assignment should generate code to go to all the * connected devices/events. */ void target_vvm::proc_assign(ostream&os, const NetAssign*net) { string rval = emit_proc_rval(os, indent_, net->rval()); const NetNet*lval; unsigned msb, lsb; net->find_lval_range(lval, msb, lsb); if ((lsb == 0) && (msb == (lval->pin_count()-1))) { os << setw(indent_) << "" << "// " << lval->name() << " = "; net->rval()->dump(os); os << endl; os << setw(indent_) << "" << mangle(lval->name()) << " = " << rval << ";" << endl; } else { assert(0); } os << setw(indent_) << "" << mangle(lval->name()) << "_mon.trigger(sim_);" << endl; /* Not only is the lvalue signal assigned to, send the bits to all the other pins that are connected to this signal. */ for (unsigned idx = 0 ; idx < net->pin_count() ; idx += 1) { const NetObj*cur; unsigned pin; for (net->pin(idx).next_link(cur, pin) ; net->pin(idx) != cur->pin(pin) ; cur->pin(pin).next_link(cur, pin)) { // Skip NetAssign nodes. They are output-only. if (dynamic_cast(cur)) continue; if (const NetNet*sig = dynamic_cast(cur)) { os << setw(indent_) << "" << mangle(sig->name()) << "[" << pin << "] = " << rval << "[" << idx << "];" << endl; os <name()) << "_mon.trigger(sim_);" << endl; } else { os << setw(indent_) << "" << mangle(cur->name()) << ".set(sim_, " << pin << ", " << rval << "[" << idx << "]);" << endl; } } } } void target_vvm::proc_block(ostream&os, const NetBlock*net) { net->emit_recurse(os, this); } void target_vvm::proc_condit(ostream&os, const NetCondit*net) { unsigned ind = indent_; indent_ += 4; string expr = emit_proc_rval(os, indent_, net->expr()); os << setw(ind) << "" << "if (" << expr << "[0] == V1) {" << endl; net->emit_recurse_if(os, this); os << setw(ind) << "" << "} else {" << endl; net->emit_recurse_else(os, this); os << setw(ind) << "" << "}" << endl; indent_ = ind; } void target_vvm::proc_task(ostream&os, const NetTask*net) { if (net->name()[0] == '$') { string ptmp = make_temp(); os << " struct vvm_calltf_parm " << ptmp << "[" << net->nparms() << "];" << endl; for (unsigned idx = 0 ; idx < net->nparms() ; idx += 1) if (net->parm(idx)) { string val = emit_parm_rval(os, net->parm(idx)); os << " " << ptmp << "[" << idx << "] = " << val << ";" << endl; } os << " vvm_calltask(sim_, \"" << net->name() << "\", " << net->nparms() << ", " << ptmp << ");" << endl; } else { os << " // Huh? " << net->name() << endl; } } void target_vvm::proc_while(ostream&os, const NetWhile*net) { unsigned ind = indent_; indent_ += 4; os << setw(ind) << "" << "for (;;) {" << endl; string expr = emit_proc_rval(os, indent_, net->expr()); os << setw(indent_) << "" << "if (" << expr << "[0] != V1)" " break;" << endl; net->emit_proc_recurse(os, this); os << setw(ind) << "" << "}" << endl; indent_ = ind; } /* * Within a process, the proc_event is a statement that is blocked * until the event is signalled. */ void target_vvm::proc_event(ostream&os, const NetPEvent*proc) { thread_step_ += 1; os << setw(indent_) << "" << "step_ = &step_" << thread_step_ << "_;" << endl; /* POSITIVE is for the wait construct, and needs to be handled specially. The structure of the generated code is: if (event.get()==V1) { return true; } else { event.wait(vvm_pevent::POSEDGE, this); return false; } This causes the wait to not even block the thread if the event value is already positive, otherwise wait for a rising edge. All the edge triggers look like this: event.wait(vvm_pevent::POSEDGE, this); return false; POSEDGE is replaced with the correct type for the desired edge. */ if (proc->edge() == NetPEvent::POSITIVE) { os << setw(indent_) << "" << "if (" << mangle(proc->name()) << ".get()==V1) {" << endl; os << setw(indent_+3) << "" << "return true;" << endl; os << setw(indent_) << "" << "} else {" << endl; os << setw(indent_+3) << "" << mangle(proc->name()) << ".wait(vvm_pevent::POSEDGE, this);" << endl; os << setw(indent_+3) << "" << "return false;" << endl; os << setw(indent_) << "" << "}" << endl; } else { os << setw(indent_) << "" << mangle(proc->name()) << ".wait(vvm_pevent::"; switch (proc->edge()) { case NetPEvent::ANYEDGE: os << "ANYEDGE"; break; case NetPEvent::POSITIVE: case NetPEvent::POSEDGE: os << "POSEDGE"; break; case NetPEvent::NEGEDGE: os << "NEGEDGE"; break; } os << ", this);" << endl; os << setw(indent_+3) << "" << "return false;" << endl; } os << " }" << endl; os << " bool step_" << thread_step_ << "_()" << endl; os << " {" << endl; proc->emit_proc_recurse(os, this); } /* * A delay suspends the thread for a period of time. */ void target_vvm::proc_delay(ostream&os, const NetPDelay*proc) { thread_step_ += 1; os << " step_ = &step_" << thread_step_ << "_;" << endl; os << " sim_->thread_delay(" << proc->delay() << ", this);" << endl; os << " return false;" << endl; os << " }" << endl; os << " bool step_" << thread_step_ << "_()" << endl; os << " {" << endl; proc->emit_proc_recurse(os, this); } void target_vvm::end_process(ostream&os, const NetProcTop*proc) { if (proc->type() == NetProcTop::KALWAYS) { os << setw(indent_) << "" << "step_ = &step_0_;" << endl; os << setw(indent_) << "" << "return true;" << endl; } else { os << setw(indent_) << "" << "step_ = 0;" << endl; os << setw(indent_) << "" << "return false;" << endl; } os << " }" << endl; os << "};" << endl; } static target_vvm target_vvm_obj; extern const struct target tgt_vvm = { "vvm", &target_vvm_obj }; /* * $Log: t-vvm.cc,v $ * Revision 1.6 1998/11/23 00:20:23 steve * NetAssign handles lvalues as pin links * instead of a signal pointer, * Wire attributes added, * Ability to parse UDP descriptions added, * XNF generates EXT records for signals with * the PAD attribute. * * Revision 1.5 1998/11/10 00:48:31 steve * Add support it vvm target for level-sensitive * triggers (i.e. the Verilog wait). * Fix display of $time is format strings. * * Revision 1.4 1998/11/09 18:55:34 steve * Add procedural while loops, * Parse procedural for loops, * Add procedural wait statements, * Add constant nodes, * Add XNOR logic gate, * Make vvm output look a bit prettier. * * Revision 1.3 1998/11/07 19:17:10 steve * Calculate expression widths at elaboration time. * * Revision 1.2 1998/11/07 17:05:06 steve * Handle procedural conditional, and some * of the conditional expressions. * * Elaborate signals and identifiers differently, * allowing the netlist to hold signal information. * * Revision 1.1 1998/11/03 23:29:05 steve * Introduce verilog to CVS. * */