#include "11.h"

enum {
	SR_C = 1,
	SR_AC_EQ_MQ15 = 2,
	SR_AC_MQ_0 = 4,
	SR_MQ_0 = 010,
	SR_AC_0 = 020,
	SR_AC_1 = 040,
	SR_NEG = 0100,
	SR_OV = 0200
};

enum {
	KE_DIV  = 0777300,
	KE_AC   = 0777302,
	KE_MQ   = 0777304,
	KE_MULT = 0777306,
	KE_SCSR = 0777310,
	KE_NORM = 0777312,
	KE_LSH  = 0777314,
	KE_ASH  = 0777316,
};

#define B30 (1<<30)

static void
setflags(KE11 *ke)
{
	ke->sr &= ~(SR_AC_EQ_MQ15|SR_AC_MQ_0|SR_MQ_0|SR_AC_0|SR_AC_1);
	if(ke->ac == 0 && sgn(ke->mq)==0 ||
	   ke->ac == 0177777 && sgn(ke->mq))
		ke->sr |= SR_AC_EQ_MQ15;
	if(ke->mq == 0 && ke->ac == 0)
		ke->sr |= SR_AC_MQ_0;
	if(ke->mq == 0)
		ke->sr |= SR_MQ_0;
	if(ke->ac == 0)
		ke->sr |= SR_AC_0;
	if(ke->ac == 0177777)
		ke->sr |= SR_AC_1;
}

static void
setflag(KE11 *ke, int flg, uint32 c)
{
	if(c)
		ke->sr |= flg;
	else
		ke->sr &= ~flg;
}

/* Functions are a transcription of the flow charts in the manual.
   May be a bit inefficient but it's nice to be accurate. */

static void
mult(KE11 *ke)
{
	uint32 sum, md;
	int c;

	sum = 0;
	md = ke->ac<<16 | ke->mq;
	c = 0;
	ke->sc = 16;
	do{
		if((ke->x & 1) != c){
			if(ke->x&1)
				sum += ~md + 1;
			else
				sum += md;
		}
		md <<= 1;
		c = ke->x&1;
		ke->x >>= 1;
		ke->sc--;
	}while(ke->sc);
	ke->ac = sum>>16;
	ke->mq = sum;
	ke->sr &= ~SR_C;
	setflags(ke);
}

static uint32
abs32(uint32 x)
{
	return x&B31 ? ~x + 1 : x;
}

static void
divide(KE11 *ke)
{
	uint32 dd, dr;
	uint16 q;
	int c;
	int s;
	int ov;

	dd = ke->ac<<16 | ke->mq;
	dr = ke->x<<16;
	q = 0;
	ov = 0;
	ke->sr &= ~SR_C;
	s = !!(dd&B31);
	ke->sc = 16;
	do{
		c = (dr&B31) == (dd&B31);
		dr >>= 1;
		if(dr&B30)
			dr |= B31;
		q <<= 1;
		q |= c;
		if(c)
			dd += ~dr + 1;
		else
			dd += dr;
		if(ke->sc == 16 && dd != 0 && !!(dd>>31 & 1) == s)
			goto ov;
		ke->sc--;
	}while(ke->sc);

	if(dd == 0 ||
/* is this correct? */
	   dd + abs32(dr) != 0 && !!(dd>>31 & 1) == s){
		c = 1;
		q <<= 1;
		q |= c;
	}else{
		c = (dd&B31) == (dr&B31);
		if(c)
			dd += ~dr + 1;
		else
			dd += dr;
		q += c;
		c = 0;
		q <<= 1;
	}

	/* DOCU manual says DD in one place and DR in another...
	 * which is correct? have to check */
	if((dr>>31 & 1) != (q>>15 & 1) != s)
ov:
		ov = 1;

	ke->ac = dd;
	ke->mq = q;
	setflag(ke, SR_OV, ov != (ke->sr>>6 & 1));
	setflags(ke);
	if(ov)
		setflag(ke, SR_NEG, s);
	return;
}

