618 lines
14 KiB
C
618 lines
14 KiB
C
|
/*
|
||
|
* cpu.c Jaguar RISC cpu description file
|
||
|
* (c) in 2014-2017 by Frank Wille
|
||
|
*/
|
||
|
|
||
|
#include "vasm.h"
|
||
|
|
||
|
mnemonic mnemonics[] = {
|
||
|
#include "opcodes.h"
|
||
|
};
|
||
|
int mnemonic_cnt = sizeof(mnemonics) / sizeof(mnemonics[0]);
|
||
|
|
||
|
char *cpu_copyright = "vasm Jaguar RISC cpu backend 0.4c (c) 2014-2017 Frank Wille";
|
||
|
char *cpuname = "jagrisc";
|
||
|
int bitsperbyte = 8;
|
||
|
int bytespertaddr = 4;
|
||
|
|
||
|
int jag_big_endian = 1; /* defaults to big-endian (Atari Jaguar 68000) */
|
||
|
|
||
|
static uint8_t cpu_type = GPU|DSP;
|
||
|
static int OC_MOVEI,OC_UNPACK;
|
||
|
|
||
|
/* condition codes */
|
||
|
static regsym cc_regsyms[] = {
|
||
|
{"T", RTYPE_CC, 0, 0x00},
|
||
|
{"NE", RTYPE_CC, 0, 0x01},
|
||
|
{"EQ", RTYPE_CC, 0, 0x02},
|
||
|
{"CC", RTYPE_CC, 0, 0x04},
|
||
|
{"HI", RTYPE_CC, 0, 0x05},
|
||
|
{"CS", RTYPE_CC, 0, 0x08},
|
||
|
{"PL", RTYPE_CC, 0, 0x14},
|
||
|
{"MI", RTYPE_CC, 0, 0x18},
|
||
|
{"t", RTYPE_CC, 0, 0x00},
|
||
|
{"ne", RTYPE_CC, 0, 0x01},
|
||
|
{"eq", RTYPE_CC, 0, 0x02},
|
||
|
{"cc", RTYPE_CC, 0, 0x04},
|
||
|
{"hi", RTYPE_CC, 0, 0x05},
|
||
|
{"cs", RTYPE_CC, 0, 0x08},
|
||
|
{"pl", RTYPE_CC, 0, 0x14},
|
||
|
{"mi", RTYPE_CC, 0, 0x18},
|
||
|
{NULL, 0, 0, 0}
|
||
|
};
|
||
|
|
||
|
|
||
|
int init_cpu(void)
|
||
|
{
|
||
|
int i;
|
||
|
regsym *r;
|
||
|
|
||
|
for (i=0; i<mnemonic_cnt; i++) {
|
||
|
if (!strcmp(mnemonics[i].name,"movei"))
|
||
|
OC_MOVEI = i;
|
||
|
else if (!strcmp(mnemonics[i].name,"unpack"))
|
||
|
OC_UNPACK = i;
|
||
|
}
|
||
|
|
||
|
/* define all condition code register symbols */
|
||
|
for (r=cc_regsyms; r->reg_name!=NULL; r++)
|
||
|
add_regsym(r);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
int cpu_args(char *p)
|
||
|
{
|
||
|
if (!strncmp(p,"-m",2)) {
|
||
|
p += 2;
|
||
|
if (!stricmp(p,"gpu") || !stricmp(p,"tom"))
|
||
|
cpu_type = GPU;
|
||
|
else if (!stricmp(p,"dsp") || !stricmp(p,"jerry"))
|
||
|
cpu_type = DSP;
|
||
|
else if (!strcmp(p,"any"))
|
||
|
cpu_type = GPU|DSP;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
else if (!strcmp(p,"-big"))
|
||
|
jag_big_endian = 1;
|
||
|
else if (!strcmp(p,"-little"))
|
||
|
jag_big_endian = 0;
|
||
|
else
|
||
|
return 0;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int parse_reg(char **p)
|
||
|
{
|
||
|
int reg = -1;
|
||
|
char *rp = skip(*p);
|
||
|
char *s;
|
||
|
|
||
|
if (s = skip_identifier(rp)) {
|
||
|
regsym *sym = find_regsym(rp,s-rp);
|
||
|
|
||
|
if (sym!=NULL && sym->reg_type==RTYPE_R) {
|
||
|
reg = sym->reg_num;
|
||
|
}
|
||
|
else if (toupper((unsigned char)*rp++) == 'R') {
|
||
|
if (sscanf(rp,"%d",®)!=1 || reg<0 || reg>31)
|
||
|
reg = -1;
|
||
|
}
|
||
|
|
||
|
if (reg >= 0)
|
||
|
*p = s;
|
||
|
}
|
||
|
|
||
|
return reg;
|
||
|
}
|
||
|
|
||
|
|
||
|
static expr *parse_cc(char **p)
|
||
|
{
|
||
|
char *end;
|
||
|
|
||
|
*p = skip(*p);
|
||
|
|
||
|
if (end = skip_identifier(*p)) {
|
||
|
regsym *sym = find_regsym(*p,end-*p);
|
||
|
|
||
|
if (sym!=NULL && sym->reg_type==RTYPE_CC) {
|
||
|
*p = end;
|
||
|
return number_expr((taddr)sym->reg_num);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* otherwise the condition code is any expression */
|
||
|
return parse_expr(p);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void jagswap32(char *d,int32_t w)
|
||
|
/* write a 32-bit word with swapped halfs (Jaguar MOVEI) */
|
||
|
{
|
||
|
if (jag_big_endian) {
|
||
|
*d++ = (w >> 8) & 0xff;
|
||
|
*d++ = w & 0xff;
|
||
|
*d++ = (w >> 24) & 0xff;
|
||
|
*d = (w >> 16) & 0xff;
|
||
|
}
|
||
|
else {
|
||
|
/* @@@ Need to verify this! */
|
||
|
*d++ = w & 0xff;
|
||
|
*d++ = (w >> 8) & 0xff;
|
||
|
*d++ = (w >> 16) & 0xff;
|
||
|
*d = (w >> 24) & 0xff;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
char *parse_cpu_special(char *start)
|
||
|
/* parse cpu-specific directives; return pointer to end of cpu-specific text */
|
||
|
{
|
||
|
char *name=start;
|
||
|
char *s;
|
||
|
|
||
|
if (s = skip_identifier(name)) {
|
||
|
/* Atari MadMac compatibility directives */
|
||
|
if (*name == '.') /* ignore leading dot */
|
||
|
name++;
|
||
|
|
||
|
if (s-name==3 && !strnicmp(name,"dsp",3)) {
|
||
|
cpu_type = DSP;
|
||
|
eol(s);
|
||
|
return skip_line(s);
|
||
|
}
|
||
|
|
||
|
else if (s-name==3 && !strnicmp(name,"gpu",3)) {
|
||
|
cpu_type = GPU;
|
||
|
eol(s);
|
||
|
return skip_line(s);
|
||
|
}
|
||
|
|
||
|
else if (s-name==8 && !strnicmp(name,"regundef",8) ||
|
||
|
s-name==9 && !strnicmp(name,"equrundef",9)) {
|
||
|
/* undefine a register symbol */
|
||
|
s = skip(s);
|
||
|
if (name = parse_identifier(&s)) {
|
||
|
undef_regsym(name,0,RTYPE_R);
|
||
|
myfree(name);
|
||
|
eol(s);
|
||
|
return skip_line(s);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (s-name==7 && !strnicmp(name,"ccundef",7)) {
|
||
|
/* undefine a condition code symbol */
|
||
|
s = skip(s);
|
||
|
if (name = parse_identifier(&s)) {
|
||
|
undef_regsym(name,0,RTYPE_CC);
|
||
|
myfree(name);
|
||
|
eol(s);
|
||
|
return skip_line(s);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return start;
|
||
|
}
|
||
|
|
||
|
|
||
|
int parse_cpu_label(char *labname,char **start)
|
||
|
/* parse cpu-specific directives following a label field,
|
||
|
return zero when no valid directive was recognized */
|
||
|
{
|
||
|
char *dir=*start;
|
||
|
char *s,*name;
|
||
|
hashdata data;
|
||
|
|
||
|
if (*dir == '.') /* ignore leading dot */
|
||
|
dir++;
|
||
|
|
||
|
if (s = skip_identifier(dir)) {
|
||
|
|
||
|
if (s-dir==6 && !strnicmp(dir,"regequ",6) ||
|
||
|
s-dir==4 && !strnicmp(dir,"equr",4)) {
|
||
|
/* label REGEQU Rn || label EQUR Rn */
|
||
|
int r;
|
||
|
|
||
|
if ((r = parse_reg(&s)) >= 0)
|
||
|
new_regsym(0,0,labname,RTYPE_R,0,r);
|
||
|
else
|
||
|
cpu_error(3); /* register expected */
|
||
|
eol(s);
|
||
|
*start = skip_line(s);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
else if (s-dir==5 && !strnicmp(dir,"ccdef",5)) {
|
||
|
/* label CCDEF expr */
|
||
|
expr *ccexp;
|
||
|
taddr val;
|
||
|
|
||
|
if ((ccexp = parse_cc(&s)) != NULL) {
|
||
|
if (eval_expr(ccexp,&val,NULL,0))
|
||
|
new_regsym(0,0,labname,RTYPE_CC,0,(int)val);
|
||
|
else
|
||
|
general_error(30); /* expression must be a constant */
|
||
|
}
|
||
|
else
|
||
|
general_error(9); /* @@@ */
|
||
|
eol(s);
|
||
|
*start = skip_line(s);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
operand *new_operand(void)
|
||
|
{
|
||
|
operand *new = mymalloc(sizeof(*new));
|
||
|
|
||
|
new->type = NO_OP;
|
||
|
return new;
|
||
|
}
|
||
|
|
||
|
|
||
|
int jag_data_operand(int bits)
|
||
|
/* return data operand type for these number of bits */
|
||
|
{
|
||
|
if (bits & OPSZ_SWAP)
|
||
|
return DATAI_OP;
|
||
|
return bits==64 ? DATA64_OP : DATA_OP;
|
||
|
}
|
||
|
|
||
|
|
||
|
int parse_operand(char *p, int len, operand *op, int required)
|
||
|
{
|
||
|
int reg;
|
||
|
|
||
|
switch (required) {
|
||
|
case IMM0:
|
||
|
case IMM1:
|
||
|
case IMM1S:
|
||
|
case SIMM:
|
||
|
case IMMLW:
|
||
|
if (*p == '#')
|
||
|
p = skip(p+1); /* skip optional '#' */
|
||
|
case REL:
|
||
|
case DATA_OP:
|
||
|
case DATAI_OP:
|
||
|
if (required == IMM1S) {
|
||
|
op->val = make_expr(SUB,number_expr(32),parse_expr(&p));
|
||
|
required = IMM1; /* turn into IMM1 32-val for SHLQ */
|
||
|
}
|
||
|
else
|
||
|
op->val = parse_expr(&p);
|
||
|
break;
|
||
|
|
||
|
case DATA64_OP:
|
||
|
op->val = parse_expr_huge(&p);
|
||
|
break;
|
||
|
|
||
|
case REG: /* Rn */
|
||
|
op->reg = parse_reg(&p);
|
||
|
if (op->reg < 0)
|
||
|
return PO_NOMATCH;
|
||
|
break;
|
||
|
|
||
|
case IREG: /* (Rn) */
|
||
|
if (*p++ != '(')
|
||
|
return PO_NOMATCH;
|
||
|
op->reg = parse_reg(&p);
|
||
|
if (op->reg < 0)
|
||
|
return PO_NOMATCH;
|
||
|
if (*p != ')')
|
||
|
return PO_NOMATCH;
|
||
|
break;
|
||
|
|
||
|
case IR14D: /* (R14+d) */
|
||
|
case IR15D: /* (R15+d) */
|
||
|
if (*p++ != '(')
|
||
|
return PO_NOMATCH;
|
||
|
reg = parse_reg(&p);
|
||
|
if ((required==IR14D && reg!=14) || (required==IR15D && reg!=15))
|
||
|
return PO_NOMATCH;
|
||
|
if (*p++ != '+')
|
||
|
return PO_NOMATCH;
|
||
|
p = skip(p);
|
||
|
op->val = parse_expr(&p);
|
||
|
p = skip(p);
|
||
|
if (*p != ')')
|
||
|
return PO_NOMATCH;
|
||
|
break;
|
||
|
|
||
|
case IR14R: /* (R14+Rn) */
|
||
|
case IR15R: /* (R15+Rn) */
|
||
|
if (*p++ != '(')
|
||
|
return PO_NOMATCH;
|
||
|
reg = parse_reg(&p);
|
||
|
if ((required==IR14R && reg!=14) || (required==IR15R && reg!=15))
|
||
|
return PO_NOMATCH;
|
||
|
if (*p++ != '+')
|
||
|
return PO_NOMATCH;
|
||
|
op->reg = parse_reg(&p);
|
||
|
if (op->reg < 0)
|
||
|
return PO_NOMATCH;
|
||
|
if (*p != ')')
|
||
|
return PO_NOMATCH;
|
||
|
break;
|
||
|
|
||
|
case CC: /* condition code: t, eq, ne, mi, pl, cc, cs, ... */
|
||
|
op->val = parse_cc(&p);
|
||
|
break;
|
||
|
|
||
|
case PC: /* PC register */
|
||
|
if (toupper((unsigned char)*p) != 'P' ||
|
||
|
toupper((unsigned char)*(p+1)) != 'C' ||
|
||
|
ISIDCHAR(*(p+2)))
|
||
|
return PO_NOMATCH;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return PO_NOMATCH;
|
||
|
}
|
||
|
|
||
|
op->type = required;
|
||
|
return PO_MATCH;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int32_t eval_oper(instruction *ip,operand *op,section *sec,
|
||
|
taddr pc,dblock *db)
|
||
|
{
|
||
|
symbol *base = NULL;
|
||
|
int optype = op->type;
|
||
|
int btype;
|
||
|
taddr val,loval,hival,mask;
|
||
|
|
||
|
switch (optype) {
|
||
|
case PC:
|
||
|
return 0;
|
||
|
|
||
|
case REG:
|
||
|
case IREG:
|
||
|
case IR14R:
|
||
|
case IR15R:
|
||
|
return op->reg;
|
||
|
|
||
|
case IMM0:
|
||
|
case IMM1:
|
||
|
case SIMM:
|
||
|
case IMMLW:
|
||
|
case IR14D:
|
||
|
case IR15D:
|
||
|
case REL:
|
||
|
case CC:
|
||
|
mask = 0x1f;
|
||
|
if (!eval_expr(op->val,&val,sec,pc))
|
||
|
btype = find_base(op->val,&base,sec,pc);
|
||
|
|
||
|
if (optype==IMM0 || optype==CC || optype==IMM1 || optype==SIMM) {
|
||
|
if (base != NULL) {
|
||
|
loval = -32;
|
||
|
hival = 32;
|
||
|
if (btype != BASE_ILLEGAL) {
|
||
|
if (db) {
|
||
|
add_extnreloc_masked(&db->relocs,base,val,
|
||
|
btype==BASE_PCREL?REL_PC:REL_ABS,
|
||
|
jag_big_endian?6:5,5,0,0x1f);
|
||
|
base = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (optype==IMM1) {
|
||
|
loval = 1;
|
||
|
hival = 32;
|
||
|
}
|
||
|
else if (optype==SIMM) {
|
||
|
loval = -16;
|
||
|
hival = 15;
|
||
|
}
|
||
|
else {
|
||
|
loval = 0;
|
||
|
hival = 31;
|
||
|
}
|
||
|
}
|
||
|
else if (optype==IR14D || optype==IR15D) {
|
||
|
if (base==NULL && val==0) {
|
||
|
/* Optimize (Rn+0) to (Rn). Assume that the "load/store (Rn+d)"
|
||
|
instructions follow directly after "load/store (Rn)". */
|
||
|
ip->code -= optype==IR14D ? 1 : 2;
|
||
|
op->type = IREG;
|
||
|
op->reg = optype==IR14D ? 14 : 15;
|
||
|
return op->reg;
|
||
|
}
|
||
|
loval = 1;
|
||
|
hival = 32;
|
||
|
}
|
||
|
else if (optype==IMMLW) {
|
||
|
mask = ~0;
|
||
|
if (base != NULL) {
|
||
|
if (btype != BASE_ILLEGAL) {
|
||
|
if (db) {
|
||
|
/* two relocations for LSW first, then MSW */
|
||
|
add_extnreloc_masked(&db->relocs,base,val,
|
||
|
btype==BASE_PCREL?REL_PC:REL_ABS,
|
||
|
0,16,2,0xffff);
|
||
|
add_extnreloc_masked(&db->relocs,base,val,
|
||
|
btype==BASE_PCREL?REL_PC:REL_ABS,
|
||
|
16,16,2,0xffff0000);
|
||
|
base = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (optype==REL) {
|
||
|
loval = -16;
|
||
|
hival = 15;
|
||
|
if (base!=NULL && btype==BASE_OK) {
|
||
|
if (is_pc_reloc(base,sec)) {
|
||
|
/* external label or from a different section (distance / 2) */
|
||
|
add_extnreloc_masked(&db->relocs,base,val-2,REL_PC,
|
||
|
jag_big_endian?6:5,5,0,0x3e);
|
||
|
}
|
||
|
else if (LOCREF(base)) {
|
||
|
/* known label from the same section doesn't need a reloc */
|
||
|
val = (val - (pc + 2)) / 2;
|
||
|
}
|
||
|
base = NULL;
|
||
|
}
|
||
|
}
|
||
|
else ierror(0);
|
||
|
|
||
|
if (base != NULL)
|
||
|
general_error(38); /* bad or unhandled reloc: illegal relocation */
|
||
|
|
||
|
/* range check for this addressing mode */
|
||
|
if (mask!=~0 && (val<loval || val>hival))
|
||
|
cpu_error(1,(long)loval,(long)hival);
|
||
|
return val & mask;
|
||
|
|
||
|
default:
|
||
|
ierror(0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0; /* default */
|
||
|
}
|
||
|
|
||
|
|
||
|
size_t instruction_size(instruction *ip, section *sec, taddr pc)
|
||
|
{
|
||
|
return ip->code==OC_MOVEI ? 6 : 2;
|
||
|
}
|
||
|
|
||
|
|
||
|
dblock *eval_instruction(instruction *ip, section *sec, taddr pc)
|
||
|
{
|
||
|
dblock *db = new_dblock();
|
||
|
int32_t src=0,dst=0,extra;
|
||
|
int size = 2;
|
||
|
uint16_t inst;
|
||
|
|
||
|
/* get source and destination argument, when present */
|
||
|
if (ip->op[0])
|
||
|
dst = eval_oper(ip,ip->op[0],sec,pc,db);
|
||
|
if (ip->op[1]) {
|
||
|
if (ip->code == OC_MOVEI) {
|
||
|
extra = dst;
|
||
|
size = 6;
|
||
|
}
|
||
|
else
|
||
|
src = dst;
|
||
|
dst = eval_oper(ip,ip->op[1],sec,pc,db);
|
||
|
}
|
||
|
else if (ip->code == OC_UNPACK)
|
||
|
src = 1; /* pack(src=0)/unpack(src=1) use the same opcode */
|
||
|
|
||
|
/* store and jump instructions need the second operand in the source field */
|
||
|
if (mnemonics[ip->code].ext.flags & OPSWAP) {
|
||
|
extra = src;
|
||
|
src = dst;
|
||
|
dst = extra;
|
||
|
}
|
||
|
|
||
|
/* allocate dblock data for instruction */
|
||
|
db->size = size;
|
||
|
db->data = mymalloc(size);
|
||
|
|
||
|
/* construct the instruction word out of opcode and source/dest. value */
|
||
|
inst = (mnemonics[ip->code].ext.opcode & 63) << 10;
|
||
|
inst |= ((src&31) << 5) | (dst & 31);
|
||
|
|
||
|
/* write instruction */
|
||
|
if (jag_big_endian) {
|
||
|
db->data[0] = (inst >> 8) & 0xff;
|
||
|
db->data[1] = inst & 0xff;
|
||
|
}
|
||
|
else {
|
||
|
db->data[0] = inst & 0xff;
|
||
|
db->data[1] = (inst >> 8) & 0xff;
|
||
|
}
|
||
|
|
||
|
/* extra words for MOVEI are always written in the order lo-word, hi-word */
|
||
|
if (size == 6)
|
||
|
jagswap32(&db->data[2],extra);
|
||
|
|
||
|
return db;
|
||
|
}
|
||
|
|
||
|
|
||
|
dblock *eval_data(operand *op, size_t bitsize, section *sec, taddr pc)
|
||
|
{
|
||
|
dblock *db = new_dblock();
|
||
|
taddr val;
|
||
|
|
||
|
if (bitsize!=8 && bitsize!=16 && bitsize!=32 && bitsize!=64)
|
||
|
cpu_error(0,bitsize); /* data size not supported */
|
||
|
|
||
|
if (op->type!=DATA_OP && op->type!=DATA64_OP && op->type!=DATAI_OP)
|
||
|
ierror(0);
|
||
|
|
||
|
db->size = bitsize >> 3;
|
||
|
db->data = mymalloc(db->size);
|
||
|
|
||
|
if (op->type == DATA64_OP) {
|
||
|
thuge hval;
|
||
|
|
||
|
if (!eval_expr_huge(op->val,&hval))
|
||
|
general_error(59); /* cannot evaluate huge integer */
|
||
|
huge_to_mem(jag_big_endian,db->data,db->size,hval);
|
||
|
}
|
||
|
else {
|
||
|
if (!eval_expr(op->val,&val,sec,pc)) {
|
||
|
symbol *base;
|
||
|
int btype;
|
||
|
|
||
|
btype = find_base(op->val,&base,sec,pc);
|
||
|
if (base!=NULL && btype!=BASE_ILLEGAL) {
|
||
|
if (op->type == DATAI_OP) {
|
||
|
/* swapped: two relocations for LSW first, then MSW */
|
||
|
add_extnreloc_masked(&db->relocs,base,val,
|
||
|
btype==BASE_PCREL?REL_PC:REL_ABS,
|
||
|
0,16,0,0xffff);
|
||
|
add_extnreloc_masked(&db->relocs,base,val,
|
||
|
btype==BASE_PCREL?REL_PC:REL_ABS,
|
||
|
16,16,0,0xffff0000);
|
||
|
}
|
||
|
else /* normal 8, 16, 32 bit relocation */
|
||
|
add_extnreloc(&db->relocs,base,val,
|
||
|
btype==BASE_PCREL?REL_PC:REL_ABS,0,bitsize,0);
|
||
|
}
|
||
|
else if (btype != BASE_NONE)
|
||
|
general_error(38); /* illegal relocation */
|
||
|
}
|
||
|
|
||
|
switch (db->size) {
|
||
|
case 1:
|
||
|
db->data[0] = val & 0xff;
|
||
|
break;
|
||
|
case 2:
|
||
|
case 4:
|
||
|
if (op->type == DATAI_OP)
|
||
|
jagswap32(db->data,(int32_t)val);
|
||
|
else
|
||
|
setval(jag_big_endian,db->data,db->size,val);
|
||
|
break;
|
||
|
default:
|
||
|
ierror(0);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return db;
|
||
|
}
|
||
|
|
||
|
|
||
|
int cpu_available(int idx)
|
||
|
{
|
||
|
return (mnemonics[idx].ext.flags & cpu_type) != 0;
|
||
|
}
|