/* * 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; ireg_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 && !is_pc_reloc(base,sec)) || base==NULL) { /* known label from same section or absolute label */ val = (val - (pc + 2)) / 2; } else if (btype == BASE_OK) { /* 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); } 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 && (valhival)) 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; }