static void
norm(KE11 *ke)
{
	uint32 sh;

	sh = ke->ac<<16 | ke->mq;
	ke->sc = 0;
	ke->sr &= ~(SR_OV|SR_C);
	setflag(ke, SR_NEG, sh & B31);
	if(ke->sr & SR_NEG)
		ke->sr |= SR_OV;
loop:
	if((sh>>31 & 1) != (sh>>30 & 1))
		goto done;
	if(sh == (3U<<30))
		goto done;
	if(ke->sc == 31)
		goto done;
	sh <<= 1;
	ke->sc++;
	goto loop;
done:
	ke->ac = sh>>16;
	ke->mq = sh;
	setflags(ke);
}

static void
lsh(KE11 *ke)
{
	uint32 sh;
	int ov;

	sh = ke->ac<<16 | ke->mq;
	setflag(ke, SR_NEG, sh&B31);
	ov = 0;
loop:
	ke->sc &= 077;
	if(ke->sc == 0)
		goto done;
	if(ke->sc&040){
		/* negative, shift right */
		setflag(ke, SR_C, sh&1);
		sh >>= 1;
		ke->sr &= ~SR_NEG;
		ke->sc++;
	}else{
		/* positive, shift left */
		setflag(ke, SR_C, sh&B31);
		sh <<= 1;
		if((ke->sr>>6 & 1) != (sh>>31 & 1))
			ov = 1;	/* sign changed */
		setflag(ke, SR_NEG, sh&B31);
		ke->sc--;
	}
	goto loop;
done:
	setflag(ke, SR_OV, ov != (ke->sr>>6 & 1));
	ke->ac = sh>>16;
	ke->mq = sh;
	setflags(ke);
}

static void
ash(KE11 *ke)
{
	uint32 sh;
	int ov;

	sh = ke->ac<<16 | ke->mq;
	setflag(ke, SR_NEG, sh&B31);
	ov = 0;
loop:
	ke->sc &= 077;
	if(ke->sc == 0)
		goto done;
	if(ke->sc&040){
		/* negative, shift right */
		setflag(ke, SR_C, sh&1);
		sh >>= 1;
		if(ke->sr & SR_NEG)
			sh |= B31;
		ke->sc++;
	}else{
		/* positive, shift left */
		sh <<= 1;
		setflag(ke, SR_C, sh&B31);
		if(ke->sr & SR_NEG)
			sh |= B31;
		else
			sh &= ~B31;
		if((ke->sr & SR_C) != (ke->sr>>6 & 1))
			ov = 1;	/* sign changed */
		ke->sc--;
	}
	goto loop;
done:
	setflag(ke, SR_OV, ov != (ke->sr>>6 & 1));
	ke->ac = sh>>16;
	ke->mq = sh;
	setflags(ke);
}

int
dato_ke11(Bus *bus, void *dev)
{
	KE11 *ke = dev;
	if(bus->addr >= 0777300 && bus->addr < 0777320){
//		printf("EAE DATO %o %o\n", bus->addr, bus->data);
		switch(bus->addr){
		case KE_DIV:
			ke->x = bus->data;
			divide(ke);
			break;
		case KE_AC:
			ke->ac = bus->data;
			break;
		case KE_MQ:
			ke->mq = bus->data;
			/* sign extend into AC */
			if(ke->mq&0100000)
				ke->ac = 0177777;
			else
				ke->ac = 0;
			break;
		case KE_MULT:
			ke->x = bus->data;
			mult(ke);
			break;
		case KE_SCSR:
			ke->sc = bus->data & 077;
			SETMASK(ke->sr, bus->data>>8, 0301);
			break;
		case KE_NORM:
			norm(ke);
			break;
		case KE_LSH:
			ke->sc = bus->data & 077;
			lsh(ke);
			break;
		case KE_ASH:
			ke->sc = bus->data & 077;
			ash(ke);
			break;
		default: assert(0);	/* can't happen */
		}
		return 0;
	}
	return 1;
}

