/* tc-microblaze.c -- Assemble code for Xilinx MicroBlaze

   Copyright 2009 Free Software Foundation.

   This file is part of GAS, the GNU Assembler.

   GAS 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, or (at your option)
   any later version.

   GAS 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 GAS; see the file COPYING.  If not, write to the Free
   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
   02110-1301, USA.  */

#include <stdio.h>
#include "as.h"
#include "bfd.h"
#include "subsegs.h"
#define DEFINE_TABLE
#include "../opcodes/microblaze-opc.h"
#include "../opcodes/microblaze-opcm.h"
#include "safe-ctype.h"
#include <string.h>
#include <dwarf2dbg.h>
#include "aout/stab_gnu.h"

#ifndef streq
#define streq(a,b) (strcmp (a, b) == 0)
#endif

void microblaze_generate_symbol (char *sym);
static bfd_boolean check_spl_reg (unsigned *);

/* Several places in this file insert raw instructions into the
   object. They should generate the instruction
   and then use these four macros to crack the instruction value into
   the appropriate byte values.  */
#define	INST_BYTE0(x)  (target_big_endian ? (((x) >> 24) & 0xFF) : ((x) & 0xFF))
#define	INST_BYTE1(x)  (target_big_endian ? (((x) >> 16) & 0xFF) : (((x) >> 8) & 0xFF))
#define	INST_BYTE2(x)  (target_big_endian ? (((x) >> 8) & 0xFF) : (((x) >> 16) & 0xFF))
#define	INST_BYTE3(x)  (target_big_endian ? ((x) & 0xFF) : (((x) >> 24) & 0xFF))

/* This array holds the chars that always start a comment.  If the
   pre-processor is disabled, these aren't very useful.  */
const char comment_chars[] = "#";

const char line_separator_chars[] = ";";

/* This array holds the chars that only start a comment at the beginning of
   a line.  */
const char line_comment_chars[] = "#";

const int md_reloc_size = 8; /* Size of relocation record.  */

/* Chars that can be used to separate mant
   from exp in floating point numbers.  */
const char EXP_CHARS[] = "eE";

/* Chars that mean this number is a floating point constant
   As in 0f12.456
   or    0d1.2345e12.  */
const char FLT_CHARS[] = "rRsSfFdDxXpP";

/* INST_PC_OFFSET and INST_NO_OFFSET are 0 and 1.  */
#define UNDEFINED_PC_OFFSET  2
#define DEFINED_ABS_SEGMENT  3
#define DEFINED_PC_OFFSET    4
#define DEFINED_RO_SEGMENT   5
#define DEFINED_RW_SEGMENT   6
#define LARGE_DEFINED_PC_OFFSET 7
#define GOT_OFFSET           8
#define PLT_OFFSET           9
#define GOTOFF_OFFSET        10


/* Initialize the relax table.  */
const relax_typeS md_relax_table[] =
{
  {          1,          1,                0, 0 },  /*  0: Unused.  */
  {          1,          1,                0, 0 },  /*  1: Unused.  */
  {          1,          1,                0, 0 },  /*  2: Unused.  */
  {          1,          1,                0, 0 },  /*  3: Unused.  */
  {      32767,   -32768, INST_WORD_SIZE, LARGE_DEFINED_PC_OFFSET }, /* 4: DEFINED_PC_OFFSET.  */
  {    1,     1,       0, 0 },                      /*  5: Unused.  */
  {    1,     1,       0, 0 },                      /*  6: Unused.  */
  { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 },  /*  7: LARGE_DEFINED_PC_OFFSET.  */
  { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 },  /*  8: GOT_OFFSET.  */
  { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 },  /*  9: PLT_OFFSET.  */
  { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 },  /* 10: GOTOFF_OFFSET.  */
};

static struct hash_control * opcode_hash_control;	/* Opcode mnemonics.  */

static segT sbss_segment = 0; 	/* Small bss section.  */
static segT sbss2_segment = 0; 	/* Section not used.  */
static segT sdata_segment = 0; 	/* Small data section.  */
static segT sdata2_segment = 0; /* Small read-only section.  */
static segT rodata_segment = 0; /* read-only section.  */

/* Generate a symbol for stabs information.  */

void
microblaze_generate_symbol (char *sym)
{
#define MICROBLAZE_FAKE_LABEL_NAME "XL0\001"
  static int microblaze_label_count;
  sprintf (sym, "%sL%d", MICROBLAZE_FAKE_LABEL_NAME, microblaze_label_count);
  ++microblaze_label_count;
}

/* Handle the section changing pseudo-ops. */

static void
microblaze_s_text (int ignore ATTRIBUTE_UNUSED)
{
#ifdef OBJ_ELF
  obj_elf_text (ignore);
#else
  s_text (ignore);
#endif
}

static void
microblaze_s_data (int ignore ATTRIBUTE_UNUSED)
{
#ifdef OBJ_ELF
  obj_elf_change_section (".data", SHT_PROGBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0);
#else
  s_data (ignore);
#endif
}

/* Things in the .sdata segment are always considered to be in the small data section.  */

static void
microblaze_s_sdata (int ignore ATTRIBUTE_UNUSED)
{
#ifdef OBJ_ELF
  obj_elf_change_section (".sdata", SHT_PROGBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0);
#else
  s_data (ignore);
#endif
}

/* Pseudo op to make file scope bss items.  */

static void
microblaze_s_lcomm (int xxx ATTRIBUTE_UNUSED)
{
  char *name;
  char c;
  char *p;
  offsetT size;
  symbolS *symbolP;
  offsetT align;
  segT old_sec;
  int old_subsec;
  char *pfrag;
  int align2;
  segT current_seg = now_seg;
  subsegT current_subseg = now_subseg;

  name = input_line_pointer;
  c = get_symbol_end ();

  /* Just after name is now '\0'.  */
  p = input_line_pointer;
  *p = c;
  SKIP_WHITESPACE ();
  if (*input_line_pointer != ',')
    {
      as_bad (_("Expected comma after symbol-name: rest of line ignored."));
      ignore_rest_of_line ();
      return;
    }

  input_line_pointer++;		/* skip ',' */
  if ((size = get_absolute_expression ()) < 0)
    {
      as_warn (_(".COMMon length (%ld.) <0! Ignored."), (long) size);
      ignore_rest_of_line ();
      return;
    }

  /* The third argument to .lcomm is the alignment.  */
  if (*input_line_pointer != ',')
    align = 8;
  else
    {
      ++input_line_pointer;
      align = get_absolute_expression ();
      if (align <= 0)
	{
	  as_warn (_("ignoring bad alignment"));
	  align = 8;
	}
    }

  *p = 0;
  symbolP = symbol_find_or_make (name);
  *p = c;

  if (S_IS_DEFINED (symbolP) && ! S_IS_COMMON (symbolP))
    {
      as_bad (_("Ignoring attempt to re-define symbol `%s'."),
	      S_GET_NAME (symbolP));
      ignore_rest_of_line ();
      return;
    }

  if (S_GET_VALUE (symbolP) && S_GET_VALUE (symbolP) != (valueT) size)
    {
      as_bad (_("Length of .lcomm \"%s\" is already %ld. Not changed to %ld."),
	      S_GET_NAME (symbolP),
	      (long) S_GET_VALUE (symbolP),
	      (long) size);

      ignore_rest_of_line ();
      return;
    }

  /* Allocate_bss.  */
  old_sec = now_seg;
  old_subsec = now_subseg;
  if (align)
    {
      /* Convert to a power of 2 alignment.  */
      for (align2 = 0; (align & 1) == 0; align >>= 1, ++align2);
      if (align != 1)
	{
	  as_bad (_("Common alignment not a power of 2"));
	  ignore_rest_of_line ();
	  return;
	}
    }
  else
    align2 = 0;

  record_alignment (current_seg, align2);
  subseg_set (current_seg, current_subseg);
  if (align2)
    frag_align (align2, 0, 0);
  if (S_GET_SEGMENT (symbolP) == current_seg)
    symbol_get_frag (symbolP)->fr_symbol = 0;
  symbol_set_frag (symbolP, frag_now);
  pfrag = frag_var (rs_org, 1, 1, (relax_substateT) 0, symbolP, size,
		    (char *) 0);
  *pfrag = 0;
  S_SET_SIZE (symbolP, size);
  S_SET_SEGMENT (symbolP, current_seg);
  subseg_set (current_seg, current_subseg);
  demand_empty_rest_of_line ();
}

