/* instruction.c */

#include <string.h>
#include "project.h"

static void do_la(unsigned preg1, unsigned nreg2, unsigned imm16)
{
	int i;
	char pre;
	char base;

	reg_t* reg1 = reg_by_code(preg1, R_PSEUDO);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	int word = mem_read(reg_read(reg2) + imm16);

	if (strcmp(reg1->name, "uh") == 0)      {pre = 'u'; base = '4';}
	else if (strcmp(reg1->name, "ul") == 0) {pre = 'u'; base = '0';}
	else if (strcmp(reg1->name, "vh") == 0) {pre = 'v'; base = '4';}
	else if (strcmp(reg1->name, "vl") == 0) {pre = 'v'; base = '0';}
	else die("Invalid register '%d'", preg1);
	
	for (i = 0; i < 4; i++) {
		char name[3] = {pre, base + i, 0};
		reg_t* dest = reg_by_name(name);
		reg_write(dest, (word >> (i * 8)) - '0');
	}
}

static void do_lb(unsigned preg1, unsigned nreg2, unsigned imm16)
{
	int i;
	char pre;

	reg_t* reg1 = reg_by_code(preg1, R_PSEUDO);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	int word = mem_read(reg_read(reg2) + imm16);

	if (strcmp(reg1->name, "u") == 0)      pre = 'u';
	else if (strcmp(reg1->name, "v") == 0) pre = 'v';
	else die("Invalid register '%d'", preg1);
	
	for (i = 0; i < 8; i++) {
		char name[3] = {pre, '7' - i, 0};
		reg_write(reg_by_name(name), word >> (i * 4));
	}
}

static void do_sb(unsigned preg1, unsigned nreg2, unsigned imm16)
{
	int i;
	int word = 0;
	char pre;

	reg_t* reg1 = reg_by_code(preg1, R_PSEUDO);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);

	if (strcmp(reg1->name, "u") == 0)      pre = 'u';
	else if (strcmp(reg1->name, "v") == 0) pre = 'v';
	else die("Invalid register '%d'", preg1);
	
	for (i = 0; i < 8; i++) {
		char name[3] = {pre, '7' - i, 0};
		word |= reg_read(reg_by_name(name)) << (i * 4);
	}

	mem_write(reg_read(reg2) + imm16, word);
}

static void do_sw(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	mem_write(reg_read(reg2) + imm16, reg_read(reg1));
}

static void do_beqi(unsigned nreg1, unsigned imm4, unsigned imm16)
{
	unsigned val = reg_read(reg_by_code(nreg1, R_NORMAL));
	if (val == imm4) reg_write(reg_by_name("pc"), imm16);
}

static void do_bgt(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	unsigned val1 = reg_read(reg_by_code(nreg1, R_NORMAL));
	unsigned val2 = reg_read(reg_by_code(nreg2, R_NORMAL));
	if (val1 > val2) reg_write(reg_by_name("pc"),imm16);
}

static void do_lui(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_write(reg1, (((reg_read(reg2) + imm16) & 0xffff) << 16) |
			(reg_read(reg1) & 0xffff));
}

static void do_min(unsigned nreg1, unsigned nreg2, unsigned nreg3)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_t* reg3 = reg_by_code(nreg3, R_NORMAL);
	reg_write(reg1, reg_read(reg_read(reg2) < reg_read(reg3) ? reg2 :reg3));
}

static void do_add(unsigned nreg1, unsigned nreg2, unsigned nreg3)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_t* reg3 = reg_by_code(nreg3, R_NORMAL);
	reg_write(reg1, reg_read(reg2) + reg_read(reg3));
}

static void do_addi(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_write(reg1, reg_read(reg2) + imm16);
}

static void do_adds(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_write(reg1, reg_read(reg1) + (reg_read(reg2) << imm16));
}

static void do_subi(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_write(reg1, imm16 - reg_read(reg2));
}

static void do_cshri(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	int val = reg_read(reg2);
	int ceil = (val & ((1 << imm16) - 1)) > 0 ? 1 : 0;
	reg_write(reg1, ceil + (val >> imm16));
}

/*
static void do_shli(unsigned nreg1, unsigned nreg2, unsigned imm16)
{
	reg_t* reg1 = reg_by_code(nreg1, R_NORMAL);
	reg_t* reg2 = reg_by_code(nreg2, R_NORMAL);
	reg_write(reg1, reg_read(reg2) << imm16);
}
*/

