/*
 * Copyright (c) 1999-2008 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
 */

# include "config.h"
# include "compiler.h"

# include  <iostream>

# include  "netlist.h"
# include  "netmisc.h"
# include  "ivl_assert.h"

NetNet* convert_to_real_const(Design*des, NetExpr*expr, NetExpr*obj)
{
      NetNet* sig;

      if (NetEConst*tmp = dynamic_cast<NetEConst*>(expr)) {
	    verireal vrl(tmp->value().as_double());
	    NetECReal rlval(vrl);
	    sig = rlval.synthesize(des);
      } else {
	    cerr << obj->get_fileline() << ": sorry: Cannot convert "
	    "bit based value (" << *expr << ") to real." << endl;
	    des->errors += 1;
	    sig = 0;
      }

      return sig;
}

  /* Note that lsig, rsig and real_args are references. */
bool process_binary_args(Design*des, NetExpr*left, NetExpr*right,
                         NetNet*&lsig, NetNet*&rsig, bool&real_args,
                         NetExpr*obj)
{
      if (left->expr_type() == IVL_VT_REAL ||
          right->expr_type() == IVL_VT_REAL) {
	    real_args = true;

	      /* Currently we will have a runtime assert if both expressions
	         are not real, though we can convert constants. */
	    if (left->expr_type() == IVL_VT_REAL) {
		  lsig = left->synthesize(des);
	    } else {
		  lsig = convert_to_real_const(des, left, obj);
	    }

	    if (right->expr_type() == IVL_VT_REAL) {
		  rsig = right->synthesize(des);
	    } else {
		  rsig = convert_to_real_const(des, right, obj);
	    }
      } else {
            real_args = false;
	    lsig = left->synthesize(des);
	    rsig = right->synthesize(des);

      }

      if (lsig == 0 || rsig == 0) return true;
      else return false;
}

NetNet* NetExpr::synthesize(Design*des)
{
      cerr << get_fileline() << ": internal error: cannot synthesize expression: "
	   << *this << endl;
      des->errors += 1;
      return 0;
}

/*
 * Make an LPM_ADD_SUB device from addition operators.
 */
NetNet* NetEBAdd::synthesize(Design*des)
{
      assert((op()=='+') || (op()=='-'));

      NetNet *lsig=0, *rsig=0;
      bool real_args=false;
      if (process_binary_args(des, left_, right_, lsig, rsig,
                              real_args, this)) {
	    return 0;
      }

      assert(expr_width() >= lsig->vector_width());
      assert(expr_width() >= rsig->vector_width());

      lsig = pad_to_width(des, lsig, expr_width());
      rsig = pad_to_width(des, rsig, expr_width());

      assert(lsig->vector_width() == rsig->vector_width());
      unsigned width=lsig->vector_width();

      perm_string path = lsig->scope()->local_symbol();
      NetNet*osig = new NetNet(lsig->scope(), path, NetNet::IMPLICIT, width);
      osig->local_flag(true);
      osig->data_type(expr_type());

      perm_string oname = osig->scope()->local_symbol();
      NetAddSub *adder = new NetAddSub(lsig->scope(), oname, width);
      connect(lsig->pin(0), adder->pin_DataA());
      connect(rsig->pin(0), adder->pin_DataB());
      connect(osig->pin(0), adder->pin_Result());
      des->add_node(adder);

      switch (op()) {
	  case '+':
	    adder->attribute(perm_string::literal("LPM_Direction"), verinum("ADD"));
	    break;
	  case '-':
	    adder->attribute(perm_string::literal("LPM_Direction"), verinum("SUB"));
	    break;
      }

      return osig;
}

/*
 * The bitwise logic operators are turned into discrete gates pretty
 * easily. Synthesize the left and right sub-expressions to get
 * signals, then just connect a single gate to each bit of the vector
 * of the expression.
 */