static void
microblaze_s_rdata (int localvar)
{
#ifdef OBJ_ELF
  if (localvar == 0)
    {
      /* rodata.  */
      obj_elf_change_section (".rodata", SHT_PROGBITS, SHF_ALLOC, 0, 0, 0, 0);
      if (rodata_segment == 0)
	rodata_segment = subseg_new (".rodata", 0);
    }
  else
    {
      /* 1 .sdata2.  */
      obj_elf_change_section (".sdata2", SHT_PROGBITS, SHF_ALLOC, 0, 0, 0, 0);
    }
#else
  s_data (ignore);
#endif
}

static void
microblaze_s_bss (int localvar)
{
#ifdef OBJ_ELF
  if (localvar == 0) /* bss.  */
    obj_elf_change_section (".bss", SHT_NOBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0);
  else if (localvar == 1)
    {
      /* sbss.  */
      obj_elf_change_section (".sbss", SHT_NOBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0);
      if (sbss_segment == 0)
	sbss_segment = subseg_new (".sbss", 0);
    }
#else
  s_data (ignore);
#endif
}

/* endp_p is always 1 as this func is called only for .end <funcname>
   This func consumes the <funcname> and calls regular processing
   s_func(1) with arg 1 (1 for end). */

static void
microblaze_s_func (int end_p ATTRIBUTE_UNUSED)
{
  *input_line_pointer = get_symbol_end ();
  s_func (1);
}

/* Handle the .weakext pseudo-op as defined in Kane and Heinrich.  */

static void
microblaze_s_weakext (int ignore ATTRIBUTE_UNUSED)
{
  char *name;
  int c;
  symbolS *symbolP;
  expressionS exp;

  name = input_line_pointer;
  c = get_symbol_end ();
  symbolP = symbol_find_or_make (name);
  S_SET_WEAK (symbolP);
  *input_line_pointer = c;

  SKIP_WHITESPACE ();

  if (!is_end_of_line[(unsigned char) *input_line_pointer])
    {
      if (S_IS_DEFINED (symbolP))
	{
	  as_bad ("Ignoring attempt to redefine symbol `%s'.",
		  S_GET_NAME (symbolP));
	  ignore_rest_of_line ();
	  return;
	}

      if (*input_line_pointer == ',')
	{
	  ++input_line_pointer;
	  SKIP_WHITESPACE ();
	}

      expression (&exp);
      if (exp.X_op != O_symbol)
	{
	  as_bad ("bad .weakext directive");
	  ignore_rest_of_line ();
	  return;
	}
      symbol_set_value_expression (symbolP, &exp);
    }

  demand_empty_rest_of_line ();
}

/* This table describes all the machine specific pseudo-ops the assembler
   has to support.  The fields are:
   Pseudo-op name without dot
   Function to call to execute this pseudo-op
   Integer arg to pass to the function.  */
/* If the pseudo-op is not found in this table, it searches in the obj-elf.c,
   and then in the read.c table.  */
const pseudo_typeS md_pseudo_table[] =
{
  {"lcomm", microblaze_s_lcomm, 1},
  {"data", microblaze_s_data, 0},
  {"data8", cons, 1},      /* Same as byte.  */
  {"data16", cons, 2},     /* Same as hword.  */
  {"data32", cons, 4},     /* Same as word.  */
  {"ent", s_func, 0}, /* Treat ent as function entry point.  */
  {"end", microblaze_s_func, 1}, /* Treat end as function end point.  */
  {"gpword", s_rva, 4}, /* gpword label => store resolved label address in data section.  */
  {"weakext", microblaze_s_weakext, 0},
  {"rodata", microblaze_s_rdata, 0},
  {"sdata2", microblaze_s_rdata, 1},
  {"sdata", microblaze_s_sdata, 0},
  {"bss", microblaze_s_bss, 0},
  {"sbss", microblaze_s_bss, 1},
  {"text", microblaze_s_text, 0},
  {"word", cons, 4},
  {"frame", s_ignore, 0},
  {"mask", s_ignore, 0}, /* Emitted by gcc.  */
  {NULL, NULL, 0}
};

/* This function is called once, at assembler startup time.  This should
   set up all the tables, etc that the MD part of the assembler needs.  */

void
md_begin (void)
{
  struct op_code_struct * opcode;

  opcode_hash_control = hash_new ();

  /* Insert unique names into hash table.  */
  for (opcode = opcodes; opcode->name; opcode ++)
    hash_insert (opcode_hash_control, opcode->name, (char *) opcode);
}

/* Try to parse a reg name.  */

static char *
parse_reg (char * s, unsigned * reg)
{
  unsigned tmpreg = 0;

  /* Strip leading whitespace.  */
  while (ISSPACE (* s))
    ++ s;

  if (strncasecmp (s, "rpc", 3) == 0)
    {
      *reg = REG_PC;
      return s + 3;
    }
  else if (strncasecmp (s, "rmsr", 4) == 0)
    {
      *reg = REG_MSR;
      return s + 4;
    }
  else if (strncasecmp (s, "rear", 4) == 0)
    {
      *reg = REG_EAR;
      return s + 4;
    }
  else if (strncasecmp (s, "resr", 4) == 0)
    {
      *reg = REG_ESR;
      return s + 4;
    }
  else if (strncasecmp (s, "rfsr", 4) == 0)
    {
      *reg = REG_FSR;
      return s + 4;
    }
  else if (strncasecmp (s, "rbtr", 4) == 0)
    {
      *reg = REG_BTR;
      return s + 4;
    }
  else if (strncasecmp (s, "redr", 4) == 0)
    {
      *reg = REG_EDR;
      return s + 4;
    }
  /* MMU registers start.  */
  else if (strncasecmp (s, "rpid", 4) == 0)
    {
      *reg = REG_PID;
      return s + 4;
    }
  else if (strncasecmp (s, "rzpr", 4) == 0)
    {
      *reg = REG_ZPR;
      return s + 4;
    }
  else if (strncasecmp (s, "rtlbx", 5) == 0)
    {
      *reg = REG_TLBX;
      return s + 5;
    }
  else if (strncasecmp (s, "rtlblo", 6) == 0)
    {
      *reg = REG_TLBLO;
      return s + 6;
    }
  else if (strncasecmp (s, "rtlbhi", 6) == 0)
    {
      *reg = REG_TLBHI;
      return s + 6;
    }
  else if (strncasecmp (s, "rtlbsx", 6) == 0)
    {
      *reg = REG_TLBSX;
      return s + 6;
    }
  /* MMU registers end.  */
  else if (strncasecmp (s, "rpvr", 4) == 0)
    {
      if (ISDIGIT (s[4]) && ISDIGIT (s[5]))
        {
          tmpreg = (s[4]-'0')*10 + s[5] - '0';
          s += 6;
        }

      else if (ISDIGIT (s[4]))
        {
          tmpreg = s[4] - '0';
          s += 5;
        }
      else
        as_bad (_("register expected, but saw '%.6s'"), s);
      if ((int) tmpreg >= MIN_PVR_REGNUM && tmpreg <= MAX_PVR_REGNUM)
        *reg = REG_PVR + tmpreg;
      else
        {
          as_bad (_("Invalid register number at '%.6s'"), s);
          *reg = REG_PVR;
        }
      return s;
    }
  else if (strncasecmp (s, "rsp", 3) == 0)
    {
      *reg = REG_SP;
      return s + 3;
    }
  else if (strncasecmp (s, "rfsl", 4) == 0)
    {
      if (ISDIGIT (s[4]) && ISDIGIT (s[5]))
        {
          tmpreg = (s[4] - '0') * 10 + s[5] - '0';
          s += 6;
        }
      else if (ISDIGIT (s[4]))
        {
          tmpreg = s[4] - '0';
          s += 5;
        }
      else
	as_bad (_("register expected, but saw '%.6s'"), s);

      if ((int) tmpreg >= MIN_REGNUM && tmpreg <= MAX_REGNUM)
        *reg = tmpreg;
      else
	{
          as_bad (_("Invalid register number at '%.6s'"), s);
          *reg = 0;
	}
      return s;
    }
  else
    {
      if (TOLOWER (s[0]) == 'r')
        {
          if (ISDIGIT (s[1]) && ISDIGIT (s[2]))
            {
              tmpreg = (s[1] - '0') * 10 + s[2] - '0';
              s += 3;
            }
          else if (ISDIGIT (s[1]))
            {
              tmpreg = s[1] - '0';
              s += 2;
            }
          else
            as_bad (_("register expected, but saw '%.6s'"), s);

          if ((int)tmpreg >= MIN_REGNUM && tmpreg <= MAX_REGNUM)
            *reg = tmpreg;
          else
	    {
              as_bad (_("Invalid register number at '%.6s'"), s);
              *reg = 0;
	    }
          return s;
        }
    }
  as_bad (_("register expected, but saw '%.6s'"), s);
  *reg = 0;
  return s;
}

