nand2tetris_寄存器
时间
之前的介绍里,我们理所当然的认为有一硬件电路,给其输入,得到输出;我们所构建的只是输入-输出的逻辑映射,没有任何变化的概念。在现实世界中,我们需要引入时间
引入时间的概念
计算机里为什么需要时间。主要有两个原因,我们需要硬件重复运算,而且能够保存中间结果
这又会引入一个新的问题,计算机里如何表示时间
时间的表示
计算机执行的很快,但终究是有限制的,因为电路里电流汇聚需要时间的。现实物理时间中,时间总是滴答滴答的向前走。在计算机中,每一个操作都会带来有延迟,我们不想考虑这些延迟
将连续时间分为一个一个的离散时间周期,时间周期不用设置的太快,保证在一个周期内硬件状态稳定下来即可(灰色区域)。这样在时钟周期结束时,就是我们所认为的此刻系统真实逻辑状态
时序逻辑
电路系统中是有两种电路,一种是组合逻辑(Combinational Logic),其输出只是当前输入的函数,与之前状态无关,无存储功能;另一种是时序逻辑(Sequential Logic),能够存储数据供以后使用,如触发器,寄存器(register,由多个触发器组成)
这里摘抄知乎里的例子加深理解
实现累加计算
s=0;
for (i=0;i<n;i++)
s = s+X{i};
如果不用触发器,只用组合逻辑,设计为下图。但可以看到这个设计有问题,无法实现结果,因为无法存储中间值,无法控制状态转换
所以就需要触发器存储中间值,结构如下
所以在实际的电路系统中,是由时序逻辑与组合逻辑共同组成。时序逻辑储存中间值,可分割组合逻辑,让每个组合逻辑变成一小块
寄存器
触发器
触发器是实现时序逻辑的一种方式,他和我们之前学习的门电路都不一样,他的输出取决于之前发生的事情以及保存在芯片中的状态
这个芯片的物理实现方式不在这里介绍,实现方式有别于我们常见的电路设计。再举一个例子,说明触发器在数字电路中的作用
你手上有两个一元硬币,需要去自动售货机买一瓶价值2元的水。如果没有触发器,你永远买不到,因为电路记不住你之前投进去的硬币,不管你投多少次,电路都觉得你只投了1元。简而言之,触发器让电路有了记忆的功能
计算机中构建通用逻辑有一个通用的范例,通过触发器集合来记住中间状态,然后使用前两节课中的组合逻辑来操作他们
1比特寄存器
我们已经有触发器了,开始尝试构建寄存器,从最基础的1比特开始。这里定义了简单的API
那么,如何实现呢,这里需要判断load状态,很容易想到选择器,通过不同状态来改变触发器的输出。由于寄存器需要维持上一时刻的状态,所以触发器的输出需要多引一条线至选择器的输入
HDL描述也很简单,还记得之前介绍HDL规范时说的么,HDL不是编程语言,他是为描述电路而设计的
// 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/3/a/Bit.hdl
/**
* 1-bit register:
* If load is asserted, the register's value is set to in;
* Otherwise, the register maintains its current value:
* if (load(t)) out(t+1) = in(t), else out(t+1) = out(t)
*/
CHIP Bit {
IN in, load;
OUT out;
PARTS:
Mux(a = dIn, b = in , sel = load, out = muxout);
DFF(in = muxout, out = dIn, out = out);
}
16位寄存器
有了1bit的寄存器,将16个1bit寄存器放在一起,就组成了16位通用寄存器,尽管现在只能存储1个16位数字
/**
* 16-bit register:
* If load is asserted, the register's value is set to in;
* Otherwise, the register maintains its current value:
* if (load(t)) out(t+1) = int(t), else out(t+1) = out(t)
*/
CHIP Register {
IN in[16], load;
OUT out[16];
PARTS:
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]);
Bit(in = in[5], load = load, out = out[5]);
Bit(in = in[6], load = load, out = out[6]);
Bit(in = in[7], load = load, out = out[7]);
Bit(in = in[8], load = load, out = out[8]);
Bit(in = in[9], load = load, out = out[9]);
Bit(in = in[10], load = load, out = out[10]);
Bit(in = in[11], load = load, out = out[11]);
Bit(in = in[12], load = load, out = out[12]);
Bit(in = in[13], load = load, out = out[13]);
Bit(in = in[14], load = load, out = out[14]);
Bit(in = in[15], load = load, out = out[15]);
}
RAM unit
只能保存一个16位数字还是不够的,我们需要将多个16寄存器组合起来,以存储更多的内容
这里的key同样适用与于当前的内存规则,32位电脑的最大内存限制是4G就是这个原因。当把许多寄存器组合之后,需要考虑的难点就变成了,如何在load值改变的时候选中我们需要的寄存器
RAM读请求
RAM写请求
那么,如何做到呢。这里课程老师提示了一句,请求时,需要应用于所有的寄存器,再通过选择器、分配器来找到合适的位置。有了这样的思路,RAM8、RAM64、RAM512.. 都是一样的道理了
RAM8
/**
* Memory of eight 16-bit registers.
* If load is asserted, the value of the register selected by
* address is set to in; Otherwise, the value does not change.
* The value of the selected register is emitted by out.
*/
CHIP RAM8 {
IN in[16], load, address[3];
OUT out[16];
PARTS:
DMux8Way(in=load,sel=address,a=r1,b=r2,c=r3,d=r4,e=r5,f=r6,g=r7,h=r8);
Register(in=in, load=r1, out=out1);
Register(in=in, load=r2, out=out2);
Register(in=in, load=r3, out=out3);
Register(in=in, load=r4, out=out4);
Register(in=in, load=r5, out=out5);
Register(in=in, load=r6, out=out6);
Register(in=in, load=r7, out=out7);
Register(in=in, load=r8, out=out8);
Mux8Way16(a=out1,b=out2,c=out3,d=out4,e=out5,f=out6,g=out7,h=out8,sel=address,out=out);
}
RAM64
/**
* Memory of sixty four 16-bit registers.
* If load is asserted, the value of the register selected by
* address is set to in; Otherwise, the value does not change.
* The value of the selected register is emitted by out.
*/
CHIP RAM64 {
IN in[16], load, address[6];
OUT out[16];
PARTS:
DMux8Way(in=load,sel=address[3..5],a=r1,b=r2,c=r3,d=r4,e=r5,f=r6,g=r7,h=r8);
RAM8(in=in, load=r1, address=address[0..2], out=out1);
RAM8(in=in, load=r2, address=address[0..2], out=out2);
RAM8(in=in, load=r3, address=address[0..2], out=out3);
RAM8(in=in, load=r4, address=address[0..2], out=out4);
RAM8(in=in, load=r5, address=address[0..2], out=out5);
RAM8(in=in, load=r6, address=address[0..2], out=out6);
RAM8(in=in, load=r7, address=address[0..2], out=out7);
RAM8(in=in, load=r8, address=address[0..2], out=out8);
Mux8Way16(a=out1,b=out2,c=out3,d=out4,e=out5,f=out6,g=out7,h=out8,sel=address[3..5],out=out);
}
这里补一句老师的感叹,“也许这节课没有给到这个芯片足够的尊重。因为这款芯片发生了一些非常了不起的事情,不管这个芯片的大小如何,不管它有8个寄存器还是800万个寄存器,我都可以从这个芯片中选择每个寄存器,然后在完全相同的时间内对其进行操作,这确实非常了不起。”,所以数组根据下标索引数据,复杂度相同的原因是不是就来源于此
计数器
这是一个封装组件,实现CPU内部程序计数器的功能。上一节实现的递增组件,就可以应用在这里
HDL实现有点绕的,不仅要判断三个标识位的先后关系,还要考虑往寄存器里写值。我看了之前写的代码,虽然能知道在表达什么意思,但已经不知道当时是怎么想的了,才过去半个月.. 看变量命名,可以想到当时实现还是费了脑子的。所以又把测试脚本执行了一遍,再次感谢课程老师提供的软件包
/**
* A 16-bit counter.
* if reset(t): out(t+1) = 0
* else if load(t): out(t+1) = in(t)
* else if inc(t): out(t+1) = out(t) + 1
* else out(t+1) = out(t)
*/
CHIP PC {
IN in[16],inc, load, reset;
OUT out[16];
PARTS:
Inc16(in=out9,out=out2);
Mux16(a=out9,b=out2,sel=inc,out=out6);
Mux16(a=out6,b=in,sel=load,out=out7);
Mux16(a=out7,b=false,sel=reset,out=out8);
Register(in=out8,load=true,out=out9);
And16(a=out9,b=true,out=out);
}
思考
这节课引入的概念有点多,当时学的时候稀里糊涂把测验过了就看下一节。回头来写这篇博客时,才反应过来时序和触发器那里有那么多的知识点。有一点感叹,我本身是通信专业的,照理说这些应该很熟悉的,可是我仿佛是第一次遇见一样。唉,当年是真的一点没学啊