NetNet* NetEBBits::synthesize(Design*des)
{
      NetNet*lsig = left_->synthesize(des);
      NetNet*rsig = right_->synthesize(des);

      if (lsig == 0 || rsig == 0) return 0;

        /* You cannot do bitwise operations on real values. */
      if (lsig->data_type() == IVL_VT_REAL ||
          rsig->data_type() == IVL_VT_REAL) {
	    cerr << get_fileline() << ": error: " << human_readable_op(op_)
	         << " operator may not have REAL operands." << endl;
	    des->errors += 1;
	    return 0;
      }

      NetScope*scope = lsig->scope();
      assert(scope);

      unsigned width = lsig->vector_width();
      if (rsig->vector_width() > width) width = rsig->vector_width();

      lsig = pad_to_width(des, lsig, width);
      rsig = pad_to_width(des, rsig, width);

      assert(lsig->vector_width() == rsig->vector_width());
      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, width);
      osig->local_flag(true);
      osig->data_type(expr_type());

      perm_string oname = scope->local_symbol();
      NetLogic*gate;

      switch (op()) {
	  case '&':
	    gate = new NetLogic(scope, oname, 3, NetLogic::AND, width);
	    break;
	  case 'A':
	    gate = new NetLogic(scope, oname, 3, NetLogic::NAND, width);
	    break;
	  case '|':
	    gate = new NetLogic(scope, oname, 3, NetLogic::OR, width);
	    break;
	  case '^':
	    gate = new NetLogic(scope, oname, 3, NetLogic::XOR, width);
	    break;
	  case 'O':
	    gate = new NetLogic(scope, oname, 3, NetLogic::NOR, width);
	    break;
	  case 'X':
	    gate = new NetLogic(scope, oname, 3, NetLogic::XNOR, width);
	    break;
	  default:
	    assert(0);
      }

      connect(osig->pin(0), gate->pin(0));
      connect(lsig->pin(0), gate->pin(1));
      connect(rsig->pin(0), gate->pin(2));

      gate->set_line(*this);
      des->add_node(gate);

      return osig;
}

NetNet* NetEBComp::synthesize(Design*des)
{

      NetNet *lsig=0, *rsig=0;
      unsigned width;
      bool real_args=false;
      if (process_binary_args(des, left_, right_, lsig, rsig,
                              real_args, this)) {
	    return 0;
      }

      if (real_args) {
	    width = 1;
      } else {
	    width = lsig->vector_width();
	    if (rsig->vector_width() > width) width = rsig->vector_width();

	    lsig = pad_to_width(des, lsig, width);
	    rsig = pad_to_width(des, rsig, width);
      }

      NetScope*scope = lsig->scope();
      assert(scope);

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, 1);
      osig->set_line(*this);
      osig->local_flag(true);
      osig->data_type(IVL_VT_LOGIC);

      bool signed_compare = lsig->get_signed() && rsig->get_signed();

	/* Handle the special case of a single bit equality
	   operation. Make an XNOR gate instead of a comparator. */
      if ((width == 1) && ((op_ == 'e') || (op_ == 'E')) && !real_args) {
	    NetLogic*gate = new NetLogic(scope, scope->local_symbol(),
					 3, NetLogic::XNOR, 1);
	    gate->set_line(*this);
	    connect(gate->pin(0), osig->pin(0));
	    connect(gate->pin(1), lsig->pin(0));
	    connect(gate->pin(2), rsig->pin(0));
	    des->add_node(gate);
	    return osig;
      }

	/* Handle the special case of a single bit inequality
	   operation. This is similar to single bit equality, but uses
	   an XOR instead of an XNOR gate. */
      if ((width == 1) && ((op_ == 'n') || (op_ == 'N')) && !real_args) {
	    NetLogic*gate = new NetLogic(scope, scope->local_symbol(),
					 3, NetLogic::XOR, 1);
	    gate->set_line(*this);
	    connect(gate->pin(0), osig->pin(0));
	    connect(gate->pin(1), lsig->pin(0));
	    connect(gate->pin(2), rsig->pin(0));
	    des->add_node(gate);
	    return osig;
      }


      NetCompare*dev = new NetCompare(scope, scope->local_symbol(), width);
      dev->set_line(*this);
      des->add_node(dev);

      connect(dev->pin_DataA(), lsig->pin(0));
      connect(dev->pin_DataB(), rsig->pin(0));


      switch (op_) {
	  case '<':
	    connect(dev->pin_ALB(), osig->pin(0));
	    dev->set_signed(signed_compare);
	    break;
	  case '>':
	    connect(dev->pin_AGB(), osig->pin(0));
	    dev->set_signed(signed_compare);
	    break;
	  case 'E': // === ?
	    if (real_args) {
		  cerr << get_fileline() << ": error: Case equality may "
		          "not have real operands." << endl;
		  des->errors += 1;
		  return 0;
	    }
	  case 'e': // ==
	    connect(dev->pin_AEB(), osig->pin(0));
	    break;
	  case 'G': // >=
	    connect(dev->pin_AGEB(), osig->pin(0));
	    dev->set_signed(signed_compare);
	    break;
	  case 'L': // <=
	    connect(dev->pin_ALEB(), osig->pin(0));
	    dev->set_signed(signed_compare);
	    break;
	  case 'N': // !==
	    if (real_args) {
		  cerr << get_fileline() << ": error: Case inequality may "
		          "not have real operands." << endl;
		  des->errors += 1;
		  return 0;
	    }
	  case 'n': // !=
	    connect(dev->pin_ANEB(), osig->pin(0));
	    break;

	  default:
	    cerr << get_fileline() << ": internal error: cannot synthesize "
		  "comparison: " << *this << endl;
	    des->errors += 1;
	    return 0;
      }

      return osig;
}