static char *
parse_exp (char *s, expressionS *e)
{
  char *save;
  char *new_pointer;

  /* Skip whitespace.  */
  while (ISSPACE (* s))
    ++ s;

  save = input_line_pointer;
  input_line_pointer = s;

  expression (e);

  if (e->X_op == O_absent)
    as_fatal (_("missing operand"));

  new_pointer = input_line_pointer;
  input_line_pointer = save;

  return new_pointer;
}

/* Symbol modifiers (@GOT, @PLT, @GOTOFF).  */
#define IMM_GOT    1
#define IMM_PLT    2
#define IMM_GOTOFF 3

static symbolS * GOT_symbol;

#define GOT_SYMBOL_NAME "_GLOBAL_OFFSET_TABLE_"

static char *
parse_imm (char * s, expressionS * e, int min, int max)
{
  char *new_pointer;
  char *atp;

  /* Find the start of "@GOT" or "@PLT" suffix (if any) */
  for (atp = s; *atp != '@'; atp++)
    if (is_end_of_line[(unsigned char) *atp])
      break;

  if (*atp == '@')
    {
      if (strncmp (atp + 1, "GOTOFF", 5) == 0)
	{
	  *atp = 0;
	  e->X_md = IMM_GOTOFF;
	}
      else if (strncmp (atp + 1, "GOT", 3) == 0)
	{
	  *atp = 0;
	  e->X_md = IMM_GOT;
	}
      else if (strncmp (atp + 1, "PLT", 3) == 0)
	{
	  *atp = 0;
	  e->X_md = IMM_PLT;
	}
      else
	{
	  atp = NULL;
	  e->X_md = 0;
	}
      *atp = 0;
    }
  else
    {
      atp = NULL;
      e->X_md = 0;
    }

  if (atp && !GOT_symbol)
    {
      GOT_symbol = symbol_find_or_make (GOT_SYMBOL_NAME);
    }

  new_pointer = parse_exp (s, e);

  if (e->X_op == O_absent)
    ; /* An error message has already been emitted.  */
  else if ((e->X_op != O_constant && e->X_op != O_symbol) )
    as_fatal (_("operand must be a constant or a label"));
  else if ((e->X_op == O_constant) && ((int) e->X_add_number < min
				       || (int) e->X_add_number > max))
    {
      as_fatal (_("operand must be absolute in range %d..%d, not %d"),
                min, max, (int) e->X_add_number);
    }

  if (atp)
    {
      *atp = '@'; /* restore back (needed?)  */
      if (new_pointer >= atp)
        new_pointer += (e->X_md == IMM_GOTOFF)?7:4;
      /* sizeof("@GOTOFF", "@GOT" or "@PLT") */

    }
  return new_pointer;
}

static char *
check_got (int * got_type, int * got_len)
{
  char *new_pointer;
  char *atp;
  char *past_got;
  int first, second;
  char *tmpbuf;

  /* Find the start of "@GOT" or "@PLT" suffix (if any).  */
  for (atp = input_line_pointer; *atp != '@'; atp++)
    if (is_end_of_line[(unsigned char) *atp])
      return NULL;

  if (strncmp (atp + 1, "GOTOFF", 5) == 0)
    {
      *got_len = 6;
      *got_type = IMM_GOTOFF;
    }
  else if (strncmp (atp + 1, "GOT", 3) == 0)
    {
      *got_len = 3;
      *got_type = IMM_GOT;
    }
  else if (strncmp (atp + 1, "PLT", 3) == 0)
    {
      *got_len = 3;
      *got_type = IMM_PLT;
    }
  else
    return NULL;

  if (!GOT_symbol)
    GOT_symbol = symbol_find_or_make (GOT_SYMBOL_NAME);

  first = atp - input_line_pointer;

  past_got = atp + *got_len + 1;
  for (new_pointer = past_got; !is_end_of_line[(unsigned char) *new_pointer++];)
    ;
  second = new_pointer - past_got;
  tmpbuf = xmalloc (first + second + 2); /* One extra byte for ' ' and one for NUL.  */
  memcpy (tmpbuf, input_line_pointer, first);
  tmpbuf[first] = ' '; /* @GOTOFF is replaced with a single space.  */
  memcpy (tmpbuf + first + 1, past_got, second);
  tmpbuf[first + second + 1] = '\0';

  return tmpbuf;
}

extern void
parse_cons_expression_microblaze (expressionS *exp, int size)
{
  if (size == 4)
    {
      /* Handle @GOTOFF et.al.  */
      char *save, *gotfree_copy;
      int got_len, got_type;

      save = input_line_pointer;
      gotfree_copy = check_got (& got_type, & got_len);
      if (gotfree_copy)
        input_line_pointer = gotfree_copy;

      expression (exp);

      if (gotfree_copy)
	{
          exp->X_md = got_type;
          input_line_pointer = save + (input_line_pointer - gotfree_copy)
	    + got_len;
          free (gotfree_copy);
        }
    }
  else
    expression (exp);
}

/* This is the guts of the machine-dependent assembler.  STR points to a
   machine dependent instruction.  This function is supposed to emit
   the frags/bytes it assembles to.  */

static char * str_microblaze_ro_anchor = "RO";
static char * str_microblaze_rw_anchor = "RW";

static bfd_boolean
check_spl_reg (unsigned * reg)
{
  if ((*reg == REG_MSR)   || (*reg == REG_PC)
      || (*reg == REG_EAR)   || (*reg == REG_ESR)
      || (*reg == REG_FSR)   || (*reg == REG_BTR) || (*reg == REG_EDR)
      || (*reg == REG_PID)   || (*reg == REG_ZPR)
      || (*reg == REG_TLBX)  || (*reg == REG_TLBLO)
      || (*reg == REG_TLBHI) || (*reg == REG_TLBSX)
      || (*reg >= REG_PVR+MIN_PVR_REGNUM && *reg <= REG_PVR+MAX_PVR_REGNUM))
    return TRUE;

  return FALSE;
}

/* Here we decide which fixups can be adjusted to make them relative to
   the beginning of the section instead of the symbol.  Basically we need
   to make sure that the dynamic relocations are done correctly, so in
   some cases we force the original symbol to be used.  */

int
tc_microblaze_fix_adjustable (struct fix *fixP)
{
  if (GOT_symbol && fixP->fx_subsy == GOT_symbol)
    return 0;

  if (fixP->fx_r_type == BFD_RELOC_MICROBLAZE_64_GOTOFF
      || fixP->fx_r_type == BFD_RELOC_MICROBLAZE_32_GOTOFF
      || fixP->fx_r_type == BFD_RELOC_MICROBLAZE_64_GOT
      || fixP->fx_r_type == BFD_RELOC_MICROBLAZE_64_PLT)
    return 0;

  return 1;
}

