/*
 * cpu.c 6800, 68hc11 cpu description file
 * (c) in 2013-2014 by Esben Norby and Frank Wille
 */

#include "vasm.h"

mnemonic mnemonics[] = {
#include "opcodes.h"
};

int mnemonic_cnt = sizeof(mnemonics) / sizeof(mnemonics[0]);

int		bitsperbyte = 8;
int		bytespertaddr = 2;
char *		cpu_copyright = "vasm 6800/68hc11 cpu backend 0.2 (c) 2013-2014 Esben Norby";
char *		cpuname = "6800";

static uint8_t	cpu_type = M6800;
static int 	modifier;	/* set by find_base() */


int
init_cpu()
{
	return 1;
}


int
cpu_args(char *p)
{
	if (!strncmp(p, "-m68", 4)) {
		p += 4;
		if (!strcmp(p, "00"))
			cpu_type = M6800;
		else if (!stricmp(p, "hc11"))
			cpu_type = M68HC11;
		else
			return 0;
	}
	return 1;
}


char *
parse_cpu_special(char *start)
{
	return start;
}


operand *
new_operand()
{
	operand *new = mymalloc(sizeof(*new));
	new->type = -1;
	return new;
}


int
parse_operand(char *p, int len, operand *op, int required)
{
	char *start = p;

	op->value = NULL;

	switch (required) {
	    case IMM:
	    case IMM16:
		if (*p++ != '#')
			return PO_NOMATCH;
		p = skip(p);
                /* fall through */
	    case REL:
	    case DATAOP:
	    	op->value = parse_expr(&p);
	    	break;

	    case ADDR:
		if (*p == '<') {
			required = DIR;
			p++;
		}
		else if (*p == '>') {
			required = EXT;
			p++;
		}
	    	op->value = parse_expr(&p);
	    	break;

	    case DIR:
		if (*p == '>')
			return PO_NOMATCH;
		else if (*p == '<')
			p++;
	    	op->value = parse_expr(&p);
		break;

	    case EXT:
		if (*p == '<')
			return PO_NOMATCH;
		else if (*p == '>')
			p++;
	    	op->value = parse_expr(&p);
		break;

	    case REGX:
		if (toupper((unsigned char)*p++) != 'X')
			return PO_NOMATCH;
		break;
	    case REGY:
		if (toupper((unsigned char)*p++) != 'Y')
			return PO_NOMATCH;
		break;

	    default:
		return PO_NOMATCH;
	}

	op->type = required;
	return PO_MATCH;
}


static size_t
eval_oper(operand *op, section *sec, taddr pc, taddr offs, dblock *db)
{
	size_t size = 0;
        symbol *base = NULL;
        int btype;
	taddr val;

	if (op->value != NULL && !eval_expr(op->value, &val, sec, pc)) {
		modifier = 0;
		btype = find_base(op->value, &base, sec, pc);
	}

	switch (op->type) {
	    case ADDR:
		if (base != NULL || val < 0 || val > 0xff) {
			op->type = EXT;
			size = 2;
		}
		else {
			op->type = DIR;
			size = 1;
		}
		break;
	    case DIR:
		size = 1;
		if (db != NULL && (val < 0 || val > 0xff))
			cpu_error(1);  /* operand doesn't fit into 8-bits */
                break;
	    case IMM:
		size = 1;
		if (db != NULL && !modifier && (val < -0x80 || val > 0xff))
			cpu_error(1);  /* operand doesn't fit into 8-bits */
		break;
	    case EXT:
	    case IMM16:
		size = 2;
		break;
	    case REL:
		size = 1;
		break;
	}

	if (size > 0 && db != NULL) {
		/* create relocation entry and code for this operand */
		if (base != NULL && btype == BASE_OK && op->type == REL) {
			/* relative branches */
			if (!is_pc_reloc(base, sec)) {
				val = val - (pc + offs + 1);
				if (val < -0x80 || val > 0x7f)
					cpu_error(2); /* branch out of range */
			}
			else
				add_nreloc(&db->relocs, base, val, REL_PC,
				           8, offs << 3);
		}
		else if (base != NULL && btype != BASE_ILLEGAL) {
			rlist *rl;

			rl = add_nreloc(&db->relocs, base, val,
			                btype==BASE_PCREL? REL_PC : REL_ABS,
					size << 3, offs << 3);
			switch (modifier) {
			    case LOBYTE:
				if (rl) ((nreloc *)rl->reloc)->mask = 0xff;
				val &= 0xff;
				break;
			    case HIBYTE:
				if (rl) ((nreloc *)rl->reloc)->mask = 0xff00;
				val = (val >> 8) & 0xff;
				break;
			}
		}
		else if (base != NULL)
			general_error(38);  /* illegal relocation */

		if (size == 1) {
			op->code[0] = val & 0xff;
		}
		else if (size == 2) {
			op->code[0] = (val >> 8) & 0xff;
			op->code[1] = val & 0xff;
		}
		else
			ierror(0);
	}
	return (size);
}