NetNet* NetEBPow::synthesize(Design*des)
{
      NetNet *lsig=0, *rsig=0;
      unsigned width;
      bool real_args=false;
      if (process_binary_args(des, left_, right_, lsig, rsig,
                              real_args, this)) {
	    return 0;
      }

      if (real_args) width = 1;
      else width = expr_width();

      NetScope*scope = lsig->scope();
      assert(scope);

      NetPow*powr = new NetPow(scope, scope->local_symbol(), width,
			       lsig->vector_width(),
			       rsig->vector_width());
      des->add_node(powr);

      powr->set_signed( has_sign() );
      powr->set_line(*this);

      connect(powr->pin_DataA(), lsig->pin(0));
      connect(powr->pin_DataB(), rsig->pin(0));

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, width);
      osig->set_line(*this);
      osig->data_type(expr_type());
      osig->local_flag(true);

      connect(powr->pin_Result(), osig->pin(0));

      return osig;
}

NetNet* NetEBMult::synthesize(Design*des)
{
      NetNet *lsig=0, *rsig=0;
      unsigned width;
      bool real_args=false;
      if (process_binary_args(des, left_, right_, lsig, rsig,
                              real_args, this)) {
	    return 0;
      }

      if (real_args) width = 1;
      else width = expr_width();

      NetScope*scope = lsig->scope();
      assert(scope);

      NetMult*mult = new NetMult(scope, scope->local_symbol(),
				 width,
				 lsig->vector_width(),
				 rsig->vector_width());
      des->add_node(mult);

      mult->set_signed( has_sign() );
      mult->set_line(*this);

      connect(mult->pin_DataA(), lsig->pin(0));
      connect(mult->pin_DataB(), rsig->pin(0));

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, width);
      osig->set_line(*this);
      osig->data_type(expr_type());
      osig->local_flag(true);

      connect(mult->pin_Result(), osig->pin(0));

      return osig;
}

NetNet* NetEBDiv::synthesize(Design*des)
{
      NetNet *lsig=0, *rsig=0;
      unsigned width;
      bool real_args=false;
      if (process_binary_args(des, left_, right_, lsig, rsig,
                              real_args, this)) {
	    return 0;
      }

      if (real_args) width = 1;
      else width = expr_width();

      NetScope*scope = lsig->scope();

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, width);
      osig->set_line(*this);
      osig->data_type(lsig->data_type());
      osig->local_flag(true);

      switch (op()) {

	  case '/': {
		NetDivide*div = new NetDivide(scope, scope->local_symbol(),
					      width,
					      lsig->vector_width(),
					      rsig->vector_width());
		div->set_line(*this);
		des->add_node(div);

		connect(div->pin_DataA(), lsig->pin(0));
		connect(div->pin_DataB(), rsig->pin(0));
		connect(div->pin_Result(),osig->pin(0));
		break;
	  }

	  case '%': {
		  /* Baseline Verilog does not support the % operator with
		     real arguments, but we allow it in our extended form. */
		if (real_args && !gn_icarus_misc_flag) {
		      cerr << get_fileline() << ": error: Modulus operator "
		              "may not have REAL operands." << endl;
		      des->errors += 1;
		      return 0;
		}
		NetModulo*div = new NetModulo(scope, scope->local_symbol(),
					      width,
					      lsig->vector_width(),
					      rsig->vector_width());
		div->set_line(*this);
		des->add_node(div);

		connect(div->pin_DataA(), lsig->pin(0));
		connect(div->pin_DataB(), rsig->pin(0));
		connect(div->pin_Result(),osig->pin(0));
		break;
	  }

	  default: {
		cerr << get_fileline() << ": internal error: "
		     << "NetEBDiv has unexpected op() code: "
		     << op() << endl;
		des->errors += 1;

		delete osig;
		return 0;
	  }
      }

      return osig;
}

