//==============================================================================
//
// d/wilson_d520/bgp_asm_code.cc
//
//------------------------------------------------------------------------------
//
// Copyright (C) 2010 Thomas Streuer 
//
// This file is part of BQCD -- Berlin Quantum ChromoDynamics program
//
// BQCD is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// BQCD 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 BQCD.  If not, see <http://www.gnu.org/licenses/>.
//
//------------------------------------------------------------------------------
#include "bgp_asm_code.h"

#include <sstream>
#include <stdexcept>

using std::string;
using std::ostringstream;

bgp_code_element_t bgp_code_element_t::comment(const std::string& s)
{
  return bgp_code_element_t((string("# ") + s));
}

bgp_code_element_t bgp_code_element_t::add(unsigned dest, unsigned src1, unsigned src2)
{
  ostringstream oss;
  oss << "\tadd\t" << dest << ", " << src1 << ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::sub(unsigned dest, unsigned src1, unsigned src2)
{
  ostringstream oss;
  oss << "\tsub\t" << dest << ", " << src1 << ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::andd(unsigned dest, unsigned src1, unsigned src2, bool upd_cr)
{
  ostringstream oss;
  oss << "\tand" <<  (upd_cr? ".\t" : "\t") << dest << ", " << src1 << ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::add_i(unsigned dest, unsigned src, int imm)
{
  ostringstream oss;
  oss << "\taddi \t" << dest << ", " << src << ", " << imm;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::and_i(
  unsigned dest, unsigned src, int imm, bool shifted, bool upd_cr)
{
  ostringstream oss;
  oss << "\tandi" << (shifted? "s" : "") << (upd_cr? ".\t" : "\t") <<
    dest << ", " << src << ", " << imm;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::xor_i(
  unsigned dest, unsigned src, int imm, bool shifted, bool upd_cr)
{
  ostringstream oss;
  oss << "\txori" << (shifted? "s" : "") << (upd_cr? ".\t" : "\t") <<
    dest << ", " << src << ", " << imm;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::mul_i(unsigned dest, unsigned src, int imm)
{
  ostringstream oss;
  oss << "\tmulli\t" << dest << ", " << src << ", " << imm;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::crand(
  unsigned field_dest, unsigned field1, unsigned field2, unsigned bit)
{
  unsigned d  = 4 * field_dest + bit;
  unsigned s1 = 4 * field1     + bit;
  unsigned s2 = 4 * field2     + bit;
  ostringstream oss;
  oss << "\tcrand\t" << d << ", " << s1 << ", " << s2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::crset(
  unsigned field_dest, unsigned bit, unsigned val)
{
  unsigned d  = 4 * field_dest + bit;
  string opcode = val==0? "crxor" : "creqv";
  ostringstream oss;
  oss << "\t" << opcode << "\t" << d << ", " << d << ", " << d;
  return bgp_code_element_t(oss.str());
}


bgp_code_element_t bgp_code_element_t::mac(unsigned target, unsigned src1, unsigned src2,
					   bool high1, bool high2, bool sign, bool upd_cr)
{
  string opcodes[2][2] = {{"maclhw", "macchw"}, {"macchw", "maclhw"}};
  string opcode = opcodes[high1? 1 : 0][high2? 1 : 0];
  if (!sign)
    opcode += "u";
  if (upd_cr)
    opcode += ".";
  if (high1 && !high2)
  {
    unsigned tmp = src1;
    src1 = src2;
    src2 = tmp;
  }
  ostringstream oss;
  oss << "\t" << opcode << "\t" << target << ", " << src1 << ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::rlwinm(unsigned dest, unsigned src, int shift, int begin, int end)
{
  ostringstream oss;
  oss << "\trlwinm\t" << dest << ", " << src << ", " << shift << ", " << begin << ", " << end;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::rlwimi(unsigned dest, unsigned src, int shift, int begin, int end)
{
  ostringstream oss;
  oss << "\trlwimi\t" << dest << ", " << src << ", " << shift << ", " << begin << ", " << end;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::select(unsigned target, unsigned src1, unsigned src2, unsigned cr)
{
  ostringstream oss;
  oss << "\tisel\t" << target << ", " << src1 << ", " << src2 << ", " << cr;
  return bgp_code_element_t(oss.str());
}


bgp_code_element_t bgp_code_element_t::load_i(unsigned dest, int imm)
{
  return add_i(dest, 0, imm);
}

bgp_code_element_t bgp_code_element_t::load_i32(unsigned target, unsigned imm)
{
  ostringstream oss;
  if (imm & 0x8000)
  {
    oss << "\tli\t" << target << ", " << (int(imm & 0xffff)-0x10000);
    oss << "\n\taddis\t" << target << ", " << target << ", " << ((imm >> 16)+1);
  }
  else
  {
    oss << "\tli\t" << target << ", " << (imm & 0xffff);
    oss << "\n\taddis\t" << target << ", " << target << ", " << (imm >> 16);
  }
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::load_hi(
  unsigned target, unsigned src, const string& s)
{
  ostringstream oss;
  oss << "\taddis\t" << target << ", " << src << ", " << s << "@ha";
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::load_lo(
  unsigned target, unsigned src, const string& s)
{
  ostringstream oss;
  oss << "\taddi\t" << target << ", " << src << ", " << s << "@l";
  return bgp_code_element_t(oss.str());
}


bgp_code_element_t bgp_code_element_t::load_hword_a(
  unsigned target, int offs, unsigned src, bool upd)
{
  ostringstream oss;
  oss << (upd? "\tlhau\t" : "\tlha\t") << target << ", " << offs << "(" << src << ")";
  return bgp_code_element_t(oss.str());
}


bgp_code_element_t bgp_code_element_t::load(unsigned dest, unsigned src, int offs,
  bool upd)
{
  ostringstream oss;
  oss << (upd? "\tlwzu\t" : "\tlwz\t") << dest << ", " << offs << "(" << src << ")";
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::prefetch(unsigned src1, unsigned src2)
{
  ostringstream oss;
  oss << "\tdcbt\t" << src1<< ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::clear_cache(unsigned src1, unsigned src2)
{
  ostringstream oss;
  oss << "\tdcbz\t" << src1<< ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::store(
  unsigned src, unsigned dest, int offs, bool upd)
{
  ostringstream oss;
  oss << "\tstw" << (upd? "u\t" : "\t") << src << ", " << offs << "(" << dest << ")";
  return bgp_code_element_t(oss.str());
}


bgp_code_element_t bgp_code_element_t::fload(
  unsigned dest, unsigned src1, unsigned src2, bool par, bool upd, bool cross)
{
  ostringstream oss;
  oss << "\tlf" << (par? (cross? "x" : "p") : "") << "d" <<
    (upd? "u" : "") << "x \t" << dest << ", " << src1 << ", " << src2;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::fstore(
  unsigned src, unsigned dest1, unsigned dest2, bool par, bool upd, bool cross)
{
  ostringstream oss;
  oss << "\tstf" << (par? (cross? "x" : "p") : "") << "d" <<
    (upd? "u" : "") << "x \t" << src << ", " << dest1 << ", " << dest2;
  return bgp_code_element_t(oss.str());
}

// dest = a+b
bgp_code_element_t bgp_code_element_t::fadd(
  unsigned dest, unsigned a, unsigned b, bool par)
{
  ostringstream oss;
  oss << (par? "\tfpadd\t" : "\tfadd\t") << dest << ", " << a << ", " << b;
  return bgp_code_element_t(oss.str());
}


// freg_i must contain "1"in secondary reg.

bgp_code_element_t bgp_code_element_t::fadd_isign(
  unsigned dest, unsigned src, unsigned isign, unsigned freg_i, int acc)
{
  if (acc == -1)
    acc = dest;
  ostringstream oss;
  if (isign==0 || isign==3)
    oss << (isign==0? "\tfpadd\t" : "\tfpsub\t") << dest << ", " << acc << ", " << src;
  else if (isign==1 || isign==2)
    oss << (isign==1? "\tfxcxnpma\t" : "\tfxcxnsma\t") << dest << ", " <<
      freg_i << ", " << src << ", " << acc;
  return bgp_code_element_t(oss.str());
}

// dest = acc +/- a*b
// pass  0/1
// cj    if true, conjugate a
bgp_code_element_t bgp_code_element_t::cplxmul(
  unsigned dest, unsigned a, unsigned b, unsigned pass, bool add,
  bool cj, bool negate, int acc)
{
  ostringstream oss;
  if (acc == -1)
    acc = dest;
  if (negate)
  {
    if (add)
      oss << (pass==0? "\tfxcpnmadd\t" : (cj? "\tfxcxnpma\t" : "\tfxcxnsma\t")) <<
	dest << ", " << a << ", " << b << ", " << acc;
    else if (pass==0)
      oss << "\tfxpmul\t" << dest << ", " << a << ", " << b;
    else
      throw std::runtime_error(string("Error in cplxnum"));
  }
  else
  {
    if (add)
      oss << (pass==0? "\tfxcpmadd\t" : (cj? "\tfxcxnsma\t" : "\tfxcxnpma\t")) <<
	dest << ", " << a << ", " << b << ", " << acc;
    else if (pass==0)
      oss << "\tfxpmul\t" << dest << ", " << a << ", " << b;
    else
      throw std::runtime_error(string("Error in cplxnum"));
  }
  return bgp_code_element_t(oss.str());
}


bgp_code_element_t bgp_code_element_t::jump_ctr(const string& label)
{
  ostringstream oss;
  oss << "\tbdnz\t" << label; 
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::jump_cond(
  const std::string& label, unsigned field, unsigned bit, unsigned val,
  bool link, bool ctr)
{
  string opcode = ctr? (val==0? "bdnzf" : "bdnzt") : (val==0? "bf" : "bt");
  if (link)
    opcode += "l";
  ostringstream oss;
  unsigned bi = 4 * field + bit;
  oss << "\t" << opcode << "\t" << bi << ", " << label;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::jump_lr()
{
  return bgp_code_element_t(string("\tblr"));
}

bgp_code_element_t bgp_code_element_t::jump(const string& l, bool link)
{
  string opcode = "b";
  if (link)
    opcode += "l";
  return bgp_code_element_t(string("\t" + opcode + "\t" + l));
}

bgp_code_element_t bgp_code_element_t::mflr(unsigned r)
{
  ostringstream oss;
  oss << "\tmflr \t" << r;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::mtlr(unsigned r)
{
  ostringstream oss;
  oss << "\tmtlr \t" << r;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::mfctr(unsigned r)
{
  ostringstream oss;
  oss << "\tmfctr\t" << r;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::mtctr(unsigned r)
{
  ostringstream oss;
  oss << "\tmtctr\t" << r;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::mfcr(unsigned r)
{
  ostringstream oss;
  oss << "\tmfcr\t" << r;
  return bgp_code_element_t(oss.str());
}

bgp_code_element_t bgp_code_element_t::mbar()
{
  return bgp_code_element_t("\tmbar");
}