void
md_assemble (char * str)
{
  char * op_start;
  char * op_end;
  struct op_code_struct * opcode, *opcode1;
  char * output = NULL;
  int nlen = 0;
  int i;
  unsigned long inst, inst1;
  unsigned reg1;
  unsigned reg2;
  unsigned reg3;
  unsigned isize;
  unsigned int immed, temp;
  expressionS exp;
  char name[20];

  /* Drop leading whitespace.  */
  while (ISSPACE (* str))
    str ++;

  /* Find the op code end.  */
  for (op_start = op_end = str;
       * op_end && nlen < 20 && !is_end_of_line [(int)*op_end] && *op_end != ' ';
       op_end++)
    {
      name[nlen] = op_start[nlen];
      nlen++;
    }

  name [nlen] = 0;

  if (nlen == 0)
    {
      as_bad (_("can't find opcode "));
      return;
    }

  opcode = (struct op_code_struct *) hash_find (opcode_hash_control, name);
  if (opcode == NULL)
    {
      as_bad (_("unknown opcode \"%s\""), name);
      return;
    }

  inst = opcode->bit_sequence;
  isize = 4;

  switch (opcode->inst_type)
    {
    case INST_TYPE_RD_R1_R2:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
        {
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg3);  /* Get r2.  */
      else
 	{
          as_fatal (_("Error in statement syntax"));
          reg3 = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (& reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (& reg2))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (& reg3))
        as_fatal (_("Cannot use special register with this instruction"));

      if (streq (name, "sub"))
	{
          /* sub rd, r1, r2 becomes rsub rd, r2, r1.  */
          inst |= (reg1 << RD_LOW) & RD_MASK;
          inst |= (reg3 << RA_LOW) & RA_MASK;
          inst |= (reg2 << RB_LOW) & RB_MASK;
        }
      else
        {
          inst |= (reg1 << RD_LOW) & RD_MASK;
          inst |= (reg2 << RA_LOW) & RA_MASK;
          inst |= (reg3 << RB_LOW) & RB_MASK;
        }
      output = frag_more (isize);
      break;

    case INST_TYPE_RD_R1_IMM:
      if (strcmp (op_end, ""))
	op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
 	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
	op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }
      if (strcmp (op_end, ""))
	op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM);
      else
	as_fatal (_("Error in statement syntax"));

      /* Check for spl registers.  */
      if (check_spl_reg (& reg1))
	as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (& reg2))
	as_fatal (_("Cannot use special register with this instruction"));

      if (exp.X_op != O_constant)
	{
          char *opc;
	  relax_substateT subtype;

          if (streq (name, "lmi"))
	    as_fatal (_("lmi pseudo instruction should not use a label in imm field"));
	  else if (streq (name, "smi"))
	    as_fatal (_("smi pseudo instruction should not use a label in imm field"));

	  if (reg2 == REG_ROSDP)
	    opc = str_microblaze_ro_anchor;
	  else if (reg2 == REG_RWSDP)
	    opc = str_microblaze_rw_anchor;
	  else
	    opc = NULL;
	  if (exp.X_md == IMM_GOT)
	    subtype = GOT_OFFSET;
	  else if (exp.X_md == IMM_PLT)
	    subtype = PLT_OFFSET;
	  else if (exp.X_md == IMM_GOTOFF)
	    subtype = GOTOFF_OFFSET;
	  else
	    subtype = opcode->inst_offset_type;

	  output = frag_var (rs_machine_dependent,
			     isize * 2, /* maxm of 2 words.  */
			     isize,     /* minm of 1 word.  */
			     subtype,   /* PC-relative or not.  */
			     exp.X_add_symbol,
			     exp.X_add_number,
			     opc);
	  immed = 0;
        }
      else
	{
          output = frag_more (isize);
          immed = exp.X_add_number;
        }

      if (streq (name, "lmi") || streq (name, "smi"))
	{
          /* Load/store 32-d consecutive registers.  Used on exit/entry
             to subroutines to save and restore registers to stack.
             Generate 32-d insts.  */
          int count;

          count = 32 - reg1;
          if (streq (name, "lmi"))
            opcode = (struct op_code_struct *) hash_find (opcode_hash_control, "lwi");
          else
            opcode = (struct op_code_struct *) hash_find (opcode_hash_control, "swi");
          if (opcode == NULL)
            {
              as_bad (_("unknown opcode \"%s\""), "lwi");
              return;
            }
          inst  = opcode->bit_sequence;
          inst |= (reg1 << RD_LOW) & RD_MASK;
          inst |= (reg2 << RA_LOW) & RA_MASK;
          inst |= (immed << IMM_LOW) & IMM_MASK;

          for (i = 0; i < count - 1; i++)
	    {
              output[0] = INST_BYTE0 (inst);
              output[1] = INST_BYTE1 (inst);
              output[2] = INST_BYTE2 (inst);
              output[3] = INST_BYTE3 (inst);
              output = frag_more (isize);
              immed = immed + 4;
              reg1++;
              inst = opcode->bit_sequence;
              inst |= (reg1 << RD_LOW) & RD_MASK;
              inst |= (reg2 << RA_LOW) & RA_MASK;
              inst |= (immed << IMM_LOW) & IMM_MASK;
            }
	}
      else
	{
          temp = immed & 0xFFFF8000;
          if ((temp != 0) && (temp != 0xFFFF8000))
	    {
              /* Needs an immediate inst.  */
              opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm");
              if (opcode1 == NULL)
                {
                  as_bad (_("unknown opcode \"%s\""), "imm");
                  return;
                }

              inst1 = opcode1->bit_sequence;
              inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK;
              output[0] = INST_BYTE0 (inst1);
              output[1] = INST_BYTE1 (inst1);
              output[2] = INST_BYTE2 (inst1);
              output[3] = INST_BYTE3 (inst1);
              output = frag_more (isize);
	    }
	  inst |= (reg1 << RD_LOW) & RD_MASK;
	  inst |= (reg2 << RA_LOW) & RA_MASK;
	  inst |= (immed << IMM_LOW) & IMM_MASK;
	}
      break;

    case INST_TYPE_RD_R1_IMM5:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM);
      else
        as_fatal (_("Error in statement syntax"));

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (&reg2))
        as_fatal (_("Cannot use special register with this instruction"));

      if (exp.X_op != O_constant)
        as_warn (_("Symbol used as immediate for shift instruction"));
      else
	{
          output = frag_more (isize);
          immed = exp.X_add_number;
        }

      if (immed != (immed % 32))
	{
          as_warn (_("Shift value > 32. using <value %% 32>"));
          immed = immed % 32;
        }
      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (reg2 << RA_LOW) & RA_MASK;
      inst |= (immed << IMM_LOW) & IMM5_MASK;
      break;

    case INST_TYPE_R1_R2:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r2.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (& reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (& reg2))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RA_LOW) & RA_MASK;
      inst |= (reg2 << RB_LOW) & RB_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_RD_R1:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 =0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (&reg2))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (reg2 << RA_LOW) & RA_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_RD_RFSL:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &immed);  /* Get rfslN.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          immed = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (immed << IMM_LOW) & RFSL_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_RD_IMM15:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }

      if (strcmp (op_end, ""))
        op_end = parse_imm (op_end + 1, & exp, MIN_IMM15, MAX_IMM15);
      else
        as_fatal (_("Error in statement syntax"));

      /* Check for spl registers. */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      if (exp.X_op != O_constant)
        as_fatal (_("Symbol used as immediate value for msrset/msrclr instructions"));
      else
	{
          output = frag_more (isize);
          immed = exp.X_add_number;
        }
      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (immed << IMM_LOW) & IMM15_MASK;
      break;

    case INST_TYPE_R1_RFSL:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &immed);  /* Get rfslN.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          immed = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RA_LOW) & RA_MASK;
      inst |= (immed << IMM_LOW) & RFSL_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_RFSL:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &immed);  /* Get rfslN.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          immed = 0;
        }
      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      inst |= (immed << IMM_LOW) & RFSL_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_R1:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RA_LOW) & RA_MASK;
      output = frag_more (isize);
      break;

      /* For tuqula insn...:) */
    case INST_TYPE_RD:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RD_LOW) & RD_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_RD_SPECIAL:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }

      if (reg2 == REG_MSR)
        immed = opcode->immval_mask | REG_MSR_MASK;
      else if (reg2 == REG_PC)
        immed = opcode->immval_mask | REG_PC_MASK;
      else if (reg2 == REG_EAR)
        immed = opcode->immval_mask | REG_EAR_MASK;
      else if (reg2 == REG_ESR)
        immed = opcode->immval_mask | REG_ESR_MASK;
      else if (reg2 == REG_FSR)
        immed = opcode->immval_mask | REG_FSR_MASK;
      else if (reg2 == REG_BTR)
        immed = opcode->immval_mask | REG_BTR_MASK;
      else if (reg2 == REG_EDR)
        immed = opcode->immval_mask | REG_EDR_MASK;
      else if (reg2 == REG_PID)
        immed = opcode->immval_mask | REG_PID_MASK;
      else if (reg2 == REG_ZPR)
        immed = opcode->immval_mask | REG_ZPR_MASK;
      else if (reg2 == REG_TLBX)
        immed = opcode->immval_mask | REG_TLBX_MASK;
      else if (reg2 == REG_TLBLO)
        immed = opcode->immval_mask | REG_TLBLO_MASK;
      else if (reg2 == REG_TLBHI)
        immed = opcode->immval_mask | REG_TLBHI_MASK;
      else if (reg2 >= (REG_PVR+MIN_PVR_REGNUM) && reg2 <= (REG_PVR+MAX_PVR_REGNUM))
	immed = opcode->immval_mask | REG_PVR_MASK | reg2;
      else
        as_fatal (_("invalid value for special purpose register"));
      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (immed << IMM_LOW) & IMM_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_SPECIAL_R1:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }

      if (reg1 == REG_MSR)
        immed = opcode->immval_mask | REG_MSR_MASK;
      else if (reg1 == REG_PC)
        immed = opcode->immval_mask | REG_PC_MASK;
      else if (reg1 == REG_EAR)
        immed = opcode->immval_mask | REG_EAR_MASK;
      else if (reg1 == REG_ESR)
        immed = opcode->immval_mask | REG_ESR_MASK;
      else if (reg1 == REG_FSR)
        immed = opcode->immval_mask | REG_FSR_MASK;
      else if (reg1 == REG_BTR)
        immed = opcode->immval_mask | REG_BTR_MASK;
      else if (reg1 == REG_EDR)
        immed = opcode->immval_mask | REG_EDR_MASK;
      else if (reg1 == REG_PID)
        immed = opcode->immval_mask | REG_PID_MASK;
      else if (reg1 == REG_ZPR)
        immed = opcode->immval_mask | REG_ZPR_MASK;
      else if (reg1 == REG_TLBX)
        immed = opcode->immval_mask | REG_TLBX_MASK;
      else if (reg1 == REG_TLBLO)
        immed = opcode->immval_mask | REG_TLBLO_MASK;
      else if (reg1 == REG_TLBHI)
        immed = opcode->immval_mask | REG_TLBHI_MASK;
      else if (reg1 == REG_TLBSX)
        immed = opcode->immval_mask | REG_TLBSX_MASK;
      else
        as_fatal (_("invalid value for special purpose register"));
      inst |= (reg2 << RA_LOW) & RA_MASK;
      inst |= (immed << IMM_LOW) & IMM_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_RD_R1_SPECIAL:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 =0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (&reg2))
        as_fatal (_("Cannot use special register with this instruction"));

      /* insn wic ra, rb => wic ra, ra, rb.  */
      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (reg1 << RA_LOW) & RA_MASK;
      inst |= (reg2 << RB_LOW) & RB_MASK;

      output = frag_more (isize);
      break;

    case INST_TYPE_RD_R2:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r2.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));
      if (check_spl_reg (&reg2))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (reg2 << RB_LOW) & RB_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_R1_IMM:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get r1.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM);
      else
        as_fatal (_("Error in statement syntax"));

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      if (exp.X_op != O_constant)
	{
          char *opc = NULL;
          relax_substateT subtype;

	  if (exp.X_md == IMM_GOT)
	    subtype = GOT_OFFSET;
	  else if (exp.X_md == IMM_PLT)
	    subtype = PLT_OFFSET;
	  else
	    subtype = opcode->inst_offset_type;
	  output = frag_var (rs_machine_dependent,
			     isize * 2, /* maxm of 2 words.  */
			     isize,     /* minm of 1 word.  */
			     subtype,   /* PC-relative or not.  */
			     exp.X_add_symbol,
			     exp.X_add_number,
			     opc);
	  immed = 0;
	}
      else
	{
          output = frag_more (isize);
          immed = exp.X_add_number;
        }

      temp = immed & 0xFFFF8000;
      if ((temp != 0) && (temp != 0xFFFF8000))
	{
          /* Needs an immediate inst.  */
          opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm");
          if (opcode1 == NULL)
            {
              as_bad (_("unknown opcode \"%s\""), "imm");
	      return;
            }

          inst1 = opcode1->bit_sequence;
          inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK;
          output[0] = INST_BYTE0 (inst1);
          output[1] = INST_BYTE1 (inst1);
          output[2] = INST_BYTE2 (inst1);
          output[3] = INST_BYTE3 (inst1);
          output = frag_more (isize);
        }

      inst |= (reg1 << RA_LOW) & RA_MASK;
      inst |= (immed << IMM_LOW) & IMM_MASK;
      break;

    case INST_TYPE_RD_IMM:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg1);  /* Get rd.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg1 = 0;
        }
      if (strcmp (op_end, ""))
        op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM);
      else
        as_fatal (_("Error in statement syntax"));

      /* Check for spl registers.  */
      if (check_spl_reg (&reg1))
        as_fatal (_("Cannot use special register with this instruction"));

      if (exp.X_op != O_constant)
	{
          char *opc = NULL;
          relax_substateT subtype;

          if (exp.X_md == IMM_GOT)
            subtype = GOT_OFFSET;
          else if (exp.X_md == IMM_PLT)
            subtype = PLT_OFFSET;
          else
	    subtype = opcode->inst_offset_type;
          output = frag_var (rs_machine_dependent,
			     isize * 2, /* maxm of 2 words.  */
			     isize,     /* minm of 1 word.  */
			     subtype,   /* PC-relative or not.  */
			     exp.X_add_symbol,
			     exp.X_add_number,
			     opc);
          immed = 0;
	}
      else
	{
          output = frag_more (isize);
          immed = exp.X_add_number;
        }

      temp = immed & 0xFFFF8000;
      if ((temp != 0) && (temp != 0xFFFF8000))
	{
          /* Needs an immediate inst.  */
          opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm");
          if (opcode1 == NULL)
            {
              as_bad (_("unknown opcode \"%s\""), "imm");
              return;
            }

          inst1 = opcode1->bit_sequence;
          inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK;
          output[0] = INST_BYTE0 (inst1);
          output[1] = INST_BYTE1 (inst1);
          output[2] = INST_BYTE2 (inst1);
          output[3] = INST_BYTE3 (inst1);
          output = frag_more (isize);
        }

      inst |= (reg1 << RD_LOW) & RD_MASK;
      inst |= (immed << IMM_LOW) & IMM_MASK;
      break;

    case INST_TYPE_R2:
      if (strcmp (op_end, ""))
        op_end = parse_reg (op_end + 1, &reg2);  /* Get r2.  */
      else
	{
          as_fatal (_("Error in statement syntax"));
          reg2 = 0;
        }

      /* Check for spl registers.  */
      if (check_spl_reg (&reg2))
        as_fatal (_("Cannot use special register with this instruction"));

      inst |= (reg2 << RB_LOW) & RB_MASK;
      output = frag_more (isize);
      break;

    case INST_TYPE_IMM:
      if (streq (name, "imm"))
        as_fatal (_("An IMM instruction should not be present in the .s file"));

      op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM);

      if (exp.X_op != O_constant)
	{
          char *opc = NULL;
          relax_substateT subtype;

          if (exp.X_md == IMM_GOT)
            subtype = GOT_OFFSET;
          else if (exp.X_md == IMM_PLT)
            subtype = PLT_OFFSET;
          else
            subtype = opcode->inst_offset_type;
          output = frag_var (rs_machine_dependent,
			     isize * 2, /* maxm of 2 words.  */
			     isize,     /* minm of 1 word.  */
			     subtype,   /* PC-relative or not.  */
			     exp.X_add_symbol,
			     exp.X_add_number,
			     opc);
          immed = 0;
        }
      else
	{
          output = frag_more (isize);
          immed = exp.X_add_number;
        }


      temp = immed & 0xFFFF8000;
      if ((temp != 0) && (temp != 0xFFFF8000))
	{
          /* Needs an immediate inst.  */
          opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm");
          if (opcode1 == NULL)
            {
              as_bad (_("unknown opcode \"%s\""), "imm");
              return;
            }

          inst1 = opcode1->bit_sequence;
          inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK;
          output[0] = INST_BYTE0 (inst1);
          output[1] = INST_BYTE1 (inst1);
          output[2] = INST_BYTE2 (inst1);
          output[3] = INST_BYTE3 (inst1);
          output = frag_more (isize);
        }
      inst |= (immed << IMM_LOW) & IMM_MASK;
      break;

    case INST_TYPE_NONE:
      output = frag_more (isize);
      break;

    default:
      as_fatal (_("unimplemented opcode \"%s\""), name);
    }

  /* Drop whitespace after all the operands have been parsed.  */
  while (ISSPACE (* op_end))
    op_end ++;

  /* Give warning message if the insn has more operands than required.  */
  if (strcmp (op_end, opcode->name) && strcmp (op_end, ""))
    as_warn (_("ignoring operands: %s "), op_end);

  output[0] = INST_BYTE0 (inst);
  output[1] = INST_BYTE1 (inst);
  output[2] = INST_BYTE2 (inst);
  output[3] = INST_BYTE3 (inst);