NetNet* NetEBLogic::synthesize(Design*des)
{
      NetNet*lsig = left_->synthesize(des);
      NetNet*rsig = right_->synthesize(des);

      if (lsig == 0 || rsig == 0) return 0;

        /* You cannot currently do logical operations on real values. */
      if (lsig->data_type() == IVL_VT_REAL ||
          rsig->data_type() == IVL_VT_REAL) {
	    cerr << get_fileline() << ": sorry: " << human_readable_op(op_)
	         << " is currently unsupported for real values." << endl;
	    des->errors += 1;
	    return 0;
      }

      NetScope*scope = lsig->scope();
      assert(scope);

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, 1);
      osig->data_type(expr_type());
      osig->local_flag(true);


      if (op() == 'o') {

	      /* Logic OR can handle the reduction *and* the logical
		 comparison with a single wide OR gate. So handle this
		 magically. */

	    perm_string oname = scope->local_symbol();

	    NetLogic*olog = new NetLogic(scope, oname,
					 lsig->pin_count()+rsig->pin_count()+1,
					 NetLogic::OR, 1);

	    connect(osig->pin(0), olog->pin(0));

	    unsigned pin = 1;
	    for (unsigned idx = 0 ;  idx < lsig->pin_count() ;  idx = 1)
		  connect(olog->pin(pin+idx), lsig->pin(idx));

	    pin += lsig->pin_count();
	    for (unsigned idx = 0 ;  idx < rsig->pin_count() ;  idx = 1)
		  connect(olog->pin(pin+idx), rsig->pin(idx));

	    des->add_node(olog);

      } else {
	    assert(op() == 'a');

	      /* Create the logic AND gate. This is a single bit
		 output, with inputs for each of the operands. */
	    NetLogic*olog;
	    perm_string oname = scope->local_symbol();

	    olog = new NetLogic(scope, oname, 3, NetLogic::AND, 1);

	    connect(osig->pin(0), olog->pin(0));
	    des->add_node(olog);

	      /* XXXX Here, I need to reduce the parameters with
		 reduction or. */


	      /* By this point, the left and right parameters have been
		 reduced to single bit values. Now we just connect them to
		 the logic gate. */
	    assert(lsig->pin_count() == 1);
	    connect(lsig->pin(0), olog->pin(1));

	    assert(rsig->pin_count() == 1);
	    connect(rsig->pin(0), olog->pin(2));
      }


      return osig;
}

