// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/5/CPU.hdl
/**
 * The Hack Central Processing unit (CPU).
 * Parses the binary code in the instruction input and executes it according to the
 * Hack machine language specification. In the case of a C-instruction, computes the
 * function specified by the instruction. If the instruction specifies to read a memory
 * value, the inM input is expected to contain this value. If the instruction specifies
 * to write a value to the memory, sets the outM output to this value, sets the addressM
 * output to the target address, and asserts the writeM output (when writeM = 0, any
 * value may appear in outM).
 * If the reset input is 0, computes the address of the next instruction and sets the
 * pc output to that value. If the reset input is 1, sets pc to 0.
 * Note: The outM and writeM outputs are combinational: they are affected by the
 * instruction's execution during the current cycle. The addressM and pc outputs are
 * clocked: although they are affected by the instruction's execution, they commit to
 * their new values only in the next cycle.
 */
CHIP CPU {

    IN  inM[16],         // M value input  (M = contents of RAM[A])
        instruction[16], // Instruction for execution
        reset;           // Signals whether to re-start the current
                         // program (reset==1) or continue executing
                         // the current program (reset==0).

    OUT outM[16],        // M value output
        writeM,          // Write to M? 
        addressM[15],    // Address in data memory (of M)
        pc[15];          // address of next instruction

    PARTS:
    

    // Instructions.
    // Decode the instruction bit.
    // i xx a cccccc ddd jjj
    // For C-Instruction the byte is stored as i(opcode) xx(notUsed) a cccccc(a/c = comp) ddd(destination) jjj(jump)
    // i xxxxxxxxxxxxxxx
    // For A-Instruction the i(opcode) is 0 and the rest is the 16(15)-bit value that is stored there.
    
    // Decode Instruction
    Not(in=instruction[15], out=isAInstruction); // Flip it and if 1 it's an A Instruction
    And(a=instruction[15], b=true, out=isCInstruction); // Check it with an And static true to check if both 1 then it's C instruction.
    // Don't need decoding for the rest when A instruction.
    // Just use instruction because instruction[15] will always be 0
    // Decode C & Create Wires
    // 1xA
    And(a=instruction[12], b=true , out=a1);
    // 6xC
    And(a=instruction[11], b=true , out=c1);
    And(a=instruction[10], b=true , out=c2);
    And(a=instruction[9], b=true , out=c3);
    And(a=instruction[8], b=true , out=c4);
    And(a=instruction[7], b=true , out=c5);
    And(a=instruction[6], b=true , out=c6);
    // 3xD
    And(a=instruction[5], b=true , out=d1);
    And(a=instruction[4], b=true , out=d2);
    And(a=instruction[3], b=true , out=d3);
    // 3xJ
    And(a=instruction[2], b=true , out=j1);
    And(a=instruction[1], b=true , out=j2);
    And(a=instruction[0], b=true , out=j3);

    // aluOut is not made yet 
    // A Register
    // Should Load from ALU if 
    And(a=isCInstruction, b=d1, out=loadAFromALU);
    Mux16(a=instruction, b=aluOut, sel=loadAFromALU,out=aRegIn);
    Or(a=isAInstruction, b=loadAFromALU, out=loadA);
    Register(in=aRegIn, 
            load=loadA, 
            // Out
            out=aRegOut, 
            out[0..14]=addressM
    );

    // D Register
    And(a=isCInstruction, b=d2, out=loadD);
    Register(in=aluOut, load=loadD, out=dRegOut);
    // a - instruction12 - a1
    // a=0 → use A register
    // a=1 → use M (inM from memory)
    // ALU
    Mux16(a=aRegOut, b=inM , sel=a1 , out=aluYin);
    ALU(x=dRegOut,
        y=aluYin,
        zx=c1, nx=c2, zy=c3, ny=c4 , f=c5 , no=c6 , 
        zr=aluZrOut, 
        ng=aluNgOut,
        // Out
        out=aluOut, 
        out=outM
    );

    // IN in[16], reset, load, inc;
    // OUT out[16];
    // inc almost all the time. 
    // only when not load. load = jump

    // JGT
    // not negative and not zero
    Not(in=aluNgOut, out=notAluNgOut);
    Not(in=aluZrOut, out=notAluZrOut);
    And(a=notAluZrOut, b=notAluNgOut , out=isPositive);
    And(a=isPositive, b=j3, out=shouldJumpJGT);
    // JEQ
    // Jump if zero can be checked it zr
    And(a=aluZrOut, b=j2 , out=shouldJumpJEQ);
    // JLT
    // Jump if less than 0
    And(a=aluNgOut, b=j1, out=shouldJumpJLT);

    // Should Jump
    Or(a=shouldJumpJGT, b=shouldJumpJEQ , out=shouldJumpJGTJEQ);
    Or(a=shouldJumpJGTJEQ , b= shouldJumpJLT, out=hasJumpInstruction );

    // And C instruction
    And(a=hasJumpInstruction, b=isCInstruction , out=shouldJump);

    //Inc
    Not(in=shouldJump, out=shouldIncrement);


    PC(in=aRegOut, load=shouldJump, inc=shouldIncrement, reset=reset , out[0..14]=pc);

    // Out
    And(a=isCInstruction, b=d3 , out=writeM);
}