size_t
instruction_size(instruction *ip, section *sec, taddr pc)
{
	operand op;
	int i;
	size_t size;

	size = (mnemonics[ip->code].ext.prebyte != 0) ? 2 : 1;

	for (i = 0; i < MAX_OPERANDS && ip->op[i] != NULL; i++) {
		op = *(ip->op[i]);
		size += eval_oper(&op, sec, pc, size, NULL);
	}

	return (size);
}


dblock *
eval_instruction(instruction *ip, section *sec, taddr pc)
{
	dblock *db = new_dblock();
	uint8_t opcode;
	uint8_t *d;
	int i;
	size_t size;

	/* evaluate operands and determine instruction size */
	opcode = mnemonics[ip->code].ext.opcode;
	size = (mnemonics[ip->code].ext.prebyte != 0) ? 2 : 1;
	for (i = 0; i < MAX_OPERANDS && ip->op[i] != NULL; i++) {
		size += eval_oper(ip->op[i], sec, pc, size, db);
		if (ip->op[i]->type == DIR)
			opcode = mnemonics[ip->code].ext.dir_opcode;
	}

	/* allocate and fill data block */
	db->size = size;
	d = db->data = mymalloc(size);

	if (mnemonics[ip->code].ext.prebyte != 0)
		*d++ = mnemonics[ip->code].ext.prebyte;
	*d++ = opcode;

	/* write operands */
	for (i = 0; i < MAX_OPERANDS && ip->op[i] != NULL; i++) {
		switch (ip->op[i]->type) {
		    case IMM:
		    case DIR:
		    case REL:
			*d++ = ip->op[i]->code[0];
			break;
		    case IMM16:
		    case EXT:
			*d++ = ip->op[i]->code[0];
			*d++ = ip->op[i]->code[1];
			break;
		}
	}

	return (db);
}

dblock *
eval_data(operand *op, size_t bitsize, section *sec, taddr pc)
{
	dblock *db = new_dblock();
	uint8_t *d;
	taddr val;

	if (bitsize != 8 && bitsize != 16)
		cpu_error(0,bitsize);  /* data size not supported */

	db->size = bitsize >> 3;
	d = 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_nreloc(&db->relocs, base, val,
			                btype==BASE_PCREL ? REL_PC : REL_ABS,
			                bitsize, 0);
			switch (modifier) {
			    case LOBYTE:
				if (rl) ((nreloc *)rl->reloc)->mask = 0xff;
				val &= 0xff;
				break;
			    case HIBYTE:
				if (rl) ((nreloc *)rl->reloc)->mask = 0xff00;
				val = (val >> 8) & 0xff;
				break;
			}
		}
		else
			general_error(38);  /* illegal relocation */
	}

	if (bitsize == 8) {
		if (val < -0x80 || val > 0xff)
			cpu_error(1);  /* operand doesn't fit into 8-bits */
	}
	else  /* 16 bits */
		*d++ = (val >> 8) & 0xff;
	*d = val & 0xff;

	return (db);
}


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
cpu_available(int idx)
{
	return (mnemonics[idx].ext.available & cpu_type) != 0;
}