NetNet* NetEBShift::synthesize(Design*des)
{
      eval_expr(right_);

      NetNet*lsig = left_->synthesize(des);

      if (lsig == 0) return 0;

        /* Cannot shift a real values. */
      if (lsig->data_type() == IVL_VT_REAL) {
	    cerr << get_fileline() << ": error: shift operator ("
	         << human_readable_op(op_)
	         << ") cannot shift a real values." << endl;
	    des->errors += 1;
	    return 0;
      }

      bool right_flag  =  op_ == 'r' || op_ == 'R';
      bool signed_flag =  op_ == 'R';

      NetScope*scope = lsig->scope();

	/* Detect the special case where the shift amount is
	   constant. Evaluate the shift amount, and simply reconnect
	   the left operand to the output, but shifted. */
      if (NetEConst*rcon = dynamic_cast<NetEConst*>(right_)) {
	    verinum shift_v = rcon->value();
	    long shift = shift_v.as_long();

	    if (op() == 'r')
		  shift = 0-shift;

	    if (shift == 0)
		  return lsig;

	    NetNet*osig = new NetNet(scope, scope->local_symbol(),
				     NetNet::IMPLICIT, expr_width());
	    osig->data_type(expr_type());
	    osig->local_flag(true);

	      // ushift is the amount of pad created by the shift.
	    unsigned long ushift = shift>=0? shift : -shift;
	    if (ushift > osig->vector_width())
		  ushift = osig->vector_width();

	      // part_width is the bits of the vector that survive the shift.
	    unsigned long part_width = osig->vector_width() - ushift;

	    verinum znum (verinum::V0, ushift, true);
	    NetConst*zcon = new NetConst(scope, scope->local_symbol(),
					 znum);
	    des->add_node(zcon);

	      /* Detect the special case that the shift is the size of
		 the whole expression. Simply connect the pad to the
		 osig and escape. */
	    if (ushift >= osig->vector_width()) {
		  connect(zcon->pin(0), osig->pin(0));
		  return osig;
	    }

	    NetNet*zsig = new NetNet(scope, scope->local_symbol(),
				     NetNet::WIRE, znum.len());
	    zsig->data_type(osig->data_type());
	    zsig->local_flag(true);
	    zsig->set_line(*this);
	    connect(zcon->pin(0), zsig->pin(0));

	      /* Create a part select to reduce the width of the lsig
	         to the amount left by the shift. */
	    NetPartSelect*psel = new NetPartSelect(lsig, shift<0? ushift : 0,
						   part_width,
						   NetPartSelect::VP);
	    des->add_node(psel);

	    NetNet*psig = new NetNet(scope, scope->local_symbol(),
				     NetNet::IMPLICIT, part_width);
	    psig->data_type(expr_type());
	    psig->local_flag(true);
	    psig->set_line(*this);
	    connect(psig->pin(0), psel->pin(0));

	    NetConcat*ccat = new NetConcat(scope, scope->local_symbol(),
					   osig->vector_width(), 2);
	    ccat->set_line(*this);
	    des->add_node(ccat);

	    connect(ccat->pin(0), osig->pin(0));
	    if (shift > 0) {
		    // Left shift.
		  connect(ccat->pin(1), zsig->pin(0));
		  connect(ccat->pin(2), psig->pin(0));
	    } else {
		    // Right shift
		  connect(ccat->pin(1), psig->pin(0));
		  connect(ccat->pin(2), zsig->pin(0));
	    }

	    return osig;
      }

      NetNet*rsig = right_->synthesize(des);

      if (rsig == 0) return 0;

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, expr_width());
      osig->data_type(expr_type());
      osig->local_flag(true);

      NetCLShift*dev = new NetCLShift(scope, scope->local_symbol(),
				      osig->vector_width(),
				      rsig->vector_width(),
				      right_flag, signed_flag);
      dev->set_line(*this);
      des->add_node(dev);

      connect(dev->pin_Result(), osig->pin(0));

      assert(lsig->vector_width() == dev->width());
      connect(dev->pin_Data(), lsig->pin(0));

      connect(dev->pin_Distance(), rsig->pin(0));

      return osig;
}

NetNet* NetEConcat::synthesize(Design*des)
{
	/* First, synthesize the operands. */
      NetNet**tmp = new NetNet*[parms_.count()];
      bool flag = true;
      for (unsigned idx = 0 ;  idx < parms_.count() ;  idx += 1) {
	    tmp[idx] = parms_[idx]->synthesize(des);
	    if (tmp[idx] == 0)
		  flag = false;
      }

      if (flag == false)
	    return 0;

      assert(tmp[0]);
      NetScope*scope = tmp[0]->scope();
      assert(scope);

	/* Make a NetNet object to carry the output vector. */
      perm_string path = scope->local_symbol();
      NetNet*osig = new NetNet(scope, path, NetNet::IMPLICIT, expr_width());
      osig->local_flag(true);
      osig->data_type(tmp[0]->data_type());

      NetConcat*concat = new NetConcat(scope, scope->local_symbol(),
				       osig->vector_width(),
				       parms_.count() * repeat());
      concat->set_line(*this);
      des->add_node(concat);
      connect(concat->pin(0), osig->pin(0));

      unsigned cur_pin = 1;
      for (unsigned rpt = 0; rpt < repeat(); rpt += 1) {
	    for (unsigned idx = 0 ;  idx < parms_.count() ;  idx += 1) {
		  connect(concat->pin(cur_pin), tmp[parms_.count()-idx-1]->pin(0));
		  cur_pin += 1;
	    }
      }

      delete[]tmp;
      return osig;
}