#ifdef OBJ_ELF
  dwarf2_emit_insn (4);
#endif
}

symbolS *
md_undefined_symbol (char * name ATTRIBUTE_UNUSED)
{
  return NULL;
}

/* Various routines to kill one day.  */
/* Equal to MAX_PRECISION in atof-ieee.c */
#define MAX_LITTLENUMS 6

/* Turn a string in input_line_pointer into a floating point constant of type
   type, and store the appropriate bytes in *litP.  The number of LITTLENUMS
   emitted is stored in *sizeP.  An error message is returned, or NULL on OK.*/
char *
md_atof (int type, char * litP, int * sizeP)
{
  int prec;
  LITTLENUM_TYPE words[MAX_LITTLENUMS];
  int    i;
  char * t;

  switch (type)
    {
    case 'f':
    case 'F':
    case 's':
    case 'S':
      prec = 2;
      break;

    case 'd':
    case 'D':
    case 'r':
    case 'R':
      prec = 4;
      break;

    case 'x':
    case 'X':
      prec = 6;
      break;

    case 'p':
    case 'P':
      prec = 6;
      break;

    default:
      *sizeP = 0;
      return _("Bad call to MD_NTOF()");
    }

  t = atof_ieee (input_line_pointer, type, words);

  if (t)
    input_line_pointer = t;

  *sizeP = prec * sizeof (LITTLENUM_TYPE);

  if (! target_big_endian)
    {
      for (i = prec - 1; i >= 0; i--)
        {
          md_number_to_chars (litP, (valueT) words[i],
                              sizeof (LITTLENUM_TYPE));
          litP += sizeof (LITTLENUM_TYPE);
        }
    }
  else
    for (i = 0; i < prec; i++)
      {
        md_number_to_chars (litP, (valueT) words[i],
                            sizeof (LITTLENUM_TYPE));
        litP += sizeof (LITTLENUM_TYPE);
      }

  return NULL;
}

