// 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/05/CPU.hdl

/**
 * The Central Processing unit (CPU).
 * Consists of an ALU and a set of registers, designed to fetch and
 * execute instructions written in the Hack machine language.
 * In particular, functions as follows:
 * Executes the inputted instruction according to the Hack machine
 * language specification. The D and A in the language specification
 * refer to CPU-resident registers, while M refers to the external
 * memory location addressed by A, i.e. to Memory[A]. The inM input
 * holds the value of this location. If the current instruction needs
 * to write a value to M, the value is placed in outM, the address
 * of the target location is placed in the addressM output, and the
 * writeM control bit is asserted. (When writeM=0, any value may
 * appear in outM). The outM and writeM outputs are combinational:
 * they are affected instantaneously by the execution of the current
 * instruction. The addressM and pc outputs are clocked: although they
 * are affected by the execution of the current instruction, they commit
 * to their new values only in the next time unit. If reset=1 then the
 * CPU jumps to address 0 (i.e. sets pc=0 in next time unit) rather
 * than to the address resulting from executing the current instruction.
 */

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 into M?
        addressM[15],    // Address in data memory (of M)
        pc[15];          // address of next instruction

    PARTS:
    // Put your code here:

    // decode
    Not(in=instruction[15], out=instA);    // 1=A命令
    Not(in=instA, out=instD);             // 1=D命令

    // A命令
    And(a=instA, b=instA, out=loadAReg); // 1=ARegisterを書き換える

    // D命令
    And(a=instD, b=instruction[12], out=readMem);  // 1=ALUにinMを入力

    // cmp
    Mux(a=instruction[9], b=false, sel=readMem, out=zyALU);

    // dest
    And(a=instD, b=instruction[5], out=saveARegTmp);
    Or(a=saveARegTmp, b=loadAReg, out=saveAReg);  // ALU計算結果をAレジスタに保存
    And(a=instD, b=instruction[4], out=saveDReg); // ALU計算結果をDレジスタに保存
    And(a=instD, b=instruction[3], out=saveM);    // ALU計算結果をMemory[A]に保存

    // jmp
    And(a=instD, b=instruction[2], out=j1);
    And(a=instD, b=instruction[1], out=j2);
    And(a=instD, b=instruction[0], out=j3);


    // ARegister
    Mux16(a=outALU, b[15]=false, b[0..14]=instruction[0..14], sel=loadAReg, out=toAReg);
    ARegister(in=toAReg, load=saveAReg, out=fromAReg,
    // addressM
                                        out[15]=false, out[0..14]=addressM);


    // DRegister
    DRegister(in=outALU, load=saveDReg, out=fromDReg);


    // ALU
    Mux16(a=fromAReg, b=inM, sel=readMem, out=toALU);
    ALU(x=fromDReg, y=toALU, zx=instruction[11],
                             nx=instruction[10],
                             zy=zyALU,
                             ny=instruction[8],
                             f= instruction[7],
                             no=instruction[6],
                             zr=zrALU,
                             ng=ngALU,
                             out=outALU, out=outM);


    // writeM
    And(a=instD, b=saveM, out=writeM);


    // jump condition
    Not(in=ngALU, out=notNgALU);
    Not(in=zrALU, out=notZrALU);
    And(a=notNgALU, b=notZrALU, out=gt); // 0 < outALU
    Not(in=gt, out=notGt);

    // jmp命令のチェック
    DMux8Way(in=true, sel[2]=j1, sel[1]=j2, sel[0]=j3, a=jnull, b=jgt, c=jeq, d=jge, e=jlt, f=jne, g=jle, h=jmp);

    // jnull
    And(a=jnull, b=false, out=jnullVal);

    // jgt
    And(a=notNgALU, b=notZrALU, out=jgt1);
    And(a=jgt, b=gt, out=jgt2);
    And(a=jgt1, b=jgt2, out=jgtVal);

    // jeq
    And(a=notNgALU, b=zrALU, out=jeq1);
    And(a=jeq, b=notGt, out=jeq2);
    And(a=jeq1, b=jeq2, out=jeqVal);

    // jge
    Or(a=gt, b=zrALU, out=jge1);
    And(a=jge, b=notNgALU, out=jge2);
    And(a=jge1, b=jge2, out=jgeVal);

    // jlt
    And(a=ngALU, b=notZrALU, out=jlt1);
    And(a=jlt, b=notGt, out=jlt2);
    And(a=jlt1, b=jlt2, out=jltVal);

    // jne
    Or(a=ngALU, b=gt, out=jne1);
    And(a=jne1, b=notZrALU, out=jne2);
    And(a=jne, b=jne2, out=jneVal);

    // jle
    Or(a=ngALU, b=zrALU, out=jle1);
    And(a=jle, b=notGt, out=jle2);
    And(a=jle1, b=jle2, out=jleVal);

    // jmp
    And(a=jmp, b=true, out=jmpVal);

    // jumpする条件か
    Or8Way(in[0]=jnullVal, in[1]=jgtVal, in[2]=jeqVal, in[3]=jgeVal, in[4]=jltVal, in[5]=jneVal, in[6]=jleVal, in[7]=jmpVal, out=doJump);


    // program counter
    Not(in=doJump, out=doInc);
    PC(in=fromAReg, load=doJump, inc=doInc, reset=reset, out[15]=false, out[0..14]=pc, out[0..14]=pcVal);
}