背景
让我们按照 From Nand to Tetris 里 Project 3 的要求,来完成下列的设计。
- 实现
Bit( 位寄存器) - 实现
Register( 位寄存器) - 实现
RAM8 - 实现
RAM64 - 实现
RAM512 - 实现
RAM4K - 实现
RAM16K - 实现
PC(Program Counter, 程序计数器)
说明
我是阅读了《计算机系统要素 (第2版)》 第 3 章的内容之后才去完成 Project 1 的。读者朋友在完成 Project 3 时,如果遇到不明白的地方,可以参考这本书中的描述。
正文
1. 实现 Bit ( 位寄存器)
前往 Nand to Tetris Online IDE,选择 Project 3 里的 Bit,效果如下图所示 ⬇️
我们的目标是用 DFF(Data Flip-Flop, 数据触发器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 Bit(即, 位寄存器)。
注释中的相关描述如下 ⬇️
/**
* 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= previousOut, b= in, sel= load, out= nextOut);
DFF(in= nextOut, out=out, out= previousOut);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
2. 实现 Register ( 位寄存器)
前往 Nand to Tetris Online IDE,选择 Project 3 里的 Register,效果如下图所示 ⬇️
我们的目标是用 Bit( 位寄存器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 Register(即, 位寄存器)。
注释中的相关描述如下 ⬇️
/**
* 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)
*/
- 输入是
- 输出是
我们在上一步里已经实现了 Bit( 位寄存器),既然 Register 是 位寄存器,那么可以把 Register 看成 个 位寄存器的组合。
代码的整体结构如下 ⬇️
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[15], load= load, out= out[15]);
}
由于这里有很多行类似的代码,自己复制粘贴加修改感觉有点浪费时间,不如用 java 代码来生成它吧。
请将以下代码保存为 RegisterHdlCodeGenerator.java。
import java.util.StringJoiner;
public class RegisterHdlCodeGenerator {
public static void main(String[] args) {
String template = """
CHIP Register {
IN in[16], load;
OUT out[16];
PARTS:
%s
}
""";
StringJoiner joiner = new StringJoiner(System.lineSeparator());
for (int i = 0; i < 16; i++) {
String line = String.format(" Bit(in= in[%s], load= load, out= out[%s]);", i, i);
joiner.add(line);
}
System.out.printf(template, joiner);
}
}
使用如下的命令可以编译 RegisterHdlCodeGenerator.java 并运行其中的 main 方法。
javac RegisterHdlCodeGenerator.java
java RegisterHdlCodeGenerator
运行结果如下
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]);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
3. 实现 RAM8
前往 Nand to Tetris Online IDE,选择 Project 3 里的 RAM8,效果如下图所示 ⬇️
我们的目标是用 Register( 位寄存器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 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.
*/
- 输入是
- 输出是
我们在上一步里已经实现了 Register( 位寄存器)。现在需要选择将 输入 个 Register 中的哪一个,可以使用 Project 1 里的 DMux8Way,对 而言,我们可以借助 Project 1 里的 Mux8Way16 从 个 Register 的输出中进行选择。
代码的整体结构如下 ⬇️
CHIP RAM8 {
IN in[16], load, address[3];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address,
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
Register(in= in, load= a, out= out0);
Register(in= in, load= b, out= out1);
...
Register(in= in, load= h, out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address, out= out);
}
由于这里有很多行类似的代码,自己复制粘贴加修改感觉有点浪费时间,不如用 java 代码来生成它吧。
请将以下代码保存为 RAM8HdlCodeGenerator.java。
import java.util.StringJoiner;
public class RAM8HdlCodeGenerator {
public static void main(String[] args) {
String template = """
CHIP RAM8 {
IN in[16], load, address[3];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address,
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
%s
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address, out= out);
}
""";
StringJoiner joiner = new StringJoiner(System.lineSeparator());
for (int i = 0; i < 8; i++) {
String line = String.format(" Register(in= in, load= %s, out= out%s);", (char) ('a' + i), i);
joiner.add(line);
}
System.out.printf(template, joiner);
}
}
使用如下的命令可以编译 RAM8HdlCodeGenerator.java 并运行其中的 main 方法。
javac RAM8HdlCodeGenerator.java
java RAM8HdlCodeGenerator
运行结果如下
CHIP RAM8 {
IN in[16], load, address[3];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address,
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
Register(in= in, load= a, out= out0);
Register(in= in, load= b, out= out1);
Register(in= in, load= c, out= out2);
Register(in= in, load= d, out= out3);
Register(in= in, load= e, out= out4);
Register(in= in, load= f, out= out5);
Register(in= in, load= g, out= out6);
Register(in= in, load= h, out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address, out= out);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
4. 实现 RAM64
前往 Nand to Tetris Online IDE,选择 Project 3 里的 RAM64,效果如下图所示 ⬇️
我们的目标是用 RAM8(即, 个 位寄存器的存储器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 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.
*/
- 输入是
- 输出是
我们在上一步里已经实现了 RAM8,可以用类似的思路来实现 RAM64。
代码的整体结构如下 ⬇️
CHIP RAM64 {
IN in[16], load, address[6];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[3..5],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
RAM8(in= in, load= a, address= address[0..2], out= out0);
RAM8(in= in, load= b, address= address[0..2], out= out1);
...
RAM8(in= in, load= h, address= address[0..2], out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[3..5], out= out);
}
由于这里有很多行类似的代码,自己复制粘贴加修改感觉有点浪费时间,不如用 java 代码来生成它吧。
请将以下代码保存为 RAM64HdlCodeGenerator.java。
import java.util.StringJoiner;
public class RAM64HdlCodeGenerator {
public static void main(String[] args) {
String template = """
CHIP RAM64 {
IN in[16], load, address[6];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[3..5],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
%s
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[3..5], out= out);
}
""";
StringJoiner joiner = new StringJoiner(System.lineSeparator());
for (int i = 0; i < 8; i++) {
String line = String.format(" RAM8(in= in, load= %s, address= address[0..2], out= out%s);", (char) ('a' + i), i);
joiner.add(line);
}
System.out.printf(template, joiner);
}
}
使用如下的命令可以编译 RAM64HdlCodeGenerator.java 并运行其中的 main 方法。
javac RAM64HdlCodeGenerator.java
java RAM64HdlCodeGenerator
运行结果如下
CHIP RAM64 {
IN in[16], load, address[6];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[3..5],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
RAM8(in= in, load= a, address= address[0..2], out= out0);
RAM8(in= in, load= b, address= address[0..2], out= out1);
RAM8(in= in, load= c, address= address[0..2], out= out2);
RAM8(in= in, load= d, address= address[0..2], out= out3);
RAM8(in= in, load= e, address= address[0..2], out= out4);
RAM8(in= in, load= f, address= address[0..2], out= out5);
RAM8(in= in, load= g, address= address[0..2], out= out6);
RAM8(in= in, load= h, address= address[0..2], out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[3..5], out= out);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
5. 实现 RAM512
前往 Nand to Tetris Online IDE,选择 Project 3 里的 RAM512,效果如下图所示 ⬇️
我们的目标是用 RAM64(即, 个 位寄存器的存储器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 RAM512(即, 个 位寄存器的存储器)。
注释中的相关描述如下 ⬇️
/**
* 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.
*/
- 输入是
- 输出是
我们在上一步里已经实现了 RAM64,可以用类似的思路来实现 RAM512。
代码的整体结构如下 ⬇️
CHIP RAM512 {
IN in[16], load, address[9];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[6..8],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
RAM64(in= in, load= a, address= address[0..5], out= out0);
RAM64(in= in, load= b, address= address[0..5], out= out1);
...
RAM64(in= in, load= h, address= address[0..5], out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[6..8], out= out);
}
由于这里有很多行类似的代码,自己复制粘贴加修改感觉有点浪费时间,不如用 java 代码来生成它吧。
请将以下代码保存为 RAM512HdlCodeGenerator.java。
import java.util.StringJoiner;
public class RAM512HdlCodeGenerator {
public static void main(String[] args) {
String template = """
CHIP RAM512 {
IN in[16], load, address[9];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[6..8],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
%s
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[6..8], out= out);
}
""";
StringJoiner joiner = new StringJoiner(System.lineSeparator());
for (int i = 0; i < 8; i++) {
String line = String.format(" RAM64(in= in, load= %s, address= address[0..5], out= out%s);", (char) ('a' + i), i);
joiner.add(line);
}
System.out.printf(template, joiner);
}
}
使用如下的命令可以编译 RAM512HdlCodeGenerator.java 并运行其中的 main 方法。
javac RAM512HdlCodeGenerator.java
java RAM512HdlCodeGenerator
运行结果如下
CHIP RAM512 {
IN in[16], load, address[9];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[6..8],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
RAM64(in= in, load= a, address= address[0..5], out= out0);
RAM64(in= in, load= b, address= address[0..5], out= out1);
RAM64(in= in, load= c, address= address[0..5], out= out2);
RAM64(in= in, load= d, address= address[0..5], out= out3);
RAM64(in= in, load= e, address= address[0..5], out= out4);
RAM64(in= in, load= f, address= address[0..5], out= out5);
RAM64(in= in, load= g, address= address[0..5], out= out6);
RAM64(in= in, load= h, address= address[0..5], out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[6..8], out= out);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
6. 实现 RAM4K
前往 Nand to Tetris Online IDE,选择 Project 3 里的 RAM4K,效果如下图所示 ⬇️
我们的目标是用 RAM512(即, 个 位寄存器的存储器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 RAM4K(即, 个 位寄存器的存储器)。
注释中的相关描述如下 ⬇️
/**
* 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.
*/
- 输入是
- 输出是
我们在上一步里已经实现了 RAM512,可以用类似的思路来实现 RAM4K。
代码的整体结构如下 ⬇️
CHIP RAM4K {
IN in[16], load, address[12];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[9..11],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
RAM512(in= in, load= a, address= address[0..8], out= out0);
RAM512(in= in, load= b, address= address[0..8], out= out1);
...
RAM512(in= in, load= h, address= address[0..8], out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[9..11], out= out);
}
由于这里有很多行类似的代码,自己复制粘贴加修改感觉有点浪费时间,不如用 java 代码来生成它吧。
请将以下代码保存为 RAM4KHdlCodeGenerator.java。
import java.util.StringJoiner;
public class RAM4KHdlCodeGenerator {
public static void main(String[] args) {
String template = """
CHIP RAM4K {
IN in[16], load, address[12];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[9..11],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
%s
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[9..11], out= out);
}
""";
StringJoiner joiner = new StringJoiner(System.lineSeparator());
for (int i = 0; i < 8; i++) {
String line = String.format(" RAM512(in= in, load= %s, address= address[0..8], out= out%s);", (char) ('a' + i), i);
joiner.add(line);
}
System.out.printf(template, joiner);
}
}
使用如下的命令可以编译 RAM4KHdlCodeGenerator.java 并运行其中的 main 方法。
javac RAM4KHdlCodeGenerator.java
java RAM4KHdlCodeGenerator
运行结果如下
CHIP RAM4K {
IN in[16], load, address[12];
OUT out[16];
PARTS:
DMux8Way(in= load, sel= address[9..11],
a= a, b= b, c= c, d= d, e= e, f= f, g= g, h= h);
RAM512(in= in, load= a, address= address[0..8], out= out0);
RAM512(in= in, load= b, address= address[0..8], out= out1);
RAM512(in= in, load= c, address= address[0..8], out= out2);
RAM512(in= in, load= d, address= address[0..8], out= out3);
RAM512(in= in, load= e, address= address[0..8], out= out4);
RAM512(in= in, load= f, address= address[0..8], out= out5);
RAM512(in= in, load= g, address= address[0..8], out= out6);
RAM512(in= in, load= h, address= address[0..8], out= out7);
Mux8Way16(a= out0, b= out1, c= out2, d= out3,
e= out4, f= out5, g= out6, h= out7,
sel= address[9..11], out= out);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
7. 实现 RAM16K
前往 Nand to Tetris Online IDE,选择 Project 3 里的 RAM16K,效果如下图所示 ⬇️
我们的目标是用 RAM4K(即, 个 位寄存器的存储器) 以及 Project 1/Project 2 里已经实现的各种 chip 实现一个 RAM16K(即, 个 位寄存器的存储器)。
注释中的相关描述如下 ⬇️
/**
* 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.
*/
- 输入是
- 输出是
我们在上一步里已经实现了 RAM4K,可以用类似的思路来实现 RAM16K。(由于 个 RAM4K 可以组成一个 RAM16K,所以这次不必通过 java 代码来生成 HDL 代码)
代码如下 ⬇️
CHIP RAM16K {
IN in[16], load, address[14];
OUT out[16];
PARTS:
DMux4Way(in= load, sel= address[12..13],
a= a, b= b, c= c, d= d);
RAM4K(in= in, load= a, address= address[0..11], out= out0);
RAM4K(in= in, load= b, address= address[0..11], out= out1);
RAM4K(in= in, load= c, address= address[0..11], out= out2);
RAM4K(in= in, load= d, address= address[0..11], out= out3);
Mux4Way16(a= out0, b= out1, c= out2, d= out3,
sel= address[12..13], out= out);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️
8. 实现 PC (Program Counter, 程序计数器)
前往 Nand to Tetris Online IDE,选择 Project 3 里的 PC,效果如下图所示 ⬇️
我们的目标是用已经实现的各个 chip,包括 Project 1/Project 2 里已经实现的各种 chip,来实现一个 PC(Program Counter, 程序计数器)。
注释中的相关描述如下 ⬇️
/**
* 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)
*/
- 输入是
- 输出是
大致可以这样构建 PC ⬇️
- 因为涉及 加一 操作,所以先用一个
Inc16(Project 2里有Inc16这个chip) 来实现加一的功能 - 在此基础上,用一个 ( 位的)
Mux在Register的输入 以及Inc16(out)之间做选择,选择结果称为 - 在此基础上,用一个 ( 位的)
Mux在 和 之间做选择,选择结果称为 - 在此基础上,用一个 ( 位的)
Mux在 和 之间做选择,选择结果称为 ,将 作为Register的输入
基于上述思路,可以写出如下的代码 ⬇️
CHIP PC {
IN in[16], reset, load, inc;
OUT out[16];
PARTS:
Register(in= out3, load= true, out= out, out= previousOut);
Inc16(in= previousOut, out= incOut);
Mux16(a= previousOut, b= incOut, sel= inc, out= out1);
Mux16(a= out1, b= in, sel= load, out= out2);
Mux16(a= out2, b= false, sel= reset, out= out3);
}
这样的代码可以通过仿真测试,效果如下图所示 ⬇️