const char * md_shortopts = "";

struct option md_longopts[] =
{
  { NULL,          no_argument, NULL, 0}
};

size_t md_longopts_size = sizeof (md_longopts);

int md_short_jump_size;

void
md_create_short_jump (char * ptr ATTRIBUTE_UNUSED,
		      addressT from_Nddr ATTRIBUTE_UNUSED,
		      addressT to_Nddr ATTRIBUTE_UNUSED,
		      fragS * frag ATTRIBUTE_UNUSED,
		      symbolS * to_symbol ATTRIBUTE_UNUSED)
{
  as_fatal (_("failed sanity check: short_jump"));
}

void
md_create_long_jump (char * ptr ATTRIBUTE_UNUSED,
		     addressT from_Nddr ATTRIBUTE_UNUSED,
		     addressT to_Nddr ATTRIBUTE_UNUSED,
		     fragS * frag ATTRIBUTE_UNUSED,
		     symbolS * to_symbol ATTRIBUTE_UNUSED)
{
  as_fatal (_("failed sanity check: long_jump"));
}

/* Called after relaxing, change the frags so they know how big they are.  */

void
md_convert_frag (bfd * abfd ATTRIBUTE_UNUSED,
	         segT sec ATTRIBUTE_UNUSED,
		 fragS * fragP)
{
  fixS *fixP;

  switch (fragP->fr_subtype)
    {
    case UNDEFINED_PC_OFFSET:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	       fragP->fr_offset, TRUE, BFD_RELOC_64_PCREL);
      fragP->fr_fix += INST_WORD_SIZE * 2;
      fragP->fr_var = 0;
      break;
    case DEFINED_ABS_SEGMENT:
      if (fragP->fr_symbol == GOT_symbol)
        fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	         fragP->fr_offset, TRUE, BFD_RELOC_MICROBLAZE_64_GOTPC);
      else
        fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	         fragP->fr_offset, FALSE, BFD_RELOC_64);
      fragP->fr_fix += INST_WORD_SIZE * 2;
      fragP->fr_var = 0;
      break;
    case DEFINED_RO_SEGMENT:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE, fragP->fr_symbol,
	       fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_32_ROSDA);
      fragP->fr_fix += INST_WORD_SIZE;
      fragP->fr_var = 0;
      break;
    case DEFINED_RW_SEGMENT:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE, fragP->fr_symbol,
	       fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_32_RWSDA);
      fragP->fr_fix += INST_WORD_SIZE;
      fragP->fr_var = 0;
      break;
    case DEFINED_PC_OFFSET:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE, fragP->fr_symbol,
	       fragP->fr_offset, TRUE, BFD_RELOC_MICROBLAZE_32_LO_PCREL);
      fragP->fr_fix += INST_WORD_SIZE;
      fragP->fr_var = 0;
      break;
    case LARGE_DEFINED_PC_OFFSET:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	       fragP->fr_offset, TRUE, BFD_RELOC_64_PCREL);
      fragP->fr_fix += INST_WORD_SIZE * 2;
      fragP->fr_var = 0;
      break;
    case GOT_OFFSET:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	       fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_64_GOT);
      fragP->fr_fix += INST_WORD_SIZE * 2;
      fragP->fr_var = 0;
      break;
    case PLT_OFFSET:
      fixP = fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	              fragP->fr_offset, TRUE, BFD_RELOC_MICROBLAZE_64_PLT);
      /* fixP->fx_plt = 1; */
      fragP->fr_fix += INST_WORD_SIZE * 2;
      fragP->fr_var = 0;
      break;
    case GOTOFF_OFFSET:
      fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol,
	       fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_64_GOTOFF);
      fragP->fr_fix += INST_WORD_SIZE * 2;
      fragP->fr_var = 0;
      break;

    default:
      abort ();
    }
}

/* Applies the desired value to the specified location.
   Also sets up addends for 'rela' type relocations.  */
