第三周
介绍
这是啥?
这是一个由希伯来大学的 Shimon Schocken与 Noam Nisan讲授的课程。
教你从最简单的与非门实现计算机,并在计算机上实现操作系统,最后在构建的计算机上完成俄罗斯方块的制作。
官网主页:http://www.nand2tetris.org
Coursera课程主页:https://www.coursera.org/learn/build-a-computer
我将它的视频课程搬运到了B站,方便大家学习:https://space.bilibili.com/69824765/#/channel/detail?cid=56426
如果能科学上网的话,也可以在youtube搜索Nand2Tetris。
新的东西 - Time
从这一周开始,计算机引进了一个新的东西:时间。在电路中用时钟信号表现。
触发器 Flip Flop
触发器是一种可以存储电路状态的电子元件,所以存储器实现的基本单位就是触发器。
项目中已经实现了D触发器,可以像最开始使用Nand门那样实用D触发器(DFF)。
在讨论D触发器的真值表之前,先得搞清楚一个问题:
Conbinatorial Logic vs. Sequential Logic - 组合逻辑 vs. 时序逻辑
对于组合逻辑,它相当于一个函数f(x),输入x,输出f(x)。
但是对于时序逻辑,它的作用时是持续的,如果将1秒规定为一个逻辑处理时间单位,时序逻辑将不会更改这一秒的逻辑输出,而是会作用于下一秒的输出。
可以简单的看作:f(x - 1) = f(x)
因为它总是作用于下一个输出,所以我们永远不会知道第一个输出是多少,所以忽略第一个输出。
对于D触发器,时序图为:
当CP为1(严格讲应该是上升沿),Q(n+1) = D,当cp = 0,Q不变化。
项目中的DFF门接受一个输入in,一个输出out。设当前D触发器的值为A
in = 0时,out(t + 1) = A
in = 1时,out(t + 1) = Not(A)
OK,然后我们来实现最小的内存单位:Bit。
Bit
Bit有两个输入,一个为in,一个为load,一个输出out。
当load为1的时候,意思就是将Bit的值置为in。
load = 0时不更改。
其中一个与之前的门不一样的就是,我们需要把当前的out,传给下一个门的输入。然而时序电路就是为了允许这样的操作,所以实现如下:
跟着它演绎一遍,即:
Mux(a=dffout, b=in, sel=load, out=muxout);
DFF(in=muxout, out=dffout);
Or(a=dffout, b=dffout, out=out);
可能你有两个问题:
- Q1: 第一行的dffout是啥,凭空出现的?
- dffout就是上一个门的输出值,在第二行的out=dffout也可以体现,它将dffout传给了下一个的输入。
- Q2: 为什么不在第二行中out=out呢?
- Q1回答中已经提到,它必须将out传出去,然后再输出值。所以第三行没有别的作用,只是为了输出out。
Register - 寄存器
此项目完成的是一个16位的计算机,所以寄存器实现为16位的。寄存器中存放16个Bit就好了。
Register芯片接受两个输入in和load,一个输出out。
功能类比Bit,当load = 1时,将寄存器的值置为in的值,否则不变。
实现如下:
Bit(in=in[0], load=load, out=out[0]);
Bit(in=in[1], load=load, out=out[1]);
Bit(in=in[2], load=load, out=out[2]);
Bit(in=in[3], load=load, out=out[3]);
Bit(in=in[4], load=load, out=out[4]);
... 以此类推
RAM8 - 8位随机存储器
这里的8位代表8个寄存器。这个芯片时用来管理8个寄存器,来修改/读取其中某个寄存器的值。
RAM8接受三个输入in,load,address,一个输出out。
其中in为16位(一个寄存器存储单位),load为0/1,address为3位(3位二进制能表示8个不同的值)。
in与load的作用不言而喻,address即用来指示in与load作用与哪一个寄存器。
使用DMux8Way16,根据load输出对应0-7对应的load(i),然后使用load(i),与in对Register(i)操作,输出对应的out(i)。
最后,使用Mux8Way16,传入out(i ~ 8),address,并最终输出out。
实现如下:
// 选出哪个Register将要被load
DMux8Way(in=load, sel=address, a=loada, b=loadb, c=loadc, d=loadd, e=loade, f=loadf, g=loadg, h=loadh);
Register(in=in, load=loada, out=outa);
Register(in=in, load=loadb, out=outb);
Register(in=in, load=loadc, out=outc);
Register(in=in, load=loadd, out=outd);
Register(in=in, load=loade, out=oute);
Register(in=in, load=loadf, out=outf);
Register(in=in, load=loadg, out=outg);
Register(in=in, load=loadh, out=outh);
// 选出哪个操作的输出将要被最终out
Mux8Way16(a=outa, b=outb, c=outc, d=outd, e=oute, f=outf, g=outg, h=outh, sel=address, out=out);
RAM64 - RAM512 - RAM4K - RAM16K
RAM64 = 8 * RAM8
RAM512 = 8 * RAM64
RAM4K = 8 * RAM512
RAM16K = 4 * RAM4K
芯片的输出都是三个in,load,address。在位数上,也只有address与RAM8不同,64个寄存器需要6位的address寻址,512个寄存器需要9位,以此类推。
在寻址上,我们需要分段,比如对于RAM64,由于其相当于8个RAM8,所以我们将6位的address拆分为两部分0-2与3-5。其中0-2用来寻找操作哪个RAM8,3-5的值只需传给RAM8即可。对于RAM512,也只需要0-2来选择RAM64,然后将3-8传给RAM64。对于RAM16K,其相当于4个RAM4K,所以只需要0-1来选择RAM4K。
这里只给出两个的实现:
RAM64
RAM16K
PC
PC芯片是用来控制指令执行的。计算机总是取一条指令,然后计算,然后再取下一条指令,然后计算....
这里PC芯片用来给计算机指示吓一条指令的位置,芯片接受四个输入:in,load,inc,reset。
其逻辑为:
/**
* if (reset[t] == 1) out[t+1] = 0
* else if (load[t] == 1) out[t+1] = in[t]
* else if (inc[t] == 1) out[t+1] = out[t] + 1 (integer addition)
* else out[t+1] = out[t]
*/
我的第一想法就是,这使用Mux就解决了呀~ (我真的太聪明了,快夸我。)
然后又思考了下,这是有优先级的,reset是最优先的,这是if-elseif-else,而不是switch。所以要倒着来:
在倒着来之前,先将上一次输出的值自增1保存下:
Inc16(in=feedback, out=inced);
先用inc来选择是否改变out的值,然后再用load选择是否改变,在用reset选择是否重置。这样如果inc为1,load为1的情况下,inc虽然也改变了out,但是load又将它的更改覆盖了,也就有了优先级之说。
最后实现:
完成
OK,这一周的芯片都完成了。从刚开始的时序逻辑电路到最后实现寄存器再到实现其他存储器,似乎构建的东西越来越接近计算机实体了。
我是谁?
我是iimT,大学生,技术宅,计算机科技爱好者,电音小王子。
我的博客:www.iimt.me
我在Weibo:@_iimT
我在B站:https://space.bilibili.com/69824765/#/
想看到我的更多更新的话,很乐意你关注我!
你是谁?
欢迎在文后留下评论,一起讨论,欢迎认识新朋友。
如果你也有博客,在分享你的东西,欢迎交流、友链(本人博客底部可申请)。
下一篇见~