int
datob_ke11(Bus *bus, void *dev)
{
	KE11 *ke = dev;
	if(bus->addr >= 0777300 && bus->addr < 0777320){
//		printf("EAE DATOB %o %o\n", bus->addr, bus->data);
		switch(bus->addr){
		case KE_DIV:
			ke->x = sxt(bus->data);
			divide(ke);
			break;
		case KE_AC:
			ke->ac = sxt(bus->data);
			break;
		case KE_AC+1:
			SETMASK(ke->ac, bus->data, 0177400);
			break;
		case KE_MQ:
			ke->mq = sxt(bus->data);
			/* sign extend into AC */
			if(ke->mq&0100000)
				ke->ac = 0177777;
			else
				ke->ac = 0;
			break;
		case KE_MQ+1:
			SETMASK(ke->mq, bus->data, 0177400);
			/* sign extend into AC */
			if(ke->mq&0100000)
				ke->ac = 0177777;
			else
				ke->ac = 0;
			break;
		case KE_MULT:
			ke->x = sxt(bus->data);
			mult(ke);
			break;
		case KE_SCSR:
			break;
		case KE_NORM:
			norm(ke);
			break;
		case KE_LSH:
			ke->sc = bus->data & 077;
			lsh(ke);
			break;
		case KE_ASH:
			ke->sc = bus->data & 077;
			ash(ke);
			break;
		case KE_DIV+1:
		case KE_MULT+1:
		case KE_SCSR+1:
		case KE_NORM+1:
		case KE_LSH+1:
		case KE_ASH+1:
			break;

		default: assert(0);	/* can't happen */
		}
		return 0;
	}
	return 1;
}

int
dati_ke11(Bus *bus, void *dev)
{
	KE11 *ke = dev;
	if(bus->addr >= 0777300 && bus->addr < 0777320){
//		printf("EAE DATI %o\n", bus->addr);
		switch(bus->addr){
		case KE_DIV:
		case KE_MULT:
		case KE_LSH:
		case KE_ASH:
			bus->data = 0;
			break;
		case KE_AC:
			bus->data = ke->ac;
			break;
		case KE_MQ:
			bus->data = ke->mq;
			break;
		case KE_SCSR:
			bus->data = WD(ke->sr, ke->sc);
			break;
		case KE_NORM:
			bus->data = ke->sc & 077;
			break;

		default: assert(0);	/* can't happen */
		}
		return 0;
	}
	return 1;
}

void
reset_ke11(void *dev)
{
	KE11 *ke = dev;
	ke->ac = 0;
	ke->mq = 0;
	ke->x = 0;
	ke->sc = 0;
	ke->sr = 0;
	setflags(ke);
}

void
eaetest(KE11 *ke)
{
/*
	// normalize
//	ke->ac = ~0; ke->mq = ~0;
//	ke->ac = 0177770; ke->mq = 0;
//	ke->ac = 0100000; ke->mq = 0;
	ke->ac = 0; ke->mq = 0;
	norm(ke);
*/
/*
	ke->sc = 0100-3;
	ke->ac = 0123456; ke->mq = 0111222;
	ash(ke);
*/
/*
	ke->ac = 0; ke->mq = 00123;
	ke->x = 0321;
	mult(ke);
*/
/*
	ke->ac = 00021; ke->mq = 0;
	ke->x = 0321;
	divide(ke);
*/

/*	int a, b, c, d;
	//some multiplication test
	for(a = 0; a < 0077777; a++){
		for(b = 0; b < 0077777; b++){
			c = a * b;
			ke->ac = 0; ke->mq = a;
			ke->x = b;
			mult(ke);
			d = ke->ac<<16 | ke->mq;
			assert(c == d);
			if(a == 0)
				continue;
			ke->x = a;
			divide(ke);
			assert(ke->ac == 0);
			assert(ke->mq == b);
		}
		printf("%o\n", a);
	}
	// division
	for(a = 0; a < 0077777; a++){
		printf("%o\n", a);
		for(b = 1; b < 0077777; b++){
			ke->ac = 0; ke->mq = a;
			ke->x = b;
			divide(ke);
			assert(ke->mq == a / b);
			assert(ke->ac == a % b);
		}
	}
*/
	
//	printf("%06o %06o %d %o\n", ke->ac, ke->mq, ke->sc, ke->sr);
}
