/*
four registers + flag + pc

Insns:
 000 mov lit16, reg
 001 mov [reg], reg
 010 mov reg, [reg]
 011 mov reg, reg
 100 add reg, reg
 101 tst reg, reg
 110 and reg, reg
 111 not reg
 
Predicates:
 000 Never
 001 Not-Equal
 010 Equal
 011 Less than
 100 Greater than
 111 Always

Opcode format:
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
|-INSN-| |-PRED-|       |SREGISTER| |TREGISTER|
*/

#include <stdio.h>

unsigned short regs[6];
enum regnames {
 REG_R0 = 0,
 REG_R1,
 REG_R2,
 REG_R3,
 REG_FR,
 REG_PC
};
#define PRED_NV 0x0
#define PRED_NE 0x1
#define PRED_EQ 0x2
#define PRED_LT 0x3
#define PRED_GT 0x4
#define PRED_AL 0x7

#define FLAG_LT 0x1
#define FLAG_GT 0x2
#define FLAG_EQ 0x4

#define INSN_MOV_LIT16_REG 0x0
#define INSN_MOV_ADR_REG_REG 0x1
#define INSN_MOV_REG_ADR_REG 0x2
#define INSN_MOV_REG_REG 0x3
#define INSN_ADD_REG_REG 0x4
#define INSN_TST_REG_REG 0x5
#define INSN_AND_REG_REG 0x6
#define INSN_NOT_REG 0x7

#define INSN(insn, pred, sreg, treg) (((insn) << 13) | ((pred) << 10) | ((sreg) << 4) | ((treg)))

unsigned short rom[16384] = {
    INSN(INSN_MOV_LIT16_REG, PRED_AL, 0, REG_R3), 9,
    INSN(INSN_MOV_LIT16_REG, PRED_AL, 0, REG_R0), '9',
    INSN(INSN_MOV_LIT16_REG, PRED_AL, 0, REG_R1), 0x4000,
    INSN(INSN_MOV_LIT16_REG, PRED_AL, 0, REG_R2), 0xFFFF,
    INSN(INSN_MOV_REG_ADR_REG, PRED_AL, REG_R0, REG_R1),		/* loop 0x0007 */
    INSN(INSN_ADD_REG_REG, PRED_AL, REG_R2, REG_R3),
    INSN(INSN_ADD_REG_REG, PRED_AL, REG_R2, REG_R0),
    INSN(INSN_TST_REG_REG, PRED_AL, REG_R2, REG_R3),
    INSN(INSN_MOV_LIT16_REG, PRED_NE, 0, REG_PC), 0x0007,
    INSN(INSN_MOV_LIT16_REG, PRED_AL, 0, REG_R0), '\n',
    INSN(INSN_MOV_REG_ADR_REG, PRED_AL, REG_R0, REG_R1),
    INSN(INSN_MOV_LIT16_REG, PRED_AL, 0, REG_PC), 0x0010,
};
unsigned short ram[32768];

unsigned short fetch(unsigned short address)
{
    if (address & 0x8000)
        return ram[address & 0x7FFF];
    else
    {
        if (address & 0x4000)
        {
            printf("Read from peripheral address %04x invalid!\n", address);
            abort();
        } else
            return rom[address];
    }
}

void store(unsigned short address, unsigned short data)
{
    if (address & 0x8000)
        ram[address & 0x7FFF] = data;
    else
    {
        if (address & 0x4000)	/* peripheral */
        {
            printf("%c", (unsigned char)data);
            fflush(stdout);
        }
        else
        {
            printf("Store to ROM address %04x invalid!\n", address);
            abort();
        }
    }
}


void reset()
{
  regs[REG_PC] = 0x0;
  regs[REG_FR] = 0x0;
}

void cpuloop()
{
    while(1)
    {
        unsigned short insn;
        unsigned char pred_match;
        unsigned short tmp;
        insn = fetch(regs[REG_PC]);
        switch ((insn >> 10) & 0x7)
        {
        case PRED_NV:	pred_match = 0;	break;
        case PRED_NE:	pred_match = regs[REG_FR] & (FLAG_LT | FLAG_GT); break;
        case PRED_EQ:	pred_match = regs[REG_FR] & FLAG_EQ; break;
        case PRED_LT:	pred_match = regs[REG_FR] & FLAG_LT; break;
        case PRED_GT:	pred_match = regs[REG_FR] & FLAG_GT; break;
        case PRED_AL:	pred_match = 1; break;
        default:		printf("Invalid predicate %1x, aborting.\n", (insn >> 10) & 0x7);
                        abort();
        }
        switch ((insn >> 13) & 0x7)
        {
        case INSN_MOV_LIT16_REG:					regs[REG_PC]++;
                                    if (pred_match) tmp = fetch(regs[REG_PC]);
                                    if (pred_match) regs[insn & 0xF] = tmp;
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        case INSN_MOV_ADR_REG_REG:	if (pred_match) tmp = fetch(regs[(insn >> 4) & 0xF]);
                                    if (pred_match) regs[insn & 0xF] = tmp;
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        case INSN_MOV_REG_ADR_REG:	if (pred_match)	tmp = regs[(insn >> 4) & 0xF];
                                    if (pred_match) store(regs[insn & 0xF], tmp);
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        case INSN_MOV_REG_REG:		if (pred_match) tmp = regs[(insn >> 4) & 0xF];
                                    if (pred_match) regs[insn & 0xF] = tmp;
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        case INSN_ADD_REG_REG:		if (pred_match) tmp = regs[(insn >> 4) & 0xF];
                                    if (pred_match) tmp += regs[insn & 0xF];
                                    if (pred_match) regs[insn & 0xF] = tmp;
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        case INSN_TST_REG_REG:		if (pred_match) tmp = regs[(insn >> 4) & 0xF];
                                    if (pred_match) regs[REG_FR] = 0;
                                    if (pred_match) if (tmp == regs[insn & 0xF]) regs[REG_FR] |= FLAG_EQ;
                                    if (pred_match) if (tmp < regs[insn & 0xF]) regs[REG_FR] |= FLAG_LT;
                                    if (pred_match) if (tmp > regs[insn & 0xF]) regs[REG_FR] |= FLAG_GT;
                                                    regs[REG_PC]++;
                                                    break;
        
        case INSN_AND_REG_REG:		if (pred_match) tmp = regs[(insn >> 4) & 0xF];
                                    if (pred_match) regs[insn & 0xF] &= tmp;
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        case INSN_NOT_REG:			if (pred_match)	tmp = ~regs[(insn >> 4) & 0xF];
                                    if (pred_match) regs[(insn >> 4) & 0xF] = tmp;
                                                    regs[REG_PC]++;
                                                    break;
                                                    
        default:					printf("Internal emulation error: Out of range opcode\n");
                                    abort();
        }
    }
}


int main()
{
  reset();
  cpuloop();
  return 0;
}