NetNet* NetEConst::synthesize(Design*des)
{
      NetScope*scope = des->find_root_scope();
      assert(scope);

      perm_string path = scope->local_symbol();
      unsigned width=expr_width();

      NetNet*osig = new NetNet(scope, path, NetNet::IMPLICIT, width-1,0);
      osig->local_flag(true);
      osig->data_type(IVL_VT_LOGIC);
      osig->set_signed(has_sign());
      NetConst*con = new NetConst(scope, scope->local_symbol(), value());
      connect(osig->pin(0), con->pin(0));

      des->add_node(con);
      return osig;
}

/*
* Create a NetLiteral object to represent real valued constants.
*/
NetNet* NetECReal::synthesize(Design*des)
{
      NetScope*scope = des->find_root_scope();
      assert(scope);

      perm_string path = scope->local_symbol();

      NetNet*osig = new NetNet(scope, path, NetNet::WIRE, 1);
      osig->local_flag(true);
      osig->data_type(IVL_VT_REAL);
      osig->set_signed(has_sign());
      osig->set_line(*this);

      NetLiteral*con = new NetLiteral(scope, scope->local_symbol(), value_);
      des->add_node(con);
      con->set_line(*this);

      connect(osig->pin(0), con->pin(0));
      return osig;
}

/*
 * The bitwise unary logic operator (there is only one) is turned
 * into discrete gates just as easily as the binary ones above.
 */
NetNet* NetEUBits::synthesize(Design*des)
{
      NetNet*isig = expr_->synthesize(des);

      if (isig == 0) return 0;

      if (isig->data_type() == IVL_VT_REAL) {
	    cerr << get_fileline() << ": error: bit-wise negation ("
	         << human_readable_op(op_)
	         << ") may not have a REAL operand." << endl;
	    des->errors += 1;
	    return 0;
      }

      NetScope*scope = isig->scope();
      assert(scope);

      unsigned width = isig->vector_width();
      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, width);
      osig->data_type(expr_type());
      osig->local_flag(true);

      perm_string oname = scope->local_symbol();
      NetLogic*gate;

      switch (op()) {
	  case '~':
	    gate = new NetLogic(scope, oname, 2, NetLogic::NOT, width);
	    break;
	  default:
	    assert(0);
      }

      connect(osig->pin(0), gate->pin(0));
      connect(isig->pin(0), gate->pin(1));

      des->add_node(gate);

      return osig;
}

NetNet* NetEUReduce::synthesize(Design*des)
{
      NetNet*isig = expr_->synthesize(des);

      if (isig == 0) return 0;

      if (isig->data_type() == IVL_VT_REAL) {
	    cerr << get_fileline() << ": error: reduction operator ("
	         << human_readable_op(op_)
	         << ") may not have a REAL operand." << endl;
	    des->errors += 1;
	    return 0;
      }

      NetScope*scope = isig->scope();
      assert(scope);

      NetNet*osig = new NetNet(scope, scope->local_symbol(),
			       NetNet::IMPLICIT, 1);
      osig->data_type(expr_type());
      osig->local_flag(true);

      NetUReduce::TYPE rtype = NetUReduce::NONE;

      switch (op()) {
	  case 'N':
	  case '!':
	    rtype = NetUReduce::NOR;
	    break;
	  case '&':
	    rtype = NetUReduce::AND;
	    break;
	  case '|':
	    rtype = NetUReduce::OR;
	    break;
	  case '^':
	    rtype = NetUReduce::XOR;
	    break;
	  case 'A':
	    rtype = NetUReduce::XNOR;
	    break;
	  case 'X':
	    rtype = NetUReduce::XNOR;
	    break;
	  default:
	    cerr << get_fileline() << ": internal error: "
		 << "Unable to synthesize " << *this << "." << endl;
	    return 0;
      }

      NetUReduce*gate = new NetUReduce(scope, scope->local_symbol(),
				       rtype, isig->vector_width());

      des->add_node(gate);
      connect(gate->pin(0), osig->pin(0));
      for (unsigned idx = 0 ;  idx < isig->pin_count() ;  idx += 1)
	    connect(gate->pin(1+idx), isig->pin(idx));

      return osig;
}

