Nand2Tetris_Part1_03

本周的主题是 Memory,先把做好的 ALU 放一边,我们来研究一下和内存(RAM)有关的问题。

Unit 3.1 Sequential Logic

本小节主要在引出新的概念:顺序逻辑(Sequential Logic)、时间等,另外也指出了顺序逻辑与前两周所学的组合逻辑(Combinational Logic)的不同。换句话来讲,这周考虑的问题会更加贴近现实生活一点。

Unit 3.2 Filp Flops

本小节介绍了本周内容所使用的最基本单元:Data Flip Flops(DFF),并说明了它的功能;接着继续讲解了内存的基本单元 1-Bit Registers,着重在说明它的运行过程。

Unit 3.3 Memory Units

本小节介绍了内存单元(Memory units)的一些特点,并通过软件模拟了 RAM8 的运行过程。需要注意内存是一个具有时间属性的硬件单元,这个课程提到的内存单元是通过上面提到的 DFF 来“展现”时间功能的(实际不清楚,但是 DFF 确实是有这个功能的)。也就是说,在某个时刻,内存单元上的寄存器的电信号改变后,内存单元的输出依然不变,下一个时刻后,内存单元的输出才会改变。

同时,老师还提到了寄存器的地址和寄存器数量的关系,也就是 2 的对数关系。比如,RAM8 内部有 8 个 Register,那么就可以用 3 个 bit 来表示这 8 个 Register 的序号,也即:000、001、010、011、100、110、101 和 111,这个数字其实也就是内存的地址,这应该就是内存地址概念的由来了。

Unit 3.4 Counters

本小节介绍了计数器(Counters)的一些功能和机制,包含 3 个方面:

  1. 能将值重置为 0。
  2. 能将值随着时间递增,且每 1s 增加 1。
  3. 能输出上一个时刻设置的值。

Unit 3.5 Project 3 Overview

本周作业需要实现 7 个与内存相关的芯片和 1 个计数器,针对各个芯片的实现,老师给出了若干提示,而我们将会以 DFF 为 primitive chip 来构建另外 8 个芯片。

Bit

这个老师已经讲过了,直接用 1 个 Mux 和 1 个 DFF 就可以实现,但是还额外需要一个 Mux 来帮助输出(不用 Mux,用其他的也可以)。

1
2
3
4
5
6
7
8
9
CHIP Bit {
IN in, load;
OUT out;

PARTS:
Mux(a=t2, b=in, sel=load, out=t1);
DFF(in=t1, out=t2);
Mux(a=t2, b=true, sel=false, out=out);
}

Register

按照提示,直接用 16 个 Bit 叠加在一起就可以了。但是要注意一下时间的概念,也即这 16 个 Bit 是都处于工作状态的,尽管有些 Bit 的值可能并没有改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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]);
}

RAM8

根据讲解,RAM8 内部包含了 8 个 Register,每个 Register 都可以存储数字,根据特定的地址(address)进行控制(包括写入值、清除值)。
设计这个芯片的难点在于如何根据地址来操作 Register,这与第二单元设计 ALU 是一样的难点。第二单元,解决这个问题的方式是用 Mux 来选择不同的寄存器进行输出。而这里,涉及到选址和输入,需要使用 DMux 来进行选址和输入,通过 Register 后,再用 Mux 进行输出。
因为 RAM8 内部包含了 8 个 Register,所以选用 DMux8Way 来处理选址和输入,再用 Mux8Way16 来输出指定地址的 Register 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CHIP RAM8 {
IN in[16], load, address[3];
OUT out[16];

PARTS:
DMux8Way(in=load, sel=address, a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);

Register(in=in, load=load0, out=u0);
Register(in=in, load=load1, out=u1);
Register(in=in, load=load2, out=u2);
Register(in=in, load=load3, out=u3);
Register(in=in, load=load4, out=u4);
Register(in=in, load=load5, out=u5);
Register(in=in, load=load6, out=u6);
Register(in=in, load=load7, out=u7);

Mux8Way16(a=u0, b=u1, c=u2, d=u3, e=u4, f=u5, g=u6, h=u7, sel=address, out=out);
}

RAM64

