7142 lines
151 KiB
C
7142 lines
151 KiB
C
/* tc-metag.c -- Assembler for the Imagination Technologies Meta.
|
|
Copyright (C) 2013-2015 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, ®s_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, ®s_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, ®s_read);
|
|
}
|
|
else
|
|
{
|
|
l = parse_mset (l, regs, &addr, ®s_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, ®s_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, ®s_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, ®, &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, ®s_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;
|
|
|
|
/* PC.r | CT.r | TR.r | TT.r are treated as if they are a single unit. */
|
|
switch (regs[0]->unit)
|
|
{
|
|
case UNIT_PC:
|
|
case UNIT_CT:
|
|
case UNIT_TR:
|
|
case UNIT_TT:
|
|
if (regs[1]->unit == UNIT_PC
|
|
|| regs[1]->unit == UNIT_CT
|
|
|| regs[1]->unit == UNIT_TR
|
|
|| regs[1]->unit == UNIT_TT)
|
|
{
|
|
as_bad (_("PC, CT, TR and TT are treated as if they are a single unit but operands must be in different units"));
|
|
return NULL;
|
|
}
|
|
|
|
default:
|
|
/* Registers must be in different units. */
|
|
if (regs[0]->unit == regs[1]->unit)
|
|
{
|
|
as_bad (_("source and destination register must be in different units"));
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
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, ®s_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 (®s[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, ®s[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, ®, tmpl, load);
|
|
|
|
if (!ll)
|
|
{
|
|
if (try_gp)
|
|
{
|
|
l = __parse_gp_reg (l, ®, !(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, ®s_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, ®s_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, ®s_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, ®s_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, ®s_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, ®s[i], TRUE);
|
|
if (l == NULL)
|
|
{
|
|
/* Try the store table too. */
|
|
l = __parse_gp_reg (l2, ®s[i], FALSE);
|
|
if (l == NULL)
|
|
{
|
|
/* Then try a DSP register. */
|
|
l = parse_dsp_insn_reg (l2, ®s[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, ®s_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, ®s[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, ®s[1], 2, ®s_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, ®s_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, ®s[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, ®s[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, ®s[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, ®s[1], TRUE);
|
|
if (ll == NULL)
|
|
{
|
|
if (template->arg_type & DSP_ARGS_DSP_SRC2)
|
|
{
|
|
l = parse_dsp_reg (l, ®s[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, ®s[1], FALSE, FALSE);
|
|
if (l == NULL)
|
|
{
|
|
as_bad (_("invalid accumulator register"));
|
|
return NULL;
|
|
}
|
|
ac = 1;
|
|
ac_shift = 0;
|
|
}
|
|
else
|
|
{
|
|
l = __parse_gp_reg (l, ®s[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, ®s[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;
|
|
}
|