NetNet* NetESelect::synthesize(Design *des)
{

      NetNet*sub = expr_->synthesize(des);

      if (sub == 0) return 0;

      NetScope*scope = sub->scope();

      NetNet*off = 0;

	// This handles the case that the NetESelect exists to do an
	// actual part/bit select. Generate a NetPartSelect object to
	// do the work, and replace "sub" with the selected output.
      if (base_ != 0) {
	    off = base_->synthesize(des);

	    NetPartSelect*sel = new NetPartSelect(sub, off, expr_width());
	    sel->set_line(*this);
	    des->add_node(sel);

	    NetNet*tmp = new NetNet(scope, scope->local_symbol(),
				    NetNet::IMPLICIT, expr_width());
	    tmp->data_type(sub->data_type());
	    tmp->local_flag(true);
	    tmp->set_line(*this);
	    sub = tmp;
	    connect(sub->pin(0), sel->pin(0));
      }


	// Now look for the case that the NetESelect actually exists
	// to change the width of the expression. (i.e. to do
	// padding.) If this was for an actual part select that at
	// this point the output vector_width is exactly right, and we
	// are done.
      if (sub->vector_width() == expr_width())
	    return sub;

	// The vector_width is not exactly right, so the source is
	// probably asking for padding. Create nodes to do sign
	// extension or 0 extension, depending on the has_sign() mode
	// of the expression.

      NetNet*net = new NetNet(scope, scope->local_symbol(),
			      NetNet::IMPLICIT, expr_width());
      net->data_type(expr_type());
      net->local_flag(true);
      net->set_line(*this);
      if (has_sign()) {
	    NetSignExtend*pad = new NetSignExtend(scope,
						  scope->local_symbol(),
						  expr_width());
	    pad->set_line(*this);
	    des->add_node(pad);

	    connect(pad->pin(1), sub->pin(0));
	    connect(pad->pin(0), net->pin(0));
	    net->set_signed(true);

      } else {

	    NetConcat*cat = new NetConcat(scope, scope->local_symbol(),
					  expr_width(), 2);
	    cat->set_line(*this);
	    des->add_node(cat);

	    assert(expr_width() > sub->vector_width());
	    unsigned pad_width = expr_width() - sub->vector_width();
	    verinum pad((uint64_t)0, pad_width);
	    NetConst*con = new NetConst(scope, scope->local_symbol(),
					pad);
	    con->set_line(*this);
	    des->add_node(con);

	    NetNet*tmp = new NetNet(scope, scope->local_symbol(),
				    NetNet::IMPLICIT, pad_width);
	    tmp->data_type(expr_type());
	    tmp->local_flag(true);
	    tmp->set_line(*this);
	    connect(tmp->pin(0), con->pin(0));

	    connect(cat->pin(0), net->pin(0));
	    connect(cat->pin(1), sub->pin(0));
	    connect(cat->pin(2), con->pin(0));
      }

      return net;
}

/*
 * Synthesize a ?: operator as a NetMux device. Connect the condition
 * expression to the select input, then connect the true and false
 * expressions to the B and A inputs. This way, when the select input
 * is one, the B input, which is the true expression, is selected.
 */
