nand2tetris_hack计算机

终于来到了这一步!!
前文里,我们学习了hack编程语言,大概知道需要实现的hack计算机是什么样子,需要实现哪些功能。同时在更早的时候,我们建造了ALU和RAM组件,加上老师内置的ROM和键盘屏幕外设,那么开干!
等一下,在开始之前,我想先聊聊当今通用计算机的体系结构,也就是大名鼎鼎的冯诺依曼体系,我们的hack计算机也是依据于此

冯诺依曼体系介绍

“有道无术,术尚可求;有术无道,止于术”,三体里秦始皇欲建造人型计算机,b站里的大神用excel实现的cpu,当然还有我们这门课程里将要在模拟器里实现的hack计算机,他们都遵循着冯诺依曼体系框架。从b站评论里看来的,“理论上,只要是可以实现与非门和时钟的,都可以用来用来实现计算机”

这张图,我记得在大一的计算机组成原理里看见过,哈哈哈,只记得冯诺依曼这四个字了
我们通常有三种信息类型在系统间传递。数据,当我们进行加法运算时,输入需要从内存移动到寄存器中,再到实际的运算单元,进行计算后返回;地址,实现执行的是什么指令和需要访问内存中的哪些数据,这些都在地址里的;控制,系统的每一个组成部分需要知道,在特定的时间点做什么事情
下面,让我们看看计算机里面的不同组件,他们可以接收什么信息和发出什么信息

CPU

我们的CPU分为两个组件,一是算术逻辑单元ALU,二是寄存器。算术逻辑单元能够加减数字,也能够进行逻辑运算;寄存器则是暂存一些数据,这些数据,在后续的计算中将要使用
ALU需要接入数据总线,因为需要拉取数据计算,然后返回;同时,他需要控制总线的信息,告诉他要执行的操作类型是什么,可能是相加,可能是取反;另一方面,根据计算结果,alu会返回标志位信息,如 结果是否大于0,这些需要放在控制总线上,告诉系统的其他部分做什么操作,比如下一个指令是否跳转
寄存器在概念上很简单,作为cpu的缓冲单元,理所当然的需要接入数据总线;同时寄存器在设计上,还支持指定地址这种来取/设值,这实际是间接寻址到RAM或者ROM上,这就需要寄存器连接到地址总线上
综上,cpu需要通过数据总线/地址总线获取输入数据,通过控制总线得到运算函数,计算得到的结果输出至数据/地址总线上,同时计算结果标志位反馈到控制总线,等待下一次计算

内存

标准的冯洛伊曼体系,应该是使用一块内存,数据和指令均放入其中,这里简化处理为数据和指令独立放在一块内存中(哈佛结构),还有一些别的原因,不在这里说明了,仍以冯洛伊曼体系介绍
内存最直接的需求就是数据读写,需要连接在数据总线上;同时上面提到的CPU的输入和输出均可以是地址,内存同样需要连接到地址总线上;其次指令内存需要知道下一条待执行的指令内容,这里cpu输出的指令地址被归纳进控制总线里
这里贴两张数据内存和指令内存的数据流转图,帮助理解内存在整个链路扮演的角色


综上,内存通过控制控制总线取出指令地址,通过地址总线取出输入数据,然后经cpu计算后,通过地址总线和数据总线写回输出数据至内存中,同时控制总线上返回下一次待执行的地址

这里,描述的有点乱。三条总线只是一个概念模型,理解每个组件做什么的就好了。知道大概是这样的思路,重要的还是引出下面的CPU和RAM的输入输出参数设计

计算机设计

总体架构


看起来复杂,实际对于计算机来说,只是单纯的取指令,执行再取

CPU


想从白纸实现出来,我感觉难度相当高,课程老师估计也知道,直接给了实现结构图,照着连线就好。我这里如果说你问我,为什么这个结构可以实现cpu,我是知道的,但若你问我cpu还有没有别的实现结构,我就懵了

白板

实现结构图

实现思路

这里照着图实现思路,就很清晰了。把指令里的每一位取出来,然后进行不同的判断,稍微复杂一点的就是现在ALU的返回值也需要用到了;这部分详细可以参照视频

