module instr;

/* Reference on the DLX instruction set---mostly a subset
   of MIPS with slightly different semantics---can be
   found at
   http://www.csee.umbc.edu/courses/undergraduate/411/spring96/dlx.html
*/

import std.string;
import std.stdio;
import cpu;

/* the full list of DLX opcodes; the only one missing is
   the arithmetic opcode, which is always 0x00. */
enum Opcode {
    addi = 0x08,
    andi = 0x0c,
    beqz = 0x04,
    bnez = 0x05,
    j    = 0x02,
    jal  = 0x03,
    jalr = 0x13,
    jr   = 0x12,
    lhi  = 0x0f,
    lw   = 0x23,
    ori  = 0x0d,
    seqi = 0x18,
    slei = 0x1c,
    slli = 0x14,
    slti = 0x1a,
    snei = 0x19,
    srai = 0x17,
    srli = 0x16,
    subi = 0x0a,
    sw   = 0x2b,
    xori = 0x0e,
    halt = 0x30, /* These two are not part of the instruction */
    pln  = 0x31, /* set but were added to make debugging easier. */
}

/* These are the arithmetic 'opcodes', which are found to
   the far right in an arithmetic instruction. */
enum Func {
    add = 0x20,
    and = 0x24,
    or  = 0x25,
    seq = 0x28,
    sle = 0x2c,
    sll = 0x04,
    slt = 0x2a,
    sne = 0x29,
    sra = 0x07,
    srl = 0x06,
    sub = 0x22,
    xor = 0x26,
    nul = 0x00,
}

/* The masks to extract the individual portions of
   the bytecode instructions. */
enum Masks {
    opcode = 0b11111100000000000000000000000000,
    rs     = 0b00000011111000000000000000000000,
    rt     = 0b00000000000111110000000000000000,
    rd     = 0b00000000000000001111100000000000,
    shamt  = 0b00000000000000000000011111000000,
    funct  = 0b00000000000000000000000000111111,
    immed  = 0b00000000000000001111111111111111,
    addr   = 0b00000011111111111111111111111111,
}

/* A set of related functions to extract and shift
 * the relevant pieces of an instruction. */
ubyte extract_opcode(uint instr) {
    return (Masks.opcode & instr) >> 26;
}

ubyte extract_rs(uint instr) {
    return (Masks.rs & instr) >> 21;
}

ubyte extract_rt(uint instr) {
    return (Masks.rt & instr) >> 16;
}

ubyte extract_rd(uint instr) {
    return (Masks.rd & instr) >> 11;
}

ubyte extract_shamt(uint instr) {
    return (Masks.shamt & instr) >> 6;
}

ubyte extract_funct(uint instr) {
    return (Masks.funct & instr);
}

ushort extract_immed(uint instr) {
    return (Masks.immed & instr);
}

uint extract_addr(uint instr) {
    return (Masks.addr & instr);
}

/* Ensure the sign still exists.*/
short extend(ushort x) {
    return cast(short)x;
}

int addr_extend(uint address) {
    uint na = address & 0x1fffffff;
    na |= (address & 0x20000000) << 6;
    return cast(int)na;
}


/* The structs representing the parts of instructions;
   one for each of the three types. */
struct R_Type {
    ubyte opcode, rs, rt, rd, shamt, funct;
};

struct I_Type {
    ubyte  opcode, rs, rt;
    ushort immediate;
};

struct J_Type {
    ubyte opcode;
    uint  address;
};

/* An Instr consists of a single flag to tell the type of the
 * instruction, and a payload that is a union whose initialized
 * member corresponds to the type. Each member of the payload
 * is a struct mirroring the constituents of that MIPS
 * instruction type. */
struct Instr {
    static r_string = "R(op:0x%x, rs:0x%x, rt:0x%x, rd:0x%x, shamt:0x%x, funct:0x%x)";
    static i_string = "I(op:0x%x, rs:0x%x, rt:0x%x, immediate:0x%x)";
    static j_string = "J(op:0x%x, addr:0x%x)";

    enum Type : short { R, I, J };
    Type type;

    union {
        R_Type r_instr;
        I_Type i_instr;
        J_Type j_instr;
    };

    /* Unless specified otherwise, an instruction takes 2 cycles to
       complete. */
    uint time = 2u;

    static Instr decode(uint raw_instruction) {
        ubyte op = extract_opcode(raw_instruction);
        Instr result;

        writefln("opcode: %02x\n", op);
        if (op == 0x00) {
            result.type = Type.R;
            with (result.r_instr) {
                opcode = op;
                rs = extract_rs(raw_instruction);
                rt = extract_rt(raw_instruction);
                rd = extract_rd(raw_instruction);
                shamt = extract_shamt(raw_instruction);
                funct = extract_funct(raw_instruction);
            }

        } else if (op == Opcode.j || op == Opcode.jal) {
            result.type = Type.J;
            with (result.j_instr) {
                opcode = op;
                address = extract_addr(raw_instruction);
            }

        } else {
            result.type = Type.I;
            with (result.i_instr) {
                opcode = op;
                rs = extract_rs(raw_instruction);
                rt = extract_rt(raw_instruction);
                immediate = extract_immed(raw_instruction);
            }
        }

        return result;
    }

