aboutsummaryrefslogtreecommitdiff
path: root/gas
diff options
context:
space:
mode:
authorNick Clifton <nickc@redhat.com>2013-01-10 09:49:22 +0000
committerNick Clifton <nickc@redhat.com>2013-01-10 09:49:22 +0000
commita3c629886c2cdaa6ee89513b64c7f989ba30eba3 (patch)
tree6076704fbfa023a83d161ff5d236d1cb0b7aefa3 /gas
parent9e9143bc874ab4742433b7498413313fb1723e8b (diff)
downloadgdb-a3c629886c2cdaa6ee89513b64c7f989ba30eba3.zip
gdb-a3c629886c2cdaa6ee89513b64c7f989ba30eba3.tar.gz
gdb-a3c629886c2cdaa6ee89513b64c7f989ba30eba3.tar.bz2
* common.h: Fix case of "Meta".
* metag.h: New file. * dis-asm.h (print_insn_metag): New declaration. * metag.h: New file. * Makefile.am: Add Meta. * Makefile.in: Regenerate. * configure: Regenerate. * configure.in: Add Meta. * disassemble.c: Add Meta support. * metag-dis.c: New file. * Makefile.am: Add Meta. * Makefile.in: Regenerate. * archures.c (bfd_mach_metag): New. * bfd-in2.h: Regenerate. * config.bfd: Add Meta. * configure: Regenerate. * configure.in: Add Meta. * cpu-metag.c: New file. * elf-bfd.h: Add Meta. * elf32-metag.c: New file. * elf32-metag.h: New file. * libbfd.h: Regenerate. * reloc.c: Add Meta relocations. * targets.c: Add Meta. * Makefile.am: Add Meta. * Makefile.in: Regenerate. * config/tc-metag.c: New file. * config/tc-metag.h: New file. * configure.tgt: Add Meta. * doc/Makefile.am: Add Meta. * doc/Makefile.in: Regenerate. * doc/all.texi: Add Meta. * doc/as.texiinfo: Document Meta options. * doc/c-metag.texi: New file. * gas/metag/labelarithmetic.d: New file. * gas/metag/labelarithmetic.s: New file. * gas/metag/metacore12.d: New file. * gas/metag/metacore12.s: New file. * gas/metag/metacore21-invalid.l: New file. * gas/metag/metacore21-invalid.s: New file. * gas/metag/metacore21.d: New file. * gas/metag/metacore21.s: New file. * gas/metag/metacore21ext.d: New file. * gas/metag/metacore21ext.s: New file. * gas/metag/metadsp21-invalid.l: New file. * gas/metag/metadsp21-invalid.s: New file. * gas/metag/metadsp21.d: New file. * gas/metag/metadsp21.s: New file. * gas/metag/metadsp21ext.d: New file. * gas/metag/metadsp21ext.s: New file. * gas/metag/metafpu21.d: New file. * gas/metag/metafpu21.s: New file. * gas/metag/metafpu21ext.d: New file. * gas/metag/metafpu21ext.s: New file. * gas/metag/metag.exp: New file. * gas/metag/tls.d: New file. * gas/metag/tls.s: New file. * Makefile.am: Add Meta. * Makefile.in: Regenerate. * configure.tgt: Add Meta. * emulparams/elf32metag.sh: New file. * emultempl/metagelf.em: New file. * ld-elf/merge.d: Mark Meta as xfail. * ld-gc/start.d: Skip this test on Meta. * ld-gc/personality.d: Skip this test on Meta. * ld-metag/external.s: New file. * ld-metag/metag.exp: New file. * ld-metag/pcrel.d: New file. * ld-metag/pcrel.s: New file. * ld-metag/shared.d: New file. * ld-metag/shared.r: New file. * ld-metag/shared.s: New file. * ld-metag/stub.d: New file. * ld-metag/stub.s: New file. * ld-metag/stub_pic_app.d: New file. * ld-metag/stub_pic_app.r: New file. * ld-metag/stub_pic_app.s: New file. * ld-metag/stub_pic_shared.d: New file. * ld-metag/stub_pic_shared.s: New file. * ld-metag/stub_shared.d: New file. * ld-metag/stub_shared.r: New file. * ld-metag/stub_shared.s: New file. * binutils/readelf.c: (guess_is_rela): Add EM_METAG. (dump_relocations): Add EM_METAG. (get_machine_name): Correct case for Meta. (is_32bit_abs_reloc): Add support for Meta ADDR32 reloc. (is_none_reloc): Add support for Meta NONE reloc.
Diffstat (limited to 'gas')
-rw-r--r--gas/ChangeLog13
-rw-r--r--gas/Makefile.am2
-rw-r--r--gas/Makefile.in17
-rw-r--r--gas/NEWS2
-rw-r--r--gas/config/tc-metag.c7115
-rw-r--r--gas/config/tc-metag.h72
-rw-r--r--gas/configure.tgt3
-rw-r--r--gas/doc/Makefile.am1
-rw-r--r--gas/doc/Makefile.in1
-rw-r--r--gas/doc/all.texi1
-rw-r--r--gas/doc/as.texinfo33
-rw-r--r--gas/doc/c-metag.texi82
-rw-r--r--gas/testsuite/ChangeLog26
13 files changed, 7368 insertions, 0 deletions
diff --git a/gas/ChangeLog b/gas/ChangeLog
index ba29792..d8563c8 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,16 @@
+2013-01-10 Will Newton <will.newton@imgtec.com>
+
+ * Makefile.am: Add Meta.
+ * Makefile.in: Regenerate.
+ * config/tc-metag.c: New file.
+ * config/tc-metag.h: New file.
+ * configure.tgt: Add Meta.
+ * doc/Makefile.am: Add Meta.
+ * doc/Makefile.in: Regenerate.
+ * doc/all.texi: Add Meta.
+ * doc/as.texiinfo: Document Meta options.
+ * doc/c-metag.texi: New file.
+
2013-01-09 Steve Ellcey <sellcey@mips.com>
* config/tc-i386.c (md_begin): Remove 'internal Error' from as_fatal
diff --git a/gas/Makefile.am b/gas/Makefile.am
index 272a311..7729d4a 100644
--- a/gas/Makefile.am
+++ b/gas/Makefile.am
@@ -155,6 +155,7 @@ TARGET_CPU_CFILES = \
config/tc-m68k.c \
config/tc-mcore.c \
config/tc-mep.c \
+ config/tc-metag.c \
config/tc-microblaze.c \
config/tc-mips.c \
config/tc-mmix.c \
@@ -225,6 +226,7 @@ TARGET_CPU_HFILES = \
config/tc-m68k.h \
config/tc-mcore.h \
config/tc-mep.h \
+ config/tc-metag.h \
config/tc-microblaze.h \
config/tc-mips.h \
config/tc-mmix.h \
diff --git a/gas/Makefile.in b/gas/Makefile.in
index a2c2c6b..c2a646c 100644
--- a/gas/Makefile.in
+++ b/gas/Makefile.in
@@ -424,6 +424,7 @@ TARGET_CPU_CFILES = \
config/tc-m68k.c \
config/tc-mcore.c \
config/tc-mep.c \
+ config/tc-metag.c \
config/tc-microblaze.c \
config/tc-mips.c \
config/tc-mmix.c \
@@ -494,6 +495,7 @@ TARGET_CPU_HFILES = \
config/tc-m68k.h \
config/tc-mcore.h \
config/tc-mep.h \
+ config/tc-metag.h \
config/tc-microblaze.h \
config/tc-mips.h \
config/tc-mmix.h \
@@ -844,6 +846,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-m68k.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-mcore.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-mep.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-metag.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-microblaze.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-mips.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tc-mmix.Po@am__quote@
@@ -1339,6 +1342,20 @@ tc-mep.obj: config/tc-mep.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tc-mep.obj `if test -f 'config/tc-mep.c'; then $(CYGPATH_W) 'config/tc-mep.c'; else $(CYGPATH_W) '$(srcdir)/config/tc-mep.c'; fi`
+tc-metag.o: config/tc-metag.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tc-metag.o -MD -MP -MF $(DEPDIR)/tc-metag.Tpo -c -o tc-metag.o `test -f 'config/tc-metag.c' || echo '$(srcdir)/'`config/tc-metag.c
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tc-metag.Tpo $(DEPDIR)/tc-metag.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config/tc-metag.c' object='tc-metag.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tc-metag.o `test -f 'config/tc-metag.c' || echo '$(srcdir)/'`config/tc-metag.c
+
+tc-metag.obj: config/tc-metag.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tc-metag.obj -MD -MP -MF $(DEPDIR)/tc-metag.Tpo -c -o tc-metag.obj `if test -f 'config/tc-metag.c'; then $(CYGPATH_W) 'config/tc-metag.c'; else $(CYGPATH_W) '$(srcdir)/config/tc-metag.c'; fi`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tc-metag.Tpo $(DEPDIR)/tc-metag.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='config/tc-metag.c' object='tc-metag.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o tc-metag.obj `if test -f 'config/tc-metag.c'; then $(CYGPATH_W) 'config/tc-metag.c'; else $(CYGPATH_W) '$(srcdir)/config/tc-metag.c'; fi`
+
tc-microblaze.o: config/tc-microblaze.c
@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT tc-microblaze.o -MD -MP -MF $(DEPDIR)/tc-microblaze.Tpo -c -o tc-microblaze.o `test -f 'config/tc-microblaze.c' || echo '$(srcdir)/'`config/tc-microblaze.c
@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/tc-microblaze.Tpo $(DEPDIR)/tc-microblaze.Po
diff --git a/gas/NEWS b/gas/NEWS
index 2b916f4..dbe88de 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -1,5 +1,7 @@
-*- text -*-
+* Add support for the Imagination Technologies Meta processor.
+
* Add support for the v850e3v5.
Changes in 2.23:
diff --git a/gas/config/tc-metag.c b/gas/config/tc-metag.c
new file mode 100644
index 0000000..d5e603a
--- /dev/null
+++ b/gas/config/tc-metag.c
@@ -0,0 +1,7115 @@
+/* tc-metag.c -- Assembler for the Imagination Technologies Meta.
+ Copyright (C) 2013 Free Software Foundation, Inc.
+ Contributed by Imagination Technologies Ltd.
+
+ 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 "as.h"
+#include "subsegs.h"
+#include "symcat.h"
+#include "safe-ctype.h"
+#include "hashtab.h"
+#include "libbfd.h"
+
+#include <stdio.h>
+
+#include "opcode/metag.h"
+
+const char comment_chars[] = "!";
+const char line_comment_chars[] = "!#";
+const char line_separator_chars[] = ";";
+const char FLT_CHARS[] = "rRsSfFdDxXpP";
+const char EXP_CHARS[] = "eE";
+const char metag_symbol_chars[] = "[";
+
+static char register_chars[256];
+static char mnemonic_chars[256];
+
+#define is_register_char(x) (register_chars[(unsigned char) x])
+#define is_mnemonic_char(x) (mnemonic_chars[(unsigned char) x])
+#define is_whitespace_char(x) (((x) == ' ') || ((x) == '\t'))
+#define is_space_char(x) ((x) == ' ')
+
+#define FPU_PREFIX_CHAR 'f'
+#define DSP_PREFIX_CHAR 'd'
+
+/* Instruction mnemonics that need disambiguating with respect to prefixes. */
+#define FFB_INSN "ffb"
+#define DCACHE_INSN "dcache"
+#define DEFR_INSN "defr"
+
+#define FPU_DOUBLE_CHAR 'd'
+#define FPU_PAIR_CHAR 'l'
+
+#define DSP_DUAL_CHAR 'l'
+
+#define END_OF_INSN '\0'
+
+/* Maximum length of a mnemonic including all suffixes. */
+#define MAX_MNEMONIC_LEN 16
+/* Maximum length of a register name. */
+#define MAX_REG_LEN 17
+
+/* Addressing modes must be enclosed with square brackets. */
+#define ADDR_BEGIN_CHAR '['
+#define ADDR_END_CHAR ']'
+/* Immediates must be prefixed with a hash. */
+#define IMM_CHAR '#'
+
+#define COMMA ','
+#define PLUS '+'
+#define MINUS '-'
+
+/* Short units are those that can be encoded with 2 bits. */
+#define SHORT_UNITS "D0, D1, A0 or A1"
+
+static unsigned int mcpu_opt = CoreMeta12;
+static unsigned int mfpu_opt = 0;
+static unsigned int mdsp_opt = 0;
+
+const char * md_shortopts = "m:";
+
+struct option md_longopts[] =
+{
+ {NULL, no_argument, NULL, 0}
+};
+size_t md_longopts_size = sizeof (md_longopts);
+
+/* Parser hash tables. */
+static htab_t mnemonic_htab;
+static htab_t reg_htab;
+static htab_t dsp_reg_htab;
+static htab_t dsp_tmpl_reg_htab[2];
+static htab_t scond_htab;
+
+#define GOT_NAME "__GLOBAL_OFFSET_TABLE__"
+symbolS * GOT_symbol;
+
+enum fpu_insn_width {
+ FPU_WIDTH_SINGLE,
+ FPU_WIDTH_DOUBLE,
+ FPU_WIDTH_PAIR,
+};
+
+#define FPU_ACTION_ABS_CHAR 'a'
+#define FPU_ACTION_INV_CHAR 'i'
+#define FPU_ACTION_QUIET_CHAR 'q'
+#define FPU_ACTION_ZERO_CHAR 'z'
+
+#define FPU_ACTION_ABS 0x1
+#define FPU_ACTION_INV 0x2
+#define FPU_ACTION_QUIET 0x4
+#define FPU_ACTION_ZERO 0x8
+
+enum dsp_insn_width {
+ DSP_WIDTH_SINGLE,
+ DSP_WIDTH_DUAL,
+};
+
+#define DSP_ACTION_QR64_CHAR 'q'
+#define DSP_ACTION_UMUL_CHAR 'u'
+#define DSP_ACTION_ROUND_CHAR 'r'
+#define DSP_ACTION_CLAMP9_CHAR 'g'
+#define DSP_ACTION_CLAMP8_CHAR 'b'
+#define DSP_ACTION_MOD_CHAR 'm'
+#define DSP_ACTION_ACC_ZERO_CHAR 'z'
+#define DSP_ACTION_ACC_ADD_CHAR 'p'
+#define DSP_ACTION_ACC_SUB_CHAR 'n'
+#define DSP_ACTION_OV_CHAR 'o'
+
+#define DSP_ACTION_QR64 0x001
+#define DSP_ACTION_UMUL 0x002
+#define DSP_ACTION_ROUND 0x004
+#define DSP_ACTION_CLAMP9 0x008
+#define DSP_ACTION_CLAMP8 0x010
+#define DSP_ACTION_MOD 0x020
+#define DSP_ACTION_ACC_ZERO 0x040
+#define DSP_ACTION_ACC_ADD 0x080
+#define DSP_ACTION_ACC_SUB 0x100
+#define DSP_ACTION_OV 0x200
+
+#define DSP_DAOPPAME_8_CHAR 'b'
+#define DSP_DAOPPAME_16_CHAR 'w'
+#define DSP_DAOPPAME_TEMP_CHAR 't'
+#define DSP_DAOPPAME_HIGH_CHAR 'h'
+
+#define DSP_DAOPPAME_8 0x1
+#define DSP_DAOPPAME_16 0x2
+#define DSP_DAOPPAME_TEMP 0x4
+#define DSP_DAOPPAME_HIGH 0x8
+
+/* Structure holding information about a parsed instruction. */
+typedef struct {
+ /* Instruction type. */
+ enum insn_type type;
+ /* Split condition code. */
+ enum scond_code scond;
+
+ /* Instruction bits. */
+ unsigned int bits;
+ /* Size of the instruction in bytes. */
+ size_t len;
+
+ /* FPU instruction encoding. */
+ enum fpu_insn_width fpu_width;
+ unsigned int fpu_action_flags;
+
+ /* DSP instruction encoding. */
+ enum dsp_insn_width dsp_width;
+ unsigned int dsp_action_flags;
+ unsigned int dsp_daoppame_flags;
+
+ /* Reloc encoding information, maximum of one reloc per insn. */
+ enum bfd_reloc_code_real reloc_type;
+ int reloc_pcrel;
+ expressionS reloc_exp;
+ unsigned int reloc_size;
+} metag_insn;
+
+/* Structure holding information about a parsed addressing mode. */
+typedef struct {
+ const metag_reg *base_reg;
+ const metag_reg *offset_reg;
+
+ expressionS exp;
+
+ enum bfd_reloc_code_real reloc_type;
+
+ /* Whether we have an immediate or not. */
+ unsigned short immediate:1;
+ /* Whether or not the base register is updated. */
+ unsigned short update:1;
+ /* Whether the operation uses the address pre or post increment. */
+ unsigned short post_increment:1;
+ /* Whether the immediate should be negated. */
+ unsigned short negate:1;
+} metag_addr;
+
+/* Linked list of possible parsers for this instruction. */
+typedef struct _insn_templates {
+ const insn_template *template;
+ struct _insn_templates *next;
+} insn_templates;
+
+/* Parse an instruction that takes no operands. */
+static const char *
+parse_none (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ insn->bits = template->meta_opcode;
+ insn->len = 4;
+ return line;
+}
+
+/* Return the next non-whitespace character in LINE or NULL. */
+static const char *
+skip_whitespace (const char *line)
+{
+ const char *l = line;
+
+ if (is_whitespace_char (*l))
+ {
+ l++;
+ }
+
+ return l;
+}
+
+/* Return the next non-space character in LINE or NULL. */
+static const char *
+skip_space (const char *line)
+{
+ const char *l = line;
+
+ if (is_space_char (*l))
+ {
+ l++;
+ }
+
+ return l;
+}
+
+/* Return the character after the current one in LINE if the current
+ character is a comma, otherwise NULL. */
+static const char *
+skip_comma (const char *line)
+{
+ const char *l = line;
+
+ if (l == NULL || *l != COMMA)
+ return NULL;
+
+ l++;
+
+ return l;
+}
+
+/* Return the metag_reg struct corresponding to NAME or NULL if no such
+ register exists. */
+static const metag_reg *
+parse_gp_reg (const char *name)
+{
+ const metag_reg *reg;
+ metag_reg entry;
+
+ entry.name = name;
+
+ reg = (const metag_reg *) htab_find (reg_htab, &entry);
+
+ return reg;
+}
+
+/* Parse a list of up to COUNT GP registers from LINE, returning the
+ registers parsed in REGS and the number parsed in REGS_READ. Return
+ a pointer to the next character or NULL. */
+static const char *
+parse_gp_regs_list (const char *line, const metag_reg **regs, size_t count,
+ size_t *regs_read)
+{
+ const char *l = line;
+ char reg_buf[MAX_REG_LEN];
+ int seen_regs = 0;
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ {
+ size_t len = 0;
+ const char *next;
+
+ next = l;
+
+ if (i > 0)
+ {
+ l = skip_comma (l);
+ if (l == NULL)
+ {
+ *regs_read = seen_regs;
+ return next;
+ }
+ }
+
+ while (is_register_char (*l))
+ {
+ reg_buf[len] = *l;
+ l++;
+ len++;
+ if (!(len < MAX_REG_LEN))
+ return NULL;
+ }
+
+ reg_buf[len] = '\0';
+
+ if (len)
+ {
+ const metag_reg *reg = parse_gp_reg (reg_buf);
+
+ if (!reg)
+ {
+ *regs_read = seen_regs;
+ return next;
+ }
+ else
+ {
+ regs[i] = reg;
+ seen_regs++;
+ }
+ }
+ else
+ {
+ *regs_read = seen_regs;
+ return next;
+ }
+ }
+
+ *regs_read = seen_regs;
+ return l;
+}
+
+/* Parse a list of exactly COUNT GP registers from LINE, returning the
+ registers parsed in REGS. Return a pointer to the next character or NULL. */
+static const char *
+parse_gp_regs (const char *line, const metag_reg **regs, size_t count)
+{
+ const char *l = line;
+ size_t regs_read = 0;
+
+ l = parse_gp_regs_list (l, regs, count, &regs_read);
+
+ if (regs_read != count)
+ return NULL;
+ else
+ return l;
+}
+
+/* Parse a list of exactly COUNT FPU registers from LINE, returning the
+ registers parsed in REGS. Return a pointer to the next character or NULL. */
+static const char *
+parse_fpu_regs (const char *line, const metag_reg **regs, size_t count)
+{
+ const char *l = line;
+ size_t regs_read = 0;
+
+ l = parse_gp_regs_list (l, regs, count, &regs_read);
+
+ if (regs_read != count)
+ return NULL;
+ else
+ {
+ size_t i;
+ for (i = 0; i < count; i++)
+ {
+ if (regs[i]->unit != UNIT_FX)
+ return NULL;
+ }
+ return l;
+ }
+}
+
+/* Return TRUE if REG1 and REG2 are in paired units. */
+static bfd_boolean
+is_unit_pair (const metag_reg *reg1, const metag_reg *reg2)
+{
+ if ((reg1->unit == UNIT_A0 &&
+ (reg2->unit == UNIT_A1)) ||
+ (reg1->unit == UNIT_A1 &&
+ (reg2->unit == UNIT_A0)) ||
+ (reg1->unit == UNIT_D0 &&
+ (reg2->unit == UNIT_D1)) ||
+ (reg1->unit == UNIT_D1 &&
+ (reg2->unit == UNIT_D0)))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Return TRUE if REG1 and REG2 form a register pair. */
+static bfd_boolean
+is_reg_pair (const metag_reg *reg1, const metag_reg *reg2)
+{
+ if (reg1->unit == UNIT_FX &&
+ reg2->unit == UNIT_FX &&
+ reg2->no == reg1->no + 1)
+ return TRUE;
+
+ if (reg1->no != reg2->no)
+ return FALSE;
+
+ return is_unit_pair (reg1, reg2);
+}
+
+/* Parse a pair of GP registers from LINE, returning the registers parsed
+ in REGS. Return a pointer to the next character or NULL. */
+static const char *
+parse_pair_gp_regs (const char *line, const metag_reg **regs)
+{
+ const char *l = line;
+
+ l = parse_gp_regs (line, regs, 2);
+
+ if (l == NULL)
+ {
+ l = parse_gp_regs (line, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->unit == UNIT_RD)
+ return l;
+ else
+ return NULL;
+ }
+
+ if (is_reg_pair (regs[0], regs[1]))
+ return l;
+
+ return NULL;
+}
+
+/* Parse a unit-to-unit MOV instruction. */
+static const char *
+parse_mov_u2u (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const metag_reg *regs[2];
+
+ line = parse_gp_regs (line, regs, 2);
+
+ if (line == NULL)
+ return NULL;
+
+ if (!mfpu_opt && (regs[0]->unit == UNIT_FX || regs[1]->unit == UNIT_FX))
+ {
+ as_bad (_("no floating point unit specified"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[1]->no << 19) |
+ (regs[0]->no << 14) |
+ (regs[1]->unit << 10) |
+ (regs[0]->unit << 5));
+ insn->len = 4;
+ return line;
+}
+
+/* Parse a MOV to port instruction. */
+static const char *
+parse_mov_port (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ unsigned int is_movl = MINOR_OPCODE (template->meta_opcode) == MOVL_MINOR;
+ const metag_reg *dest_regs[2];
+ const metag_reg *port_regs[1];
+
+ if (is_movl)
+ l = parse_gp_regs (l, dest_regs, 2);
+ else
+ l = parse_gp_regs (l, dest_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (template->insn_type == INSN_FPU && dest_regs[0]->unit != UNIT_FX)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_gp_regs (l, port_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (port_regs[0]->unit != UNIT_RD ||
+ port_regs[0]->no != 0)
+ return NULL;
+
+ if (is_movl)
+ {
+ if (!is_unit_pair (dest_regs[0], dest_regs[1]))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 14) |
+ (dest_regs[1]->no << 9) |
+ ((dest_regs[0]->unit & SHORT_UNIT_MASK) << 5));
+ }
+ else
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 14) |
+ (dest_regs[0]->unit << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a MOVL to TTREC instruction. */
+static const char *
+parse_movl_ttrec (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *src_regs[2];
+ const metag_reg *dest_regs[1];
+
+ l = parse_gp_regs (l, dest_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (dest_regs[0]->unit != UNIT_TT ||
+ dest_regs[0]->no != 3)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_gp_regs (l, src_regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!is_unit_pair (src_regs[0], src_regs[1]))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (src_regs[0]->no << 19) |
+ (src_regs[1]->no << 14) |
+ ((src_regs[0]->unit & SHORT_UNIT_MASK) << 7));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an incrementing or decrementing addressing mode. */
+static const char *
+parse_addr_incr_op (const char *line, metag_addr *addr)
+{
+ const char *l = line;
+ const char *ll;
+
+ ll = l + 1;
+
+ if (*l == PLUS &&
+ *ll == PLUS)
+ {
+ addr->update = 1;
+ ll++;
+ return ll;
+ }
+ else if (*l == MINUS &&
+ *ll == MINUS)
+ {
+ addr->update = 1;
+ addr->negate = 1;
+ ll++;
+ return ll;
+ }
+ return NULL;
+}
+
+/* Parse an pre-incrementing or pre-decrementing addressing mode. */
+static const char *
+parse_addr_pre_incr_op (const char *line, metag_addr *addr)
+{
+ return parse_addr_incr_op (line, addr);
+}
+
+/* Parse an post-incrementing or post-decrementing addressing mode. */
+static const char *
+parse_addr_post_incr_op (const char *line, metag_addr *addr)
+{
+ const char *l;
+
+ l = parse_addr_incr_op (line, addr);
+
+ if (l == NULL)
+ return NULL;
+
+ addr->post_increment = 1;
+
+ return l;
+}
+
+/* Parse an infix addressing mode. */
+static const char *
+parse_addr_op (const char *line, metag_addr *addr)
+{
+ const char *l = line;
+ const char *ll;
+
+ ll = l + 1;
+
+ if (*l == PLUS)
+ {
+ if (*ll == PLUS)
+ {
+ addr->update = 1;
+ ll++;
+ return ll;
+ }
+ l++;
+ return l;
+ }
+ return NULL;
+}
+
+/* Parse the immediate portion of an addrssing mode. */
+static const char *
+parse_imm_addr (const char *line, metag_addr *addr)
+{
+ const char *l = line;
+ char *save_input_line_pointer;
+ expressionS *exp = &addr->exp;
+
+ /* Skip #. */
+ if (*l == '#')
+ l++;
+ else
+ return NULL;
+
+ save_input_line_pointer = input_line_pointer;
+ input_line_pointer = (char *) l;
+
+ expression (exp);
+
+ l = input_line_pointer;
+ input_line_pointer = save_input_line_pointer;
+
+ if (exp->X_op == O_absent || exp->X_op == O_big)
+ {
+ return NULL;
+ }
+ else if (exp->X_op == O_constant)
+ {
+ return l;
+ }
+ else
+ {
+ if (exp->X_op == O_PIC_reloc &&
+ exp->X_md == BFD_RELOC_METAG_GETSET_GOT)
+ {
+ exp->X_op = O_symbol;
+ addr->reloc_type = BFD_RELOC_METAG_GETSET_GOT;
+ }
+ else if (exp->X_op == O_PIC_reloc &&
+ exp->X_md == BFD_RELOC_METAG_TLS_IE)
+ {
+ exp->X_op = O_symbol;
+ addr->reloc_type = BFD_RELOC_METAG_TLS_IE;
+ }
+ else if (exp->X_op == O_PIC_reloc &&
+ exp->X_md == BFD_RELOC_METAG_GOTOFF)
+ {
+ exp->X_op = O_symbol;
+ addr->reloc_type = BFD_RELOC_METAG_GETSET_GOTOFF;
+ }
+ else
+ addr->reloc_type = BFD_RELOC_METAG_GETSETOFF;
+ return l;
+ }
+}
+
+/* Parse the offset portion of an addressing mode (register or immediate). */
+static const char *
+parse_addr_offset (const char *line, metag_addr *addr, int size)
+{
+ const char *l = line;
+ const metag_reg *regs[1];
+
+ if (*l == IMM_CHAR)
+ {
+ /* ++ is a valid operator in our addressing but not in an expr. Make
+ sure that the expression parser never sees it. */
+ char *ppp = strstr(l, "++");
+ char ppch = '+';
+
+ if (ppp)
+ *ppp = '\0';
+
+ l = parse_imm_addr (l, addr);
+
+ if (ppp)
+ *ppp = ppch;
+
+ if (l == NULL)
+ return NULL;
+
+ if (addr->exp.X_add_number % size)
+ {
+ as_bad (_("offset must be a multiple of %d"), size);
+ return NULL;
+ }
+
+ addr->immediate = 1;
+ return l;
+ }
+ else
+ {
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->unit != addr->base_reg->unit)
+ {
+ as_bad (_("offset and base must be from the same unit"));
+ return NULL;
+ }
+
+ addr->offset_reg = regs[0];
+ return l;
+ }
+}
+
+/* Parse an addressing mode. */
+static const char *
+parse_addr (const char *line, metag_addr *addr, unsigned int size)
+{
+ const char *l = line;
+ const char *ll;
+ const metag_reg *regs[1];
+
+ /* Skip opening square bracket. */
+ l++;
+
+ ll = parse_addr_pre_incr_op (l, addr);
+
+ if (ll != NULL)
+ l = ll;
+
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ addr->base_reg = regs[0];
+
+ if (*l == ADDR_END_CHAR)
+ {
+ addr->exp.X_op = O_constant;
+ addr->exp.X_add_symbol = NULL;
+ addr->exp.X_op_symbol = NULL;
+ if (addr->update == 1)
+ {
+ /* We have a pre increment/decrement. */
+ addr->exp.X_add_number = size;
+ }
+ else
+ {
+ /* Simple register with no offset (0 immediate). */
+ addr->exp.X_add_number = 0;
+ }
+ addr->immediate = 1;
+ l++;
+ return l;
+ }
+
+ /* We already had a pre increment/decrement. */
+ if (addr->update == 1)
+ return NULL;
+
+ ll = parse_addr_post_incr_op (l, addr);
+
+ if (ll && *ll == ADDR_END_CHAR)
+ {
+ if (addr->update == 1)
+ {
+ /* We have a post increment/decrement. */
+ addr->exp.X_op = O_constant;
+ addr->exp.X_add_number = size;
+ addr->exp.X_add_symbol = NULL;
+ addr->exp.X_op_symbol = NULL;
+ addr->post_increment = 1;
+ }
+ addr->immediate = 1;
+ ll++;
+ return ll;
+ }
+
+ addr->post_increment = 0;
+
+ l = parse_addr_op (l, addr);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_addr_offset (l, addr, size);
+
+ if (l == NULL)
+ return NULL;
+
+ if (*l == ADDR_END_CHAR)
+ {
+ l++;
+ return l;
+ }
+
+ /* We already had a pre increment/decrement. */
+ if (addr->update == 1)
+ return NULL;
+
+ l = parse_addr_post_incr_op (l, addr);
+
+ if (l == NULL)
+ return NULL;
+
+ if (*l == ADDR_END_CHAR)
+ {
+ l++;
+ return l;
+ }
+
+ return NULL;
+}
+
+/* Parse a GET or pipeline MOV instruction. */
+static const char *
+parse_get (const char *line, const metag_reg **regs, metag_addr *addr,
+ unsigned int size, bfd_boolean is_mov)
+{
+ const char *l = line;
+
+ if (size == 8)
+ {
+ l = parse_pair_gp_regs (l, regs);
+
+ if (l == NULL)
+ return NULL;
+ }
+ else
+ {
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ {
+ if (!is_mov)
+ as_bad (_("invalid destination register"));
+ return NULL;
+ }
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_addr (l, addr, size);
+
+ if (l == NULL)
+ {
+ if (!is_mov)
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ return l;
+}
+
+/* Parse a SET instruction. */
+static const char *
+parse_set (const char *line, const metag_reg **regs, metag_addr *addr,
+ unsigned int size)
+{
+ const char *l = line;
+
+ l = parse_addr (l, addr, size);
+
+ if (l == NULL)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ if (size == 8)
+ {
+ const char *ll = l;
+
+ ll = parse_pair_gp_regs (l, regs);
+
+ if (ll == NULL)
+ {
+ /* Maybe this is an RD register, which is 64 bits wide so needs no
+ pair. */
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL ||
+ regs[0]->unit != UNIT_RD)
+ {
+ return NULL;
+ }
+ }
+ else
+ l = ll;
+ }
+ else
+ {
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ {
+ as_bad (_("invalid source register"));
+ return NULL;
+ }
+ }
+
+ return l;
+}
+
+/* Check a signed integer value can be represented in the given number
+ of bits. */
+static bfd_boolean
+within_signed_range (int value, unsigned int bits)
+{
+ int min_val = -(1 << (bits - 1));
+ int max_val = (1 << (bits - 1)) - 1;
+ return (value <= max_val) && (value >= min_val);
+}
+
+/* Check an unsigned integer value can be represented in the given number
+ of bits. */
+static bfd_boolean
+within_unsigned_range (unsigned int value, unsigned int bits)
+{
+ return value < (unsigned int)(1 << bits);
+}
+
+/* Return TRUE if UNIT can be expressed using a short code. */
+static bfd_boolean
+is_short_unit (enum metag_unit unit)
+{
+ switch (unit)
+ {
+ case UNIT_A0:
+ case UNIT_A1:
+ case UNIT_D0:
+ case UNIT_D1:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* Copy reloc data from ADDR to INSN. */
+static void
+copy_addr_reloc (metag_insn *insn, metag_addr *addr)
+{
+ memcpy (&insn->reloc_exp, &addr->exp, sizeof(insn->reloc_exp));
+ insn->reloc_type = addr->reloc_type;
+}
+
+/* Parse a GET, SET or pipeline MOV instruction. */
+static const char *
+parse_get_set (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+ metag_addr addr;
+ unsigned int size = metag_get_set_size_bytes (template->meta_opcode);
+ bfd_boolean is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET;
+ unsigned int reg_no;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ if (is_get)
+ {
+ bfd_boolean is_mov = strncmp (template->name, "MOV", 3) == 0;
+
+ l = parse_get (l, regs, &addr, size, is_mov);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!(regs[0]->unit == UNIT_D0 ||
+ regs[0]->unit == UNIT_D1 ||
+ regs[0]->unit == UNIT_A0 ||
+ regs[0]->unit == UNIT_A1 ||
+ (regs[0]->unit == UNIT_RD && is_mov) ||
+ (regs[0]->unit == UNIT_CT && size == 4) ||
+ (regs[0]->unit == UNIT_PC && size == 4) ||
+ (regs[0]->unit == UNIT_TR && size == 4) ||
+ (regs[0]->unit == UNIT_TT && (size == 4 || size == 8)) ||
+ regs[0]->unit == UNIT_FX))
+ {
+ as_bad (_("invalid destination unit"));
+ return NULL;
+ }
+
+ if (regs[0]->unit == UNIT_RD)
+ {
+ if (regs[0]->no == 0)
+ {
+ as_bad (_("mov cannot use RD port as destination"));
+ return NULL;
+ }
+ }
+
+ reg_no = regs[0]->no;
+ }
+ else
+ {
+ l = parse_set (l, regs, &addr, size);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!(regs[0]->unit == UNIT_D0 ||
+ regs[0]->unit == UNIT_D1 ||
+ regs[0]->unit == UNIT_A0 ||
+ regs[0]->unit == UNIT_A1 ||
+ regs[0]->unit == UNIT_RD ||
+ (regs[0]->unit == UNIT_CT && size == 4) ||
+ (regs[0]->unit == UNIT_PC && size == 4) ||
+ (regs[0]->unit == UNIT_TR && size == 4) ||
+ (regs[0]->unit == UNIT_TT && (size == 4 || size == 8)) ||
+ regs[0]->unit == UNIT_FX))
+ {
+ as_bad (_("invalid source unit"));
+ return NULL;
+ }
+
+ if (addr.immediate == 0 &&
+ (regs[0]->unit == addr.base_reg->unit ||
+ (size == 8 && is_unit_pair (regs[0], addr.base_reg))))
+ {
+ as_bad (_("source and address units must not be shared for this addressing mode"));
+ return NULL;
+ }
+
+ if (regs[0]->unit == UNIT_RD)
+ {
+ if (regs[0]->no != 0)
+ {
+ as_bad (_("set can only use RD port as source"));
+ return NULL;
+ }
+ reg_no = 16;
+ }
+ else
+ reg_no = regs[0]->no;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (reg_no << 19) |
+ (regs[0]->unit << 1));
+
+ if (!is_short_unit (addr.base_reg->unit))
+ {
+ as_bad (_("base unit must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ insn->bits |= ((addr.base_reg->no << 14) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5));
+
+ if (addr.immediate)
+ {
+ int offset = addr.exp.X_add_number;
+
+ copy_addr_reloc (insn, &addr);
+
+ if (addr.negate)
+ offset = -offset;
+
+ offset = offset / (int)size;
+
+ if (!within_signed_range (offset, GET_SET_IMM_BITS))
+ {
+ /* We already tried to encode as an extended GET/SET. */
+ as_bad (_("offset value out of range"));
+ return NULL;
+ }
+
+ offset = offset & GET_SET_IMM_MASK;
+
+ insn->bits |= (0x1 << 25);
+ insn->bits |= (offset << 8);
+ }
+ else
+ {
+ insn->bits |= (addr.offset_reg->no << 9);
+ }
+
+ if (addr.update)
+ insn->bits |= (0x1 << 7);
+
+ if (addr.post_increment)
+ insn->bits |= 0x1;
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an extended GET or SET instruction. */
+static const char *
+parse_get_set_ext (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+ metag_addr addr;
+ unsigned int size = metag_get_set_ext_size_bytes (template->meta_opcode);
+ bfd_boolean is_get = MINOR_OPCODE (template->meta_opcode) == GET_EXT_MINOR;
+ bfd_boolean is_mov = MINOR_OPCODE (template->meta_opcode) == MOV_EXT_MINOR;
+ unsigned int reg_unit;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ if (is_get || is_mov)
+ {
+ l = parse_get (l, regs, &addr, size, is_mov);
+ }
+ else
+ {
+ l = parse_set (l, regs, &addr, size);
+ }
+
+ if (l == NULL)
+ return NULL;
+
+ /* Extended GET/SET does not support incrementing addressing. */
+ if (addr.update)
+ return NULL;
+
+ if (is_mov)
+ {
+ if (regs[0]->unit != UNIT_RD)
+ {
+ as_bad (_("destination unit must be RD"));
+ return NULL;
+ }
+ reg_unit = 0;
+ }
+ else
+ {
+ if (!is_short_unit (regs[0]->unit))
+ {
+ return NULL;
+ }
+ reg_unit = regs[0]->unit;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ ((reg_unit & SHORT_UNIT_MASK) << 3));
+
+ if (!is_short_unit (addr.base_reg->unit))
+ {
+ as_bad (_("base unit must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ if (addr.base_reg->no > 1)
+ {
+ return NULL;
+ }
+
+ insn->bits |= ((addr.base_reg->no & EXT_BASE_REG_MASK) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5));
+
+ if (addr.immediate)
+ {
+ int offset = addr.exp.X_add_number;
+
+ copy_addr_reloc (insn, &addr);
+
+ if (addr.negate)
+ offset = -offset;
+
+ offset = offset / (int)size;
+
+ if (!within_signed_range (offset, GET_SET_EXT_IMM_BITS))
+ {
+ /* Parsing as a standard GET/SET provides a smaller offset. */
+ as_bad (_("offset value out of range"));
+ return NULL;
+ }
+
+ offset = offset & GET_SET_EXT_IMM_MASK;
+
+ insn->bits |= (offset << 7);
+ }
+ else
+ {
+ return NULL;
+ }
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an MGET or MSET instruction addressing mode. */
+static const char *
+parse_mget_mset_addr (const char *line, metag_addr *addr)
+{
+ const char *l = line;
+ const char *ll;
+ const metag_reg *regs[1];
+
+ /* Skip opening square bracket. */
+ l++;
+
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ addr->base_reg = regs[0];
+
+ ll = parse_addr_post_incr_op (l, addr);
+
+ if (ll != NULL)
+ l = ll;
+
+ if (addr->negate == 1)
+ return NULL;
+
+ if (*l == ADDR_END_CHAR)
+ {
+ l++;
+ return l;
+ }
+
+ return NULL;
+}
+
+/* Parse an MGET instruction. */
+static const char *
+parse_mget (const char *line, const metag_reg **regs, metag_addr *addr,
+ size_t *regs_read)
+{
+ const char *l = line;
+
+ l = parse_gp_regs_list (l, regs, MGET_MSET_MAX_REGS, regs_read);
+
+ if (l == NULL ||
+ *regs_read == 0)
+ {
+ as_bad (_("invalid destination register list"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_mget_mset_addr (l, addr);
+
+ if (l == NULL)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ return l;
+}
+
+/* Parse an MSET instruction. */
+static const char *
+parse_mset (const char *line, const metag_reg **regs, metag_addr *addr,
+ size_t *regs_read)
+{
+ const char *l = line;
+
+ l = parse_mget_mset_addr (l, addr);
+
+ if (l == NULL)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_gp_regs_list (l, regs, MGET_MSET_MAX_REGS, regs_read);
+
+ if (l == NULL ||
+ *regs_read == 0)
+ {
+ as_bad (_("invalid source register list"));
+ return NULL;
+ }
+
+ return l;
+}
+
+/* Take a register list REGS of size REGS_READ and convert it into an
+ rmask value if possible. Return the rmask value in RMASK and the
+ lowest numbered register in LOWEST_REG. Return TRUE if the conversion
+ was successful. */
+static bfd_boolean
+check_rmask (const metag_reg **regs, size_t regs_read, bfd_boolean is_fpu,
+ bfd_boolean is_64bit, unsigned int *lowest_reg,
+ unsigned int *rmask)
+{
+ unsigned int reg_unit = regs[0]->unit;
+ size_t i;
+
+ for (i = 0; i < regs_read; i++)
+ {
+ if (is_fpu)
+ {
+ if (is_64bit && regs[i]->no % 2)
+ {
+ as_bad (_("register list must be even numbered"));
+ return FALSE;
+ }
+ }
+ else if (regs[i]->unit != reg_unit)
+ {
+ as_bad (_("register list must be from the same unit"));
+ return FALSE;
+ }
+
+ if (regs[i]->no < *lowest_reg)
+ *lowest_reg = regs[i]->no;
+ }
+
+ for (i = 0; i < regs_read; i++)
+ {
+ unsigned int next_bit, next_reg;
+ if (regs[i]->no == *lowest_reg)
+ continue;
+
+ if (is_fpu && is_64bit)
+ next_reg = ((regs[i]->no / 2) - ((*lowest_reg / 2) + 1));
+ else
+ next_reg = (regs[i]->no - (*lowest_reg + 1));
+
+ next_bit = (1 << next_reg);
+
+ if (*rmask & next_bit)
+ {
+ as_bad (_("register list must not contain duplicates"));
+ return FALSE;
+ }
+
+ *rmask |= next_bit;
+ }
+
+ return TRUE;
+}
+
+/* Parse an MGET or MSET instruction. */
+static const char *
+parse_mget_mset (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[MGET_MSET_MAX_REGS];
+ metag_addr addr;
+ bfd_boolean is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET;
+ bfd_boolean is_fpu = (MINOR_OPCODE (template->meta_opcode) & 0x6) == 0x6;
+ bfd_boolean is_64bit = (MINOR_OPCODE (template->meta_opcode) & 0x1) == 0x1;
+ size_t regs_read = 0;
+ unsigned int rmask = 0, reg_unit = 0, lowest_reg = 0xffffffff;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ if (is_get)
+ {
+ l = parse_mget (l, regs, &addr, &regs_read);
+ }
+ else
+ {
+ l = parse_mset (l, regs, &addr, &regs_read);
+ }
+
+ if (l == NULL)
+ return NULL;
+
+ if (!check_rmask (regs, regs_read, is_fpu, is_64bit, &lowest_reg, &rmask))
+ return NULL;
+
+ reg_unit = regs[0]->unit;
+
+ if (is_fpu)
+ {
+ if (reg_unit != UNIT_FX)
+ return NULL;
+
+ reg_unit = 0;
+ }
+ else if (reg_unit == UNIT_FX)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (lowest_reg << 19) |
+ ((reg_unit & SHORT_UNIT_MASK) << 3));
+
+ if (!is_short_unit (addr.base_reg->unit))
+ {
+ as_bad (_("base unit must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ insn->bits |= ((addr.base_reg->no << 14) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5));
+
+ insn->bits |= (rmask & RMASK_MASK) << 7;
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a list of registers for MMOV pipeline prime. */
+static const char *
+parse_mmov_prime_list (const char *line, const metag_reg **regs,
+ unsigned int *rmask)
+{
+ const char *l = line;
+ const metag_reg *ra_regs[MMOV_MAX_REGS];
+ size_t regs_read = 0, i;
+ unsigned int mask = 0;
+
+ l = parse_gp_regs_list (l, regs, 1, &regs_read);
+
+ /* First register must be a port. */
+ if (l == NULL || regs[0]->unit != UNIT_RD)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_gp_regs_list (l, ra_regs, MMOV_MAX_REGS, &regs_read);
+
+ if (l == NULL)
+ return NULL;
+
+ /* Check remaining registers match the first.
+
+ Note that we also accept RA (0x10) as input for the remaining registers.
+ Whilst this doesn't represent the instruction in any way we're stuck
+ with it because the embedded assembler accepts it. */
+ for (i = 0; i < regs_read; i++)
+ {
+ if (ra_regs[i]->unit != UNIT_RD ||
+ (ra_regs[i]->no != 0x10 && ra_regs[i]->no != regs[0]->no))
+ return NULL;
+
+ mask = (mask << 1) | 0x1;
+ }
+
+ *rmask = mask;
+
+ return l;
+}
+
+/* Parse a MMOV instruction. */
+static const char *
+parse_mmov (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ unsigned int is_fpu = template->insn_type == INSN_FPU;
+ unsigned int is_prime = ((MINOR_OPCODE (template->meta_opcode) & 0x2) &&
+ !is_fpu);
+ unsigned int is_64bit = MINOR_OPCODE (template->meta_opcode) & 0x1;
+ unsigned int rmask = 0;
+
+ if (is_prime)
+ {
+ const metag_reg *reg;
+ metag_addr addr;
+
+ memset (&addr, 0, sizeof(addr));
+
+ l = parse_mmov_prime_list (l, &reg, &rmask);
+
+ if (l == NULL)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_mget_mset_addr (l, &addr);
+
+ if (l == NULL)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (reg->no << 19) |
+ (addr.base_reg->no << 14) |
+ ((rmask & RMASK_MASK) << 7) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5));
+ }
+ else
+ {
+ const metag_reg *regs[MMOV_MAX_REGS + 1];
+ unsigned int lowest_reg = 0xffffffff;
+ size_t regs_read = 0;
+
+ l = parse_gp_regs_list (l, regs, MMOV_MAX_REGS + 1, &regs_read);
+
+ if (l == NULL || regs_read == 0)
+ return NULL;
+
+ if (!is_short_unit (regs[0]->unit) &&
+ !(is_fpu && regs[0]->unit == UNIT_FX))
+ {
+ return NULL;
+ }
+
+ if (!(regs[regs_read-1]->unit == UNIT_RD &&
+ regs[regs_read-1]->no == 0))
+ {
+ return NULL;
+ }
+
+ if (!check_rmask (regs, regs_read - 1, is_fpu, is_64bit, &lowest_reg,
+ &rmask))
+ return NULL;
+
+ if (is_fpu)
+ {
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 14) |
+ ((rmask & RMASK_MASK) << 7));
+ }
+ else
+ {
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ ((rmask & RMASK_MASK) << 7) |
+ ((regs[0]->unit & SHORT_UNIT_MASK) << 3));
+ }
+ }
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an immediate constant. */
+static const char *
+parse_imm_constant (const char *line, metag_insn *insn, int *value)
+{
+ const char *l = line;
+ char *save_input_line_pointer;
+ expressionS *exp = &insn->reloc_exp;
+
+ /* Skip #. */
+ if (*l == '#')
+ l++;
+ else
+ return NULL;
+
+ save_input_line_pointer = input_line_pointer;
+ input_line_pointer = (char *) l;
+
+ expression (exp);
+
+ l = input_line_pointer;
+ input_line_pointer = save_input_line_pointer;
+
+ if (exp->X_op == O_constant)
+ {
+ *value = exp->X_add_number;
+
+ return l;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+/* Parse an MDRD instruction. */
+static const char *
+parse_mdrd (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ unsigned int rmask = 0;
+ int value = 0, i;
+
+ l = parse_imm_constant (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (value < 1 || value > 8)
+ {
+ as_bad (_("MDRD value must be between 1 and 8"));
+ return NULL;
+ }
+
+ for (i = 1; i < value; i++)
+ {
+ rmask <<= 1;
+ rmask |= 1;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (rmask << 7));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a conditional SET instruction. */
+static const char *
+parse_cond_set (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+ metag_addr addr;
+ unsigned int size = metag_cond_set_size_bytes (template->meta_opcode);
+ unsigned int reg_no;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ l = parse_set (l, regs, &addr, size);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->unit == UNIT_RD)
+ {
+ if (regs[0]->no != 0)
+ {
+ as_bad (_("set can only use RD port as source"));
+ return NULL;
+ }
+ reg_no = 16;
+ }
+ else
+ reg_no = regs[0]->no;
+
+ if (addr.update)
+ return NULL;
+
+ if (!(addr.immediate &&
+ addr.exp.X_add_number == 0))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (reg_no << 19) |
+ (regs[0]->unit << 10));
+
+ if (!is_short_unit (addr.base_reg->unit))
+ {
+ as_bad (_("base unit must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ insn->bits |= ((addr.base_reg->no << 14) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an XFR instruction. */
+static const char *
+parse_xfr (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ metag_addr dest_addr, src_addr;
+ unsigned int size = 4;
+
+ memset(&dest_addr, 0, sizeof(dest_addr));
+ memset(&src_addr, 0, sizeof(src_addr));
+ dest_addr.reloc_type = BFD_RELOC_UNUSED;
+ src_addr.reloc_type = BFD_RELOC_UNUSED;
+
+ l = parse_addr (l, &dest_addr, size);
+
+ if (l == NULL ||
+ dest_addr.immediate == 1)
+ {
+ as_bad (_("invalid destination memory operand"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_addr (l, &src_addr, size);
+
+ if (l == NULL ||
+ src_addr.immediate == 1)
+ {
+ as_bad (_("invalid source memory operand"));
+ return NULL;
+ }
+
+ if (!is_short_unit (dest_addr.base_reg->unit) ||
+ !is_short_unit (src_addr.base_reg->unit))
+ {
+ as_bad (_("address units must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ if ((dest_addr.base_reg->unit != dest_addr.offset_reg->unit) ||
+ (src_addr.base_reg->unit != src_addr.offset_reg->unit))
+ {
+ as_bad (_("base and offset must be from the same unit"));
+ return NULL;
+ }
+
+ if (dest_addr.update == 1 &&
+ src_addr.update == 1 &&
+ dest_addr.post_increment != src_addr.post_increment)
+ {
+ as_bad (_("source and destination increment mode must agree"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (src_addr.base_reg->no << 19) |
+ (src_addr.offset_reg->no << 14) |
+ ((src_addr.base_reg->unit & SHORT_UNIT_MASK) << 2));
+
+ insn->bits |= ((dest_addr.base_reg->no << 9) |
+ (dest_addr.offset_reg->no << 4) |
+ ((dest_addr.base_reg->unit & SHORT_UNIT_MASK)));
+
+ if (dest_addr.update == 1)
+ insn->bits |= (1 << 26);
+
+ if (src_addr.update == 1)
+ insn->bits |= (1 << 27);
+
+ if (dest_addr.post_increment == 1 ||
+ src_addr.post_increment == 1)
+ insn->bits |= (1 << 24);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an 8bit immediate value. */
+static const char *
+parse_imm8 (const char *line, metag_insn *insn, int *value)
+{
+ const char *l = line;
+ char *save_input_line_pointer;
+ expressionS *exp = &insn->reloc_exp;
+
+ /* Skip #. */
+ if (*l == '#')
+ l++;
+ else
+ return NULL;
+
+ save_input_line_pointer = input_line_pointer;
+ input_line_pointer = (char *) l;
+
+ expression (exp);
+
+ l = input_line_pointer;
+ input_line_pointer = save_input_line_pointer;
+
+ if (exp->X_op == O_absent || exp->X_op == O_big)
+ {
+ return NULL;
+ }
+ else if (exp->X_op == O_constant)
+ {
+ *value = exp->X_add_number;
+ }
+ else
+ {
+ insn->reloc_type = BFD_RELOC_METAG_REL8;
+ insn->reloc_pcrel = 0;
+ }
+
+ return l;
+}
+
+/* Parse a 16bit immediate value. */
+static const char *
+parse_imm16 (const char *line, metag_insn *insn, int *value)
+{
+ const char *l = line;
+ char *save_input_line_pointer;
+ expressionS *exp = &insn->reloc_exp;
+ bfd_boolean is_hi = FALSE;
+ bfd_boolean is_lo = FALSE;
+
+ /* Skip #. */
+ if (*l == '#')
+ l++;
+ else
+ return NULL;
+
+ if (strncasecmp (l, "HI", 2) == 0)
+ {
+ is_hi = TRUE;
+ l += 2;
+ }
+ else if (strncasecmp (l, "LO", 2) == 0)
+ {
+ is_lo = TRUE;
+ l += 2;
+ }
+
+ save_input_line_pointer = input_line_pointer;
+ input_line_pointer = (char *) l;
+
+ expression (exp);
+
+ l = input_line_pointer;
+ input_line_pointer = save_input_line_pointer;
+
+ if (exp->X_op == O_absent || exp->X_op == O_big)
+ {
+ return NULL;
+ }
+ else if (exp->X_op == O_constant)
+ {
+ if (is_hi)
+ *value = (exp->X_add_number >> 16) & IMM16_MASK;
+ else if (is_lo)
+ *value = exp->X_add_number & IMM16_MASK;
+ else
+ *value = exp->X_add_number;
+ }
+ else
+ {
+ if (exp->X_op == O_PIC_reloc)
+ {
+ exp->X_op = O_symbol;
+
+ if (exp->X_md == BFD_RELOC_METAG_GOTOFF)
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_HI16_GOTOFF;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_LO16_GOTOFF;
+ else
+ return NULL;
+ }
+ else if (exp->X_md == BFD_RELOC_METAG_PLT)
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_HI16_PLT;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_LO16_PLT;
+ else
+ return NULL;
+ }
+ else if (exp->X_md == BFD_RELOC_METAG_TLS_LDO)
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_TLS_LDO_HI16;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_TLS_LDO_LO16;
+ else
+ return NULL;
+ }
+ else if (exp->X_md == BFD_RELOC_METAG_TLS_IENONPIC)
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_TLS_IENONPIC_HI16;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_TLS_IENONPIC_LO16;
+ else
+ return NULL;
+ }
+ else if (exp->X_md == BFD_RELOC_METAG_TLS_LE)
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_TLS_LE_HI16;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_TLS_LE_LO16;
+ else
+ return NULL;
+ }
+ else if (exp->X_md == BFD_RELOC_METAG_TLS_GD ||
+ exp->X_md == BFD_RELOC_METAG_TLS_LDM)
+ insn->reloc_type = exp->X_md;
+ }
+ else
+ {
+ if (exp->X_op == O_symbol && exp->X_add_symbol == GOT_symbol)
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_HI16_GOTPC;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_LO16_GOTPC;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (is_hi)
+ insn->reloc_type = BFD_RELOC_METAG_HIADDR16;
+ else if (is_lo)
+ insn->reloc_type = BFD_RELOC_METAG_LOADDR16;
+ else
+ insn->reloc_type = BFD_RELOC_METAG_REL16;
+ }
+ }
+
+ insn->reloc_pcrel = 0;
+ }
+
+ return l;
+}
+
+/* Parse a MOV to control unit instruction. */
+static const char *
+parse_mov_ct (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[1];
+ unsigned int top = template->meta_opcode & 0x1;
+ unsigned int is_trace = (template->meta_opcode >> 2) & 0x1;
+ unsigned int sign_extend = 0;
+ int value = 0;
+
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (is_trace)
+ {
+ if (regs[0]->unit != UNIT_TT)
+ return NULL;
+ }
+ else
+ {
+ if (regs[0]->unit != UNIT_CT)
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_imm16 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (value < 0)
+ sign_extend = 1;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ ((value & IMM16_MASK) << 3));
+
+ if (sign_extend == 1 && top == 0)
+ insn->bits |= (1 << 1);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a SWAP instruction. */
+static const char *
+parse_swap (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+
+ l = parse_gp_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[1]->no << 19) |
+ (regs[0]->no << 14) |
+ (regs[1]->unit << 10) |
+ (regs[0]->unit << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a JUMP instruction. */
+static const char *
+parse_jump (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[1];
+ int value = 0;
+
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!is_short_unit (regs[0]->unit))
+ {
+ as_bad (_("register unit must be one of %s"), SHORT_UNITS);
+ return FALSE;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_imm16 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[0]->unit & SHORT_UNIT_MASK) |
+ ((value & IMM16_MASK) << 3));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a 19bit immediate value. */
+static const char *
+parse_imm19 (const char *line, metag_insn *insn, int *value)
+{
+ const char *l = line;
+ char *save_input_line_pointer;
+ expressionS *exp = &insn->reloc_exp;
+
+ /* Skip #. */
+ if (*l == '#')
+ l++;
+
+ save_input_line_pointer = input_line_pointer;
+ input_line_pointer = (char *) l;
+
+ expression (exp);
+
+ l = input_line_pointer;
+ input_line_pointer = save_input_line_pointer;
+
+ if (exp->X_op == O_absent || exp->X_op == O_big)
+ {
+ return NULL;
+ }
+ else if (exp->X_op == O_constant)
+ {
+ *value = exp->X_add_number;
+ }
+ else
+ {
+ if (exp->X_op == O_PIC_reloc)
+ {
+ exp->X_op = O_symbol;
+
+ if (exp->X_md == BFD_RELOC_METAG_PLT)
+ insn->reloc_type = BFD_RELOC_METAG_RELBRANCH_PLT;
+ else
+ return NULL;
+ }
+ else
+ insn->reloc_type = BFD_RELOC_METAG_RELBRANCH;
+ insn->reloc_pcrel = 1;
+ }
+
+ return l;
+}
+
+/* Parse a CALLR instruction. */
+static const char *
+parse_callr (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[1];
+ int value = 0;
+
+ l = parse_gp_regs (l, regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!is_short_unit (regs[0]->unit))
+ {
+ as_bad (_("link register unit must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ if (regs[0]->no & ~CALLR_REG_MASK)
+ {
+ as_bad (_("link register must be in a low numbered register"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_imm19 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_signed_range (value / 4, IMM19_BITS))
+ {
+ as_bad (_("target out of range"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no & CALLR_REG_MASK) |
+ ((regs[0]->unit & SHORT_UNIT_MASK) << 3) |
+ ((value & IMM19_MASK) << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Return the value for the register field if we apply the O2R modifier
+ to operand 2 REG, combined with UNIT_BIT derived from the destination
+ register or source1. Uses address unit O2R if IS_ADDR is set. */
+static int
+lookup_o2r (unsigned int is_addr, unsigned int unit_bit, const metag_reg *reg)
+{
+ if (reg->no & ~O2R_REG_MASK)
+ return -1;
+
+ if (is_addr)
+ {
+ if (unit_bit)
+ {
+ switch (reg->unit)
+ {
+ case UNIT_D1:
+ return reg->no;
+ case UNIT_D0:
+ return (1 << 3) | reg->no;
+ case UNIT_RD:
+ return (2 << 3) | reg->no;
+ case UNIT_A0:
+ return (3 << 3) | reg->no;
+ default:
+ return -1;
+ }
+ }
+ else
+ {
+ switch (reg->unit)
+ {
+ case UNIT_A1:
+ return reg->no;
+ case UNIT_D0:
+ return (1 << 3) | reg->no;
+ case UNIT_RD:
+ return (2 << 3) | reg->no;
+ case UNIT_D1:
+ return (3 << 3) | reg->no;
+ default:
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ if (unit_bit)
+ {
+ switch (reg->unit)
+ {
+ case UNIT_A1:
+ return reg->no;
+ case UNIT_D0:
+ return (1 << 3) | reg->no;
+ case UNIT_RD:
+ return (2 << 3) | reg->no;
+ case UNIT_A0:
+ return (3 << 3) | reg->no;
+ default:
+ return -1;
+ }
+ }
+ else
+ {
+ switch (reg->unit)
+ {
+ case UNIT_A1:
+ return reg->no;
+ case UNIT_D1:
+ return (1 << 3) | reg->no;
+ case UNIT_RD:
+ return (2 << 3) | reg->no;
+ case UNIT_A0:
+ return (3 << 3) | reg->no;
+ default:
+ return -1;
+ }
+ }
+ }
+}
+
+/* Parse GP ALU instruction. */
+static const char *
+parse_alu (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *dest_regs[1];
+ const metag_reg *src_regs[2];
+ int value = 0;
+ unsigned int o1z = 0;
+ unsigned int imm = (template->meta_opcode >> 25) & 0x1;
+ unsigned int cond = (template->meta_opcode >> 26) & 0x1;
+ unsigned int ca = (template->meta_opcode >> 5) & 0x1;
+ unsigned int top = template->meta_opcode & 0x1;
+ unsigned int sign_extend = 0;
+ unsigned int is_addr_op = MAJOR_OPCODE (template->meta_opcode) == OPC_ADDR;
+ unsigned int is_mul = MAJOR_OPCODE (template->meta_opcode) == OPC_MUL;
+ unsigned int unit_bit = 0;
+ bfd_boolean is_quickrot = template->arg_type & GP_ARGS_QR;
+
+ l = parse_gp_regs (l, dest_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ if (is_addr_op)
+ {
+ if (dest_regs[0]->unit == UNIT_A0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_A1)
+ unit_bit = 1;
+ }
+ else
+ {
+ if (dest_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ }
+
+ if ((MAJOR_OPCODE (template->meta_opcode) == OPC_ADDR ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_ADD ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_SUB) &&
+ ((template->meta_opcode >> 2) & 0x1))
+ o1z = 1;
+
+ if (imm)
+ {
+ if (!cond)
+ {
+ if (is_addr_op)
+ {
+ if (dest_regs[0]->unit == UNIT_A0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_A1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (dest_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ }
+
+ if (cond)
+ {
+ l = parse_gp_regs (l, src_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ if (is_addr_op)
+ {
+ if (src_regs[0]->unit == UNIT_A0)
+ unit_bit = 0;
+ else if (src_regs[0]->unit == UNIT_A1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (src_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (src_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+
+ if (src_regs[0]->unit != dest_regs[0]->unit && !ca)
+ return NULL;
+
+ l = parse_imm8 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_unsigned_range (value, IMM8_BITS))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (src_regs[0]->no << 14) |
+ ((value & IMM8_MASK) << 6));
+
+ if (ca)
+ {
+ if (is_addr_op)
+ {
+ if (src_regs[0]->unit == UNIT_A0)
+ unit_bit = 0;
+ else if (src_regs[0]->unit == UNIT_A1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (src_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (src_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+
+ insn->bits |= dest_regs[0]->unit << 1;
+ }
+ }
+ else if (o1z)
+ {
+ l = parse_imm16 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (value < 0)
+ {
+ if (!within_signed_range (value, IMM16_BITS))
+ {
+ as_bad (_("immediate out of range"));
+ return NULL;
+ }
+ sign_extend = 1;
+ }
+ else
+ {
+ if (!within_unsigned_range (value, IMM16_BITS))
+ {
+ as_bad (_("immediate out of range"));
+ return NULL;
+ }
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ ((value & IMM16_MASK) << 3));
+ }
+ else
+ {
+ l = parse_gp_regs (l, src_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!(src_regs[0]->unit == dest_regs[0]->unit))
+ return NULL;
+
+ /* CPC is valid for address ops. */
+ if (src_regs[0]->no != dest_regs[0]->no &&
+ !(is_addr_op && src_regs[0]->no == 0x10))
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_imm16 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (value < 0)
+ {
+ if (!within_signed_range (value, IMM16_BITS))
+ {
+ as_bad (_("immediate out of range"));
+ return NULL;
+ }
+ sign_extend = 1;
+ }
+ else
+ {
+ if (!within_unsigned_range (value, IMM16_BITS))
+ {
+ as_bad (_("immediate out of range"));
+ return NULL;
+ }
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (src_regs[0]->no << 19) |
+ ((value & IMM16_MASK) << 3));
+ }
+ }
+ else
+ {
+ unsigned int o2r = 0;
+ int rs2;
+
+ if (cond || !o1z)
+ l = parse_gp_regs (l, src_regs, 2);
+ else
+ l = parse_gp_regs (l, src_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (cond || !o1z)
+ {
+ if (is_addr_op)
+ {
+ if (src_regs[0]->unit == UNIT_A0)
+ unit_bit = 0;
+ else if (src_regs[0]->unit == UNIT_A1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (src_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (src_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ }
+ else
+ {
+ if (is_addr_op)
+ {
+ if (dest_regs[0]->unit == UNIT_A0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_A1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (dest_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+ }
+ }
+
+ if (cond)
+ {
+ if (src_regs[0]->unit != src_regs[1]->unit)
+ {
+ rs2 = lookup_o2r (is_addr_op, unit_bit, src_regs[1]);
+
+ if (rs2 < 0)
+ return NULL;
+
+ o2r = 1;
+ }
+ else
+ {
+ rs2 = src_regs[1]->no;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (src_regs[0]->no << 14) |
+ (rs2 << 9));
+
+ if (is_mul)
+ {
+ if (dest_regs[0]->unit != src_regs[0]->unit && is_mul)
+ {
+ if (ca)
+ {
+ insn->bits |= dest_regs[0]->unit << 1;
+ }
+ else
+ return NULL;
+ }
+ }
+ else
+ insn->bits |= dest_regs[0]->unit << 5;
+ }
+ else if (o1z)
+ {
+ if (dest_regs[0]->unit != src_regs[0]->unit)
+ {
+ rs2 = lookup_o2r (is_addr_op, unit_bit, src_regs[0]);
+
+ if (rs2 < 0)
+ return NULL;
+
+ o2r = 1;
+ }
+ else
+ {
+ rs2 = src_regs[0]->no;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (rs2 << 9));
+ }
+ else
+ {
+ if (dest_regs[0]->unit != src_regs[0]->unit)
+ return NULL;
+
+ if (dest_regs[0]->unit != src_regs[1]->unit)
+ {
+ rs2 = lookup_o2r (is_addr_op, unit_bit, src_regs[1]);
+
+ if (rs2 < 0)
+ return NULL;
+
+ o2r = 1;
+ }
+ else
+ {
+ rs2 = src_regs[1]->no;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (src_regs[0]->no << 14) |
+ (rs2 << 9));
+ }
+
+ if (o2r)
+ insn->bits |= 1;
+ }
+
+ if (is_quickrot)
+ {
+ const metag_reg *qr_regs[1];
+ bfd_boolean limit_regs = imm && cond;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_gp_regs (l, qr_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!((unit_bit == 0 && qr_regs[0]->unit != UNIT_A0) ||
+ !(unit_bit == 1 && qr_regs[0]->unit != UNIT_A1)))
+ {
+ as_bad (_("invalid quickrot unit specified"));
+ return NULL;
+ }
+
+ switch (qr_regs[0]->no)
+ {
+ case 2:
+ break;
+ case 3:
+ if (!limit_regs)
+ {
+ insn->bits |= (1 << 7);
+ break;
+ }
+ default:
+ as_bad (_("invalid quickrot register specified"));
+ return NULL;
+ }
+ }
+
+ if (sign_extend == 1 && top == 0)
+ insn->bits |= (1 << 1);
+
+ insn->bits |= unit_bit << 24;
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a B instruction. */
+static const char *
+parse_branch (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ int value = 0;
+
+ l = parse_imm19 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_signed_range (value / 4, IMM19_BITS))
+ {
+ as_bad (_("target out of range"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ ((value & IMM19_MASK) << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a KICK instruction. */
+static const char *
+parse_kick (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+
+ l = parse_gp_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[1]->unit != UNIT_TR)
+ {
+ as_bad (_("source register must be in the trigger unit"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[1]->no << 19) |
+ (regs[0]->no << 14) |
+ (regs[0]->unit << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a SWITCH instruction. */
+static const char *
+parse_switch (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ int value = 0;
+
+ l = parse_imm_constant (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_unsigned_range (value, IMM24_BITS))
+ {
+ as_bad (_("target out of range"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (value & IMM24_MASK));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a shift instruction. */
+static const char *
+parse_shift (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+ const metag_reg *src2_regs[1];
+ int value = 0;
+ unsigned int cond = (template->meta_opcode >> 26) & 0x1;
+ unsigned int ca = (template->meta_opcode >> 5) & 0x1;
+ unsigned int unit_bit = 0;
+
+ l = parse_gp_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ if (regs[1]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (regs[1]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+
+ if (regs[0]->unit != regs[1]->unit && !(cond && ca))
+ return NULL;
+
+ if (*l == '#')
+ {
+ l = parse_imm_constant (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_unsigned_range (value, IMM5_BITS))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (1 << 25) |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ ((value & IMM5_MASK) << 9));
+ }
+ else
+ {
+ l = parse_gp_regs (l, src2_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ (src2_regs[0]->no << 9));
+
+ if (src2_regs[0]->unit != regs[1]->unit)
+ {
+ as_bad(_("Source registers must be in the same unit"));
+ return NULL;
+ }
+ }
+
+ if (regs[0]->unit != regs[1]->unit)
+ {
+ if (cond && ca)
+ {
+ if (regs[1]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (regs[1]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+
+ insn->bits |= ((1 << 5) |
+ (regs[0]->unit << 1));
+ }
+ else
+ return NULL;
+ }
+
+ insn->bits |= unit_bit << 24;
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a MIN or MAX instruction. */
+static const char *
+parse_min_max (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[3];
+
+ l = parse_gp_regs (l, regs, 3);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!(regs[0]->unit == UNIT_D0 ||
+ regs[0]->unit == UNIT_D1))
+ return NULL;
+
+ if (!(regs[0]->unit == regs[1]->unit &&
+ regs[1]->unit == regs[2]->unit))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ (regs[2]->no << 9));
+
+ if (regs[0]->unit == UNIT_D1)
+ insn->bits |= (1 << 24);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a bit operation instruction. */
+static const char *
+parse_bitop (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+ unsigned int swap_inst = MAJOR_OPCODE (template->meta_opcode) == OPC_MISC;
+ unsigned int is_bexl = 0;
+
+ if (swap_inst &&
+ ((template->meta_opcode >> 1) & 0xb) == 0xa)
+ is_bexl = 1;
+
+ l = parse_gp_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!(regs[0]->unit == UNIT_D0 ||
+ regs[0]->unit == UNIT_D1))
+ return NULL;
+
+ if (is_bexl)
+ {
+ if (regs[0]->unit == UNIT_D0 &&
+ regs[1]->unit != UNIT_D1)
+ return NULL;
+ else if (regs[0]->unit == UNIT_D1 &&
+ regs[1]->unit != UNIT_D0)
+ return NULL;
+ }
+ else if (!(regs[0]->unit == regs[1]->unit))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14));
+
+ if (swap_inst)
+ {
+ if (regs[1]->unit == UNIT_D1)
+ insn->bits |= 1;
+ }
+ else
+ {
+ if (regs[1]->unit == UNIT_D1)
+ insn->bits |= (1 << 24);
+ }
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a CMP or TST instruction. */
+static const char *
+parse_cmp (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *dest_regs[1];
+ const metag_reg *src_regs[1];
+ int value = 0;
+ unsigned int imm = (template->meta_opcode >> 25) & 0x1;
+ unsigned int cond = (template->meta_opcode >> 26) & 0x1;
+ unsigned int top = template->meta_opcode & 0x1;
+ unsigned int sign_extend = 0;
+ unsigned int unit_bit = 0;
+
+ l = parse_gp_regs (l, dest_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ if (dest_regs[0]->unit == UNIT_D0)
+ unit_bit = 0;
+ else if (dest_regs[0]->unit == UNIT_D1)
+ unit_bit = 1;
+ else
+ return NULL;
+
+ if (imm)
+ {
+ if (cond)
+ {
+ l = parse_imm_constant (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_unsigned_range (value, IMM8_BITS))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 14) |
+ ((value & IMM8_MASK) << 6));
+
+ }
+ else
+ {
+ l = parse_imm16 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ if (value < 0)
+ {
+ if (!within_signed_range (value, IMM16_BITS))
+ {
+ as_bad (_("immediate out of range"));
+ return NULL;
+ }
+ sign_extend = 1;
+ }
+ else
+ {
+ if (!within_unsigned_range (value, IMM16_BITS))
+ {
+ as_bad (_("immediate out of range"));
+ return NULL;
+ }
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ ((value & IMM16_MASK) << 3));
+ }
+ }
+ else
+ {
+ unsigned int o2r = 0;
+ int rs2;
+
+ l = parse_gp_regs (l, src_regs, 1);
+
+ if (l == NULL)
+ return NULL;
+
+ if (dest_regs[0]->unit != src_regs[0]->unit)
+ {
+ rs2 = lookup_o2r (0, unit_bit, src_regs[0]);
+
+ if (rs2 < 0)
+ return NULL;
+
+ o2r = 1;
+ }
+ else
+ {
+ rs2 = src_regs[0]->no;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 14) |
+ (rs2 << 9));
+
+ if (o2r)
+ insn->bits |= 1;
+ }
+
+ if (sign_extend == 1 && top == 0)
+ insn->bits |= (1 << 1);
+
+ insn->bits |= unit_bit << 24;
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a CACHEW instruction. */
+static const char *
+parse_cachew (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *src_regs[2];
+ unsigned int size = ((template->meta_opcode >> 1) & 0x1) ? 8 : 4;
+ metag_addr addr;
+ int offset;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ l = parse_addr (l, &addr, size);
+
+ if (l == NULL ||
+ !is_short_unit (addr.base_reg->unit) ||
+ addr.update ||
+ !addr.immediate)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ if (size == 4)
+ l = parse_gp_regs (l, src_regs, 1);
+ else
+ l = parse_pair_gp_regs (l, src_regs);
+
+ if (l == NULL ||
+ !is_short_unit (src_regs[0]->unit))
+ {
+ as_bad (_("invalid source register"));
+ return NULL;
+ }
+
+ offset = addr.exp.X_add_number;
+
+ if (addr.negate)
+ offset = -offset;
+
+ offset = offset / 64;
+
+ if (!within_signed_range (offset, GET_SET_IMM_BITS))
+ {
+ as_bad (_("offset value out of range"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (src_regs[0]->no << 19) |
+ (addr.base_reg->no << 14) |
+ ((offset & GET_SET_IMM_MASK) << 8) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5) |
+ ((src_regs[0]->unit & SHORT_UNIT_MASK) << 3));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a CACHEW instruction. */
+static const char *
+parse_cacher (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *dest_regs[2];
+ unsigned int size = ((template->meta_opcode >> 1) & 0x1) ? 8 : 4;
+ metag_addr addr;
+ int offset;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ if (size == 4)
+ l = parse_gp_regs (l, dest_regs, 1);
+ else
+ l = parse_pair_gp_regs (l, dest_regs);
+
+ if (l == NULL ||
+ !is_short_unit (dest_regs[0]->unit))
+ {
+ as_bad (_("invalid destination register"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_addr (l, &addr, size);
+
+ if (l == NULL ||
+ !is_short_unit (addr.base_reg->unit) ||
+ addr.update ||
+ !addr.immediate)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ offset = addr.exp.X_add_number;
+
+ if (addr.negate)
+ offset = -offset;
+
+ offset = offset / (int)size;
+
+ if (!within_signed_range (offset, GET_SET_IMM_BITS))
+ {
+ as_bad (_("offset value out of range"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (addr.base_reg->no << 14) |
+ ((offset & GET_SET_IMM_MASK) << 8) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5) |
+ ((dest_regs[0]->unit & SHORT_UNIT_MASK) << 3));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an ICACHE instruction. */
+static const char *
+parse_icache (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ int offset;
+ int pfcount;
+
+ l = parse_imm_constant (l, insn, &offset);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_signed_range (offset, IMM15_BITS))
+ return NULL;
+
+ l = skip_comma (l);
+
+ l = parse_imm_constant (l, insn, &pfcount);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!within_unsigned_range (pfcount, IMM4_BITS))
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ ((offset & IMM15_MASK) << 9) |
+ ((pfcount & IMM4_MASK) << 1));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a LNKGET instruction. */
+static const char *
+parse_lnkget (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *dest_regs[2];
+ unsigned int size = metag_get_set_ext_size_bytes (template->meta_opcode);
+ metag_addr addr;
+ int offset;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ if (size == 8)
+ l = parse_pair_gp_regs (l, dest_regs);
+ else
+ l = parse_gp_regs (l, dest_regs, 1);
+
+ if (l == NULL ||
+ !is_short_unit (dest_regs[0]->unit))
+ {
+ as_bad (_("invalid destination register"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_addr (l, &addr, size);
+
+ if (l == NULL ||
+ !is_short_unit (addr.base_reg->unit) ||
+ addr.update ||
+ !addr.immediate)
+ {
+ as_bad (_("invalid memory operand"));
+ return NULL;
+ }
+
+ offset = addr.exp.X_add_number;
+
+ if (addr.negate)
+ offset = -offset;
+
+ offset = offset / size;
+
+ if (!within_signed_range (offset, GET_SET_IMM_BITS))
+ {
+ as_bad (_("offset value out of range"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (dest_regs[0]->no << 19) |
+ (addr.base_reg->no << 14) |
+ ((offset & GET_SET_IMM_MASK) << 8) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5) |
+ ((dest_regs[0]->unit & SHORT_UNIT_MASK) << 3));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU MOV instruction. */
+static const char *
+parse_fmov (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+
+ l = parse_fpu_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14));
+
+ if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 5);
+ else if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU MMOV instruction. */
+static const char *
+parse_fmmov (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ bfd_boolean to_fpu = MAJOR_OPCODE (template->meta_opcode) == OPC_GET;
+ bfd_boolean is_mmovl = MINOR_OPCODE (template->meta_opcode) & 0x1;
+ size_t regs_read = 0;
+ const metag_reg *regs[16];
+ unsigned int lowest_data_reg = 0xffffffff;
+ unsigned int lowest_fpu_reg = 0xffffffff;
+ unsigned int rmask = 0, data_unit;
+ size_t i;
+ int last_reg = -1;
+
+ if (insn->fpu_width != FPU_WIDTH_SINGLE)
+ return NULL;
+
+ l = parse_gp_regs_list (l, regs, 16, &regs_read);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs_read % 2)
+ return NULL;
+
+ if (to_fpu)
+ {
+ for (i = 0; i < regs_read / 2; i++)
+ {
+ if (regs[i]->unit != UNIT_FX)
+ return NULL;
+
+ if (last_reg == -1)
+ {
+ last_reg = regs[i]->no;
+ lowest_fpu_reg = last_reg;
+ }
+ else
+ {
+ if (is_mmovl)
+ {
+ if (regs[i]->no != (unsigned int)(last_reg + 2))
+ return NULL;
+ }
+ else if (regs[i]->no != (unsigned int)(last_reg + 1))
+ return NULL;
+
+ last_reg = regs[i]->no;
+ }
+ }
+
+ if (regs[i]->unit == UNIT_D0)
+ data_unit = 0;
+ else if (regs[i]->unit == UNIT_D1)
+ data_unit = 1;
+ else
+ return NULL;
+
+ if (!check_rmask (&regs[i], regs_read / 2, TRUE, FALSE, &lowest_data_reg,
+ &rmask))
+ return NULL;
+ }
+ else
+ {
+ if (regs[0]->unit == UNIT_D0)
+ data_unit = 0;
+ else if (regs[0]->unit == UNIT_D1)
+ data_unit = 1;
+ else
+ return NULL;
+
+ if (!check_rmask (regs, regs_read / 2, TRUE, FALSE, &lowest_data_reg,
+ &rmask))
+ return NULL;
+
+ for (i = regs_read / 2; i < regs_read; i++)
+ {
+ if (regs[i]->unit != UNIT_FX)
+ return NULL;
+
+ if (last_reg == -1)
+ {
+ last_reg = regs[i]->no;
+ lowest_fpu_reg = last_reg;
+ }
+ else
+ {
+ if (is_mmovl)
+ {
+ if (regs[i]->no != (unsigned int)(last_reg + 2))
+ return NULL;
+ }
+ else if (regs[i]->no != (unsigned int)(last_reg + 1))
+ return NULL;
+
+ last_reg = regs[i]->no;
+ }
+ }
+ }
+
+ insn->bits = (template->meta_opcode |
+ ((lowest_data_reg & REG_MASK) << 19) |
+ ((lowest_fpu_reg & REG_MASK) << 14) |
+ ((rmask & RMASK_MASK) << 7) |
+ data_unit);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU data unit MOV instruction. */
+static const char *
+parse_fmov_data (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ unsigned int to_fpu = ((template->meta_opcode >> 7) & 0x1);
+ const metag_reg *regs[2];
+ unsigned int base_unit;
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ return NULL;
+
+ l = parse_gp_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ if (to_fpu)
+ {
+ if (regs[0]->unit != UNIT_FX)
+ return NULL;
+
+ if (regs[1]->unit == UNIT_D0)
+ base_unit = 0;
+ else if (regs[1]->unit == UNIT_D1)
+ base_unit = 1;
+ else
+ return NULL;
+ }
+ else
+ {
+ if (regs[0]->unit == UNIT_D0)
+ base_unit = 0;
+ else if (regs[0]->unit == UNIT_D1)
+ base_unit = 1;
+ else
+ return NULL;
+
+ if (regs[1]->unit != UNIT_FX)
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (base_unit << 24) |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 9));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU immediate MOV instruction. */
+static const char *
+parse_fmov_i (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[1];
+ int value = 0;
+
+ l = parse_fpu_regs (l, regs, 1);
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_imm16 (l, insn, &value);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ ((value & IMM16_MASK) << 3));
+
+ if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 1);
+ else if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 2);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU PACK instruction. */
+static const char *
+parse_fpack (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[3];
+
+ l = parse_fpu_regs (l, regs, 3);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->no % 2)
+ {
+ as_bad (_("destination register should be even numbered"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ (regs[2]->no << 9));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU SWAP instruction. */
+static const char *
+parse_fswap (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+
+ if (insn->fpu_width != FPU_WIDTH_PAIR)
+ return NULL;
+
+ l = parse_fpu_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->no % 2)
+ return NULL;
+
+ if (regs[1]->no % 2)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU CMP instruction. */
+static const char *
+parse_fcmp (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line, *l2;
+ const metag_reg *regs1[1];
+ const metag_reg *regs2[1];
+
+ l = parse_fpu_regs (l, regs1, 1);
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l2 = parse_fpu_regs (l, regs2, 1);
+
+ if (l2 != NULL)
+ {
+ insn->bits = (regs2[0]->no << 9);
+ }
+ else
+ {
+ int constant = 0;
+ l2 = parse_imm_constant (l, insn, &constant);
+ if (!l2 || constant != 0)
+ {
+ as_bad (_("comparison must be with register or #0"));
+ return NULL;
+ }
+ insn->bits = (1 << 8);
+ }
+
+ insn->bits |= (template->meta_opcode |
+ (regs1[0]->no << 14));
+
+ if (insn->fpu_action_flags & FPU_ACTION_ABS)
+ insn->bits |= (1 << 19);
+
+ if (insn->fpu_action_flags & FPU_ACTION_QUIET)
+ insn->bits |= (1 << 7);
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+ else if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 5);
+
+ insn->len = 4;
+ return l2;
+}
+
+/* Parse an FPU MIN or MAX instruction. */
+static const char *
+parse_fminmax (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[3];
+
+ l = parse_fpu_regs (l, regs, 3);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ (regs[2]->no << 9));
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+ else if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 5);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU data conversion instruction. */
+static const char *
+parse_fconv (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ {
+ if (strncasecmp (template->name, "FTOH", 4) &&
+ strncasecmp (template->name, "HTOF", 4) &&
+ strncasecmp (template->name, "FTOI", 4) &&
+ strncasecmp (template->name, "ITOF", 4))
+ {
+ as_bad (_("instruction cannot operate on pair values"));
+ return NULL;
+ }
+ }
+
+ if (insn->fpu_action_flags & FPU_ACTION_ZERO)
+ {
+ if (strncasecmp (template->name, "FTOI", 4) &&
+ strncasecmp (template->name, "DTOI", 4) &&
+ strncasecmp (template->name, "DTOL", 4))
+ {
+ as_bad (_("zero flag is not valid for this instruction"));
+ return NULL;
+ }
+ }
+
+ l = parse_fpu_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!strncasecmp (template->name, "DTOL", 4) ||
+ !strncasecmp (template->name, "LTOD", 4))
+ {
+ if (regs[0]->no % 2)
+ {
+ as_bad (_("destination register should be even numbered"));
+ return NULL;
+ }
+
+ if (regs[1]->no % 2)
+ {
+ as_bad (_("source register should be even numbered"));
+ return NULL;
+ }
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14));
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+
+ if (insn->fpu_action_flags & FPU_ACTION_ZERO)
+ insn->bits |= (1 << 12);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU extended data conversion instruction. */
+static const char *
+parse_fconvx (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+ int fraction_bits = 0;
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ {
+ if (strncasecmp (template->name, "FTOX", 4) &&
+ strncasecmp (template->name, "XTOF", 4))
+ {
+ as_bad (_("instruction cannot operate on pair values"));
+ return NULL;
+ }
+ }
+
+ l = parse_fpu_regs (l, regs, 2);
+
+ l = skip_comma (l);
+
+ if (l == NULL ||
+ *l == END_OF_INSN)
+ return NULL;
+
+ l = parse_imm_constant (l, insn, &fraction_bits);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14));
+
+ if (strncasecmp (template->name, "DTOXL", 5) &&
+ strncasecmp (template->name, "XLTOD", 5))
+ {
+ if (!within_unsigned_range (fraction_bits, IMM5_BITS))
+ {
+ as_bad (_("fraction bits value out of range"));
+ return NULL;
+ }
+ insn->bits |= ((fraction_bits & IMM5_MASK) << 9);
+ }
+ else
+ {
+ if (!within_unsigned_range (fraction_bits, IMM6_BITS))
+ {
+ as_bad (_("fraction bits value out of range"));
+ return NULL;
+ }
+ insn->bits |= ((fraction_bits & IMM6_MASK) << 8);
+ }
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU basic arithmetic instruction. */
+static const char *
+parse_fbarith (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[3];
+
+ l = parse_fpu_regs (l, regs, 3);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ (regs[2]->no << 9));
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+ else if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 5);
+
+ if (insn->fpu_action_flags & FPU_ACTION_INV)
+ insn->bits |= (1 << 7);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse a floating point accumulator name. */
+static const char *
+parse_acf (const char *line, int *part)
+{
+ const char *l = line;
+ size_t i;
+
+ for (i = 0; i < sizeof(metag_acftab)/sizeof(metag_acftab[0]); i++)
+ {
+ const metag_acf *acf = &metag_acftab[i];
+ size_t name_len = strlen (acf->name);
+
+ if (strncasecmp (l, acf->name, name_len) == 0)
+ {
+ l += name_len;
+ *part = acf->part;
+ return l;
+ }
+ }
+ return NULL;
+}
+
+/* Parse an FPU extended arithmetic instruction. */
+static const char *
+parse_fearith (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[3];
+ bfd_boolean is_muz = (MINOR_OPCODE (template->meta_opcode) == 0x6 &&
+ ((template->meta_opcode >> 4) & 0x1));
+ unsigned int is_o3o = template->meta_opcode & 0x1;
+ unsigned int is_mac = 0;
+ unsigned int is_maw = 0;
+
+ if (!strncasecmp (template->name, "MAW", 3))
+ is_maw = 1;
+
+ if (!strncasecmp (template->name, "MAC", 3))
+ {
+ int part;
+ l = parse_acf (l, &part);
+
+ if (l == NULL || part != 0)
+ return NULL;
+
+ l = skip_comma (l);
+
+ l = parse_fpu_regs (l, &regs[1], 2);
+
+ is_mac = 1;
+ }
+ else
+ {
+ if (is_o3o && is_maw)
+ l = parse_fpu_regs (l, regs, 2);
+ else
+ l = parse_fpu_regs (l, regs, 3);
+ }
+
+ if (l == NULL)
+ return NULL;
+
+ if (is_o3o && is_maw)
+ insn->bits = (template->meta_opcode |
+ (regs[1]->no << 9));
+ else
+ insn->bits = (template->meta_opcode |
+ (regs[1]->no << 14));
+
+ if (!(is_o3o && is_maw))
+ insn->bits |= (regs[2]->no << 9);
+
+ if (is_o3o && is_maw)
+ insn->bits |= (regs[0]->no << 14);
+ else if (!is_mac)
+ insn->bits |= (regs[0]->no << 19);
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+ else if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 5);
+
+ if (!is_mac && !is_maw)
+ if (insn->fpu_action_flags & FPU_ACTION_INV)
+ insn->bits |= (1 << 7);
+
+ if (is_muz)
+ if (insn->fpu_action_flags & FPU_ACTION_QUIET)
+ insn->bits |= (1 << 1);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU RCP or RSQ instruction. */
+static const char *
+parse_frec (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[2];
+
+ l = parse_fpu_regs (l, regs, 2);
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14));
+
+ if (insn->fpu_width == FPU_WIDTH_PAIR)
+ insn->bits |= (1 << 6);
+ else if (insn->fpu_width == FPU_WIDTH_DOUBLE)
+ insn->bits |= (1 << 5);
+
+ if (insn->fpu_action_flags & FPU_ACTION_ZERO)
+ insn->bits |= (1 << 10);
+ else if (insn->fpu_action_flags & FPU_ACTION_QUIET)
+ insn->bits |= (1 << 9);
+
+ if (insn->fpu_action_flags & FPU_ACTION_INV)
+ insn->bits |= (1 << 7);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU vector arithmetic instruction. */
+static const char *
+parse_fsimd (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[3];
+
+ if (insn->fpu_width != FPU_WIDTH_PAIR)
+ {
+ as_bad (_("simd instructions operate on pair values (L prefix)"));
+ return NULL;
+ }
+
+ l = parse_fpu_regs (l, regs, 3);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->no % 2)
+ {
+ as_bad (_("destination register should be even numbered"));
+ return NULL;
+ }
+
+ if ((regs[1]->no % 2) ||
+ (regs[2]->no % 2))
+ {
+ as_bad (_("source registers should be even numbered"));
+ return NULL;
+ }
+
+ insn->bits = (template->meta_opcode |
+ (regs[0]->no << 19) |
+ (regs[1]->no << 14) |
+ (regs[2]->no << 9));
+
+ if (insn->fpu_action_flags & FPU_ACTION_INV)
+ insn->bits |= (1 << 7);
+
+ insn->len = 4;
+ return l;
+}
+
+/* Parse an FPU accumulator GET or SET instruction. */
+static const char *
+parse_fget_set_acf (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ int part;
+ metag_addr addr;
+ bfd_boolean is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ if (is_get)
+ {
+ l = parse_acf (l, &part);
+
+ l = skip_comma (l);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_mget_mset_addr (l, &addr);
+ }
+ else
+ {
+ l = parse_mget_mset_addr (l, &addr);
+
+ l = skip_comma (l);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_acf (l, &part);
+ }
+
+ if (l == NULL)
+ return NULL;
+
+ insn->bits = (template->meta_opcode |
+ (part << 19));
+
+ if (!is_short_unit (addr.base_reg->unit))
+ {
+ as_bad (_("base unit must be one of %s"), SHORT_UNITS);
+ return NULL;
+ }
+
+ insn->bits |= ((addr.base_reg->no << 14) |
+ ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5));
+
+ insn->len = 4;
+ return l;
+}
+
+/* Copy the name of the next register in LINE to REG_BUF. */
+static size_t
+strip_reg_name(const char *line, char *reg_buf)
+{
+ const char *l = line;
+ size_t len = 0;
+
+ while (is_register_char (*l))
+ {
+ reg_buf[len] = *l;
+ l++;
+ len++;
+ if (!(len < MAX_REG_LEN))
+ return 0;
+ }
+
+ if (len)
+ reg_buf[len] = '\0';
+
+ return len;
+}
+
+/* Parse a DSP register from LINE into REG using only the registers
+ from DSP_REGTAB. Return the next character or NULL. */
+static const char *
+__parse_dsp_reg (const char *line, const metag_reg **reg, htab_t dsp_regtab)
+{
+ const char *l = line;
+ char name[MAX_REG_LEN];
+ size_t len = 0;
+ metag_reg entry;
+ const metag_reg *_reg;
+
+ /* We don't entirely strip the register name because we might
+ actually want to match whole string in the register table,
+ e.g. "D0AW.1++" not just "D0AW.1". The string length of the table
+ entry limits our comaprison to a reasonable bound anyway. */
+ while (is_register_char (*l) || *l == PLUS)
+ {
+ name[len] = *l;
+ l++;
+ len++;
+ if (!(len < MAX_REG_LEN))
+ return NULL;
+ }
+
+ if (!len)
+ return NULL;
+
+ name[len] = '\0';
+ entry.name = name;
+
+ _reg = (const metag_reg *) htab_find (dsp_regtab, &entry);
+ if (!_reg)
+ return NULL;
+
+ *reg = _reg;
+
+ return l;
+}
+
+/* Parse a DSP register and setup "reg" with a metag_reg whose "no"
+ member is suitable for encoding into a DSP insn register field. */
+static const char *
+parse_dsp_insn_reg (const char *line, const metag_reg **reg)
+{
+ return __parse_dsp_reg (line, reg, dsp_reg_htab);
+}
+
+/* Parse a DSP register and setup "reg" with a metag_reg whose "no"
+ member is suitable for encoding into a DSP template definition insn
+ register field.
+
+ There is a separate table for whether we're doing a load or a store
+ definition. "load" specifies which table to look at. */
+static const char *
+parse_dsp_template_reg (const char *line, const metag_reg **reg,
+ bfd_boolean load)
+{
+ return __parse_dsp_reg (line, reg, dsp_tmpl_reg_htab[load]);
+}
+
+/* Parse a single DSP register from LINE. */
+static const char *
+parse_dsp_reg (const char *line, const metag_reg **reg,
+ bfd_boolean tmpl, bfd_boolean load)
+{
+ if (tmpl)
+ return parse_dsp_template_reg (line, reg, load);
+ else
+ return parse_dsp_insn_reg (line, reg);
+}
+
+/* Return TRUE if UNIT is an address unit. */
+static bfd_boolean
+is_addr_unit (enum metag_unit unit)
+{
+ switch (unit)
+ {
+ case UNIT_A0:
+ case UNIT_A1:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* Return TRUE if UNIT1 and UNIT2 are equivalent units. */
+static bfd_boolean
+is_same_data_unit (enum metag_unit unit1, enum metag_unit unit2)
+{
+ if (unit1 == unit2)
+ return TRUE;
+
+ switch (unit1)
+ {
+ case UNIT_D0:
+ if (unit2 == UNIT_ACC_D0 || unit2 == UNIT_RAM_D0)
+ return TRUE;
+ break;
+ case UNIT_D1:
+ if (unit2 == UNIT_ACC_D1 || unit2 == UNIT_RAM_D1)
+ return TRUE;
+ break;
+ case UNIT_ACC_D0:
+ if (unit2 == UNIT_D0 || unit2 == UNIT_RAM_D0)
+ return TRUE;
+ break;
+ case UNIT_ACC_D1:
+ if (unit2 == UNIT_D1 || unit2 == UNIT_RAM_D1)
+ return TRUE;
+ break;
+ case UNIT_RAM_D0:
+ if (unit2 == UNIT_ACC_D0 || unit2 == UNIT_D0)
+ return TRUE;
+ break;
+ case UNIT_RAM_D1:
+ if (unit2 == UNIT_ACC_D1 || unit2 == UNIT_D1)
+ return TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+/* Return TRUE if the register NUM is a quickrot control register. */
+static bfd_boolean
+is_quickrot_reg (unsigned int num)
+{
+ switch (num)
+ {
+ case 2:
+ case 3:
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Return TRUE if REG is an accumulator register. */
+static bfd_boolean
+is_accumulator_reg (const metag_reg *reg)
+{
+ if (reg->unit == UNIT_ACC_D0 || reg->unit == UNIT_ACC_D1)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Return TRUE if REG is a DSP RAM register. */
+static bfd_boolean
+is_dspram_reg (const metag_reg *reg)
+{
+ if (reg->unit == UNIT_RAM_D0 || reg->unit == UNIT_RAM_D1)
+ return TRUE;
+
+ return FALSE;
+}
+
+static const char *
+__parse_gp_reg (const char *line, const metag_reg **reg, bfd_boolean load)
+{
+ const char *l = line;
+ char reg_buf[MAX_REG_LEN];
+ size_t len = 0;
+
+ if (l == NULL)
+ return NULL;
+
+ /* Parse [DSPRAM.x]. */
+ if (*l == ADDR_BEGIN_CHAR)
+ {
+ l++;
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_dsp_reg (l, reg, TRUE, load);
+ if (l == NULL)
+ return NULL;
+
+ if (*l == ADDR_END_CHAR)
+ l++;
+ else
+ {
+ as_bad (_("expected ']', not %c in %s"), *l, l);
+ return NULL;
+ }
+
+ return l;
+ }
+ else
+ {
+
+ len = strip_reg_name (l, reg_buf);
+ if (!len)
+ return NULL;
+
+ l += len;
+ *reg = parse_gp_reg (reg_buf);
+ if (*reg == NULL)
+ return NULL;
+ }
+
+ return l;
+}
+
+/* Parse a list of DSP/GP registers. TRY_GP indicates whether we
+ should try to parse the register as a general-purpose register if
+ we fail to parse it as a DSP one. TMPL indicates whether the
+ registers are part of a template definition instruction. If this is
+ a template definition instruction LOAD says whether it's a load
+ template insn. FIRST_DST indicates whether the first register is
+ a destination operand. */
+static const char *
+parse_dsp_regs_list (const char *line, const metag_reg **regs, size_t count,
+ size_t *regs_read, bfd_boolean try_gp, bfd_boolean tmpl,
+ bfd_boolean load, bfd_boolean first_dst)
+{
+ const char *l = line;
+ int seen_regs = 0;
+ size_t i;
+ const metag_reg *reg;
+
+ for (i = 0; i < count; i++)
+ {
+ const char *next, *ll;
+
+ next = l;
+
+ if (i > 0)
+ {
+ l = skip_comma (l);
+ if (l == NULL)
+ {
+ *regs_read = seen_regs;
+ return next;
+ }
+ }
+
+ ll = parse_dsp_reg (l, &reg, tmpl, load);
+
+ if (!ll)
+ {
+ if (try_gp)
+ {
+ l = __parse_gp_reg (l, &reg, !(first_dst && i == 0));
+ if (l == NULL)
+ {
+ *regs_read = seen_regs;
+ return next;
+ }
+ regs[i] = reg;
+ seen_regs++;
+ }
+ else
+ {
+ *regs_read = seen_regs;
+ return l;
+ }
+ }
+ else
+ {
+ regs[i] = reg;
+ seen_regs++;
+ l = ll;
+ }
+ }
+
+ *regs_read = seen_regs;
+ return l;
+}
+
+/* Parse the following memory references:
+
+ - [Ax.r]
+ - [Ax.r++]
+ - [Ax.r--]
+ - [Ax.r+Ax.r++]
+ - [Ax.r-Ax.r--]
+
+ - [DSPRam]
+ - [DSPRam++]
+ - [DSPRam+DSPRam++]
+ - [DSPRam-DSPRam--] */
+static const char *
+parse_dsp_addr (const char *line, metag_addr *addr, unsigned int size,
+ bfd_boolean load)
+{
+ const char *l = line, *ll;
+ const metag_reg *regs[1];
+ size_t regs_read;
+
+ /* Skip opening square bracket. */
+ l++;
+
+ l = parse_dsp_regs_list (l, regs, 1, &regs_read, TRUE, TRUE, load, FALSE);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!is_addr_unit (regs[0]->unit) &&
+ !is_dspram_reg (regs[0]))
+ {
+ as_bad (_("invalid register for memory access"));
+ return NULL;
+ }
+
+ addr->base_reg = regs[0];
+
+ if (*l == ADDR_END_CHAR)
+ {
+ addr->exp.X_op = O_constant;
+ addr->exp.X_add_symbol = NULL;
+ addr->exp.X_op_symbol = NULL;
+
+ /* Simple register with no offset (0 immediate). */
+ addr->exp.X_add_number = 0;
+
+ addr->immediate = 1;
+ l++;
+
+ return l;
+ }
+
+ ll = parse_addr_post_incr_op (l, addr);
+
+ if (ll && *ll == ADDR_END_CHAR)
+ {
+ if (addr->update == 1)
+ {
+ /* We have a post increment/decrement. */
+ addr->exp.X_op = O_constant;
+ addr->exp.X_add_number = size;
+ addr->exp.X_add_symbol = NULL;
+ addr->exp.X_op_symbol = NULL;
+ addr->post_increment = 1;
+ }
+ addr->immediate = 1;
+ ll++;
+ return ll;
+ }
+
+ addr->post_increment = 0;
+
+ l = parse_addr_op (l, addr);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_dsp_regs_list (l, regs, 1, &regs_read, TRUE, TRUE, load, FALSE);
+
+ if (l == NULL)
+ return NULL;
+
+ if (regs[0]->unit != addr->base_reg->unit)
+ {
+ as_bad (_("offset and base must be from the same unit"));
+ return NULL;
+ }
+
+ addr->offset_reg = regs[0];
+
+ if (*l == ADDR_END_CHAR)
+ {
+ l++;
+ return l;
+ }
+
+ l = parse_addr_post_incr_op (l, addr);
+
+ if (l == NULL)
+ return NULL;
+
+ if (*l == ADDR_END_CHAR)
+ {
+ l++;
+ return l;
+ }
+
+ return NULL;
+}
+
+/* Parse a DSP GET or SET instruction. */
+static const char *
+parse_dget_set (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ metag_addr addr;
+ int unit = 0;
+ int rd_reg = 0;
+ bfd_boolean is_get = (template->meta_opcode & 0x100);
+ bfd_boolean is_dual = (template->meta_opcode & 0x4);
+ bfd_boolean is_template = FALSE;
+ const metag_reg *regs[2];
+ unsigned int size;
+ size_t count, regs_read;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.reloc_type = BFD_RELOC_UNUSED;
+
+ size = is_dual ? 8 : 4;
+ count = is_dual ? 2 : 1;
+
+ if (is_get)
+ {
+ /* GETL can be used on one template table entry. */
+ if (*l == 'T')
+ count = 1;
+
+ l = parse_dsp_regs_list (l, regs, count, &regs_read, FALSE,
+ FALSE, FALSE, FALSE);
+ l = skip_comma (l);
+
+ if (l == NULL)
+ {
+ as_bad (_("unexpected end of line"));
+ return NULL;
+ }
+
+ l = parse_addr (l, &addr, size);
+ }
+ else
+ {
+ l = parse_addr (l, &addr, size);
+
+ l = skip_comma (l);
+
+ if (l == NULL)
+ return NULL;
+
+ /* GETL can be used on one template table entry. */
+ if (*l == 'T')
+ count = 1;
+
+ l = parse_dsp_regs_list (l, regs, count, &regs_read, FALSE, FALSE,
+ FALSE, FALSE);
+ }
+
+ if (l == NULL)
+ return NULL;
+
+ /* The first register dictates the unit. */
+ if (regs[0]->unit == UNIT_DT)
+ is_template = TRUE;
+ else
+ {
+ if (regs[0]->unit == UNIT_D0 || regs[0]->unit == UNIT_RAM_D0 ||
+ regs[0]->unit == UNIT_ACC_D0)
+ unit = 0;
+ else
+ unit = 1;
+ }
+
+ rd_reg = regs[0]->no;
+
+ /* The 'H' modifier allows a DSP GET/SET instruction to target the
+ upper 8-bits of an accumulator. It is _only_ valid for the
+ accumulators. */
+ if (insn->dsp_daoppame_flags & DSP_DAOPPAME_HIGH)
+ {
+ if (is_template || !(rd_reg >= 16 && rd_reg < 20))
+ {
+ as_bad (_("'H' modifier only valid for accumulator registers"));
+ return NULL;
+ }
+
+ /* Top 8-bits of the accumulator. */
+ rd_reg |= 8;
+ }
+
+ if (is_template)
+ {
+ insn->bits = (template->meta_opcode | (1 << 1));
+ }
+ else
+ {
+ insn->bits = (template->meta_opcode | unit);
+ }
+
+ insn->bits |= (rd_reg << 19);
+
+ if (addr.immediate)
+ {
+ int offset = addr.exp.X_add_number;
+
+ if (addr.negate)
+ offset = -offset;
+
+ offset = offset / (int)size;
+
+ if (!within_signed_range (offset, DGET_SET_IMM_BITS))
+ {
+ as_bad (_("offset value out of range"));
+ return NULL;
+ }
+
+ offset = offset & DGET_SET_IMM_MASK;
+
+ insn->bits |= (1 << 13);
+ insn->bits |= (offset << 9);
+ }
+ else
+ {
+ int au = (addr.base_reg->unit == UNIT_A1);
+
+ insn->bits |= (au << 18);
+ insn->bits |= ((addr.base_reg->no & REG_MASK) << 14);
+ insn->bits |= ((addr.offset_reg->no & REG_MASK) << 9);
+ }
+
+ if (is_dual)
+ insn->bits |= (1 << 2);
+
+ if (!is_addr_unit (addr.base_reg->unit))
+ {
+ as_bad (_("base unit must be either A0 or A1"));
+ return NULL;
+ }
+
+ unit = (addr.base_reg->unit == UNIT_A0) ? 0 : 1;
+ insn->bits |= ((addr.base_reg->no << 14) | (unit << 18));
+
+ insn->len = 4;
+
+ return l;
+}
+
+/* Parse a DSP template instruction. */
+static const char *
+parse_dtemplate (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const metag_reg *regs[TEMPLATE_NUM_REGS];
+ bfd_boolean daop_only = FALSE;
+ int regs_val[4];
+ int regs_which[4] = { -1, -1, -1, -1}; /* Register or immediate? */
+ int i;
+
+ for (i = 0; i < TEMPLATE_NUM_REGS; i++)
+ {
+ if (l == NULL)
+ {
+ as_bad (_("unexpected end of line"));
+ return NULL;
+ }
+
+ /* We may only have 3 register operands. */
+ if (*l == END_OF_INSN && i == 3)
+ {
+ daop_only = TRUE;
+ break;
+ }
+
+ if (i != 0)
+ {
+ l = skip_comma (l);
+ if (l == NULL)
+ return NULL;
+ }
+
+ if (*l == IMM_CHAR)
+ {
+ l = parse_imm_constant (l, insn, &regs_val[i]);
+ if (l == NULL)
+ {
+ as_bad (_("invalid immediate"));
+ return NULL;
+ }
+ regs_which[i] = 0;
+ }
+ else
+ {
+ /* We can't tell from the template instantiation whether
+ this is a load or store. So we have to try looking up the
+ register name in both the load and store tables. */
+ const char *l2 = l;
+ l = __parse_gp_reg (l, &regs[i], TRUE);
+ if (l == NULL)
+ {
+ /* Try the store table too. */
+ l = __parse_gp_reg (l2, &regs[i], FALSE);
+ if (l == NULL)
+ {
+ /* Then try a DSP register. */
+ l = parse_dsp_insn_reg (l2, &regs[i]);
+ if (l == NULL || regs[i]->unit == UNIT_DT)
+ {
+ as_bad (_("invalid register"));
+ return NULL;
+ }
+ }
+ }
+ regs_which[i] = 1;
+ }
+ }
+
+ insn->bits = template->meta_opcode;
+
+ if (regs_which[0] == 0)
+ insn->bits |= (regs_val[0] << 19);
+ else if (regs_which[0] == 1)
+ insn->bits |= (regs[0]->no << 19);
+
+ if (regs_which[1] == 0)
+ insn->bits |= (regs_val[1] << 14);
+ else if (regs_which[1] == 1)
+ insn->bits |= (regs[1]->no << 14);
+
+ if (regs_which[2] == 0)
+ insn->bits |= (regs_val[2] << 9);
+ else if (regs_which[2] == 1)
+ insn->bits |= (regs[2]->no << 9);
+
+ if (regs_which[3] == 0)
+ insn->bits |= (regs_val[3] << 4);
+ else if (regs_which[3] == 1)
+ insn->bits |= (regs[3]->no << 4);
+
+ /* DaOp only. */
+ if (daop_only)
+ insn->bits |= (0x3 << 24); /* Set the minor opcode. */
+ else if (insn->dsp_daoppame_flags & DSP_DAOPPAME_HIGH) /* Half Load/Store. */
+ insn->bits |= (0x5 << 24); /* Set the minor opcode. */
+
+ insn->len = 4;
+
+ return l;
+}
+
+/* Parse a DSP Template definiton memory reference, e.g
+ [A0.7+A0.5++]. DSPRAM is set to true by this function if this
+ template definition is a DSP RAM template definition. */
+static const char *
+template_mem_ref(const char *line, metag_addr *addr,
+ bfd_boolean *dspram, int size, bfd_boolean load)
+{
+ const char *l = line;
+
+ l = parse_dsp_addr (l, addr, size, load);
+
+ if (l != NULL)
+ {
+ if (is_addr_unit(addr->base_reg->unit))
+ *dspram = FALSE;
+ else
+ *dspram = TRUE;
+ }
+
+ return l;
+}
+
+/* Sets LOAD to TRUE if this is a Template load definiton (otherwise
+ it's a store). Fills out ADDR, TEMPLATE_REG and ADDR_UNIT. */
+static const char *
+parse_template_regs (const char *line, bfd_boolean *load,
+ unsigned int *addr_unit,
+ const metag_reg **template_reg, metag_addr *addr,
+ bfd_boolean *dspram, int size)
+{
+ const char *l = line;
+
+ if (l == NULL)
+ return NULL;
+
+ /* DSP Template load definition (Tx, [Ax]) */
+ if (*l == 'T')
+ {
+ *load = TRUE;
+ l = parse_dsp_reg (l, &template_reg[0], FALSE, FALSE);
+ if (l == NULL)
+ return NULL;
+
+ l = skip_comma (l);
+
+ l = template_mem_ref (l, addr, dspram, size, *load);
+
+ if (addr->base_reg->unit == UNIT_A1)
+ *addr_unit = 1;
+
+ }
+ else if (*l == ADDR_BEGIN_CHAR) /* DSP Template store ([Ax], Tx) */
+ {
+ *load = FALSE;
+ l = template_mem_ref (l, addr, dspram, size, *load);
+ l = skip_comma(l);
+
+ if (l == NULL)
+ return NULL;
+
+ l = parse_dsp_reg (l, &template_reg[0], FALSE, FALSE);
+ if (l == NULL)
+ return NULL;
+
+ if (addr->base_reg->unit == UNIT_A1)
+ *addr_unit = 1;
+ }
+ else
+ {
+ as_bad (_("invalid register operand"));
+ return NULL;
+ }
+
+ return l;
+}
+
+#define INVALID_SHIFT (-1)
+
+static metag_reg _reg;
+
+/* Parse a template instruction definition. */
+static const char *
+interpret_template_regs(const char *line, metag_insn *insn,
+ const metag_reg **regs,
+ int *regs_shift, bfd_boolean *load, bfd_boolean *dspram,
+ int size, int *ls_shift, int *au_shift,
+ unsigned int *au, int *imm, int *imm_shift,
+ unsigned int *imm_mask)
+{
+ const char *l = line;
+ metag_addr addr;
+ const metag_reg *template_reg[1];
+
+ memset (&addr, 0, sizeof(addr));
+
+ regs_shift[0] = 19;
+ regs_shift[1] = INVALID_SHIFT;
+
+ insn->bits |= (1 << 1);
+
+ l = skip_whitespace (l);
+
+ l = parse_template_regs (l, load, au, template_reg,
+ &addr, dspram, size);
+ if (l == NULL)
+ {
+ as_bad (_("could not parse template definition"));
+ return NULL;
+ }
+
+ regs[2] = template_reg[0];
+ regs_shift[2] = 9;
+
+ /* DSPRAM definition. */
+ if (*dspram)
+ {
+
+ _reg = *addr.base_reg;
+
+ if (addr.immediate)
+ {
+ /* Set the post-increment bit in the register field. */
+ if (addr.update)
+ _reg.no |= 0x1;
+ }
+ else
+ {
+ /* The bottom bit of the increment register tells us
+ whether it's increment register 0 or 1. */
+ if (addr.offset_reg->no & 0x1)
+ _reg.no |= 0x3;
+ else
+ _reg.no |= 0x2;
+ }
+
+ regs[0] = &_reg;
+
+ insn->bits |= (0x3 << 17); /* This signifies a DSPRAM definition. */
+ }
+ else /* DaOpPaMe definition. */
+ {
+ regs[0] = addr.base_reg;
+ if (addr.immediate)
+ {
+ /* Set the I bit. */
+ insn->bits |= (1 << 18);
+
+ if (addr.update == 1)
+ {
+ if (addr.negate == 1)
+ *imm = 0x3;
+ else
+ *imm = 0x1;
+ }
+
+ *imm_shift = 14;
+ *imm_mask = 0x3;
+ }
+ else
+ {
+ /* Setup the offset register. */
+ regs[1] = addr.offset_reg;
+ regs_shift[1] = 14;
+ }
+ *au_shift = 23;
+ }
+
+ *ls_shift = 13;
+
+ return l;
+}
+
+/* Does this combination of units need the O2R bit and can it be encoded? */
+static bfd_boolean
+units_need_o2r (enum metag_unit unit1, enum metag_unit unit2)
+{
+ if (unit1 == unit2)
+ return FALSE;
+
+ if (unit1 == UNIT_D0 || unit1 == UNIT_ACC_D0 || unit1 == UNIT_RAM_D0)
+ {
+ if (unit2 == UNIT_ACC_D0 || unit2 == UNIT_RAM_D0 || unit2 == UNIT_D0)
+ return FALSE;
+
+ switch (unit2)
+ {
+ case UNIT_A1:
+ case UNIT_D1:
+ case UNIT_RD:
+ case UNIT_A0:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+ }
+
+ if (unit1 == UNIT_D1 || unit1 == UNIT_ACC_D1 || unit1 == UNIT_RAM_D1)
+ {
+ if (unit2 == UNIT_ACC_D1 || unit2 == UNIT_RAM_D1 || unit2 == UNIT_D1)
+ return FALSE;
+
+ switch (unit2)
+ {
+ case UNIT_A1:
+ case UNIT_D0:
+ case UNIT_RD:
+ case UNIT_A0:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Return TRUE if this is a DSP data unit. */
+static bfd_boolean
+is_dsp_data_unit (const metag_reg *reg)
+{
+ switch (reg->unit)
+ {
+ case UNIT_D0:
+ case UNIT_D1:
+ case UNIT_ACC_D0:
+ case UNIT_ACC_D1:
+ case UNIT_RAM_D0:
+ case UNIT_RAM_D1:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static metag_reg o2r_reg;
+
+/* Parse a DaOpPaMe load template definition. */
+static const char *
+parse_dalu (const char *line, metag_insn *insn,
+ const insn_template *template)
+{
+ const char *l = line;
+ const char *ll;
+ const metag_reg *regs[4];
+ metag_addr addr;
+ size_t regs_read;
+ bfd_boolean is_mov = MAJOR_OPCODE (template->meta_opcode) == OPC_ADD;
+ bfd_boolean is_cmp = ((MAJOR_OPCODE (template->meta_opcode) == OPC_CMP) &&
+ ((template->meta_opcode & 0xee) == 0));
+ bfd_boolean is_dual = (insn->dsp_width == DSP_WIDTH_DUAL);
+ bfd_boolean is_quickrot64 = ((insn->dsp_action_flags & DSP_ACTION_QR64) != 0);
+ int l1_shift = INVALID_SHIFT;
+ bfd_boolean load = FALSE;
+ int ls_shift = INVALID_SHIFT;
+ bfd_boolean ar = FALSE;
+ int ar_shift = INVALID_SHIFT;
+ int regs_shift[3] = { INVALID_SHIFT, INVALID_SHIFT, INVALID_SHIFT };
+ int imm = 0;
+ int imm_shift = INVALID_SHIFT;
+ unsigned int imm_mask = 0;
+ unsigned int au = 0;
+ int au_shift = INVALID_SHIFT;
+ unsigned int du = 0;
+ int du_shift = INVALID_SHIFT;
+ unsigned int sc = ((insn->dsp_action_flags & DSP_ACTION_OV) != 0);
+ int sc_shift = INVALID_SHIFT;
+ unsigned int om = ((insn->dsp_action_flags & DSP_ACTION_MOD) != 0);
+ int om_shift = INVALID_SHIFT;
+ unsigned int o2r = 0;
+ int o2r_shift = INVALID_SHIFT;
+ unsigned int qr = 0;
+ int qr_shift = INVALID_SHIFT;
+ int qd_shift = INVALID_SHIFT;
+ unsigned int qn = 0;
+ int qn_shift = INVALID_SHIFT;
+ unsigned int a1 = ((insn->dsp_action_flags & (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ZERO)) != 0);
+ int a1_shift = INVALID_SHIFT;
+ unsigned int a2 = ((insn->dsp_action_flags & (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ADD)) != 0);
+ int a2_shift = INVALID_SHIFT;
+ unsigned su = ((insn->dsp_action_flags & DSP_ACTION_UMUL) != 0);
+ int su_shift = INVALID_SHIFT;
+ unsigned int ac;
+ int ac_shift = INVALID_SHIFT;
+ unsigned int mx = (((insn->dsp_daoppame_flags & DSP_DAOPPAME_8) != 0) ||
+ (insn->dsp_daoppame_flags & DSP_DAOPPAME_16) != 0);
+ int mx_shift = INVALID_SHIFT;
+ int size = is_dual ? 8 : 4;
+ bfd_boolean dspram;
+ bfd_boolean conditional = (MINOR_OPCODE (template->meta_opcode) & 0x4);
+
+ /* XFIXME: check the flags are valid with the instruction. */
+ if (is_quickrot64 && !(template->arg_type & DSP_ARGS_QR))
+ {
+ as_bad (_("QUICKRoT 64-bit extension not applicable to this instruction"));
+ return NULL;
+ }
+
+ insn->bits = template->meta_opcode;
+
+ memset (regs, 0, sizeof (regs));
+ memset (&addr, 0, sizeof (addr));
+
+ /* There are the following forms of DSP ALU instructions,
+
+ * Group 1:
+ 19. D[T] Op De.r,Dx.r,De.r
+ 1. D[T] Op De.r,Dx.r,De.r|ACe.r [Accumulator in src 2]
+ 3. D[T] Op De.r,Dx.r,De.r[,Ae.r] [QUICKRoT]
+ 2. D[T] Op ACe.e,ACx.r,ACo.e [cross-unit accumulator op]
+ 5. D[T] Op De.r|ACe.r,Dx.r,De.r
+ 20. D[T] Op De.r,Dx.r|ACx.r,De.r
+ 8. D Opcc De.r,Dx.r,Rx.r
+ 6. D Op De.r,Dx.r,Rx.r|RD
+ 17. D Op De.r|ACe.r,Dx.r,Rx.r|RD
+ 7. D Op De.e,Dx.r,#I16
+
+ * Group 2:
+ 4. D[T] Op Dx.r,De.r
+ 10. D Op Dx.r,Rx.r|RD
+ 13. D Op Dx.r,Rx.r
+ 11. D Op Dx.r,#I16
+ 12. D[T] Op De.r,Dx.r
+ 14. D Op DSPe.r,Dx.r
+ 15. D Op DSPx.r,#I16
+ 16. D Op De.r,DSPx.r
+ 18. D Op De.r,Dx.r|ACx.r
+
+ * Group 3:
+ 22. D Op De.r,Dx.r|ACx.r,De.r|#I5
+ 23. D Op Ux.r,Dx.r|ACx.r,De.r|#I5
+ 21. D Op De.r,Dx.r|ACx.r,#I5 */
+
+ /* Group 1. */
+ if (template->arg_type & DSP_ARGS_1)
+ {
+ du_shift = 24;
+
+ /* Could this be a cross-unit accumulator op,
+ e.g. ACe.e,ACx.r,ACo.e */
+ if (template->arg_type & DSP_ARGS_XACC)
+ {
+ ll = parse_dsp_regs_list (l, regs, 3, &regs_read, FALSE, FALSE,
+ FALSE, FALSE);
+ if (ll != NULL && regs_read == 3
+ && is_accumulator_reg (regs[0]))
+ {
+ if (regs[0]->unit != regs[1]->unit ||
+ regs[2]->unit == regs[1]->unit)
+ {
+ as_bad (_("invalid operands for cross-unit op"));
+ return NULL;
+ }
+
+ du = (regs[1]->unit == UNIT_ACC_D1);
+ regs_shift[1] = 19;
+ l = ll;
+
+ /* All cross-unit accumulator ops have bits 8 and 6 set. */
+ insn->bits |= (5 << 6);
+
+ goto check_for_template;
+ }
+
+ /* If we reach here, this instruction is not a
+ cross-unit accumulator op. */
+ }
+
+ if (template->arg_type & DSP_ARGS_SPLIT8)
+ om_shift = 7;
+
+ sc_shift = 5;
+ l1_shift = 4;
+ o2r_shift = 0;
+
+ /* De.r|ACe.r,Dx.r,De.r */
+ if (template->arg_type & DSP_ARGS_DACC)
+ {
+ /* XFIXME: these need moving? */
+ a2_shift = 7;
+ su_shift = 6;
+ a1_shift = 2;
+ om_shift = 3;
+
+ ll = parse_dsp_reg (l, &regs[0], FALSE, FALSE);
+ if (ll != NULL)
+ {
+ /* Using ACe.r as the dst requires one of the P,N or Z
+ flags to be used. */
+ if (!(insn->dsp_action_flags &
+ (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ADD|DSP_ACTION_ACC_ZERO)))
+ {
+ as_bad (_("missing flags: one of 'P', 'N' or 'Z' required"));
+ return NULL;
+ }
+
+ l = ll;
+ l = skip_comma (l);
+ l = parse_dsp_regs_list (l, &regs[1], 2, &regs_read,
+ TRUE, FALSE, FALSE, FALSE);
+ if (l == NULL || regs_read != 2)
+ {
+ as_bad (_("invalid register"));
+ return NULL;
+ }
+
+ if (regs[1]->unit == UNIT_D1 || regs[1]->unit == UNIT_RAM_D1)
+ du = 1;
+
+ regs_shift[0] = 19;
+ regs_shift[1] = 14;
+ regs_shift[2] = 9;
+ goto check_for_template;
+ }
+
+ /* If we reach here, this instruction does not use the
+ accumulator as the destination register. */
+ if ((insn->dsp_action_flags &
+ (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ADD|DSP_ACTION_ACC_ZERO)))
+ {
+ as_bad (_("'P', 'N' or 'Z' flags may only be specified when accumulating"));
+ return NULL;
+ }
+ }
+
+ regs_shift[0] = 19;
+
+
+ l = parse_dsp_regs_list (l, regs, 2, &regs_read, TRUE, FALSE, FALSE, TRUE);
+ if (l == NULL || regs_read != 2)
+ return NULL;
+
+ l = skip_comma (l);
+ if (l == NULL)
+ return NULL;
+
+ if (regs[1]->unit == UNIT_D1 || regs[1]->unit == UNIT_RAM_D1)
+ du = 1;
+
+ if (is_accumulator_reg(regs[0]) && !(template->arg_type & DSP_ARGS_DACC))
+ {
+ as_bad (_("accumulator not a valid destination"));
+ return NULL;
+ }
+
+ /* Check for immediate, e.g. De.r,Dx.r,#I16 */
+ if (*l == IMM_CHAR)
+ {
+ l = parse_imm16 (l, insn, &imm);
+ if (l == NULL)
+ {
+ as_bad (_("invalid immediate value"));
+ return NULL;
+ }
+
+ if (!within_signed_range (imm, IMM16_BITS))
+ {
+ as_bad (_("immediate value out of range"));
+ return NULL;
+ }
+
+ if (regs[0]->unit != regs[1]->unit || regs[0]->no != regs[1]->no)
+ {
+ as_bad (_("immediate value not allowed when source & dest differ"));
+ return NULL;
+ }
+
+ imm_mask = 0xffff;
+ imm_shift = 3;
+
+ /* Set the I-bit */
+ insn->bits |= (1 << 25);
+
+ insn->bits |= (0x3 << 0);
+
+ l1_shift = 2;
+
+ /* Remove any bits that have been set in the immediate
+ field. */
+ insn->bits &= ~(imm_mask << imm_shift);
+ }
+ else
+ {
+
+ regs_shift[1] = 14;
+ regs_shift[2] = 9;
+
+ /* Is Rs2 an accumulator reg, e.g. De.r,Dx.r,De.r|ACe.r */
+ ll = parse_dsp_reg (l, &regs[2], FALSE, FALSE);
+ if (ll != NULL)
+ {
+ l = ll;
+
+ if (!(template->arg_type & DSP_ARGS_ACC2))
+ {
+ as_bad (_("invalid register operand: %s"), regs[2]->name);
+ return NULL;
+ }
+
+ om_shift = 3;
+ ar_shift = 7;
+ ar = TRUE;
+ }
+ else
+ {
+ /* De.r,Dx.r,De.r */
+ l = __parse_gp_reg (l, &regs[2], TRUE);
+ if (l == NULL)
+ return NULL;
+ }
+
+ if (template->arg_type & DSP_ARGS_ACC2)
+ om_shift = 3;
+
+ /* Is this a QUICKRoT instruction? De.r,Dx.r,De.r[,Ae.r] */
+ if (template->arg_type & DSP_ARGS_QR)
+ {
+ if (conditional)
+ qn_shift = 5;
+ else
+ {
+ qn_shift = 7;
+ qr_shift = 6;
+ qd_shift = 5;
+ }
+
+ l = skip_comma (l);
+ if (l == NULL)
+ {
+ as_bad (_("QUICKRoT extension requires 4 registers"));
+ return NULL;
+ }
+
+ l = __parse_gp_reg (l, &regs[3], TRUE);
+ if (l == NULL)
+ {
+ as_bad (_("invalid fourth register"));
+ return NULL;
+ }
+
+ if (!is_addr_unit (regs[3]->unit) ||
+ !is_quickrot_reg (regs[3]->no))
+ {
+ as_bad (_("A0.2,A0.3,A1.2,A1.3 required for QUICKRoT register"));
+ return NULL;
+ }
+
+ qn = (regs[3]->no == 3);
+ }
+ }
+
+ check_for_template:
+ /* This is the common exit path. Check for o2r. */
+ if (regs[2] != NULL)
+ {
+ o2r = units_need_o2r (regs[1]->unit, regs[2]->unit);
+ if (o2r)
+ {
+ o2r_reg.no = lookup_o2r (0, du, regs[2]);
+ o2r_reg.unit = regs[2]->unit;
+ regs[2] = &o2r_reg;
+ }
+ }
+
+ /* Check any DSP RAM pointers are valid for this unit. */
+ if ((du && (regs[0]->unit == UNIT_RAM_D0)) ||
+ (!du && (regs[0]->unit == UNIT_RAM_D1)) ||
+ (du && (regs[1]->unit == UNIT_RAM_D0)) ||
+ (!du && (regs[1]->unit == UNIT_RAM_D1)) ||
+ (du && regs[2] && (regs[2]->unit == UNIT_RAM_D0)) ||
+ (!du && regs[2] && (regs[2]->unit == UNIT_RAM_D1))) {
+ as_bad (_("DSP RAM pointer in incorrect unit"));
+ return NULL;
+ }
+
+ /* Is this a template definition? */
+ if (IS_TEMPLATE_DEF (insn))
+ {
+ l = interpret_template_regs(l, insn, regs, regs_shift, &load,
+ &dspram, size, &ls_shift, &au_shift,
+ &au, &imm, &imm_shift, &imm_mask);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!dspram)
+ mx_shift = 0;
+ }
+
+ goto matched;
+ }
+
+ /* Group 2. */
+ if (template->arg_type & DSP_ARGS_2)
+ {
+ bfd_boolean is_xsd = ((MAJOR_OPCODE (template->meta_opcode) == OPC_MISC) &&
+ (MINOR_OPCODE (template->meta_opcode) == 0xa));
+ bfd_boolean is_fpu_mov = template->insn_type == INSN_DSP_FPU;
+ bfd_boolean to_fpu = (template->meta_opcode >> 7) & 0x1;
+
+ if (is_xsd)
+ du_shift = 0;
+ else
+ du_shift = 24;
+
+ l1_shift = 4;
+
+ /* CMPs and TSTs don't store to their destination operand. */
+ ll = __parse_gp_reg (l, regs, is_cmp);
+ if (ll == NULL)
+ {
+ /* DSPe.r,Dx.r or DSPx.r,#I16 */
+ if (template->arg_type & DSP_ARGS_DSP_SRC1)
+ {
+ l = parse_dsp_reg (l, regs, FALSE, FALSE);
+ if (l == NULL)
+ {
+ as_bad (_("invalid register operand #1"));
+ return NULL;
+ }
+
+ /* Only MOV instructions have a DSP register as a
+ destination. Set the MOV DSPe.r opcode. The simple
+ OR'ing is OK because the usual MOV opcode is 0x00. */
+ insn->bits = (0x91 << 24);
+ du_shift = 0;
+ l1_shift = 2;
+ regs_shift[0] = 19;
+ }
+ else
+ {
+ as_bad (_("invalid register operand #2"));
+ return NULL;
+ }
+ }
+ else
+ {
+ l = ll;
+
+ /* Everything but CMP and TST. */
+ if (MAJOR_OPCODE (template->meta_opcode) == OPC_ADD ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_SUB ||
+ MAJOR_OPCODE (insn->bits) == OPC_9 ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_MISC ||
+ ((template->meta_opcode & 0x0000002c) != 0))
+ regs_shift[0] = 19;
+ else
+ regs_shift[0] = 14;
+ }
+
+ if (!is_dsp_data_unit (regs[0]) && !(regs[0]->unit == UNIT_FX &&
+ is_fpu_mov && to_fpu))
+ return NULL;
+
+ du = (regs[0]->unit == UNIT_D1 || regs[0]->unit == UNIT_RAM_D1 ||
+ regs[0]->unit == UNIT_ACC_D1);
+
+ l = skip_comma (l);
+
+ if (*l == IMM_CHAR)
+ {
+ if (template->arg_type & DSP_ARGS_IMM &&
+ !(is_mov && (MAJOR_OPCODE (insn->bits) != OPC_9)))
+ {
+ l = parse_imm16 (l, insn, &imm);
+ if (l == NULL)
+ {
+ as_bad (_("invalid immediate value"));
+ return NULL;
+ }
+
+ if (!within_signed_range (imm, IMM16_BITS))
+ return NULL;
+
+ l1_shift = 2;
+ regs_shift[0] = 19;
+
+ imm_mask = 0xffff;
+ imm_shift = 3;
+
+ /* Set the I-bit unless it's a MOV because they're
+ different. */
+ if (!(is_mov && MAJOR_OPCODE (insn->bits) == OPC_9))
+ insn->bits |= (1 << 25);
+
+ /* All instructions that takes immediates also have bit 1 set. */
+ insn->bits |= (1 << 1);
+
+ if (MAJOR_OPCODE (insn->bits) != OPC_9)
+ insn->bits |= (1 << 0);
+
+ insn->bits &= ~(1 << 8);
+ }
+ else
+ {
+ as_bad (_("this instruction does not accept an immediate"));
+ return NULL;
+ }
+ }
+ else
+ {
+ if (MAJOR_OPCODE (insn->bits) != OPC_9)
+ {
+ insn->bits |= (1 << 8);
+ l1_shift = 4;
+ }
+
+ ll = __parse_gp_reg (l, &regs[1], TRUE);
+ if (ll == NULL)
+ {
+ if (template->arg_type & DSP_ARGS_DSP_SRC2)
+ {
+ l = parse_dsp_reg (l, &regs[1], FALSE, FALSE);
+ if (l == NULL)
+ {
+ as_bad (_("invalid register operand #3"));
+ return NULL;
+ }
+
+ /* MOV and NEG. */
+ if ((is_mov && (MAJOR_OPCODE (insn->bits) != OPC_9)) ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_SUB)
+ {
+ if (is_accumulator_reg (regs[1]))
+ {
+ if (is_fpu_mov)
+ {
+ as_bad (_("this instruction does not accept an accumulator"));
+ return NULL;
+ }
+ ar_shift = 7;
+ ar = 1;
+ regs_shift[1] = 9;
+ }
+ else
+ {
+ du_shift = 0;
+ l1_shift = 2;
+ regs_shift[1] = 14;
+ insn->bits = (0x92 << 24); /* Set opcode. */
+ }
+ }
+ }
+ else
+ {
+ as_bad (_("invalid register operand #4"));
+ return NULL;
+ }
+ }
+ else
+ {
+ /* Set the o2r bit if required. */
+ if (!is_fpu_mov && units_need_o2r (regs[0]->unit, regs[1]->unit))
+ {
+ o2r_reg = *regs[1];
+ o2r_reg.no = lookup_o2r (0, du, regs[1]);
+ regs[1] = &o2r_reg;
+ o2r_shift = 0;
+ o2r = 1;
+ }
+ else if (!is_dsp_data_unit (regs[1]) &&
+ !(is_fpu_mov && !to_fpu && regs[1]->unit == UNIT_FX))
+ return NULL;
+
+ if (is_fpu_mov && to_fpu)
+ du = (regs[1]->unit == UNIT_D1 ||
+ regs[1]->unit == UNIT_RAM_D1 ||
+ regs[1]->unit == UNIT_ACC_D1);
+
+ l = ll;
+
+ if (MAJOR_OPCODE (insn->bits) == OPC_ADD ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_SUB ||
+ (((template->meta_opcode & 0x0000002c) == 0) &&
+ MAJOR_OPCODE (template->meta_opcode) != OPC_MISC))
+ regs_shift[1] = 9;
+ else
+ regs_shift[1] = 14;
+ }
+ }
+
+ /* If it's an 0x0 MOV or NEG set some lower bits. */
+ if ((MAJOR_OPCODE (insn->bits) == OPC_ADD ||
+ MAJOR_OPCODE (template->meta_opcode) == OPC_SUB) && !is_fpu_mov)
+ {
+ om_shift = 3;
+ sc_shift = 5;
+ insn->bits |= (1 << 2);
+ }
+
+ /* Check for template definitons. */
+ if (IS_TEMPLATE_DEF (insn))
+ {
+ l = interpret_template_regs(l, insn, regs, regs_shift, &load,
+ &dspram, size, &ls_shift, &au_shift,
+ &au, &imm, &imm_shift, &imm_mask);
+ mx_shift = 0;
+
+ if (l == NULL)
+ return NULL;
+ }
+ goto matched;
+ }
+
+ /* Group 3. */
+ du_shift = 24;
+ l1_shift = 4;
+
+ l = __parse_gp_reg (l, regs, FALSE);
+ if (l == NULL)
+ {
+ as_bad (_("invalid register operand"));
+ return NULL;
+ }
+
+ l = skip_comma (l);
+
+ if (*l == 'A')
+ {
+ l = parse_dsp_reg (l, &regs[1], FALSE, FALSE);
+ if (l == NULL)
+ {
+ as_bad (_("invalid accumulator register"));
+ return NULL;
+ }
+ ac = 1;
+ ac_shift = 0;
+ }
+ else
+ {
+ l = __parse_gp_reg (l, &regs[1], TRUE);
+ if (l == NULL)
+ {
+ as_bad (_("invalid register operand"));
+ return NULL;
+ }
+ }
+
+ regs_shift[0] = 19;
+ regs_shift[1] = 14;
+
+ du = (regs[1]->unit == UNIT_D1 || regs[1]->unit == UNIT_ACC_D1
+ || regs[1]->unit == UNIT_RAM_D1);
+
+ l = skip_comma (l);
+
+ if (*l == IMM_CHAR)
+ {
+ l = parse_imm_constant (l, insn, &imm);
+ if (l == NULL)
+ {
+ as_bad (_("invalid immediate value"));
+ return NULL;
+ }
+
+ if (!within_unsigned_range (imm, IMM5_BITS))
+ return NULL;
+
+ imm_mask = 0x1f;
+ imm_shift = 9;
+
+ /* Set the I-bit */
+ insn->bits |= (1 << 25);
+ }
+ else
+ {
+ regs_shift[2] = 9;
+ l = __parse_gp_reg (l, &regs[2], TRUE);
+ if (l == NULL)
+ return NULL;
+ }
+
+ /* Check for post-processing R,G,B flags. Conditional instructions
+ do not have these bits. */
+ if (insn->dsp_action_flags & DSP_ACTION_CLAMP9)
+ {
+ if ((template->meta_opcode >> 26) & 0x1)
+ {
+ as_bad (_("conditional instruction cannot use G flag"));
+ return NULL;
+ }
+
+ insn->bits |= (1 << 3);
+ }
+
+ if (insn->dsp_action_flags & DSP_ACTION_CLAMP8)
+ {
+ if ((template->meta_opcode >> 26) & 0x1)
+ {
+ as_bad (_("conditional instruction cannot use B flag"));
+ return NULL;
+ }
+
+ insn->bits |= (0x3 << 2);
+ }
+
+ if (insn->dsp_action_flags & DSP_ACTION_ROUND)
+ {
+ if ((template->meta_opcode >> 26) & 0x1)
+ {
+ as_bad (_("conditional instruction cannot use R flag"));
+ return NULL;
+ }
+ insn->bits |= (1 << 2);
+ }
+
+ /* Conditional Data Unit Shift instructions cannot be dual unit. */
+ if ((template->meta_opcode >> 26) & 0x1)
+ ls_shift = INVALID_SHIFT;
+
+ /* The Condition Is Always (CA) bit must be set if we're targetting a
+ Ux.r register as the destination. This means that we can't have
+ any other condition bits set. */
+ if (!is_same_data_unit (regs[1]->unit, regs[0]->unit))
+ {
+ /* Set both the Conditional bit and the Condition is Always bit. */
+ insn->bits |= (1 << 26);
+ insn->bits |= (1 << 5);
+
+ /* Fill out the Ud field. */
+ insn->bits |= (regs[0]->unit << 1);
+ }
+
+ if (IS_TEMPLATE_DEF (insn))
+ {
+ l = interpret_template_regs(l, insn, regs, regs_shift, &load,
+ &dspram, size, &ls_shift, &au_shift,
+ &au, &imm, &imm_shift, &imm_mask);
+
+ if (l == NULL)
+ return NULL;
+
+ if (!dspram)
+ mx_shift = 5;
+ }
+
+ /* Fall through. */
+ matched:
+
+ /* Set the registers and immediate values. */
+ if (regs_shift[0] != INVALID_SHIFT)
+ insn->bits |= (regs[0]->no << regs_shift[0]);
+
+ if (regs_shift[1] != INVALID_SHIFT)
+ insn->bits |= (regs[1]->no << regs_shift[1]);
+
+ if (regs_shift[2] != INVALID_SHIFT)
+ insn->bits |= (regs[2]->no << regs_shift[2]);
+
+ /* Does this insn have an 'IMM' bit? The immediate value should
+ already have been masked. */
+ if (imm_shift != INVALID_SHIFT)
+ insn->bits |= ((imm & imm_mask) << imm_shift);
+
+ /* Does this insn have an 'AU' bit? */
+ if (au_shift != INVALID_SHIFT)
+ insn->bits |= (au << au_shift);
+
+ /* Does this instruction have an 'LS' bit? */
+ if (ls_shift != INVALID_SHIFT)
+ insn->bits |= (load << ls_shift);
+
+ /* Does this instruction have an 'AR' bit? */
+ if (ar)
+ insn->bits |= (1 << ar_shift);
+
+ if (du_shift != INVALID_SHIFT)
+ insn->bits |= (du << du_shift);
+
+ if (sc_shift != INVALID_SHIFT)
+ insn->bits |= (sc << sc_shift);
+
+ if (om_shift != INVALID_SHIFT)
+ insn->bits |= (om << om_shift);
+
+ if (o2r_shift != INVALID_SHIFT)
+ insn->bits |= (o2r << o2r_shift);
+
+ if (qn_shift != INVALID_SHIFT)
+ insn->bits |= (qn << qn_shift);
+
+ if (qr_shift != INVALID_SHIFT)
+ insn->bits |= (qr << qr_shift);
+
+ if (qd_shift != INVALID_SHIFT)
+ insn->bits |= (is_quickrot64 << qd_shift);
+
+ if (a1_shift != INVALID_SHIFT)
+ insn->bits |= (a1 << a1_shift);
+
+ if (a2_shift != INVALID_SHIFT)
+ insn->bits |= (a2 << a2_shift);
+
+ if (su_shift != INVALID_SHIFT)
+ insn->bits |= (su << su_shift);
+
+ if (imm_shift != INVALID_SHIFT)
+ insn->bits |= ((imm & imm_mask) << imm_shift);
+
+ if (ac_shift != INVALID_SHIFT)
+ insn->bits |= (ac << ac_shift);
+
+ if (mx_shift != INVALID_SHIFT)
+ insn->bits |= (mx << mx_shift);
+
+ if (is_dual)
+ {
+ if (l1_shift == INVALID_SHIFT)
+ {
+ as_bad (_("'L' modifier not valid for this instruction"));
+ return NULL;
+ }
+
+ insn->bits |= (1 << l1_shift);
+ }
+
+ insn->len = 4;
+
+ return l;
+}
+
+typedef const char *(*insn_parser)(const char *, metag_insn *,
+ const insn_template *);
+
+/* Parser table. */
+static const insn_parser insn_parsers[ENC_MAX] =
+ {
+ [ENC_NONE] = parse_none,
+ [ENC_MOV_U2U] = parse_mov_u2u,
+ [ENC_MOV_PORT] = parse_mov_port,
+ [ENC_MMOV] = parse_mmov,
+ [ENC_MDRD] = parse_mdrd,
+ [ENC_MOVL_TTREC] = parse_movl_ttrec,
+ [ENC_GET_SET] = parse_get_set,
+ [ENC_GET_SET_EXT] = parse_get_set_ext,
+ [ENC_MGET_MSET] = parse_mget_mset,
+ [ENC_COND_SET] = parse_cond_set,
+ [ENC_XFR] = parse_xfr,
+ [ENC_MOV_CT] = parse_mov_ct,
+ [ENC_SWAP] = parse_swap,
+ [ENC_JUMP] = parse_jump,
+ [ENC_CALLR] = parse_callr,
+ [ENC_ALU] = parse_alu,
+ [ENC_SHIFT] = parse_shift,
+ [ENC_MIN_MAX] = parse_min_max,
+ [ENC_BITOP] = parse_bitop,
+ [ENC_CMP] = parse_cmp,
+ [ENC_BRANCH] = parse_branch,
+ [ENC_KICK] = parse_kick,
+ [ENC_SWITCH] = parse_switch,
+ [ENC_CACHER] = parse_cacher,
+ [ENC_CACHEW] = parse_cachew,
+ [ENC_ICACHE] = parse_icache,
+ [ENC_LNKGET] = parse_lnkget,
+ [ENC_FMOV] = parse_fmov,
+ [ENC_FMMOV] = parse_fmmov,
+ [ENC_FMOV_DATA] = parse_fmov_data,
+ [ENC_FMOV_I] = parse_fmov_i,
+ [ENC_FPACK] = parse_fpack,
+ [ENC_FSWAP] = parse_fswap,
+ [ENC_FCMP] = parse_fcmp,
+ [ENC_FMINMAX] = parse_fminmax,
+ [ENC_FCONV] = parse_fconv,
+ [ENC_FCONVX] = parse_fconvx,
+ [ENC_FBARITH] = parse_fbarith,
+ [ENC_FEARITH] = parse_fearith,
+ [ENC_FREC] = parse_frec,
+ [ENC_FSIMD] = parse_fsimd,
+ [ENC_FGET_SET_ACF] = parse_fget_set_acf,
+ [ENC_DGET_SET] = parse_dget_set,
+ [ENC_DTEMPLATE] = parse_dtemplate,
+ [ENC_DALU] = parse_dalu,
+ };
+
+struct metag_core_option
+{
+ char *name;
+ unsigned int value;
+};
+
+/* CPU type options. */
+static const struct metag_core_option metag_cpus[] =
+ {
+ {"all", CoreMeta11|CoreMeta12|CoreMeta21},
+ {"metac11", CoreMeta11},
+ {"metac12", CoreMeta12},
+ {"metac21", CoreMeta21},
+ {NULL, 0},
+ };
+
+/* FPU type options. */
+static const struct metag_core_option metag_fpus[] =
+ {
+ {"metac21", FpuMeta21},
+ {NULL, 0},
+ };
+
+/* DSP type options. */
+static const struct metag_core_option metag_dsps[] =
+ {
+ {"metac21", DspMeta21},
+ {NULL, 0},
+ };
+
+/* Parse a CPU command line option. */
+static int
+metag_parse_cpu (char * str)
+{
+ const struct metag_core_option * opt;
+ int optlen;
+
+ optlen = strlen (str);
+
+ if (optlen == 0)
+ {
+ as_bad (_("missing cpu name `%s'"), str);
+ return 0;
+ }
+
+ for (opt = metag_cpus; opt->name != NULL; opt++)
+ if (strncmp (opt->name, str, optlen) == 0)
+ {
+ mcpu_opt = opt->value;
+ return 1;
+ }
+
+ as_bad (_("unknown cpu `%s'"), str);
+ return 0;
+}
+
+/* Parse an FPU command line option. */
+static int
+metag_parse_fpu (char * str)
+{
+ const struct metag_core_option * opt;
+ int optlen;
+
+ optlen = strlen (str);
+
+ if (optlen == 0)
+ {
+ as_bad (_("missing fpu name `%s'"), str);
+ return 0;
+ }
+
+ for (opt = metag_fpus; opt->name != NULL; opt++)
+ if (strncmp (opt->name, str, optlen) == 0)
+ {
+ mfpu_opt = opt->value;
+ return 1;
+ }
+
+ as_bad (_("unknown fpu `%s'"), str);
+ return 0;
+}
+
+/* Parse a DSP command line option. */
+static int
+metag_parse_dsp (char * str)
+{
+ const struct metag_core_option * opt;
+ int optlen;
+
+ optlen = strlen (str);
+
+ if (optlen == 0)
+ {
+ as_bad (_("missing DSP name `%s'"), str);
+ return 0;
+ }
+
+ for (opt = metag_dsps; opt->name != NULL; opt++)
+ if (strncmp (opt->name, str, optlen) == 0)
+ {
+ mdsp_opt = opt->value;
+ return 1;
+ }
+
+ as_bad (_("unknown DSP `%s'"), str);
+ return 0;
+}
+
+struct metag_long_option
+{
+ char * option; /* Substring to match. */
+ char * help; /* Help information. */
+ int (* func) (char * subopt); /* Function to decode sub-option. */
+ char * deprecated; /* If non-null, print this message. */
+};
+
+struct metag_long_option metag_long_opts[] =
+ {
+ {"mcpu=", N_("<cpu name>\t assemble for CPU <cpu name>"),
+ metag_parse_cpu, NULL},
+ {"mfpu=", N_("<fpu name>\t assemble for FPU architecture <fpu name>"),
+ metag_parse_fpu, NULL},
+ {"mdsp=", N_("<dsp name>\t assemble for DSP architecture <dsp name>"),
+ metag_parse_dsp, NULL},
+ {NULL, NULL, 0, NULL}
+ };
+
+int
+md_parse_option (int c, char * arg)
+{
+ struct metag_long_option *lopt;
+
+ for (lopt = metag_long_opts; lopt->option != NULL; lopt++)
+ {
+ /* These options are expected to have an argument. */
+ if (c == lopt->option[0]
+ && arg != NULL
+ && strncmp (arg, lopt->option + 1,
+ strlen (lopt->option + 1)) == 0)
+ {
+#if WARN_DEPRECATED
+ /* If the option is deprecated, tell the user. */
+ if (lopt->deprecated != NULL)
+ as_tsktsk (_("option `-%c%s' is deprecated: %s"), c, arg,
+ _(lopt->deprecated));
+#endif
+
+ /* Call the sup-option parser. */
+ return lopt->func (arg + strlen (lopt->option) - 1);
+ }
+ }
+
+ return 0;
+}
+
+void
+md_show_usage (FILE * stream)
+{
+ struct metag_long_option *lopt;
+
+ fprintf (stream, _(" Meta specific command line options:\n"));
+
+ for (lopt = metag_long_opts; lopt->option != NULL; lopt++)
+ if (lopt->help != NULL)
+ fprintf (stream, " -%s%s\n", lopt->option, _(lopt->help));
+}
+
+/* The target specific pseudo-ops which we support. */
+const pseudo_typeS md_pseudo_table[] =
+{
+ { "word", cons, 2 },
+ { NULL, NULL, 0 }
+};
+
+void
+md_begin (void)
+{
+ int c;
+
+ for (c = 0; c < 256; c++)
+ {
+ if (ISDIGIT (c))
+ {
+ register_chars[c] = c;
+ /* LOCK0, LOCK1, LOCK2. */
+ mnemonic_chars[c] = c;
+ }
+ else if (ISLOWER (c))
+ {
+ register_chars[c] = c;
+ mnemonic_chars[c] = c;
+ }
+ else if (ISUPPER (c))
+ {
+ register_chars[c] = c;
+ mnemonic_chars[c] = c;
+ }
+ else if (c == '.')
+ {
+ register_chars[c] = c;
+ }
+ }
+}
+
+/* Parse a split condition code prefix. */
+static const char *
+parse_split_condition (const char *line, metag_insn *insn)
+{
+ const char *l = line;
+ const split_condition *scond;
+ split_condition entry;
+ char buf[4];
+
+ memcpy (buf, l, 4);
+ buf[3] = '\0';
+
+ entry.name = buf;
+
+ scond = (const split_condition *) htab_find (scond_htab, &entry);
+
+ if (!scond)
+ return NULL;
+
+ insn->scond = scond->code;
+
+ return l + strlen (scond->name);
+}
+
+/* Parse an instruction prefix - F for float, D for DSP - and associated
+ flags and condition codes. */
+static const char *
+parse_prefix (const char *line, metag_insn *insn)
+{
+ const char *l = line;
+
+ l = skip_whitespace (l);
+
+ insn->type = INSN_GP;
+
+ if (TOLOWER (*l) == FPU_PREFIX_CHAR)
+ {
+ if (strncasecmp (l, FFB_INSN, strlen(FFB_INSN)))
+ {
+ insn->type = INSN_FPU;
+
+ l++;
+
+ if (*l == END_OF_INSN)
+ {
+ as_bad (_("premature end of floating point prefix"));
+ return NULL;
+ }
+
+ if (TOLOWER (*l) == FPU_DOUBLE_CHAR)
+ {
+ insn->fpu_width = FPU_WIDTH_DOUBLE;
+ l++;
+ }
+ else if (TOLOWER (*l) == FPU_PAIR_CHAR)
+ {
+ const char *l2 = l;
+
+ /* Check this isn't a split condition beginning with L. */
+ l2 = parse_split_condition (l2, insn);
+
+ if (l2 && is_whitespace_char (*l2))
+ {
+ l = l2;
+ }
+ else
+ {
+ insn->fpu_width = FPU_WIDTH_PAIR;
+ l++;
+ }
+ }
+ else
+ {
+ insn->fpu_width = FPU_WIDTH_SINGLE;
+ }
+
+ if (TOLOWER (*l) == FPU_ACTION_ABS_CHAR)
+ {
+ insn->fpu_action_flags |= FPU_ACTION_ABS;
+ l++;
+ }
+ else if (TOLOWER (*l) == FPU_ACTION_INV_CHAR)
+ {
+ insn->fpu_action_flags |= FPU_ACTION_INV;
+ l++;
+ }
+
+ if (TOLOWER (*l) == FPU_ACTION_QUIET_CHAR)
+ {
+ insn->fpu_action_flags |= FPU_ACTION_QUIET;
+ l++;
+ }
+
+ if (TOLOWER (*l) == FPU_ACTION_ZERO_CHAR)
+ {
+ insn->fpu_action_flags |= FPU_ACTION_ZERO;
+ l++;
+ }
+
+ if (! is_whitespace_char (*l))
+ {
+ l = parse_split_condition (l, insn);
+
+ if (!l)
+ {
+ as_bad (_("unknown floating point prefix character"));
+ return NULL;
+ }
+ }
+
+ l = skip_space (l);
+ }
+ }
+ else if (TOLOWER (*l) == DSP_PREFIX_CHAR)
+ {
+ if (strncasecmp (l, DCACHE_INSN, strlen (DCACHE_INSN)) &&
+ strncasecmp (l, DEFR_INSN, strlen (DEFR_INSN)))
+ {
+ const char *ll = l;
+ insn->type = INSN_DSP;
+
+ l++;
+
+ insn->dsp_width = DSP_WIDTH_SINGLE;
+
+ while (!is_whitespace_char (*l))
+ {
+ /* We have to check for split condition codes first
+ because they are the longest strings to match,
+ e.g. if the string contains "LLS" we want it to match
+ the split condition code "LLS", not the dual unit
+ character "L". */
+ ll = l;
+ l = parse_split_condition (l, insn);
+
+ if (l == NULL)
+ l = ll;
+ else
+ continue;
+
+ /* Accept an FPU prefix char which may be used when doing
+ template MOV with FPU registers. */
+ if (TOLOWER(*l) == FPU_PREFIX_CHAR)
+ {
+ insn->type = INSN_DSP_FPU;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_DUAL_CHAR)
+ {
+ insn->dsp_width = DSP_WIDTH_DUAL;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_QR64_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_QR64;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_UMUL_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_UMUL;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_ROUND_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_ROUND;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_CLAMP9_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_CLAMP9;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_CLAMP8_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_CLAMP8;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_MOD_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_MOD;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_ACC_ZERO_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_ACC_ZERO;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_ACC_ADD_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_ACC_ADD;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_ACC_SUB_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_ACC_SUB;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_ACTION_OV_CHAR)
+ {
+ insn->dsp_action_flags |= DSP_ACTION_OV;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_DAOPPAME_8_CHAR)
+ {
+ insn->dsp_daoppame_flags |= DSP_DAOPPAME_8;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_DAOPPAME_16_CHAR)
+ {
+ insn->dsp_daoppame_flags |= DSP_DAOPPAME_16;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_DAOPPAME_TEMP_CHAR)
+ {
+ insn->dsp_daoppame_flags |= DSP_DAOPPAME_TEMP;
+ l++;
+ continue;
+ }
+
+ if (TOLOWER(*l) == DSP_DAOPPAME_HIGH_CHAR)
+ {
+ insn->dsp_daoppame_flags |= DSP_DAOPPAME_HIGH;
+ l++;
+ continue;
+ }
+
+ as_bad (_("unknown DSP prefix character %c %s"), *l, l);
+ return NULL;
+ }
+
+ l = skip_space (l);
+ }
+ }
+
+ return l;
+}
+
+/* Return a list of appropriate instruction parsers for MNEMONIC. */
+static insn_templates *
+find_insn_templates (const char *mnemonic)
+{
+ insn_template template;
+ insn_templates entry;
+ insn_templates *slot;
+
+ entry.template = &template;
+
+ memcpy ((void *)&entry.template->name, &mnemonic, sizeof (char *));
+
+ slot = (insn_templates *) htab_find (mnemonic_htab, &entry);
+
+ if (slot)
+ return slot;
+
+ return NULL;
+}
+
+/* Make an uppercase copy of SRC into DST and return DST. */
+static char *
+strupper (char * dst, const char *src)
+{
+ size_t i = 0;
+
+ while (src[i])
+ {
+ dst[i] = TOUPPER (src[i]);
+ i++;
+ }
+
+ dst[i] = 0;
+
+ return dst;
+}
+
+/* Calculate a hash value for a template. */
+static hashval_t
+hash_templates (const void *p)
+{
+ insn_templates *tp = (insn_templates *)p;
+ char buf[MAX_MNEMONIC_LEN];
+
+ strupper (buf, tp->template->name);
+
+ return htab_hash_string (buf);
+}
+
+/* Check if two templates are equal. */
+static int
+eq_templates (const void *a, const void *b)
+{
+ insn_templates *ta = (insn_templates *)a;
+ insn_templates *tb = (insn_templates *)b;
+ return strcasecmp (ta->template->name, tb->template->name) == 0;
+}
+
+/* Create the hash table required for parsing instructions. */
+static void
+create_mnemonic_htab (void)
+{
+ size_t i, num_templates = sizeof(metag_optab)/sizeof(metag_optab[0]);
+
+ mnemonic_htab = htab_create_alloc (num_templates, hash_templates,
+ eq_templates, NULL, xcalloc, free);
+
+ for (i = 0; i < num_templates; i++)
+ {
+ const insn_template *template = &metag_optab[i];
+ insn_templates **slot = NULL;
+ insn_templates *new_entry;
+
+ new_entry = xmalloc (sizeof (insn_templates));
+
+ new_entry->template = template;
+ new_entry->next = NULL;
+
+ slot = (insn_templates **) htab_find_slot (mnemonic_htab, new_entry,
+ INSERT);
+
+ if (*slot)
+ {
+ insn_templates *last_entry = *slot;
+
+ while (last_entry->next)
+ last_entry = last_entry->next;
+
+ last_entry->next = new_entry;
+ }
+ else
+ {
+ *slot = new_entry;
+ }
+ }
+}
+
+/* Calculate a hash value for a register. */
+static hashval_t
+hash_regs (const void *p)
+{
+ metag_reg *rp = (metag_reg *)p;
+ char buf[MAX_REG_LEN];
+
+ strupper (buf, rp->name);
+
+ return htab_hash_string (buf);
+}
+
+/* Check if two registers are equal. */
+static int
+eq_regs (const void *a, const void *b)
+{
+ metag_reg *ra = (metag_reg *)a;
+ metag_reg *rb = (metag_reg *)b;
+ return strcasecmp (ra->name, rb->name) == 0;
+}
+
+/* Create the hash table required for parsing registers. */
+static void
+create_reg_htab (void)
+{
+ size_t i, num_regs = sizeof(metag_regtab)/sizeof(metag_regtab[0]);
+
+ reg_htab = htab_create_alloc (num_regs, hash_regs,
+ eq_regs, NULL, xcalloc, free);
+
+ for (i = 0; i < num_regs; i++)
+ {
+ const metag_reg *reg = &metag_regtab[i];
+ const metag_reg **slot;
+
+ slot = (const metag_reg **) htab_find_slot (reg_htab, reg, INSERT);
+
+ if (!*slot)
+ *slot = reg;
+ }
+}
+
+/* Create the hash table required for parsing DSP registers. */
+static void
+create_dspreg_htabs (void)
+{
+ size_t i, num_regs = sizeof(metag_dsp_regtab)/sizeof(metag_dsp_regtab[0]);
+ size_t h;
+
+ dsp_reg_htab = htab_create_alloc (num_regs, hash_regs,
+ eq_regs, NULL, xcalloc, free);
+
+ for (i = 0; i < num_regs; i++)
+ {
+ const metag_reg *reg = &metag_dsp_regtab[i];
+ const metag_reg **slot;
+
+ slot = (const metag_reg **) htab_find_slot (dsp_reg_htab, reg, INSERT);
+
+ /* Make sure there are no hash table collisions, which would
+ require chaining entries. */
+ BFD_ASSERT (*slot == NULL);
+ *slot = reg;
+ }
+
+ num_regs = sizeof(metag_dsp_tmpl_regtab[0])/sizeof(metag_dsp_tmpl_regtab[0][0]);
+
+ for (h = 0; h < 2; h++)
+ {
+ dsp_tmpl_reg_htab[h] = htab_create_alloc (num_regs, hash_regs,
+ eq_regs, NULL, xcalloc, free);
+ }
+
+ for (h = 0; h < 2; h++)
+ {
+ for (i = 0; i < num_regs; i++)
+ {
+ const metag_reg *reg = &metag_dsp_tmpl_regtab[h][i];
+ const metag_reg **slot;
+ slot = (const metag_reg **) htab_find_slot (dsp_tmpl_reg_htab[h],
+ reg, INSERT);
+
+ /* Make sure there are no hash table collisions, which would
+ require chaining entries. */
+ BFD_ASSERT (*slot == NULL);
+ *slot = reg;
+ }
+ }
+}
+
+/* Calculate a hash value for a split condition code. */
+static hashval_t
+hash_scond (const void *p)
+{
+ split_condition *cp = (split_condition *)p;
+ char buf[4];
+
+ strupper (buf, cp->name);
+
+ return htab_hash_string (buf);
+}
+
+/* Check if two split condition codes are equal. */
+static int
+eq_scond (const void *a, const void *b)
+{
+ split_condition *ra = (split_condition *)a;
+ split_condition *rb = (split_condition *)b;
+
+ return strcasecmp (ra->name, rb->name) == 0;
+}
+
+/* Create the hash table required for parsing split condition codes. */
+static void
+create_scond_htab (void)
+{
+ size_t i, nentries;
+
+ nentries = sizeof (metag_scondtab) / sizeof (metag_scondtab[0]);
+
+ scond_htab = htab_create_alloc (nentries, hash_scond, eq_scond,
+ NULL, xcalloc, free);
+ for (i = 0; i < nentries; i++)
+ {
+ const split_condition *scond = &metag_scondtab[i];
+ const split_condition **slot;
+
+ slot = (const split_condition **) htab_find_slot (scond_htab,
+ scond, INSERT);
+ /* Make sure there are no hash table collisions, which would
+ require chaining entries. */
+ BFD_ASSERT (*slot == NULL);
+ *slot = scond;
+ }
+}
+
+/* Entry point for instruction parsing. */
+static bfd_boolean
+parse_insn (const char *line, metag_insn *insn)
+{
+ char mnemonic[MAX_MNEMONIC_LEN];
+ const char *l = line;
+ size_t mnemonic_len = 0;
+ insn_templates *templates;
+
+ l = skip_space (l);
+
+ while (is_mnemonic_char(*l))
+ {
+ l++;
+ mnemonic_len++;
+ }
+
+ if (mnemonic_len >= MAX_MNEMONIC_LEN)
+ {
+ as_bad (_("instruction mnemonic too long: %s"), line);
+ return FALSE;
+ }
+
+ strncpy(mnemonic, line, mnemonic_len);
+
+ mnemonic[mnemonic_len] = '\0';
+
+ templates = find_insn_templates (mnemonic);
+
+ if (templates)
+ {
+ insn_templates *current_template = templates;
+
+ l = skip_space (l);
+
+ while (current_template)
+ {
+ const insn_template *template = current_template->template;
+ enum insn_encoding encoding = template->encoding;
+ insn_parser parser = insn_parsers[encoding];
+
+ current_template = current_template->next;
+
+ if (template->insn_type == INSN_GP &&
+ !(template->core_flags & mcpu_opt))
+ continue;
+
+ if (template->insn_type == INSN_FPU &&
+ !(template->core_flags & mfpu_opt))
+ continue;
+
+ if (template->insn_type == INSN_DSP &&
+ !(template->core_flags & mdsp_opt))
+ continue;
+
+ if (template->insn_type == INSN_DSP_FPU &&
+ !((template->core_flags & mdsp_opt) &&
+ (template->core_flags & mfpu_opt)))
+ continue;
+
+ /* DSP instructions always require special decoding */
+ if ((insn->type == INSN_DSP && (template->insn_type != INSN_DSP)) ||
+ ((template->insn_type == INSN_DSP) && insn->type != INSN_DSP) ||
+ (insn->type == INSN_DSP_FPU && (template->insn_type != INSN_DSP_FPU)) ||
+ ((template->insn_type == INSN_DSP_FPU) && insn->type != INSN_DSP_FPU))
+ continue;
+
+ if (parser)
+ {
+ const char *end = parser(l, insn, template);
+
+ if (end != NULL)
+ {
+ if (*end != END_OF_INSN)
+ as_bad (_("junk at end of line: \"%s\""), line);
+ else
+ return TRUE;
+ }
+ }
+ }
+
+ as_bad (_("failed to assemble instruction: \"%s\""), line);
+ }
+ else
+ {
+ if (insn->type == INSN_FPU)
+ as_bad (_("unknown floating point mnemonic: \"%s\""), mnemonic);
+ else
+ as_bad (_("unknown mnemonic: \"%s\""), mnemonic);
+ }
+ return FALSE;
+}
+
+static void
+output_insn (metag_insn *insn)
+{
+ char *output;
+
+ output = frag_more (insn->len);
+ dwarf2_emit_insn (insn->len);
+
+ if (insn->reloc_type != BFD_RELOC_UNUSED)
+ {
+ fix_new_exp (frag_now, output - frag_now->fr_literal,
+ insn->reloc_size, &insn->reloc_exp,
+ insn->reloc_pcrel, insn->reloc_type);
+ }
+
+ md_number_to_chars (output, insn->bits, insn->len);
+}
+
+void
+md_assemble (char *line)
+{
+ const char *l = line;
+ metag_insn insn;
+
+ memset (&insn, 0, sizeof(insn));
+
+ insn.reloc_type = BFD_RELOC_UNUSED;
+ insn.reloc_pcrel = 0;
+ insn.reloc_size = 4;
+
+ if (!mnemonic_htab)
+ {
+ create_mnemonic_htab ();
+ create_reg_htab ();
+ create_dspreg_htabs ();
+ create_scond_htab ();
+ }
+
+ l = parse_prefix (l, &insn);
+
+ if (l == NULL)
+ return;
+
+ if (insn.type == INSN_DSP &&
+ !mdsp_opt)
+ {
+ as_bad (_("cannot assemble DSP instruction, DSP option not set: %s"),
+ line);
+ return;
+ }
+ else if (insn.type == INSN_FPU &&
+ !mfpu_opt)
+ {
+ as_bad (_("cannot assemble FPU instruction, FPU option not set: %s"),
+ line);
+ return;
+ }
+
+ if (!parse_insn (l, &insn))
+ return;
+
+ output_insn (&insn);
+}
+
+void
+md_operand (expressionS * expressionP)
+{
+ if (* input_line_pointer == IMM_CHAR)
+ {
+ input_line_pointer ++;
+ expression (expressionP);
+ }
+}
+
+valueT
+md_section_align (segT segment ATTRIBUTE_UNUSED, valueT size)
+{
+ return size;
+}
+
+symbolS *
+md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
+{
+ return NULL;
+}
+
+/* Functions concerning relocs. */
+
+/* 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)
+{
+ if ((fixP->fx_addsy != (symbolS *) NULL
+ && (! S_IS_DEFINED (fixP->fx_addsy)
+ || S_GET_SEGMENT (fixP->fx_addsy) != sec))
+ || metag_force_relocation (fixP))
+ {
+ /* The symbol is undefined (or is defined but not in this section).
+ Let the linker figure it out. */
+ return 0;
+ }
+
+ return fixP->fx_frag->fr_address + fixP->fx_where;
+}
+
+/* Write a value out to the object file, using the appropriate endianness. */
+
+void
+md_number_to_chars (char * buf, valueT val, int n)
+{
+ number_to_chars_littleendian (buf, val, n);
+}
+
+/* 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.
+*/
+
+/* Equal to MAX_PRECISION in atof-ieee.c */
+#define MAX_LITTLENUMS 6
+
+char *
+md_atof (int type, char * litP, int * sizeP)
+{
+ int i;
+ int prec;
+ LITTLENUM_TYPE words [MAX_LITTLENUMS];
+ 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;
+
+ /* FIXME: Some targets allow other format chars for bigger sizes here. */
+
+ default:
+ * sizeP = 0;
+ return _("Bad call to md_atof()");
+ }
+
+ t = atof_ieee (input_line_pointer, type, words);
+ if (t)
+ input_line_pointer = t;
+ * sizeP = prec * sizeof (LITTLENUM_TYPE);
+
+ for (i = 0; i < prec; i++)
+ {
+ md_number_to_chars (litP, (valueT) words[i],
+ sizeof (LITTLENUM_TYPE));
+ litP += sizeof (LITTLENUM_TYPE);
+ }
+
+ return 0;
+}
+
+/* If this function returns non-zero, it prevents the relocation
+ against symbol(s) in the FIXP from being replaced with relocations
+ against section symbols, and guarantees that a relocation will be
+ emitted even when the value can be resolved locally. */
+
+int
+metag_force_relocation (fixS * fix)
+{
+ switch (fix->fx_r_type)
+ {
+ case BFD_RELOC_METAG_RELBRANCH_PLT:
+ case BFD_RELOC_METAG_TLS_LE:
+ case BFD_RELOC_METAG_TLS_IE:
+ case BFD_RELOC_METAG_TLS_LDO:
+ case BFD_RELOC_METAG_TLS_LDM:
+ case BFD_RELOC_METAG_TLS_GD:
+ return 1;
+ default:
+ ;
+ }
+
+ return generic_force_reloc (fix);
+}
+
+bfd_boolean
+metag_fix_adjustable (fixS * fixP)
+{
+ if (fixP->fx_addsy == NULL)
+ return 1;
+
+ /* Prevent all adjustments to global symbols. */
+ if (S_IS_EXTERNAL (fixP->fx_addsy))
+ return 0;
+ if (S_IS_WEAK (fixP->fx_addsy))
+ return 0;
+
+ if (fixP->fx_r_type == BFD_RELOC_METAG_HI16_GOTOFF ||
+ fixP->fx_r_type == BFD_RELOC_METAG_LO16_GOTOFF ||
+ fixP->fx_r_type == BFD_RELOC_METAG_GETSET_GOTOFF ||
+ fixP->fx_r_type == BFD_RELOC_METAG_GETSET_GOT ||
+ fixP->fx_r_type == BFD_RELOC_METAG_HI16_GOTPC ||
+ fixP->fx_r_type == BFD_RELOC_METAG_LO16_GOTPC ||
+ fixP->fx_r_type == BFD_RELOC_METAG_HI16_PLT ||
+ fixP->fx_r_type == BFD_RELOC_METAG_LO16_PLT ||
+ fixP->fx_r_type == BFD_RELOC_METAG_RELBRANCH_PLT)
+ return 0;
+
+ /* We need the symbol name for the VTABLE entries. */
+ if (fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT
+ || fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
+ return 0;
+
+ return 1;
+}
+
+/* Return an initial guess of the length by which a fragment must grow to
+ hold a branch to reach its destination.
+ Also updates fr_type/fr_subtype as necessary.
+
+ Called just before doing relaxation.
+ Any symbol that is now undefined will not become defined.
+ The guess for fr_var is ACTUALLY the growth beyond fr_fix.
+ Whatever we do to grow fr_fix or fr_var contributes to our returned value.
+ Although it may not be explicit in the frag, pretend fr_var starts with a
+ 0 value. */
+
+int
+md_estimate_size_before_relax (fragS * fragP ATTRIBUTE_UNUSED,
+ segT segment ATTRIBUTE_UNUSED)
+{
+ /* No assembler relaxation is defined (or necessary) for this port. */
+ abort ();
+}
+
+/* *fragP has been relaxed to its final size, and now needs to have
+ the bytes inside it modified to conform to the new size.
+
+ Called after relaxation is finished.
+ fragP->fr_type == rs_machine_dependent.
+ fragP->fr_subtype is the subtype of what the address relaxed to. */
+
+void
+md_convert_frag (bfd * abfd ATTRIBUTE_UNUSED, segT sec ATTRIBUTE_UNUSED,
+ fragS * fragP ATTRIBUTE_UNUSED)
+{
+ /* No assembler relaxation is defined (or necessary) for this port. */
+ abort ();
+}
+
+/* This is called from HANDLE_ALIGN in tc-metag.h. */
+
+void
+metag_handle_align (fragS * fragP)
+{
+ static char const noop[4] = { 0xfe, 0xff, 0xff, 0xa0 };
+ int bytes, fix;
+ char *p;
+
+ if (fragP->fr_type != rs_align_code)
+ return;
+
+ bytes = fragP->fr_next->fr_address - fragP->fr_address - fragP->fr_fix;
+ p = fragP->fr_literal + fragP->fr_fix;
+ fix = 0;
+
+ if (bytes & 3)
+ {
+ fix = bytes & 3;
+ memset (p, 0, fix);
+ p += fix;
+ bytes -= fix;
+ }
+
+ while (bytes >= 4)
+ {
+ memcpy (p, noop, 4);
+ p += 4;
+ bytes -= 4;
+ fix += 4;
+ }
+
+ fragP->fr_fix += fix;
+ fragP->fr_var = 4;
+}
+
+static char *
+metag_end_of_match (char * cont, char * what)
+{
+ int len = strlen (what);
+
+ if (strncasecmp (cont, what, strlen (what)) == 0
+ && ! is_part_of_name (cont[len]))
+ return cont + len;
+
+ return NULL;
+}
+
+int
+metag_parse_name (char const * name, expressionS * exprP, enum expr_mode mode,
+ char * nextcharP)
+{
+ char *next = input_line_pointer;
+ char *next_end;
+ int reloc_type;
+ operatorT op_type;
+ segT segment;
+
+ exprP->X_op_symbol = NULL;
+ exprP->X_md = BFD_RELOC_UNUSED;
+
+ if (strcmp (name, GOT_NAME) == 0)
+ {
+ if (! GOT_symbol)
+ GOT_symbol = symbol_find_or_make (name);
+
+ exprP->X_add_symbol = GOT_symbol;
+ no_suffix:
+ /* If we have an absolute symbol or a
+ reg, then we know its value now. */
+ segment = S_GET_SEGMENT (exprP->X_add_symbol);
+ if (mode != expr_defer && segment == absolute_section)
+ {
+ exprP->X_op = O_constant;
+ exprP->X_add_number = S_GET_VALUE (exprP->X_add_symbol);
+ exprP->X_add_symbol = NULL;
+ }
+ else if (mode != expr_defer && segment == reg_section)
+ {
+ exprP->X_op = O_register;
+ exprP->X_add_number = S_GET_VALUE (exprP->X_add_symbol);
+ exprP->X_add_symbol = NULL;
+ }
+ else
+ {
+ exprP->X_op = O_symbol;
+ exprP->X_add_number = 0;
+ }
+
+ return 1;
+ }
+
+ exprP->X_add_symbol = symbol_find_or_make (name);
+
+ if (*nextcharP != '@')
+ goto no_suffix;
+ else if ((next_end = metag_end_of_match (next + 1, "GOTOFF")))
+ {
+ reloc_type = BFD_RELOC_METAG_GOTOFF;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "GOT")))
+ {
+ reloc_type = BFD_RELOC_METAG_GETSET_GOT;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "PLT")))
+ {
+ reloc_type = BFD_RELOC_METAG_PLT;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "TLSGD")))
+ {
+ reloc_type = BFD_RELOC_METAG_TLS_GD;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "TLSLDM")))
+ {
+ reloc_type = BFD_RELOC_METAG_TLS_LDM;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "TLSLDO")))
+ {
+ reloc_type = BFD_RELOC_METAG_TLS_LDO;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "TLSIE")))
+ {
+ reloc_type = BFD_RELOC_METAG_TLS_IE;
+ op_type = O_PIC_reloc;
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "TLSIENONPIC")))
+ {
+ reloc_type = BFD_RELOC_METAG_TLS_IENONPIC;
+ op_type = O_PIC_reloc; /* FIXME: is this correct? */
+ }
+ else if ((next_end = metag_end_of_match (next + 1, "TLSLE")))
+ {
+ reloc_type = BFD_RELOC_METAG_TLS_LE;
+ op_type = O_PIC_reloc;
+ }
+ else
+ goto no_suffix;
+
+ *input_line_pointer = *nextcharP;
+ input_line_pointer = next_end;
+ *nextcharP = *input_line_pointer;
+ *input_line_pointer = '\0';
+
+ exprP->X_op = op_type;
+ exprP->X_add_number = 0;
+ exprP->X_md = reloc_type;
+
+ return 1;
+}
+
+/* If while processing a fixup, a reloc really needs to be created
+ then it is done here. */
+
+arelent *
+tc_gen_reloc (seg, fixp)
+ asection *seg ATTRIBUTE_UNUSED;
+ fixS *fixp;
+{
+ arelent *reloc;
+
+ reloc = (arelent *) xmalloc (sizeof (arelent));
+ reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
+ *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
+ reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
+
+ reloc->addend = fixp->fx_offset;
+ reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
+
+ if (reloc->howto == (reloc_howto_type *) NULL)
+ {
+ as_bad_where (fixp->fx_file, fixp->fx_line,
+ /* xgettext:c-format. */
+ _("reloc %d not supported by object file format"),
+ (int) fixp->fx_r_type);
+
+ xfree (reloc);
+
+ return NULL;
+ }
+
+ return reloc;
+}
+
+static unsigned int
+md_chars_to_number (char *val, int n)
+{
+ int retval;
+ unsigned char * where = (unsigned char *) val;
+
+ for (retval = 0; n--;)
+ {
+ retval <<= 8;
+ retval |= where[n];
+ }
+ return retval;
+}
+
+void
+md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
+{
+ char *buf = fixP->fx_where + fixP->fx_frag->fr_literal;
+ int value = (int)*valP;
+
+ switch (fixP->fx_r_type)
+ {
+ case BFD_RELOC_METAG_TLS_GD:
+ case BFD_RELOC_METAG_TLS_LE_HI16:
+ case BFD_RELOC_METAG_TLS_LE_LO16:
+ case BFD_RELOC_METAG_TLS_IE:
+ case BFD_RELOC_METAG_TLS_IENONPIC_HI16:
+ case BFD_RELOC_METAG_TLS_IENONPIC_LO16:
+ case BFD_RELOC_METAG_TLS_LDM:
+ case BFD_RELOC_METAG_TLS_LDO_HI16:
+ case BFD_RELOC_METAG_TLS_LDO_LO16:
+ S_SET_THREAD_LOCAL (fixP->fx_addsy);
+ /* Fall through */
+
+ case BFD_RELOC_METAG_HIADDR16:
+ case BFD_RELOC_METAG_LOADDR16:
+ case BFD_RELOC_VTABLE_INHERIT:
+ case BFD_RELOC_VTABLE_ENTRY:
+ fixP->fx_done = FALSE;
+ break;
+
+ case BFD_RELOC_METAG_REL8:
+ if (!within_unsigned_range (value, IMM8_BITS))
+ {
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ "rel8 out of range %d", value);
+ }
+ else
+ {
+ unsigned int newval;
+ newval = md_chars_to_number (buf, 4);
+ newval = (newval & 0xffffc03f) | ((value & IMM8_MASK) << 6);
+ md_number_to_chars (buf, newval, 4);
+ }
+ break;
+ case BFD_RELOC_METAG_REL16:
+ if (!within_unsigned_range (value, IMM16_BITS))
+ {
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ "rel16 out of range %d", value);
+ }
+ else
+ {
+ unsigned int newval;
+ newval = md_chars_to_number (buf, 4);
+ newval = (newval & 0xfff80007) | ((value & IMM16_MASK) << 3);
+ md_number_to_chars (buf, newval, 4);
+ }
+ break;
+
+ case BFD_RELOC_8:
+ md_number_to_chars (buf, value, 1);
+ break;
+ case BFD_RELOC_16:
+ md_number_to_chars (buf, value, 2);
+ break;
+ case BFD_RELOC_32:
+ md_number_to_chars (buf, value, 4);
+ break;
+ case BFD_RELOC_64:
+ md_number_to_chars (buf, value, 8);
+
+ case BFD_RELOC_METAG_RELBRANCH:
+ if (!value)
+ break;
+
+ value = value / 4;
+
+ if (!within_signed_range (value, IMM19_BITS))
+ {
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ "relbranch out of range %d", value);
+ }
+ else
+ {
+ unsigned int newval;
+ newval = md_chars_to_number (buf, 4);
+ newval = (newval & 0xff00001f) | ((value & IMM19_MASK) << 5);
+ md_number_to_chars (buf, newval, 4);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (fixP->fx_addsy == NULL)
+ fixP->fx_done = TRUE;
+}
diff --git a/gas/config/tc-metag.h b/gas/config/tc-metag.h
new file mode 100644
index 0000000..e0411f5
--- /dev/null
+++ b/gas/config/tc-metag.h
@@ -0,0 +1,72 @@
+/* tc-metag.h -- Header file for tc-metag.c.
+ Copyright (C) 2013 Free Software Foundation, Inc.
+ Contributed by Imagination Technologies Ltd.
+
+ 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. */
+
+#define TC_METAG
+
+#define LISTING_HEADER "META GAS "
+
+/* The target BFD architecture. */
+#define TARGET_ARCH bfd_arch_metag
+
+#define TARGET_FORMAT "elf32-metag"
+
+#define TARGET_BYTES_BIG_ENDIAN 0
+
+/* Permit temporary numeric labels. */
+#define LOCAL_LABELS_FB 1
+
+#define DIFF_EXPR_OK /* foo-. gets turned into PC relative relocs */
+
+/* We don't need to handle .word strangely. */
+#define WORKING_DOT_WORD
+
+/* Values passed to md_apply_fix don't include the symbol value. */
+#define MD_APPLY_SYM_VALUE(FIX) 0
+
+#define tc_fix_adjustable(FIX) metag_fix_adjustable (FIX)
+extern bfd_boolean metag_fix_adjustable (struct fix *);
+
+#define TC_FORCE_RELOCATION(fix) metag_force_relocation (fix)
+extern int metag_force_relocation (struct fix *);
+
+#define TC_HANDLES_FX_DONE
+
+/* Call md_pcrel_from_section(), not md_pcrel_from(). */
+#define MD_PCREL_FROM_SECTION(FIX, SEC) md_pcrel_from_section (FIX, SEC)
+extern long md_pcrel_from_section (struct fix *, segT);
+
+#define HANDLE_ALIGN(fragp) metag_handle_align (fragp)
+extern void metag_handle_align (struct frag *);
+
+#define DWARF2_LINE_MIN_INSN_LENGTH 1
+
+#define md_parse_name(name, exprP, mode, nextcharP) \
+ metag_parse_name ((name), (exprP), (mode), (nextcharP))
+extern int metag_parse_name (char const *, expressionS *, enum expr_mode, char *);
+
+/* This is used to construct expressions out of @GOTOFF, @PLT and @GOT
+ symbols. The relocation type is stored in X_md. */
+#define O_PIC_reloc O_md1
+
+#define TC_CASE_SENSITIVE
+
+extern const char metag_symbol_chars[];
+#define tc_symbol_chars metag_symbol_chars
diff --git a/gas/configure.tgt b/gas/configure.tgt
index 6ce3aca..8454ab9 100644
--- a/gas/configure.tgt
+++ b/gas/configure.tgt
@@ -306,6 +306,9 @@ case ${generic_target} in
mep-*-elf) fmt=elf ;;
+ metag-*-elf) fmt=elf ;;
+ metag-*-linux*) fmt=elf em=linux ;;
+
mcore-*-elf) fmt=elf ;;
mcore-*-pe) fmt=coff em=pe bfd_gas=yes ;;
diff --git a/gas/doc/Makefile.am b/gas/doc/Makefile.am
index 57c5f81..e7e1be5 100644
--- a/gas/doc/Makefile.am
+++ b/gas/doc/Makefile.am
@@ -68,6 +68,7 @@ CPU_DOCS = \
c-m32r.texi \
c-m68hc11.texi \
c-m68k.texi \
+ c-metag.texi \
c-microblaze.texi \
c-mips.texi \
c-mmix.texi \
diff --git a/gas/doc/Makefile.in b/gas/doc/Makefile.in
index d075e81..4d0eb6d 100644
--- a/gas/doc/Makefile.in
+++ b/gas/doc/Makefile.in
@@ -310,6 +310,7 @@ CPU_DOCS = \
c-m32r.texi \
c-m68hc11.texi \
c-m68k.texi \
+ c-metag.texi \
c-microblaze.texi \
c-mips.texi \
c-mmix.texi \
diff --git a/gas/doc/all.texi b/gas/doc/all.texi
index e294208..b213ad7 100644
--- a/gas/doc/all.texi
+++ b/gas/doc/all.texi
@@ -52,6 +52,7 @@
@set M68HC11
@set M680X0
@set MCORE
+@set METAG
@set MICROBLAZE
@set MIPS
@set MMIX
diff --git a/gas/doc/as.texinfo b/gas/doc/as.texinfo
index 8c43940..0d04932 100644
--- a/gas/doc/as.texinfo
+++ b/gas/doc/as.texinfo
@@ -387,6 +387,11 @@ gcc(1), ld(1), and the Info entries for @file{binutils} and @file{ld}.
[@b{-jsri2bsr}] [@b{-sifilter}] [@b{-relax}]
[@b{-mcpu=[210|340]}]
@end ifset
+@ifset METAG
+
+@emph{Target Meta options:}
+ [@b{-mcpu=@var{cpu}}] [@b{-mfpu=@var{cpu}}] [@b{-mdsp=@var{cpu}}]
+@end ifset
@ifset MICROBLAZE
@emph{Target MICROBLAZE options:}
@c MicroBlaze has no machine-dependent assembler options.
@@ -1372,7 +1377,28 @@ Assemble for a little endian target.
@end table
@end ifset
+@c man end
+@ifset METAG
+
+@ifclear man
+@xref{Meta Options}, for the options available when @value{AS} is configured
+for a Meta processor.
+@end ifclear
+
+@ifset man
+@c man begin OPTIONS
+The following options are available when @value{AS} is configured for a
+Meta processor.
+@c man end
+@c man begin INCLUDE
+@include c-metag.texi
+@c ended inside the included file
+@end ifset
+
+@end ifset
+
+@c man begin OPTIONS
@ifset MMIX
See the info pages for documentation of the MMIX-specific options.
@end ifset
@@ -7018,6 +7044,9 @@ subject, see the hardware manufacturer's manual.
@ifset M68HC11
* M68HC11-Dependent:: M68HC11 and 68HC12 Dependent Features
@end ifset
+@ifset METAG
+* Meta-Dependent :: Meta Dependent Features
+@end ifset
@ifset MICROBLAZE
* MicroBlaze-Dependent:: MICROBLAZE Dependent Features
@end ifset
@@ -7221,6 +7250,10 @@ family.
@include c-m68hc11.texi
@end ifset
+@ifset METAG
+@include c-metag.texi
+@end ifset
+
@ifset MICROBLAZE
@include c-microblaze.texi
@end ifset
diff --git a/gas/doc/c-metag.texi b/gas/doc/c-metag.texi
new file mode 100644
index 0000000..225d02d
--- /dev/null
+++ b/gas/doc/c-metag.texi
@@ -0,0 +1,82 @@
+@c Copyright 2013 Free Software Foundation, Inc.
+@c Contributed by Imagination Technologies Ltd.
+@c This is part of the GAS manual.
+@c For copying conditions, see the file as.texinfo.
+@ifset GENERIC
+@page
+@node Meta-Dependent
+@chapter Meta Dependent Features
+@end ifset
+@ifclear GENERIC
+@node Machine Dependencies
+@chapter Meta Dependent Features
+@end ifclear
+
+@cindex Meta support
+@menu
+* Meta Options:: Options
+* Meta Syntax:: Meta Assembler Syntax
+@end menu
+
+@node Meta Options
+@section Options
+
+@cindex options for Meta
+@cindex Meta options
+@cindex architectures, Meta
+@cindex Meta architectures
+
+The Imagination Technologies Meta architecture is implemented in a
+number of versions, with each new version adding new features such as
+instructions and registers. For precise details of what instructions
+each core supports, please see the chip's technical reference manual.
+
+The following table lists all available Meta options.
+
+@table @code
+@item -mcpu=metac11
+Generate code for Meta 1.1.
+
+@item -mcpu=metac12
+Generate code for Meta 1.2.
+
+@item -mcpu=metac21
+Generate code for Meta 2.1.
+
+@item -mfpu=metac21
+Allow code to use FPU hardware of Meta 2.1.
+
+@end table
+
+@node Meta Syntax
+@section Syntax
+
+@menu
+* Meta-Chars:: Special Characters
+* Meta-Regs:: Register Names
+@end menu
+
+@node Meta-Chars
+@subsection Special Characters
+
+@cindex line comment character, Meta
+@cindex Meta line comment character
+@samp{!} is the line comment character.
+
+@cindex line separator, Meta
+@cindex statement separator, Meta
+@cindex Meta line separator
+You can use @samp{;} instead of a newline to separate statements.
+
+@cindex symbol names, @samp{$} in
+@cindex @code{$} in symbol names
+Since @samp{$} has no special meaning, you may use it in symbol names.
+
+@node Meta-Regs
+@subsection Register Names
+
+@cindex Meta registers
+@cindex registers, Meta
+Registers can be specified either using their mnemonic names, such as
+@samp{D0Re0}, or using the unit plus register number separated by a @samp{.},
+such as @samp{D0.0}.
diff --git a/gas/testsuite/ChangeLog b/gas/testsuite/ChangeLog
index dc6ceb0..22e1953 100644
--- a/gas/testsuite/ChangeLog
+++ b/gas/testsuite/ChangeLog
@@ -1,3 +1,29 @@
+2013-01-10 Will Newton <will.newton@imgtec.com>
+
+ * gas/metag/labelarithmetic.d: New file.
+ * gas/metag/labelarithmetic.s: New file.
+ * gas/metag/metacore12.d: New file.
+ * gas/metag/metacore12.s: New file.
+ * gas/metag/metacore21-invalid.l: New file.
+ * gas/metag/metacore21-invalid.s: New file.
+ * gas/metag/metacore21.d: New file.
+ * gas/metag/metacore21.s: New file.
+ * gas/metag/metacore21ext.d: New file.
+ * gas/metag/metacore21ext.s: New file.
+ * gas/metag/metadsp21-invalid.l: New file.
+ * gas/metag/metadsp21-invalid.s: New file.
+ * gas/metag/metadsp21.d: New file.
+ * gas/metag/metadsp21.s: New file.
+ * gas/metag/metadsp21ext.d: New file.
+ * gas/metag/metadsp21ext.s: New file.
+ * gas/metag/metafpu21.d: New file.
+ * gas/metag/metafpu21.s: New file.
+ * gas/metag/metafpu21ext.d: New file.
+ * gas/metag/metafpu21ext.s: New file.
+ * gas/metag/metag.exp: New file.
+ * gas/metag/tls.d: New file.
+ * gas/metag/tls.s: New file.
+
2013-01-08 Yufeng Zhang <yufeng.zhang@arm.com>
* gas/aarch64/diagnostic.s: Add test.