有了前面的思路后,RAM64 实际上就是 8 个 RAM8 堆叠在一起。
不过此时的地址需要用 6 位二进制数来表示,其中前 3 位用来表示是那一块 RAM8,后 3 位用来表示是这块 RAM8 上的哪一个 Register(实际上也就是总线宽度从 3 升级到了 6)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CHIP RAM64 {
IN in[16], load, address[6];
OUT out[16];

PARTS:
DMux8Way(in=load, sel=address[0..2], a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);

RAM8(in=in, load=load0, address=address[3..5], out=u0);
RAM8(in=in, load=load1, address=address[3..5], out=u1);
RAM8(in=in, load=load2, address=address[3..5], out=u2);
RAM8(in=in, load=load3, address=address[3..5], out=u3);
RAM8(in=in, load=load4, address=address[3..5], out=u4);
RAM8(in=in, load=load5, address=address[3..5], out=u5);
RAM8(in=in, load=load6, address=address[3..5], out=u6);
RAM8(in=in, load=load7, address=address[3..5], out=u7);

Mux8Way16(a=u0, b=u1, c=u2, d=u3, e=u4, f=u5, g=u6, h=u7, sel=address[0..2], out=out);
}

RAM512

同理,RAM512 实际上就是 8 个 RAM64 堆叠在一起。
对应的,现在需要 9 位二进制数来表示地址了,也就是总线宽度扩展到了 9。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CHIP RAM512 {
IN in[16], load, address[9];
OUT out[16];

PARTS:
DMux8Way(in=load, sel=address[0..2], a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);

RAM64(in=in, load=load0, address=address[3..8], out=u0);
RAM64(in=in, load=load1, address=address[3..8], out=u1);
RAM64(in=in, load=load2, address=address[3..8], out=u2);
RAM64(in=in, load=load3, address=address[3..8], out=u3);
RAM64(in=in, load=load4, address=address[3..8], out=u4);
RAM64(in=in, load=load5, address=address[3..8], out=u5);
RAM64(in=in, load=load6, address=address[3..8], out=u6);
RAM64(in=in, load=load7, address=address[3..8], out=u7);

Mux8Way16(a=u0, b=u1, c=u2, d=u3, e=u4, f=u5, g=u6, h=u7, sel=address[0..2], out=out);
}

RAM4K

同理,RAM4k 实际上就是 8 个 RAM512 堆叠在一起。
对应的,现在需要 12 位二进制数来表示地址了,也就是总线宽度扩展到了 12。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CHIP RAM4K {
IN in[16], load, address[12];
OUT out[16];

PARTS:
DMux8Way(in=load, sel=address[0..2], a=load0, b=load1, c=load2, d=load3, e=load4, f=load5, g=load6, h=load7);

RAM512(in=in, load=load0, address=address[3..11], out=u0);
RAM512(in=in, load=load1, address=address[3..11], out=u1);
RAM512(in=in, load=load2, address=address[3..11], out=u2);
RAM512(in=in, load=load3, address=address[3..11], out=u3);
RAM512(in=in, load=load4, address=address[3..11], out=u4);
RAM512(in=in, load=load5, address=address[3..11], out=u5);
RAM512(in=in, load=load6, address=address[3..11], out=u6);
RAM512(in=in, load=load7, address=address[3..11], out=u7);

Mux8Way16(a=u0, b=u1, c=u2, d=u3, e=u4, f=u5, g=u6, h=u7, sel=address[0..2], out=out);
}

RAM16K

同理,RAM16k 实际上就是 4 个 RAM4K 堆叠在一起。
对应的,现在需要 14 位二进制数来表示地址了,也就是总线宽度扩展到了 14。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CHIP RAM16K {
IN in[16], load, address[14];
OUT out[16];

PARTS:
DMux4Way(in=load, sel=address[0..1], a=load0, b=load1, c=load2, d=load3);

RAM4K(in=in, load=load0, address=address[2..13], out=u0);
RAM4K(in=in, load=load1, address=address[2..13], out=u1);
RAM4K(in=in, load=load2, address=address[2..13], out=u2);
RAM4K(in=in, load=load3, address=address[2..13], out=u3);

Mux4Way16(a=u0, b=u1, c=u2, d=u3, sel=address[0..1], out=out);
}