void
md_apply_fix (fixS *   fixP,
	      valueT * valp,
	      segT     segment)
{
  char *       buf  = fixP->fx_where + fixP->fx_frag->fr_literal;
  char *       file = fixP->fx_file ? fixP->fx_file : _("unknown");
  const char * symname;
  /* Note: use offsetT because it is signed, valueT is unsigned.  */
  offsetT      val  = (offsetT) * valp;
  int          i;
  struct op_code_struct * opcode1;
  unsigned long inst1;

  symname = fixP->fx_addsy ? S_GET_NAME (fixP->fx_addsy) : _("<unknown>");

  /* fixP->fx_offset is supposed to be set up correctly for all
     symbol relocations.  */
  if (fixP->fx_addsy == NULL)
    {
      if (!fixP->fx_pcrel)
        fixP->fx_offset = val; /* Absolute relocation.  */
      else
        fprintf (stderr, "NULL symbol PC-relative relocation? offset = %08x, val = %08x\n",
                 (unsigned int) fixP->fx_offset, (unsigned int) val);
    }

  /* If we aren't adjusting this fixup to be against the section
     symbol, we need to adjust the value.  */
  if (fixP->fx_addsy != NULL)
    {
      if (S_IS_WEAK (fixP->fx_addsy)
	  || (symbol_used_in_reloc_p (fixP->fx_addsy)
	      && (((bfd_get_section_flags (stdoutput,
					   S_GET_SEGMENT (fixP->fx_addsy))
		    & SEC_LINK_ONCE) != 0)
		  || !strncmp (segment_name (S_GET_SEGMENT (fixP->fx_addsy)),
			       ".gnu.linkonce",
			       sizeof (".gnu.linkonce") - 1))))
	{
	  val -= S_GET_VALUE (fixP->fx_addsy);
	  if (val != 0 && ! fixP->fx_pcrel)
            {
              /* In this case, the bfd_install_relocation routine will
                 incorrectly add the symbol value back in.  We just want
                 the addend to appear in the object file.
	         FIXME: If this makes VALUE zero, we're toast.  */
              val -= S_GET_VALUE (fixP->fx_addsy);
            }
	}
    }

  /* If the fix is relative to a symbol which is not defined, or not
     in the same segment as the fix, we cannot resolve it here.  */
  /* fixP->fx_addsy is NULL if valp contains the entire relocation.  */
  if (fixP->fx_addsy != NULL
      && (!S_IS_DEFINED (fixP->fx_addsy)
          || (S_GET_SEGMENT (fixP->fx_addsy) != segment)))
    {
      fixP->fx_done = 0;
#ifdef OBJ_ELF
      /* For ELF we can just return and let the reloc that will be generated
         take care of everything.  For COFF we still have to insert 'val'
         into the insn since the addend field will be ignored.  */
      /* return; */
#endif
    }
  /* All fixups in the text section must be handled in the linker.  */
  else if (segment->flags & SEC_CODE)
    fixP->fx_done = 0;
  else if (!fixP->fx_pcrel && fixP->fx_addsy != NULL)
    fixP->fx_done = 0;
  else
    fixP->fx_done = 1;

  switch (fixP->fx_r_type)
    {
    case BFD_RELOC_MICROBLAZE_32_LO:
    case BFD_RELOC_MICROBLAZE_32_LO_PCREL:
      if (target_big_endian)
	{
	  buf[2] |= ((val >> 8) & 0xff);
	  buf[3] |= (val & 0xff);
	}
      else
	{
	  buf[1] |= ((val >> 8) & 0xff);
	  buf[0] |= (val & 0xff);
	}
      break;
    case BFD_RELOC_MICROBLAZE_32_ROSDA:
    case BFD_RELOC_MICROBLAZE_32_RWSDA:
      /* Don't do anything if the symbol is not defined.  */
      if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy))
	{
	  if (((val & 0xFFFF8000) != 0) && ((val & 0xFFFF8000) != 0xFFFF8000))
	    as_bad_where (file, fixP->fx_line,
			  _("pcrel for branch to %s too far (0x%x)"),
			  symname, (int) val);
	  if (target_big_endian)
	    {
	      buf[2] |= ((val >> 8) & 0xff);
	      buf[3] |= (val & 0xff);
	    }
	  else
	    {
	      buf[1] |= ((val >> 8) & 0xff);
	      buf[0] |= (val & 0xff);
	    }
	}
      break;
    case BFD_RELOC_32:
    case BFD_RELOC_RVA:
    case BFD_RELOC_32_PCREL:
    case BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM:
      /* Don't do anything if the symbol is not defined.  */
      if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy))
	{
	  if (target_big_endian)
	    {
	      buf[0] |= ((val >> 24) & 0xff);
	      buf[1] |= ((val >> 16) & 0xff);
	      buf[2] |= ((val >> 8) & 0xff);
	      buf[3] |= (val & 0xff);
	    }
	  else
	    {
	      buf[3] |= ((val >> 24) & 0xff);
	      buf[2] |= ((val >> 16) & 0xff);
	      buf[1] |= ((val >> 8) & 0xff);
	      buf[0] |= (val & 0xff);
	    }
	}
      break;
    case BFD_RELOC_64_PCREL:
    case BFD_RELOC_64:
      /* Add an imm instruction.  First save the current instruction.  */
      for (i = 0; i < INST_WORD_SIZE; i++)
	buf[i + INST_WORD_SIZE] = buf[i];

      /* Generate the imm instruction.  */
      opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm");
      if (opcode1 == NULL)
	{
	  as_bad (_("unknown opcode \"%s\""), "imm");
	  return;
	}

      inst1 = opcode1->bit_sequence;
      if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy))
	inst1 |= ((val & 0xFFFF0000) >> 16) & IMM_MASK;

      buf[0] = INST_BYTE0 (inst1);
      buf[1] = INST_BYTE1 (inst1);
      buf[2] = INST_BYTE2 (inst1);
      buf[3] = INST_BYTE3 (inst1);

      /* Add the value only if the symbol is defined.  */
      if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy))
	{
	  if (target_big_endian)
	    {
	      buf[6] |= ((val >> 8) & 0xff);
	      buf[7] |= (val & 0xff);
	    }
	  else
	    {
	      buf[5] |= ((val >> 8) & 0xff);
	      buf[4] |= (val & 0xff);
	    }
	}
      break;

    case BFD_RELOC_MICROBLAZE_64_GOTPC:
    case BFD_RELOC_MICROBLAZE_64_GOT:
    case BFD_RELOC_MICROBLAZE_64_PLT:
    case BFD_RELOC_MICROBLAZE_64_GOTOFF:
      /* Add an imm instruction.  First save the current instruction.  */
      for (i = 0; i < INST_WORD_SIZE; i++)
	buf[i + INST_WORD_SIZE] = buf[i];

      /* Generate the imm instruction.  */
      opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm");
      if (opcode1 == NULL)
	{
	  as_bad (_("unknown opcode \"%s\""), "imm");
	  return;
	}

      inst1 = opcode1->bit_sequence;

      /* We can fixup call to a defined non-global address
	 within the same section only.  */
      buf[0] = INST_BYTE0 (inst1);
      buf[1] = INST_BYTE1 (inst1);
      buf[2] = INST_BYTE2 (inst1);
      buf[3] = INST_BYTE3 (inst1);
      return;

    default:
      break;
    }

  if (fixP->fx_addsy == NULL)
    {
      /* This fixup has been resolved.  Create a reloc in case the linker
	 moves code around due to relaxing.  */
      if (fixP->fx_r_type == BFD_RELOC_64_PCREL)
	fixP->fx_r_type = BFD_RELOC_MICROBLAZE_64_NONE;
      else
	fixP->fx_r_type = BFD_RELOC_NONE;
      fixP->fx_addsy = section_symbol (absolute_section);
    }
  return;
}

void
md_operand (expressionS * expressionP)
{
  /* Ignore leading hash symbol, if present.  */
  if (*input_line_pointer == '#')
    {
      input_line_pointer ++;
      expression (expressionP);
    }
}

/* Called just before address relaxation, return the length
   by which a fragment must grow to reach it's destination.  */

int
md_estimate_size_before_relax (fragS * fragP,
			       segT segment_type)
{
  sbss_segment = bfd_get_section_by_name (stdoutput, ".sbss");
  sbss2_segment = bfd_get_section_by_name (stdoutput, ".sbss2");
  sdata_segment = bfd_get_section_by_name (stdoutput, ".sdata");
  sdata2_segment = bfd_get_section_by_name (stdoutput, ".sdata2");

  switch (fragP->fr_subtype)
    {
    case INST_PC_OFFSET:
      /* Used to be a PC-relative branch.  */
      if (!fragP->fr_symbol)
        {
          /* We know the abs value: Should never happen.  */
          as_bad (_("Absolute PC-relative value in relaxation code.  Assembler error....."));
          abort ();
        }
      else if ((S_GET_SEGMENT (fragP->fr_symbol) == segment_type))
        {
          fragP->fr_subtype = DEFINED_PC_OFFSET;
          /* Don't know now whether we need an imm instruction.  */
          fragP->fr_var = INST_WORD_SIZE;
        }
      else if (S_IS_DEFINED (fragP->fr_symbol)
	       && (((S_GET_SEGMENT (fragP->fr_symbol))->flags & SEC_CODE) == 0))
        {
          /* Cannot have a PC-relative branch to a diff segment.  */
          as_bad (_("PC relative branch to label %s which is not in the instruction space"),
		  S_GET_NAME (fragP->fr_symbol));
          fragP->fr_subtype = UNDEFINED_PC_OFFSET;
          fragP->fr_var = INST_WORD_SIZE*2;
        }
      else
	{
	  fragP->fr_subtype = UNDEFINED_PC_OFFSET;
	  fragP->fr_var = INST_WORD_SIZE*2;
	}
      break;

    case INST_NO_OFFSET:
      /* Used to be a reference to somewhere which was unknown.  */
      if (fragP->fr_symbol)
        {
	  if (fragP->fr_opcode == NULL)
	    {
              /* Used as an absolute value.  */
              fragP->fr_subtype = DEFINED_ABS_SEGMENT;
              /* Variable part does not change.  */
              fragP->fr_var = INST_WORD_SIZE*2;
            }
	  else if (streq (fragP->fr_opcode, str_microblaze_ro_anchor))
	    {
              /* It is accessed using the small data read only anchor.  */
              if ((S_GET_SEGMENT (fragP->fr_symbol) == &bfd_com_section)
		  || (S_GET_SEGMENT (fragP->fr_symbol) == sdata2_segment)
		  || (S_GET_SEGMENT (fragP->fr_symbol) == sbss2_segment)
		  || (! S_IS_DEFINED (fragP->fr_symbol)))
		{
                  fragP->fr_subtype = DEFINED_RO_SEGMENT;
                  fragP->fr_var = INST_WORD_SIZE;
                }
	      else
		{
                  /* Variable not in small data read only segment accessed
		     using small data read only anchor.  */
                  char *file = fragP->fr_file ? fragP->fr_file : _("unknown");

                  as_bad_where (file, fragP->fr_line,
                                _("Variable is accessed using small data read "
				  "only anchor, but it is not in the small data "
			          "read only section"));
                  fragP->fr_subtype = DEFINED_RO_SEGMENT;
                  fragP->fr_var = INST_WORD_SIZE;
                }
            }
	  else if (streq (fragP->fr_opcode, str_microblaze_rw_anchor))
	    {
              if ((S_GET_SEGMENT (fragP->fr_symbol) == &bfd_com_section)
		  || (S_GET_SEGMENT (fragP->fr_symbol) == sdata_segment)
		  || (S_GET_SEGMENT (fragP->fr_symbol) == sbss_segment)
		  || (!S_IS_DEFINED (fragP->fr_symbol)))
	        {
                  /* It is accessed using the small data read write anchor.  */
                  fragP->fr_subtype = DEFINED_RW_SEGMENT;
                  fragP->fr_var = INST_WORD_SIZE;
                }
	      else
		{
                  char *file = fragP->fr_file ? fragP->fr_file : _("unknown");

                  as_bad_where (file, fragP->fr_line,
                                _("Variable is accessed using small data read "
				  "write anchor, but it is not in the small data "
				  "read write section"));
                  fragP->fr_subtype = DEFINED_RW_SEGMENT;
                  fragP->fr_var = INST_WORD_SIZE;
                }
            }
          else
	    {
              as_bad (_("Incorrect fr_opcode value in frag.  Internal error....."));
              abort ();
            }
	}
      else
	{
	  /* We know the abs value: Should never happen.  */
	  as_bad (_("Absolute value in relaxation code.  Assembler error....."));
	  abort ();
	}
      break;

    case UNDEFINED_PC_OFFSET:
    case LARGE_DEFINED_PC_OFFSET:
    case DEFINED_ABS_SEGMENT:
    case GOT_OFFSET:
    case PLT_OFFSET:
    case GOTOFF_OFFSET:
      fragP->fr_var = INST_WORD_SIZE*2;
      break;
    case DEFINED_RO_SEGMENT:
    case DEFINED_RW_SEGMENT:
    case DEFINED_PC_OFFSET:
      fragP->fr_var = INST_WORD_SIZE;
      break;
    default:
      abort ();
    }

  return fragP->fr_var;
}

