#include "digirule2.h"

///////////////////////////////////////////////////////////////////////////////
// public data

bool_t address_blanking;
uint8 pc;
ram_t ram;

///////////////////////////////////////////////////////////////////////////////
// private data

static uint8 arg1, arg2, stack[STACK_DEPTH], sp;
static uint16 inst_timer, inst_rate;
static union16_t accum, temp, lfsr;

///////////////////////////////////////////////////////////////////////////////
// public functions

void cpu_reset( void )
{
	address_blanking = 0;
	pc = 0;
	sp = 0;
	inst_rate = 0;
	ACCUM = 0;
}

void cpu_process( void )
{
	if( RUN_LED )	// if CPU running
	{
		if( inst_rate == 0 || (t1ms && ++inst_timer == inst_rate) )		// if time to execute instruction
		{
			TEST_POINT = 1;
			
			switch( RAM[pc++] )
			{
				case HALT:			// stop the CPU (1 byte)
					RUN_LED = 0;
					break;

				case NOP:			// no operation (1 byte)
					break;

				case SPEED:			// set instruction execution rate (2 bytes)
					inst_rate = speed_to_rate[RAM[pc++]];
					break;

				case COPYLR:		// copy literal to RAM (3 bytes)
					arg1 = RAM[pc++];
					arg2 = RAM[pc++];
					RAM[arg2] = arg1;
					// status unaffected
					break;

				case COPYLA:		// copy literal to accumulator (2 bytes)
					ACCUM = RAM[pc++];
					// status unaffected
					break;

				case COPYAR:		// copy accumulator to RAM (2 bytes)
					arg1 = RAM[pc++];
					RAM[arg1] = ACCUM;
					// status unaffected
					break;

				case COPYRA:		// copy RAM to accumulator (2 bytes)
					arg1 = RAM[pc++];
					ACCUM = RAM[arg1];
					ZERO = ACCUM == 0;
					break;

				case COPYRR:		// copy RAM to RAM (3 bytes)
					arg1 = RAM[pc++];
					arg2 = RAM[pc++];
					RAM[arg2] = RAM[arg1];
					ZERO = RAM[arg2] == 0;
					break;

				case ADDLA:			// add literal to accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					accum.high_byte = 0;
					accum.word += arg1;
					CARRY = accum.bit8;
					ZERO = ACCUM == 0;
					break;

				case ADDRA:			// add RAM to accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					accum.high_byte = 0;
					accum.word += RAM[arg1];
					CARRY = accum.bit8;
					ZERO = ACCUM == 0;
					break;

				case SUBLA:			// subtract literal from accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					accum.high_byte = 0;
					accum.word -= arg1;
					CARRY = accum.bit8;
					ZERO = ACCUM == 0;
					break;

				case SUBRA:			// subtract RAM from accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					accum.high_byte = 0;
					accum.word -= RAM[arg1];
					CARRY = accum.bit8;
					ZERO = ACCUM == 0;
					break;

				case ANDLA:			// AND literal with accumulator, result in accumulator (2 bytes)
					ACCUM &= RAM[pc++];
					ZERO = ACCUM == 0;
					break;

				case ANDRA:			// AND RAM with accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					ACCUM &= RAM[arg1];
					ZERO = ACCUM == 0;
					break;

				case ORLA:			// OR literal with accumulator, result in accumulator (2 bytes)
					ACCUM |= RAM[pc++];
					ZERO = ACCUM == 0;
					break;

				case ORRA:			// OR RAM with accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					ACCUM |= RAM[arg1];
					ZERO = ACCUM == 0;
					break;

				case XORLA:			// XOR literal with accumulator, result in accumulator (2 bytes)
					ACCUM ^= RAM[pc++];
					ZERO = ACCUM == 0;
					break;

				case XORRA:			// XOR RAM with accumulator, result in accumulator (2 bytes)
					arg1 = RAM[pc++];
					ACCUM ^= RAM[arg1];
					ZERO = ACCUM == 0;
					break;

				case DECR:			// decrement RAM (2 bytes)
					arg1 = RAM[pc++];
					RAM[arg1]--;
					ZERO = RAM[arg1] == 0;
					break;

				case INCR:			// increment RAM (2 bytes)
					arg1 = RAM[pc++];
					RAM[arg1]++;
					ZERO = RAM[arg1] == 0;
					break;

				case DECRJZ:		// decrement RAM; if zero, skip next two bytes of code (2 bytes)
					arg1 = RAM[pc++];
					RAM[arg1]--;
					ZERO = RAM[arg1] == 0;
					
					if( ZERO )
						pc += 2;
					
					break;

				case INCRJZ:		// increment RAM; if zero, skip next two bytes of code (2 bytes)
					arg1 = RAM[pc++];
					RAM[arg1]++;
					ZERO = RAM[arg1] == 0;
					
					if( ZERO )
						pc += 2;
					
					break;

				case SHIFTRL:		// rotate RAM left through CARRY (2 bytes)
					arg1 = RAM[pc++];
					temp.low_byte = RAM[arg1];
					temp.word <<= 1, temp.bit0 = CARRY, CARRY = temp.bit8;
					RAM[arg1] = temp.low_byte;
					break;

				case SHIFTRR:		// rotate RAM right through CARRY (2 bytes)
					arg1 = RAM[pc++];
					temp.high_byte = RAM[arg1];
					temp.word >>= 1, temp.bit15 = CARRY, CARRY = temp.bit7;
					RAM[arg1] = temp.high_byte;
					break;

				case CBR:			// clear bit of RAM (3 bytes)
					arg1 = RAM[pc++];
					arg2 = RAM[pc++];
					RAM[arg2] &= ~(1 << (arg1 & 7));
					break;

				case SBR:			// set bit of RAM (3 bytes)
					arg1 = RAM[pc++];
					arg2 = RAM[pc++];
					RAM[arg2] |= (1 << (arg1 & 7));
					break;

				case BCRSC:			// check bit of RAM; if clear, skip next two bytes of code (3 bytes) 
					arg1 = RAM[pc++];
					arg2 = RAM[pc++];

					if( (RAM[arg2] & (1 << (arg1 & 7))) == 0 )
						pc += 2;
					
					break;

				case BCRSS:			// check bit of RAM; if set, skip next two bytes of code (3 bytes) 
					arg1 = RAM[pc++];
					arg2 = RAM[pc++];

					if( (RAM[arg2] & (1 << (arg1 & 7))) != 0 )
						pc += 2;
					
					break;

				case JUMP:			// address to PC (2 bytes)
					pc = RAM[pc];
					break;

				case CALL:			// push PC to stack, address to PC (2 bytes)
					arg1 = RAM[pc++];	// increment PC in case of halt
					
					if( sp == STACK_DEPTH )
						RUN_LED = 0;	// halt on stack overflow
					else
					{
						stack[sp++] = pc;
						pc = arg1;
					}
					
					break;

				case RETLA:			// copy literal to accumulator, RETURN (2 bytes)
					ACCUM = RAM[pc++];
					// fall through to RETURN
					
				case RETURN:		// pop stack to PC (1 byte)
					if( sp == 0 )
						RUN_LED = 0;	// halt on stack underflow
					else
						pc = stack[--sp];
					
					break;

				case ADDRPC:		// add RAM to PC (2 bytes)
					arg1 = RAM[pc++];
					pc += RAM[arg1];
#if LEGACY_ADDRPC
					pc -= 2;
#endif
					break;
					
				case INITSP:		// initialize stack pointer (1 byte)
					sp = 0;
					break;
					
				case RANDA:			// store pseudo-random number (1..255) in accumulator (1 byte)
					ACCUM = lfsr.low_byte;
					lfsr.bit8 = lfsr.bit0 ^ lfsr.bit2 ^ lfsr.bit3 ^ lfsr.bit4;		// polynomial is x8 + x6 + x5 + x4 + 1
					lfsr.word >>= 1;
					break;
					
				default:
					RUN_LED = 0;	// halt on illegal opcode
					break;
			}
			
			ADDRESS_LEDS = address_blanking ? 0b00000000 : ram.status_reg.flags.show_address_led_reg ? ram.address_led_reg : pc;
			DATA_LEDS = ram.data_led_reg;
			inst_timer = 0;
			
			TEST_POINT = 0;
		}
	}
	else	// if CPU stopped
	{
		if( ++lfsr.low_byte == 0 )	// re-seed the PRNG, but disallow illegal zero state
			lfsr.low_byte++;
		
		inst_timer = 0;				// impose instruction delay on run
	}
}