static void do_badd(unsigned breg1, unsigned breg2, unsigned breg3)
{
	reg_t* reg1 = reg_by_code(breg1, R_BCD);
	reg_t* reg2 = reg_by_code(breg2, R_BCD);
	reg_t* reg3 = reg_by_code(breg3, R_BCD);
	reg_t* cf = reg_by_name("cf");
	unsigned r2_val = reg_read(reg2);
	unsigned r3_val = reg_read(reg3);
	unsigned cf_val = reg_read(cf) != 0 ? 1 : 0;
	unsigned result = r2_val + r3_val + cf_val;
	if (r2_val > 9) die("Invalid value '%01x' in register '%s'\n",
			r2_val, reg2->name);
	if (r3_val > 9) die("Invalid value '%01x' in register '%s'\n",
			r3_val, reg3->name);
	if (result < 10) {
		reg_write(cf, 0);
		reg_write(reg1, result);
	} else {
		reg_write(cf, 1);
		reg_write(reg1, result - 10);
	}
}

static void do_baddi(unsigned breg1, unsigned breg2, unsigned imm4)
{
	reg_t* reg1 = reg_by_code(breg1, R_BCD);
	reg_t* reg2 = reg_by_code(breg2, R_BCD);
	reg_t* cf = reg_by_name("cf");
	unsigned r2_val = reg_read(reg2);
	unsigned cf_val = reg_read(cf) != 0 ? 1 : 0;
	unsigned result = r2_val + imm4 + cf_val;
	if (r2_val > 9) die("Invalid value '%01x' in register '%s'\n",
			r2_val, reg2->name);
	if (result < 10) {
		reg_write(cf, 0);
		reg_write(reg1, result);
	} else {
		reg_write(cf, 1);
		reg_write(reg1, result - 10);
	}
}

/* List of all instructions */
static insn_t insns[] = {

{"read",  0x00, I_NORMAL, vm_read,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"disp",  0x01, I_NORMAL, vm_disp,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"la",    0x10, I_MEMORY, do_la,
(arg_t){8, 4, A_PREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"lb",    0x11, I_MEMORY, do_lb,
(arg_t){8, 4, A_PREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"sb",    0x12, I_MEMORY, do_sb,
(arg_t){8, 4, A_PREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"sw",    0x13, I_MEMORY, do_sw,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"beqi",  0x20, I_NORMAL, do_beqi,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_IMM4}, (arg_t){16, 16, A_IMM16}},

{"bgt",   0x21, I_NORMAL, do_bgt,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"min",   0x30, I_NORMAL, do_min,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16,  4, A_NREG}},

{"add",   0x31, I_NORMAL, do_add,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16,  4, A_NREG}},

{"addi",  0x32, I_NORMAL, do_addi,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"adds",  0x33, I_NORMAL, do_adds,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"subi",  0x34, I_NORMAL, do_subi,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

{"cshri", 0x35, I_NORMAL, do_cshri,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},

/*
{"shli",  0x36, I_NORMAL, do_shli,
(arg_t){8, 4, A_NREG}, (arg_t){12, 4, A_NREG}, (arg_t){16, 16, A_IMM16}},
*/

{"badd",  0x40, I_NORMAL, do_badd,
(arg_t){8, 4, A_BREG}, (arg_t){12, 4, A_BREG}, (arg_t){16,  4, A_BREG}},

{"baddi", 0x41, I_NORMAL, do_baddi,
(arg_t){8, 4, A_BREG}, (arg_t){12, 4, A_BREG}, (arg_t){16,  4, A_IMM4}},

{NULL}
};

/* Given the name of an instruction, returns its insn_t*. */
insn_t* insn_by_name(char* name)
{
	insn_t* p = insns;
	while (p->name != NULL && strcmp(p->name, name) != 0) p++;
	if (p->name == NULL) return NULL;
	return p;
}

/* Given the code for an instruction, returns its insn_t*. */
insn_t* insn_by_code(unsigned code)
{
	insn_t* p = insns;
	while (p->name != NULL && p->opcode != (code & 0xff)) p++;
	if (p->name == NULL) return NULL;
	return p;
}

/* Encodes the instruction given by 'insn' through 'arg3' and returns the
 * result. */
unsigned insn_encode(insn_t* insn, unsigned arg1, unsigned arg2, unsigned arg3)
{
	return (insn->opcode & 0xff) |
	((arg1 & ((1 << insn->arg1.width) - 1)) << insn->arg1.pos) |
	((arg2 & ((1 << insn->arg2.width) - 1)) << insn->arg2.pos) |
	((arg3 & ((1 << insn->arg3.width) - 1)) << insn->arg3.pos);
}

/* Decodes the instruction given by 'code' into 'insn' through '*arg3'.  '*insn'
 * is set to NULL on failure. */
void insn_decode(unsigned code, insn_t** insn, unsigned* arg1, unsigned* arg2,
unsigned* arg3)
{
	*insn = insn_by_code(code);
	if (*insn == NULL) return;
	*arg1 = (code >> (*insn)->arg1.pos) & ((1 << (*insn)->arg1.width) - 1);
	*arg2 = (code >> (*insn)->arg2.pos) & ((1 << (*insn)->arg2.width) - 1);
	*arg3 = (code >> (*insn)->arg3.pos) & ((1 << (*insn)->arg3.width) - 1);
}

/* EOF */

