6502/vasm/cpus/jagrisc/cpu.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",&reg)!=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;
}