目录
1. 项目概况
1.1 项目背景
在前两章(第一章、第二章)已经构建了实现布尔运算和算数运算的芯片,但它们都是组合芯片,只能计算一类简单的函数:输出结果只依赖于输入变量的排列组合。这些简单芯片只能计算,不能存储,所以还必须配备记忆单元来保存数据。这些记忆单元由时序芯片组成。
1.2 需求清单
使用前述章节已经实现的芯片、原始的数据触发器DFF门、以及在DFF门基础上构建的芯片,用HDL语言实现以下芯片:
时序设备 | 芯片名 | 功能 |
---|---|---|
1位寄存器/比特 | Bit | 用来存储1位数据 |
16位寄存器 | Register | 用来存储16位数据 |
随机存取内存 | RAM8 | 可以直接访问的记忆单元,由8个16位寄存器组成,配有直接访问电路 |
… | RAM64 | 可以直接访问的记忆单元,由64个16位寄存器组成,配有直接访问电路 |
… | RAM512 | 可以直接访问的记忆单元,由512个16位寄存器组成,配有直接访问电路 |
… | RAM4K | 可以直接访问的记忆单元,由4K个16位寄存器组成,配有直接访问电路 |
… | RAM16K | 可以直接访问的记忆单元,由16K个16位寄存器组成,配有直接访问电路 |
计数器 | PC | 16位计数器,在每个时钟周期内做简单的+1操作,因此计算机能够获取程序的下一条指令 |
备注:
本章最基本的时序设备是数据触发器DFF门,它是用于设计所有记忆单元的基本组件。DFF包括1个比特位数据输入和1个比特位数据输出。
芯片名 | DFF |
---|---|
输入 | in |
输出 | out |
功能 | out(t) = in(t-1) |
说明 | 该DFF门有现成的内置实现版本(built-in implementation),因此没有必要自己去实现 |
DFF使用反馈回路来连接几个基本逻辑门,它的物理实现是个复杂的任务。因此,硬件仿真器已经将它实现,作为内置芯片使用。DFF的HDL文件位置:.\nand2tetris\tools\builtInChips\DFF.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: tools/builtIn/DFF.hdl
/**
* Data Flip-flop: out(t) = in(t-1)
* where t is the current time unit, or clock cycle.
*/
CHIP DFF {
IN in;
OUT out;
BUILTIN DFF;
CLOCKED in;
}
2. 芯片实现过程
2.1 Bit
用于实现1位寄存器的Bit芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | Bit |
输入 | in, load |
输出 | out |
功能 | If load(t-1) then out(t) = in(t-1) else out(t) = out(t-1) |
可以使用Mux和DFF循环实现:
out
(
t
)
=
DFF
(
Mux
(
out
(
t
−
1
)
,
i
n
(
t
−
1
)
,
l
o
a
d
(
t
−
1
)
)
)
\text{out}(t)=\text{DFF}(\text{Mux}(\text{out}(t-1), in(t-1), load(t-1)))
out(t)=DFF(Mux(out(t−1),in(t−1),load(t−1)))
用HDL实现Bit(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\Bit.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/03/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.
* out(t+1) = (load(t), in(t), out(t))
*/
CHIP Bit {
IN in, load;
OUT out;
PARTS:
// 时序需要循环输入,Mux的输出作为DFF的输入,反之亦然
Mux (a = out0, b = in, sel = load, out = tempout); // 在t时刻,Mux的输入一个是in(t-1),另一个是in(t),二者择一输出,记为tempout
DFF (in = tempout, out = out, out = out0); // 在t时刻,DFF的输入是Mux的输出tempout,在t+1时刻,输出为out(t+1),将out(t+1)反馈给Mux,进入下一次循环
}
2.2 Register
用于实现16位寄存器的Register芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | Bit |
输入 | in[16], load |
输出 | out[16] |
功能 | If load(t-1) then out(t) = in(t-1) else out(t) = out(t-1) |
说明 | “=”是16-位的赋值操作 |
Register与Bit类似,可以直接使用Bit实现:
out
(
t
)
=
Bit
(
i
n
(
t
−
1
)
,
l
o
a
d
(
t
−
1
)
)
\text{out}(t)=\text{Bit}(in(t-1), load(t-1))
out(t)=Bit(in(t−1),load(t−1))
用HDL实现Register(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\Register.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/03/a/Register.hdl
/**
* 16-bit register:
* If load is asserted, the register's value is set to in;
* Otherwise, the register maintains its current value.
* out(t+1) = (load(t), in(t), 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]);
}
2.3 RAMn
随机存储内存RAMn由n个w位寄存器堆叠而来,这里使用16位寄存器。用于实现RAMn的芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | RAMn |
输入 | in[16], address[k], load |
输出 | out[16] |
功能 | out(t) = RAM[address(t)](t) |
说明 | “=”是16-位的赋值操作 |
用于Hack平台的具体芯片及参数如下:
芯片名 | n | k |
---|---|---|
RAM8 | 8 | 3 |
RAM64 | 64 | 6 |
RAM512 | 512 | 9 |
RAM4K | 4096 | 12 |
RAM16K | 16384 | 14 |
2.3.1 RAM8
RAM8由8个16位寄存器组成,可以使用Register、DMux、Mux实现:
- RAM8由8个16位寄存器组成,但不论是读取还是写入操作,一次都只使用一个寄存器。利用DMux8Way,根据address寻址,并将load值存入该寄存器。
- 由于不清楚具体调用了哪个寄存器,需要将每个寄存器在in输入下的输出都计算一次。由于load值不同,各个寄存器的输出不同。
- 不管load的值是0还是1,RAM8的输出结果都已经存储在address所对应的寄存器的输出值中。因此使用Mux8Way16,根据address寻址,输出该寄存器的输出结果,即是RAM8在输入为in时的输出结果。
用HDL实现RAM8(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\RAM8.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/03/a/RAM8.hdl
/**
* 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:
// 根据address,将load写入某个寄存器
DMux8Way (in = load, sel = address, a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);
// 根据各个寄存器的load值,计算各个寄存器在输入为in时,输出的情况
Register (in = in, load = a, out = out1);
Register (in = in, load = b, out = out2);
Register (in = in, load = c, out = out3);
Register (in = in, load = d, out = out4);
Register (in = in, load = e, out = out5);
Register (in = in, load = f, out = out6);
Register (in = in, load = g, out = out7);
Register (in = in, load = h, out = out8);
// 根据address,输出某个寄存器的输出值
// 在t时刻,当load == 1时,执行写操作,输出值为in(t);当load == 0时,执行读操作,输出值为out(t+1)
Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address, out = out);
}
2.3.2 RAM64
RAM64的实现原理和RAM8类似,可以直接使用RAM8和DMux、Mux实现。
需要注意,一个RAM64由8个RAM8组成,而一个RAM8由8个寄存器组成,直接使用RAM8构建RAM64时,address中各位数值用途如下:
位置 | 表示方法 | 用途 |
---|---|---|
前3位 | address[0…2] | 定位到某个RAM64 |
后3位 | address[3…5] | 定位到某个寄存器 |
- 利用DMux8Way,根据address[0…2]寻址,并将load值存入该RAM8。
- 由于不清楚具体调用了哪个RAM8,需要将每个RAM8中,address[3…5]位置的寄存器,在in输入下的输出都计算一次。由于load值不同,各个RAM8的输出不同。
- 不管load的值是0还是1,RAM64的输出结果都已经存储在address[0…2]所对应的RAM8的输出值中。因此使用Mux8Way16,根据address[0…2]寻址,输出该RAM8的输出结果,即是RAM64在输入为in时的输出结果。
用HDL实现RAM64(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\RAM64.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/03/a/RAM64.hdl
/**
* 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:
// 根据寄存器的address前三位,定位到某个RAM8,将load写入该RAM8
DMux8Way (in = load, sel = address[0..2], a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);
// 根据各个RAM8的load值,计算各个RAM8在输入为in时,输出的情况
// 根据寄存器的address后三位,定位到某个寄存器
RAM8 (in = in, load = a, address = address[3..5], out = out1);
RAM8 (in = in, load = b, address = address[3..5], out = out2);
RAM8 (in = in, load = c, address = address[3..5], out = out3);
RAM8 (in = in, load = d, address = address[3..5], out = out4);
RAM8 (in = in, load = e, address = address[3..5], out = out5);
RAM8 (in = in, load = f, address = address[3..5], out = out6);
RAM8 (in = in, load = g, address = address[3..5], out = out7);
RAM8 (in = in, load = h, address = address[3..5], out = out8);
// 根据address,输出某个RAM8的输出值
Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address[0..2], out = out);
}
2.3.3 RAM512
RAM512的实现原理和RAM64类似,可以直接使用RAM64和DMux、Mux实现RAM512。
需要注意,一个RAM512由8个RAM64组成,而一个RAM64由64个寄存器组成。直接使用RAM64构建RAM512时,address中各位数值用途如下:
位置 | 表示方法 | 用途 |
---|---|---|
前3位 | address[0…2] | 定位到某个RAM64 |
后6位 | address[3…8] | 定位到某个寄存器 |
用HDL实现RAM512(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\b\RAM512.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/03/b/RAM512.hdl
/**
* Memory of 512 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 RAM512 {
IN in[16], load, address[9];
OUT out[16];
PARTS:
// 根据寄存器的address前3位,定位到某个RAM64,将load写入该RAM64
DMux8Way (in = load, sel = address[0..2], a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);
// 根据各个RAM64的load值,计算各个RAM64在输入为in时,输出的情况
// 根据寄存器的address后6位,定位到某个寄存器
RAM64 (in = in, load = a, address = address[3..8], out = out1);
RAM64 (in = in, load = b, address = address[3..8], out = out2);
RAM64 (in = in, load = c, address = address[3..8], out = out3);
RAM64 (in = in, load = d, address = address[3..8], out = out4);
RAM64 (in = in, load = e, address = address[3..8], out = out5);
RAM64 (in = in, load = f, address = address[3..8], out = out6);
RAM64 (in = in, load = g, address = address[3..8], out = out7);
RAM64 (in = in, load = h, address = address[3..8], out = out8);
// 根据address,输出某个RAM64的输出值
Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address[0..2], out = out);
}
2.3.4 RAM4K
RAM4K的实现原理和RAM64、RAM512类似,可以直接使用RAM512和DMux、Mux实现RAM4K。
需要注意,一个RAM4K由8个RAM512组成,而一个RAM512由512个寄存器组成。直接使用RAM512构建RAM4K时,address中各位数值用途如下:
位置 | 表示方法 | 用途 |
---|---|---|
前3位 | address[0…2] | 定位到某个RAM512 |
后9位 | address[3…11] | 定位到某个寄存器 |
用HDL实现RAM4K(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\b\RAM512.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/03/b/RAM4K.hdl
/**
* Memory of 4K 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 RAM4K {
IN in[16], load, address[12];
OUT out[16];
PARTS:
// 根据寄存器的address前3位,定位到某个RAM512,将load写入该RAM512
DMux8Way (in = load, sel = address[0..2], a = a, b = b, c = c, d = d, e = e, f = f, g = g, h = h);
// 根据各个RAM512的load值,计算各个RAM512在输入为in时,输出的情况
// 根据寄存器的address后9位,定位到某个寄存器
RAM512 (in = in, load = a, address = address[3..11], out = out1);
RAM512 (in = in, load = b, address = address[3..11], out = out2);
RAM512 (in = in, load = c, address = address[3..11], out = out3);
RAM512 (in = in, load = d, address = address[3..11], out = out4);
RAM512 (in = in, load = e, address = address[3..11], out = out5);
RAM512 (in = in, load = f, address = address[3..11], out = out6);
RAM512 (in = in, load = g, address = address[3..11], out = out7);
RAM512 (in = in, load = h, address = address[3..11], out = out8);
// 根据address,输出某个RAM512的输出值
Mux8Way16 (a = out1, b = out2, c = out3, d = out4, e = out5, f = out6, g = out7, h = out8, sel = address[0..2], out = out);
}
2.3.5 RAM16K
RAM16K的实现原理和RAM64、RAM512、RAM4K类似,可以直接使用RAM4K和DMux、Mux实现RAM4K。
需要注意,一个RAM16K由4个RAM4K组成,而一个RAM4K由4K个寄存器组成。直接使用RAM4K构建RAM16K时,address中各位数值用途如下:
位置 | 表示方法 | 用途 |
---|---|---|
前2位 | address[0…1] | 定位到某个RAM4K |
后12位 | address[2…11] | 定位到某个寄存器 |
用HDL实现RAM16K(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\b\RAM512.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/03/b/RAM16K.hdl
/**
* Memory of 16K 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 RAM16K {
IN in[16], load, address[14];
OUT out[16];
PARTS:
// 根据寄存器的address前2位,定位到某个RAM4K,将load写入该RAM4K
DMux4Way (in = load, sel = address[0..1], a = a, b = b, c = c, d = d);
// 根据各个RAM4K的load值,计算各个RAM4K在输入为in时,输出的情况
// 根据寄存器的address后12位,定位到某个寄存器
RAM4K (in = in, load = a, address = address[2..13], out = out1);
RAM4K (in = in, load = b, address = address[2..13], out = out2);
RAM4K (in = in, load = c, address = address[2..13], out = out3);
RAM4K (in = in, load = d, address = address[2..13], out = out4);
// 根据address,输出某个RAM4K的输出值
Mux4Way16 (a = out1, b = out2, c = out3, d = out4, sel = address[0..1], out = out);
}
2.4 PC
用于实现16位计数器的PC芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | PC |
输入 | in[16], inc, load, reset |
输出 | out[16] |
功能 | 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 |
… | else out(t+1) = out(t) |
说明 | “=”和“+”分别对16位的数值进行赋值操作和加法操作。 |
可以使用Mux16、Add16和Register实现:
- 默认输出out(t+1) = out(t)。
- 用Mux16判断参数inc的值,若inc(t) == 1,使用Add16芯片将输出值+1,输出值out(t+1) = out(t) + 1,否则仍输出out(t+1) = out(t)。
- 用Mux16判断参数load的值,若load(t) == 1,输出值为out(t+1) = in(t),否则仍按上一步骤输出。
- 用Mux16判断参数reset的值,若reset(t) == 1,输出值out(t+1) = 0,否则仍按上一步骤输出。
- 使用Register输出结果。
用HDL实现PC(直接在已有文件中修改,位置为 .\nand2tetris\projects\03\a\PC.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/03/a/PC.hdl
/**
* A 16-bit counter with reset, load, and inc control bits.
* 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
* else out(t+1) = out(t)
*/
CHIP PC {
IN in[16], load, inc, reset;
OUT out[16];
PARTS:
// inc(t) == 1时,输出值为out(t+1) = out(t) + 1,否则仍为out(t+1) = out(t)
Inc16 (in = preout, out = tmpincout);
Mux16 (a = preout, b = tmpincout, sel = inc, out = incout);
// load(t) == 1时,输出值为out(t+1) = in(t),否则仍按上一步骤输出
Mux16 (a = incout, b = in, sel = load, out = loadout);
// reset(t) == 1时,输出值为out(t+1) = 0,否则仍按上一步骤输出
Mux16 (a = loadout, b = false, sel = reset, out = resetout);
// 输出结果
Register (in = resetout, load = true, out = preout, out = out);
}
3. 总结
3.1 遇到的问题和解决办法
(1)实现Bit时,输出结果需要返回作为输入,一开始不知道怎么实现这个循环。
参考了其他人的代码:需要将输出拷贝一份,再作为输入来实现。其中,用于输入的必须是输出的拷贝,而不能是输出本身。
(2)实现PC时没有思路。
参考了其他人的代码:需要多次利用Mux16,来将输出结果一步步存储到单一变量中,而且和Bit一样,需要将输出作为输入来实现循环。
(3)运行测试的过程较慢,想提高效率。
一开始没有注意界面上的调节速度功能,后来感觉测试太慢,就尝试了一下把这个调节到最快,发现瞬间完成了测试!以后都要调到最快,再也不用等待了!
3.2 心得体会
整体来说,第三章的难度要明显大于前两章。引入时序概念后,逻辑变得复杂了一些,需要循环才能实现的部分尤其难以理解。只能多动手尝试,实在做不出来就参考他人的代码,多思考。