PC

Program Counter,简称 PC,直译就是程序计数器。
这个小芯片真的想了很久都没有什么思路...
准确来讲,不是没有构造的思路,而是不知道怎么用 HDL 语言来描述。没办法,只好阅读一下别人写的代码了,结果发现,大家好像在这里都有点问题,hha~🤣。
回过头来,一开始不知道怎么写,只能翻老师给的资料,看看能不能找到点思路。没想到还整的找到了,就是书上第写的:

Counter A w-bit counter consists of two main elements: a regular w-bit register, and
combinational logic. The combinational logic is designed to (a) compute the counting function, and (b) put the counter in the right operating mode, as mandated by the values of its three control bits. Tip: Most of this logic was already built in chapter 2.

仔细看最后一行:Tip: Most of this logic was already built in chapter 2.,原来第二章就做过了。再仔细一想,在做 ALU 的时候面对的也是类似的问题,那时是通过 Mux 来挑选出来合适的值进行输出的,那么用到这里应该也是可以的。

可是又该如何实现循环计数的功能呢?
回想一下循环的过程,即上一次的结果作为这一次的初始值。那么在 HDL 里面是不是也可以这样写呢?答案是可以的(总觉得这样写有语法错误😂)。
那么正常输出的值与循环递增的值又该如何保存呢?难道保存在一个 Register 里面?
然后又开始没有思路了,直到又看到了别人文章里面的:

Multiple Outputs
Sometimes you need more than one sub-bus connected to the output of a chip-part. Simply add more than one out= connection to the chip-part definition.
CHIP Foo { IN in[16]; OUT out[8]; PARTS: Not16 (in=in, out[0..7]=low8, out[8..15]=high8); Something8 (a=low8, b=high8, out=out); }
This also works if you want to use an output of a chip in further computations.
CHIP Foo { IN a, b, c; OUT out1, out2; PARTS: Something (a=a, b=b, out=x, out=out1); Whatever (a=x, b=c, out=out2); }

原来单独的某个芯片是可以有两个输出的(out)的...
最终,费了一番功夫后得到的通过代码:

1
2
3
4
5
6
7
8
9
10
11
CHIP PC {
IN in[16],load,inc,reset;
OUT out[16];

PARTS:
Inc16(in=prevalue, out=u1);
Mux16(a=prevalue, b=u1, sel=inc, out=u2);
Mux16(a=u2, b=in, sel=load, out=u3);
Mux16(a=u3, b=false, sel=reset, out=u4);
Register(in=u4, load=true, out=out, out=prevalue);
}

Unit 3.6 Perspective

这周的问题:

  1. 这周用 DFF 构建了其他的芯片,把 DFF 当作了拥有某种功能的黑盒子,那么它实际上是怎么构成的呢?
    • 大胡子老师解释说这不是我们这门课要教的内容(笑死😂),因为通过构造其他的芯片就可以学习到组合逻辑电路与时序逻辑电路的差别,所以要把重点放在构造其他的芯片上。不过,大胡子老师还是回答了这个问题,用两个 NAND 构造了一个 DFF。另外,NAND 也不是唯一用来构建内存系统的基础理论。
  2. 电脑只使用 RAM 作为内存设备吗?
    • 显然不是,还有 ROM、Flash Memory 和 Cache Memory。ROM 全称 Read Only Memory,是一种断电后也不会损失信息的存储设备(比如硬盘);而 Flash Memory 则是介于 RAM 和 ROM 之间的存储设备,它的速度快于 ROM,慢于 RAM,但它的容量大于 RAM,小于 ROM,算是存储速度和空间的折中产物(比如 Mac 的闪存,手机的闪存);最后是 Cache Memory,这个东西就是 CPU 的缓存。

这周的难点跟上周类似,用“电子语言”来构造循环、条件等复杂逻辑是真的难。第一个想到这些想法的人,真的是神来之笔,创造性太强了,估计这些发明家当时也没有想到曾经的逻辑电路能演变成现在的电脑吧。


Buy me a coffee ? :)
0%