NetNet* NetETernary::synthesize(Design *des)
{
      NetNet*csig = cond_->synthesize(des),
            *tsig = true_val_->synthesize(des),
            *fsig = false_val_->synthesize(des);

      if (csig == 0 || tsig == 0 || fsig == 0) return 0;

      if (tsig->data_type() != fsig->data_type()) {
	    cerr << get_fileline() << ": error: True and False clauses of "
	            "ternary expression have different types." << endl;
	    cerr << get_fileline() << ":      : True  clause is: "
	         << tsig->data_type() << endl;
	    cerr << get_fileline() << ":      : False clause is: "
	         << fsig->data_type() << endl;
	    des->errors += 1;
	    return 0;
      } else if (tsig->data_type() == IVL_VT_NO_TYPE) {
	    cerr << get_fileline() << ": internal error: True and False "
	            "clauses of ternary both have NO TYPE." << endl;
	    des->errors += 1;
	    return 0;
      }

      perm_string path = csig->scope()->local_symbol();

      assert(csig->vector_width() == 1);

      unsigned width=expr_width();
      NetNet*osig = new NetNet(csig->scope(), path, NetNet::IMPLICIT, width);
      osig->data_type(expr_type());
      osig->local_flag(true);

	/* Make sure both value operands are the right width. */
      tsig = crop_to_width(des, pad_to_width(des, tsig, width), width);
      fsig = crop_to_width(des, pad_to_width(des, fsig, width), width);

      assert(width == tsig->vector_width());
      assert(width == fsig->vector_width());

      perm_string oname = csig->scope()->local_symbol();
      NetMux *mux = new NetMux(csig->scope(), oname, width,
			       2, csig->vector_width());
      connect(tsig->pin(0), mux->pin_Data(1));
      connect(fsig->pin(0), mux->pin_Data(0));
      connect(osig->pin(0), mux->pin_Result());
      connect(csig->pin(0), mux->pin_Sel());
      des->add_node(mux);

      return osig;
}

/*
 * When synthesizing a signal expression, it is usually fine to simply
 * return the NetNet that it refers to. If this is an array word though,
 * a bit more work needs to be done. Return a temporary that represents
 * the selected word.
 */
NetNet* NetESignal::synthesize(Design*des)
{
      if (word_ == 0)
	    return net_;

      NetScope*scope = net_->scope();

      NetNet*tmp = new NetNet(scope, scope->local_symbol(),
			      NetNet::IMPLICIT, net_->vector_width());
      tmp->set_line(*this);
      tmp->local_flag(true);
      tmp->data_type(net_->data_type());

      if (NetEConst*index_co = dynamic_cast<NetEConst*> (word_)) {
	    long index = index_co->value().as_long();

	    assert(net_->array_index_is_valid(index));
	    index = net_->array_index_to_address(index);

	    connect(tmp->pin(0), net_->pin(index));
      } else {
	    unsigned selwid = word_->expr_width();

	    NetArrayDq*mux = new NetArrayDq(scope, scope->local_symbol(),
					    net_, selwid);
	    mux->set_line(*this);
	    des->add_node(mux);

	    NetNet*index_net = word_->synthesize(des);
	    connect(mux->pin_Address(), index_net->pin(0));

	    connect(tmp->pin(0), mux->pin_Result());
      }
      return tmp;
}

NetNet* NetESFunc::synthesize(Design*des)
{
      cerr << get_fileline() << ": sorry: cannot synthesize system function: "
	   << *this << " in this context" << endl;
      des->errors += 1;
      return 0;
}

NetNet* NetEUFunc::synthesize(Design*des)
{
      svector<NetNet*> eparms (parms_.count());

        /* Synthesize the arguments. */
      bool errors = false;
      for (unsigned idx = 0; idx < eparms.count(); idx += 1) {
	    NetNet*tmp = parms_[idx]->synthesize(des);
	    if (tmp == 0) {
		  cerr << get_fileline() << ": error: Unable to synthesize "
		          "port " << idx << " of call to "
		       << func_->basename() << "." << endl;
		  errors = true;
		  des->errors += 1;
		  continue;
	    }
	    eparms[idx] = tmp;
      }
      if (errors) return 0;

      NetUserFunc*net = new NetUserFunc(scope_, scope_->local_symbol(), func_);
      net->set_line(*this);
      des->add_node(net);

        /* Create an output signal and connect it to the function. */
      NetNet*osig = new NetNet(scope_, scope_->local_symbol(), NetNet::WIRE,
                               result_sig_->vector_width());
      osig->local_flag(true);
      osig->data_type(result_sig_->expr_type());
      connect(net->pin(0), osig->pin(0));

        /* Connect the pins to the arguments. */
      NetFuncDef*def = func_->func_def();
      for (unsigned idx = 0; idx < eparms.count(); idx += 1) {
	    NetNet*tmp = pad_to_width(des, eparms[idx], 
	                              def->port(idx)->vector_width());
	    connect(net->pin(idx+1), tmp->pin(0));
      }

      return osig;
}
