mirror of
https://github.com/elliotnunn/NetBoot.git
synced 2024-12-28 08:30:48 +00:00
589 lines
14 KiB
C
589 lines
14 KiB
C
/*
|
|
** cpu.c 650x/65C02/6510/6280 cpu-description file
|
|
** (c) in 2002,2006,2008-2012,2014-2020 by Frank Wille
|
|
*/
|
|
|
|
#include "vasm.h"
|
|
|
|
mnemonic mnemonics[] = {
|
|
#include "opcodes.h"
|
|
};
|
|
|
|
int mnemonic_cnt=sizeof(mnemonics)/sizeof(mnemonics[0]);
|
|
|
|
char *cpu_copyright="vasm 6502 cpu backend 0.8c (c) 2002,2006,2008-2012,2014-2020 Frank Wille";
|
|
char *cpuname = "6502";
|
|
int bitsperbyte = 8;
|
|
int bytespertaddr = 2;
|
|
|
|
static uint16_t cpu_type = M6502;
|
|
static int branchopt = 0;
|
|
static int modifier; /* set by find_base() */
|
|
static utaddr dpage = 0; /* default zero/direct page - set with SETDP */
|
|
static int bbcade; /* GMGM - set for BBC ADE compatibility */
|
|
|
|
|
|
int ext_unary_type(char *s)
|
|
{
|
|
if (bbcade) /* GMGM - BBC ADE assembler swaps meaning of < and > */
|
|
return *s=='<' ? HIBYTE : LOBYTE;
|
|
return *s=='<' ? LOBYTE : HIBYTE;
|
|
}
|
|
|
|
|
|
int ext_unary_eval(int type,taddr val,taddr *result,int cnst)
|
|
{
|
|
switch (type) {
|
|
case LOBYTE:
|
|
*result = cnst ? (val & 0xff) : val;
|
|
return 1;
|
|
case HIBYTE:
|
|
*result = cnst ? ((val >> 8) & 0xff) : val;
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
return 0; /* unknown type */
|
|
}
|
|
|
|
|
|
int ext_find_base(symbol **base,expr *p,section *sec,taddr pc)
|
|
{
|
|
/* addr/256 equals >addr, addr%256 and addr&255 equal <addr */
|
|
if (p->type==DIV || p->type==MOD) {
|
|
if (p->right->type==NUM && p->right->c.val==256)
|
|
p->type = p->type == DIV ? HIBYTE : LOBYTE;
|
|
}
|
|
else if (p->type==BAND && p->right->type==NUM && p->right->c.val==255)
|
|
p->type = LOBYTE;
|
|
|
|
if (p->type==LOBYTE || p->type==HIBYTE) {
|
|
modifier = p->type;
|
|
return find_base(p->left,base,sec,pc);
|
|
}
|
|
return BASE_ILLEGAL;
|
|
}
|
|
|
|
|
|
int parse_operand(char *p,int len,operand *op,int required)
|
|
{
|
|
char *start = p;
|
|
int indir = 0;
|
|
|
|
p = skip(p);
|
|
if (len>0 && required!=DATAOP && check_indir(p,start+len)) {
|
|
indir = 1;
|
|
p = skip(p+1);
|
|
}
|
|
|
|
switch (required) {
|
|
case IMMED:
|
|
if (*p++ != '#')
|
|
return PO_NOMATCH;
|
|
p = skip(p);
|
|
break;
|
|
case INDIR:
|
|
case INDIRX:
|
|
case INDX:
|
|
case INDY:
|
|
case DPINDIR:
|
|
if (!indir)
|
|
return PO_NOMATCH;
|
|
break;
|
|
case WBIT:
|
|
if (*p == '#') /* # is optional */
|
|
p = skip(p+1);
|
|
default:
|
|
if (indir)
|
|
return PO_NOMATCH;
|
|
break;
|
|
}
|
|
|
|
op->dp = dpage;
|
|
if (required < ACCU)
|
|
op->value = parse_expr(&p);
|
|
else
|
|
op->value = NULL;
|
|
|
|
switch (required) {
|
|
case INDX:
|
|
case INDIRX:
|
|
if (*p++ == ',') {
|
|
p = skip(p);
|
|
if (toupper((unsigned char)*p++) != 'X')
|
|
return PO_NOMATCH;
|
|
}
|
|
else
|
|
return PO_NOMATCH;
|
|
break;
|
|
case ACCU:
|
|
if (len != 0) {
|
|
if (len!=1 || toupper((unsigned char)*p++) != 'A')
|
|
return PO_NOMATCH;
|
|
}
|
|
break;
|
|
case DUMX:
|
|
if (toupper((unsigned char)*p++) != 'X')
|
|
return PO_NOMATCH;
|
|
break;
|
|
case DUMY:
|
|
if (toupper((unsigned char)*p++) != 'Y')
|
|
return PO_NOMATCH;
|
|
break;
|
|
}
|
|
|
|
if (required==INDIR || required==INDX || required==INDY
|
|
|| required==DPINDIR || required==INDIRX) {
|
|
p = skip(p);
|
|
if (*p++ != ')') {
|
|
cpu_error(2); /* missing closing parenthesis */
|
|
return PO_CORRUPT;
|
|
}
|
|
}
|
|
|
|
p = skip(p);
|
|
if (p-start < len)
|
|
cpu_error(1); /* trailing garbage in operand */
|
|
op->type = required;
|
|
return PO_MATCH;
|
|
}
|
|
|
|
|
|
char *parse_cpu_special(char *start)
|
|
{
|
|
char *name=start,*s=start;
|
|
|
|
if (ISIDSTART(*s)) {
|
|
s++;
|
|
while (ISIDCHAR(*s))
|
|
s++;
|
|
|
|
if (s-name==5 && !strnicmp(name,"setdp",5)) {
|
|
s = skip(s);
|
|
dpage = (utaddr)parse_constexpr(&s);
|
|
eol(s);
|
|
return skip_line(s);
|
|
}
|
|
else if (s-name==5 && !strnicmp(name,"zpage",5)) {
|
|
char *name;
|
|
s = skip(s);
|
|
if (name = parse_identifier(&s)) {
|
|
symbol *sym = new_import(name);
|
|
myfree(name);
|
|
sym->flags |= ZPAGESYM;
|
|
eol(s);
|
|
}
|
|
else
|
|
cpu_error(8); /* identifier expected */
|
|
return skip_line(s);
|
|
}
|
|
}
|
|
return start;
|
|
}
|
|
|
|
|
|
static instruction *copy_instruction(instruction *ip)
|
|
/* copy an instruction and its operands */
|
|
{
|
|
static instruction newip;
|
|
static operand newop[MAX_OPERANDS];
|
|
int i;
|
|
|
|
newip.code = ip->code;
|
|
|
|
for (i=0; i<MAX_OPERANDS; i++) {
|
|
if (ip->op[i] != NULL) {
|
|
newip.op[i] = &newop[i];
|
|
*newip.op[i] = *ip->op[i];
|
|
}
|
|
else
|
|
newip.op[i] = NULL;
|
|
}
|
|
|
|
return &newip;
|
|
}
|
|
|
|
|
|
static void optimize_instruction(instruction *ip,section *sec,
|
|
taddr pc,int final)
|
|
{
|
|
mnemonic *mnemo = &mnemonics[ip->code];
|
|
symbol *base;
|
|
operand *op;
|
|
taddr val;
|
|
int i;
|
|
|
|
for (i=0; i<MAX_OPERANDS; i++) {
|
|
if ((op = ip->op[i]) != NULL) {
|
|
if (op->value != NULL) {
|
|
if (eval_expr(op->value,&val,sec,pc))
|
|
base = NULL; /* val is constant/absolute */
|
|
else
|
|
find_base(op->value,&base,sec,pc); /* get base-symbol */
|
|
|
|
if ((op->type==ABS || op->type==ABSX || op->type==ABSY) &&
|
|
((base==NULL
|
|
&& ((val>=0 && val<=0xff) ||
|
|
((utaddr)val>=op->dp && (utaddr)val<=op->dp+0xff))) ||
|
|
(base!=NULL && (base->flags&ZPAGESYM)))
|
|
&& mnemo->ext.zp_opcode!=0) {
|
|
/* we can use a zero page addressing mode for absolute 16-bit */
|
|
op->type += ZPAGE-ABS;
|
|
}
|
|
else if (op->type==REL && (base==NULL || !is_pc_reloc(base,sec))) {
|
|
taddr bd = val - (pc + 2);
|
|
|
|
if ((bd<-0x80 || bd>0x7f) && branchopt) {
|
|
/* branch dest. out of range: use a B!cc/JMP combination */
|
|
op->type = RELJMP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static size_t get_inst_size(instruction *ip)
|
|
{
|
|
size_t sz = 1;
|
|
int i;
|
|
|
|
for (i=0; i<MAX_OPERANDS; i++) {
|
|
if (ip->op[i] != NULL) {
|
|
switch (ip->op[i]->type) {
|
|
case REL:
|
|
case INDX:
|
|
case INDY:
|
|
case DPINDIR:
|
|
case IMMED:
|
|
case ZPAGE:
|
|
case ZPAGEX:
|
|
case ZPAGEY:
|
|
sz += 1;
|
|
break;
|
|
case ABS:
|
|
case ABSX:
|
|
case ABSY:
|
|
case INDIR:
|
|
case INDIRX:
|
|
sz += 2;
|
|
break;
|
|
case RELJMP:
|
|
sz += 4;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
|
|
size_t instruction_size(instruction *ip,section *sec,taddr pc)
|
|
{
|
|
instruction *ipcopy;
|
|
int i;
|
|
|
|
for (i=0; i<MAX_OPERANDS-1; i++) {
|
|
/* convert DUMX/DUMY operands into real addressing modes first */
|
|
if (ip->op[i]!=NULL && ip->op[i+1]!=NULL) {
|
|
if (ip->op[i]->type == ABS) {
|
|
if (ip->op[i+1]->type == DUMX) {
|
|
ip->op[i]->type = ABSX;
|
|
break;
|
|
}
|
|
else if (ip->op[i+1]->type == DUMY) {
|
|
ip->op[i]->type = ABSY;
|
|
break;
|
|
}
|
|
}
|
|
else if (ip->op[i]->type == INDIR) {
|
|
if (ip->op[i+1]->type == DUMY) {
|
|
ip->op[0]->type = INDY;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (++i < MAX_OPERANDS) {
|
|
/* we removed a DUMX/DUMY operand at the end */
|
|
myfree(ip->op[i]);
|
|
ip->op[i] = NULL;
|
|
}
|
|
|
|
ipcopy = copy_instruction(ip);
|
|
optimize_instruction(ipcopy,sec,pc,0);
|
|
return get_inst_size(ipcopy);
|
|
}
|
|
|
|
|
|
static void rangecheck(taddr val,operand *op)
|
|
{
|
|
switch (op->type) {
|
|
case ZPAGE:
|
|
case ZPAGEX:
|
|
case ZPAGEY:
|
|
case INDX:
|
|
case INDY:
|
|
case DPINDIR:
|
|
if ((utaddr)val>=op->dp && (utaddr)val<=op->dp+0xff)
|
|
break;
|
|
case IMMED:
|
|
if (val<-0x80 || val>0xff)
|
|
cpu_error(5,8); /* operand doesn't fit into 8-bits */
|
|
break;
|
|
case REL:
|
|
if (val<-0x80 || val>0x7f)
|
|
cpu_error(6); /* branch destination out of range */
|
|
break;
|
|
case WBIT:
|
|
if (val<0 || val>7)
|
|
cpu_error(7); /* illegal bit number */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
dblock *eval_instruction(instruction *ip,section *sec,taddr pc)
|
|
{
|
|
dblock *db = new_dblock();
|
|
unsigned char *d,oc;
|
|
int optype,i;
|
|
taddr val;
|
|
|
|
optimize_instruction(ip,sec,pc,1); /* really execute optimizations now */
|
|
|
|
db->size = get_inst_size(ip);
|
|
d = db->data = mymalloc(db->size);
|
|
|
|
/* write opcode */
|
|
oc = mnemonics[ip->code].ext.opcode;
|
|
for (i=0; i<MAX_OPERANDS; i++) {
|
|
optype = ip->op[i]!=NULL ? ip->op[i]->type : IMPLIED;
|
|
switch (optype) {
|
|
case ZPAGE:
|
|
case ZPAGEX:
|
|
case ZPAGEY:
|
|
oc = mnemonics[ip->code].ext.zp_opcode;
|
|
break;
|
|
case RELJMP:
|
|
oc ^= 0x20; /* B!cc branch */
|
|
break;
|
|
}
|
|
}
|
|
*d++ = oc;
|
|
|
|
for (i=0; i<MAX_OPERANDS; i++) {
|
|
if (ip->op[i] != NULL){
|
|
operand *op = ip->op[i];
|
|
int offs = d - db->data;
|
|
symbol *base;
|
|
|
|
optype = (int)op->type;
|
|
if (op->value != NULL) {
|
|
if (!eval_expr(op->value,&val,sec,pc)) {
|
|
modifier = 0;
|
|
if (optype!=WBIT && find_base(op->value,&base,sec,pc) == BASE_OK) {
|
|
if (optype==REL && !is_pc_reloc(base,sec)) {
|
|
/* relative branch requires no relocation */
|
|
val = val - (pc + offs + 1);
|
|
}
|
|
else {
|
|
int type = REL_ABS;
|
|
int size;
|
|
rlist *rl;
|
|
|
|
switch (optype) {
|
|
case ABS:
|
|
case ABSX:
|
|
case ABSY:
|
|
case INDIR:
|
|
case INDIRX:
|
|
size = 16;
|
|
break;
|
|
case INDX:
|
|
case INDY:
|
|
case DPINDIR:
|
|
case ZPAGE:
|
|
case ZPAGEX:
|
|
case ZPAGEY:
|
|
case IMMED:
|
|
size = 8;
|
|
break;
|
|
case RELJMP:
|
|
size = 16;
|
|
offs = 3;
|
|
break;
|
|
case REL:
|
|
type = REL_PC;
|
|
size = 8;
|
|
break;
|
|
default:
|
|
ierror(0);
|
|
break;
|
|
}
|
|
|
|
rl = add_extnreloc(&db->relocs,base,val,type,0,size,offs);
|
|
switch (modifier) {
|
|
case LOBYTE:
|
|
if (rl)
|
|
((nreloc *)rl->reloc)->mask = 0xff;
|
|
val = val & 0xff;
|
|
break;
|
|
case HIBYTE:
|
|
if (rl)
|
|
((nreloc *)rl->reloc)->mask = 0xff00;
|
|
val = (val >> 8) & 0xff;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
general_error(38); /* illegal relocation */
|
|
}
|
|
else {
|
|
/* constant/absolute value */
|
|
if (optype == REL)
|
|
val = val - (pc + offs + 1);
|
|
}
|
|
|
|
rangecheck(val,op);
|
|
|
|
/* write operand data */
|
|
switch (optype) {
|
|
case ABSX:
|
|
case ABSY:
|
|
if (!*(db->data)) /* STX/STY allow only ZeroPage addressing mode */
|
|
cpu_error(5,8); /* operand doesn't fit into 8 bits */
|
|
case ABS:
|
|
case INDIR:
|
|
case INDIRX:
|
|
*d++ = val & 0xff;
|
|
*d++ = (val>>8) & 0xff;
|
|
break;
|
|
case DPINDIR:
|
|
case INDX:
|
|
case INDY:
|
|
case ZPAGE:
|
|
case ZPAGEX:
|
|
case ZPAGEY:
|
|
if ((utaddr)val>=op->dp && (utaddr)val<=op->dp+0xff)
|
|
val -= op->dp;
|
|
case IMMED:
|
|
case REL:
|
|
*d++ = val & 0xff;
|
|
break;
|
|
case RELJMP:
|
|
if (d - db->data > 1)
|
|
ierror(0);
|
|
*d++ = 3; /* B!cc *+3 */
|
|
*d++ = 0x4c; /* JMP */
|
|
*d++ = val & 0xff;
|
|
*d++ = (val>>8) & 0xff;
|
|
break;
|
|
case WBIT:
|
|
*(db->data) |= (val&7) << 4; /* set bit number in opcode */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
cpu_error(3,bitsize); /* data size not supported */
|
|
|
|
db->size = bitsize >> 3;
|
|
db->data = mymalloc(db->size);
|
|
if (!eval_expr(op->value,&val,sec,pc)) {
|
|
symbol *base;
|
|
int btype;
|
|
rlist *rl;
|
|
|
|
modifier = 0;
|
|
btype = find_base(op->value,&base,sec,pc);
|
|
if (btype==BASE_OK || (btype==BASE_PCREL && modifier==0)) {
|
|
rl = add_extnreloc(&db->relocs,base,val,
|
|
btype==BASE_PCREL?REL_PC:REL_ABS,0,bitsize,0);
|
|
switch (modifier) {
|
|
case LOBYTE:
|
|
if (rl)
|
|
((nreloc *)rl->reloc)->mask = 0xff;
|
|
val = val & 0xff;
|
|
break;
|
|
case HIBYTE:
|
|
if (rl)
|
|
((nreloc *)rl->reloc)->mask = 0xff00;
|
|
val = (val >> 8) & 0xff;
|
|
break;
|
|
}
|
|
}
|
|
else if (btype != BASE_NONE)
|
|
general_error(38); /* illegal relocation */
|
|
}
|
|
if (bitsize < 16) {
|
|
if (val<-0x80 || val>0xff)
|
|
cpu_error(5,8); /* operand doesn't fit into 8-bits */
|
|
} else if (bitsize < 32) {
|
|
if (val<-0x8000 || val>0xffff)
|
|
cpu_error(5,16); /* operand doesn't fit into 16-bits */
|
|
}
|
|
|
|
setval(0,db->data,db->size,val);
|
|
return db;
|
|
}
|
|
|
|
|
|
operand *new_operand()
|
|
{
|
|
operand *new = mymalloc(sizeof(*new));
|
|
new->type = -1;
|
|
return new;
|
|
}
|
|
|
|
|
|
int cpu_available(int idx)
|
|
{
|
|
return (mnemonics[idx].ext.available & cpu_type) != 0;
|
|
}
|
|
|
|
|
|
int init_cpu()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
|
|
int cpu_args(char *p)
|
|
{
|
|
if (!strcmp(p,"-opt-branch"))
|
|
branchopt = 1;
|
|
else if (!strcmp(p,"-bbcade")) /* GMGM */
|
|
bbcade = 1;
|
|
else if (!strcmp(p,"-illegal"))
|
|
cpu_type |= ILL;
|
|
else if (!strcmp(p,"-dtv"))
|
|
cpu_type |= DTV;
|
|
else if (!strcmp(p,"-c02"))
|
|
cpu_type = M6502 | M65C02;
|
|
else if (!strcmp(p,"-wdc02"))
|
|
cpu_type = M6502 | M65C02 | WDC02;
|
|
else if (!strcmp(p,"-ce02"))
|
|
cpu_type = M6502 | M65C02 | WDC02 | CSGCE02;
|
|
else if (!strcmp(p,"-6280"))
|
|
cpu_type = M6502 | M65C02 | WDC02 | HU6280;
|
|
else
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|