    /* Perform the action indicated by this instruction. Has
       access to the CPU (which also contains memory to be
       used. */
    uint compute(CPU cpu) {
        writefln("Executing %s\n", this.toString());
        switch (type) {
        case Type.R:
            with(r_instr) {
                uint vs = cpu.registers[rs];
                uint vt = cpu.registers[rt];
                switch (funct) {
                case Func.add:
                    cpu.registers[rd] = vs + vt;
                    break;
                case Func.and:
                    cpu.registers[rd] = vs & vt;
                    break;
                case Func.or:
                    cpu.registers[rd] = vs | vt;
                    break;
                case Func.seq:
                    cpu.registers[rd] = (vs == vt ? 1u : 0u);
                    break;
                case Func.sle:
                    cpu.registers[rd] = (vs <= vt ? 1u : 0u);
                    break;
                case Func.sll:
                    cpu.registers[rd] = vs << (vt % 8);
                    break;
                case Func.slt:
                    cpu.registers[rd] = (vs < vt ? 1u : 0u);
                    break;
                case Func.sne:
                    cpu.registers[rd] = (vs != vt ? 1u : 0u);
                    break;
                case Func.sra:
                    cpu.registers[rd] = vs >> (vt % 8);
                    break;
                case Func.srl:
                    cpu.registers[rd] = vs >> (vt % 8);
                    break;
                case Func.sub:
                    cpu.registers[rd] = vs - vt;
                    break;
                case Func.xor:
                    cpu.registers[rd] = rs ^ rt;
                    break;
                default:
                    throw new Exception("Unknown funct: %d".format(funct));
                }
                return cpu.registers[rd];
            }
            break;

        case Type.J:
            switch (j_instr.opcode) {
            case Opcode.j:
                cpu.pc += addr_extend(j_instr.address) - 1;
                break;
            case Opcode.jal:
                cpu.registers[31] = cpu.pc + 4;
                cpu.pc += addr_extend(j_instr.address) - 1;
                break;
            default:
                throw new Exception("Unknown opcode for J-type: %d".format(j_instr.opcode));
            }
            break;

        case Type.I:
            with (i_instr) {
                uint vs = cpu.registers[rs];
                uint eimm = extend(immediate);
                switch (opcode) {
                case Opcode.addi:
                    cpu.registers[rt] = vs + eimm;
                    break;
                case Opcode.andi:
                    cpu.registers[rt] = vs & eimm;
                    break;
                case Opcode.beqz:
                    cpu.pc += (vs == 0 ? eimm : 0);
                    break;
                case Opcode.bnez:
                    cpu.pc += (vs != 0 ? eimm : 0);
                    break;
                case Opcode.jalr:
                    cpu.registers[31] = cpu.pc + 4;
                    cpu.pc = vs - 1;
                    break;
                case Opcode.jr:
                    cpu.pc = vs - 1;
                    break;
                case Opcode.lhi:
                    cpu.registers[rt] = immediate << 16;
                    break;
                case Opcode.lw:
                    cpu.registers[rt] = cpu.memory[vs + eimm];
                    break;
                case Opcode.ori:
                    cpu.registers[rt] = vs | eimm;
                    break;
                case Opcode.seqi:
                    cpu.registers[rt] = (vs == eimm ? 1 : 0);
                    break;
                case Opcode.slei:
                    cpu.registers[rt] = (vs <= eimm ? 1 : 0);
                    break;
                case Opcode.slli:
                    cpu.registers[rt] = vs << (immediate % 8);
                    break;
                case Opcode.slti:
                    cpu.registers[rt] = (vs < eimm ? 1 : 0);
                    break;
                case Opcode.snei:
                    cpu.registers[rt] = (vs != eimm ? 1 : 0);
                    break;
                case Opcode.srai:
                    cpu.registers[rt] = vs >> (immediate % 8);
                    break;
                case Opcode.srli:
                    cpu.registers[rt] = vs >> (immediate % 8);
                    break;
                case Opcode.subi:
                    cpu.registers[rt] = vs - eimm;
                    break;
                case Opcode.sw:
                    cpu.memory[vs + eimm] = cpu.registers[rt];
                    break;
                case Opcode.xori:
                    cpu.registers[rt] = vs ^ immediate;
                    break;
                case Opcode.pln:
                    cpu.print_line(vs);
                    break;
                case Opcode.halt:
                    cpu.halt();
                    break;
                default:
                    throw new Exception("Unknown opcode in %s".format(this.toString()));
                }
                return cpu.registers[rt];
            }
            break;
        default:
            throw new Exception("Unknown type: %d".format(type));
            break;
        }
        return 0u;
    }

    string toString() {
        switch(type) {
        case Type.R:
            with(r_instr) {
                return r_string.format(opcode, rs, rt, rd, shamt, funct);
            }
        case Type.I:
            with (i_instr) {
                return i_string.format(opcode, rs, rt, immediate);
            }
        case Type.J:
            with (j_instr) {
                return j_string.format(opcode, address);
            }
        default:
            return "UNKNOWN";
        }
    }
}