跳转实现思路


RAM

这里很简单,想想怎么区分不同的地址段即可

ROM

这是内置芯片,提供的接口就是取指

计算机

计算机就是把上面的几个芯片,组装起来

hdl实现

这里还是贴一下代码,不要以后自己想看的时候,忘记了 哈哈

cpu

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:
	Mux16(a=instruction[0..15],b=aluout,sel=instruction[15],out=ain);

    // allow `A` @x -> set A 
    Not(in=instruction[15],out=opnot);
    Or(a=opnot,b=instruction[5], out=ac);
    ARegister(in=ain,load=ac,out=aout,out[0..14]=addressM);

    // D only load from `C`
    And(a=instruction[15],b=instruction[4], out=dc);
    DRegister(in=aluout,load=dc,out=aluin1);
    Mux16(a=aout,b=inM,sel=instruction[12],out=aluin2);
    ALU(x=aluin1,y=aluin2,zx=instruction[11],nx=instruction[10],zy=instruction[9],ny=instruction[8],f=instruction[7],no=instruction[6],out=aluout,out=outM,zr=zr,ng=ng);
    
    And(a=instruction[15],b=instruction[3],out=writeM);

    // PC
    Not(in=zr,out=notzr);
    Not(in=ng,out=notng);
    // great!
    DMux8Way(in=instruction[15], sel=instruction[0..2], a=null, b=jgt, c=jeq, d=jge, e=jlt, f=jne, g=jle, h=jmp);

    And(a=notzr,b=notng,out=jgt1);
    And(a=jgt,b=jgt1,out=jgt2);

    And(a=jeq,b=zr,out=jeq1);

    And(a=jge,b=notng,out=jge1);

    And(a=jlt,b=ng,out=jlt1);

    And(a=jne,b=notzr,out=jne1);

    Or(a=zr,b=ng,out=jle1);
    And(a=jle,b=jle1,out=jle2);

    Or8Way(in[0]=false, in[1]=jgt2, in[2]=jeq1, in[3]=jge1, in[4]=jlt1, in[5]=jne1, in[6]=jle2, in[7]=jmp, out=jumpout);
    PC(in=aout,inc=true,load=jumpout,reset=reset,out[0..14]=pc);
}

memory

CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    PARTS:
	DMux(in=load, sel=address[14], a=m1, b=m2);
    RAM16K(in=in, load=m1, address=address[0..13], out=out1);
    DMux(in=m2, sel=address[13], a=r1, b=r2);
    Screen(in=in, load=r1, address=address[0..12], out=out2);
    Keyboard(out=out3);

    Mux16(a=out2,b=out3,sel=address[13],out=out4);
    Mux16(a=out1,b=out4,sel=address[14],out=out);
}

computer

CHIP Computer {

    IN reset;

    PARTS:
    CPU(inM=inM,instruction=instruction,reset=reset,outM=outM,writeM=writeM,addressM=addressM,pc=pc);
    Memory(address=addressM,in=outM,load=writeM,out=inM);
    ROM32K(address=pc,out=instruction);
}

总结

到这里,这门课程其实就结束了,后面还有一章是编译器,我当时是直接用笔完成的,也没有写代码。哈哈哈,代码写多了,反而喜欢朴素的方式来实现,还是懒吧。这章是4月做完的,已经过去了5,6,7,三个月了。这学习啊,一放下就会不经意过去好久了。
记得当时学完课程就开始写这个博客了,并没有预期的那么兴奋,总觉得少了什么,所以一直没有写完,这次只是草草贴了几张图收尾。现在想来,大概是时钟这个概念还是没有理解,这门课程最大的收获可能就是cpu不再那么神秘了,它真的是由与非门实现的,这里还是有很多细节的,不过还是尽快开始下一门课吧。再放下去,今年就过去了。

好读书,不求甚解;每有会意,便欣然忘食。

posted @ 2024-07-28 18:03  柠檬水请加冰  阅读(34)  评论(0编辑  收藏  举报