/* Put number into target byte order.  */

void
md_number_to_chars (char * ptr, valueT use, int nbytes)
{
  if (target_big_endian)
    number_to_chars_bigendian (ptr, use, nbytes);
  else
    number_to_chars_littleendian (ptr, use, nbytes);
}

/* Round up a section size to the appropriate boundary.  */

valueT
md_section_align (segT segment ATTRIBUTE_UNUSED, valueT size)
{
  return size;			/* Byte alignment is fine.  */
}


/* The location from which a PC relative jump should be calculated,
   given a PC relative reloc.  */

long
md_pcrel_from_section (fixS * fixp, segT sec ATTRIBUTE_UNUSED)
{
#ifdef OBJ_ELF
  /* If the symbol is undefined or defined in another section
     we leave the add number alone for the linker to fix it later.
     Only account for the PC pre-bump (No PC-pre-bump on the Microblaze). */

  if (fixp->fx_addsy != (symbolS *) NULL
      && (!S_IS_DEFINED (fixp->fx_addsy)
          || (S_GET_SEGMENT (fixp->fx_addsy) != sec)))
    return 0;
  else
    {
      /* The case where we are going to resolve things... */
      if (fixp->fx_r_type == BFD_RELOC_64_PCREL)
        return  fixp->fx_where + fixp->fx_frag->fr_address + INST_WORD_SIZE;
      else
        return  fixp->fx_where + fixp->fx_frag->fr_address;
    }
#endif
}


#define F(SZ,PCREL)		(((SZ) << 1) + (PCREL))
#define MAP(SZ,PCREL,TYPE)	case F (SZ, PCREL): code = (TYPE); break

arelent *
tc_gen_reloc (asection * section ATTRIBUTE_UNUSED, fixS * fixp)
{
  arelent * rel;
  bfd_reloc_code_real_type code;

  switch (fixp->fx_r_type)
    {
    case BFD_RELOC_NONE:
    case BFD_RELOC_MICROBLAZE_64_NONE:
    case BFD_RELOC_32:
    case BFD_RELOC_MICROBLAZE_32_LO:
    case BFD_RELOC_MICROBLAZE_32_LO_PCREL:
    case BFD_RELOC_RVA:
    case BFD_RELOC_64:
    case BFD_RELOC_64_PCREL:
    case BFD_RELOC_MICROBLAZE_32_ROSDA:
    case BFD_RELOC_MICROBLAZE_32_RWSDA:
    case BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM:
    case BFD_RELOC_MICROBLAZE_64_GOTPC:
    case BFD_RELOC_MICROBLAZE_64_GOT:
    case BFD_RELOC_MICROBLAZE_64_PLT:
    case BFD_RELOC_MICROBLAZE_64_GOTOFF:
    case BFD_RELOC_MICROBLAZE_32_GOTOFF:
      code = fixp->fx_r_type;
      break;

    default:
      switch (F (fixp->fx_size, fixp->fx_pcrel))
        {
          MAP (1, 0, BFD_RELOC_8);
          MAP (2, 0, BFD_RELOC_16);
          MAP (4, 0, BFD_RELOC_32);
          MAP (1, 1, BFD_RELOC_8_PCREL);
          MAP (2, 1, BFD_RELOC_16_PCREL);
          MAP (4, 1, BFD_RELOC_32_PCREL);
        default:
          code = fixp->fx_r_type;
          as_bad (_("Can not do %d byte %srelocation"),
                  fixp->fx_size,
                  fixp->fx_pcrel ? _("pc-relative") : "");
        }
      break;
    }

  rel = (arelent *) xmalloc (sizeof (arelent));
  rel->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));

  if (code == BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM)
    *rel->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_subsy);
  else
    *rel->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);

  rel->address = fixp->fx_frag->fr_address + fixp->fx_where;
  /* Always pass the addend along!  */
  rel->addend = fixp->fx_offset;
  rel->howto = bfd_reloc_type_lookup (stdoutput, code);

  if (rel->howto == NULL)
    {
      as_bad_where (fixp->fx_file, fixp->fx_line,
                    _("Cannot represent relocation type %s"),
                    bfd_get_reloc_code_name (code));

      /* Set howto to a garbage value so that we can keep going.  */
      rel->howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
      gas_assert (rel->howto != NULL);
    }
  return rel;
}

int
md_parse_option (int c, char * arg ATTRIBUTE_UNUSED)
{
  switch (c)
    {
    default:
      return 0;
    }
  return 1;
}

void
md_show_usage (FILE * stream ATTRIBUTE_UNUSED)
{
  /*  fprintf(stream, _("\
      MicroBlaze options:\n\
      -noSmall         Data in the comm and data sections do not go into the small data section\n")); */
}


/* Create a fixup for a cons expression.  If parse_cons_expression_microblaze
   found a machine specific op in an expression,
   then we create relocs accordingly.  */

void
cons_fix_new_microblaze (fragS * frag,
			 int where,
			 int size,
			 expressionS *exp)
{

  bfd_reloc_code_real_type r;

  if ((exp->X_op == O_subtract) && (exp->X_add_symbol) &&
      (exp->X_op_symbol) && (now_seg != absolute_section) && (size == 4)
      && (!S_IS_LOCAL (exp->X_op_symbol)))
    r = BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM;
  else if (exp->X_md == IMM_GOTOFF && exp->X_op == O_symbol_rva)
    {
      exp->X_op = O_symbol;
      r = BFD_RELOC_MICROBLAZE_32_GOTOFF;
    }
  else
    {
      switch (size)
        {
        case 1:
          r = BFD_RELOC_8;
          break;
        case 2:
          r = BFD_RELOC_16;
          break;
        case 4:
          r = BFD_RELOC_32;
          break;
        case 8:
          r = BFD_RELOC_64;
          break;
        default:
          as_bad (_("unsupported BFD relocation size %u"), size);
          r = BFD_RELOC_32;
          break;
        }
    }
  fix_new_exp (frag, where, size, exp, 0, r);
}
