From Nand to Tetris 里的 Project 1

179 阅读3分钟

背景

只用 Nand 运算符来表示任意的布尔函数 一文已经证明了只用 Nand 运算符就可以表示任意的布尔函数,让我们按照 From Nand to TetrisProject 1 的要求,来完成下列的设计。

  1. 实现 Not
  2. 实现 And
  3. 实现 Or
  4. 实现 Xor
  5. 实现 Mux
  6. 实现 DMux
  7. 实现 Not16
  8. 实现 And16
  9. 实现 Or16
  10. 实现 Mux16
  11. 实现 Or8Way
  12. 实现 Mux4Way16
  13. 实现 Mux8Way16
  14. 实现 DMux4Way
  15. 实现 DMux8Way

image.png

说明

我是阅读了《计算机系统要素 (第2版)》 第 1 章的内容之后才去完成 Project 1 的。读者朋友在完成 Project 1 时,如果遇到不明白的地方,可以参考这本书中的描述。

正文

Project 1 中要求我们用内置的 Nand 来实现一些其他的门

1. 实现 Not

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Not,效果如下图所示 ⬇️

image.png

因为下面的转化成立,所以我们找到了一种用 Nand 来实现 Not 的方式。

  • ¬a=¬(aa)=Nand(a,a)\neg{a}=\neg({a\land a})=\text{Nand}(a,a)

基于上面的分析,可以写出如下的代码 ⬇️

CHIP Not {
    IN in;
    OUT out;

    PARTS:
    Nand(a= in, b= in, out= out);
    //// Replace this comment with your code.
}

这样的代码可以通过仿真测试 ⬇️

image.png

其他方式

下面的转化成立,于是我们找到了另一种用 Nand 来实现 Not 的方式。

  • ¬a=¬(atrue)=Nand(a,true)\neg{a}=\neg({a\land \text{true}})=\text{Nand}(a,\text{true})

对应的代码如下 ⬇️

CHIP Not {
    IN in;
    OUT out;

    PARTS:
    Nand(a= in, b= true, out= out);
}

这样的代码可以通过仿真测试 ⬇️

image.png

2. 实现 And

前往 Nand to Tetris Online IDE,选择 Project 1 里的 And,效果如下图所示 ⬇️

image.png

因为下面的转化成立,所以已经找到了用 Nand 来实现 And 的方式(因为之前已经通过 Nand 实现了 Not,所以这里可以使用 Not)。

  • ab=¬(¬(ab))=¬Nand(a,b)a \land b=\neg{(\neg{(a \land b}))}=\neg{\text{Nand}(a, b)}

基于上面的分析,可以写出如下的代码 ⬇️

CHIP And {
    IN a, b;
    OUT out;
    
    PARTS:
    Nand(a= a, b= b, out= out1);
    Not(in= out1, out= out);
}

这样的代码可以通过仿真测试 ⬇️

image.png

其他方式(只用 Nand 来实现 And

考虑到以下两式成立

  • ab=¬Nand(a,b)a \land b=\neg{\text{Nand}(a, b)}
  • ¬x=Nand(x,true)\neg{x}={\text{Nand}(x, \text{true})}

x=Nand(a,b)x=\text{Nand}(a,b),就可以得到 ⬇️

  • ab=¬Nand(a,b)=Nand(Nand(a,b),true)a\land b=\neg{\text{Nand}(a, b)}= \text{Nand}(\text{Nand}(a,b),\text{true})

基于上面的分析,可以写出如下的代码 ⬇️

CHIP And {
    IN a, b;
    OUT out;
    
    PARTS:
    Nand(a= a, b= b, out= temp);
    Nand(a= temp, b= true, out= out);
}

这样的代码可以通过仿真测试 ⬇️

image.png

3. 实现 Or

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Or

根据德摩根定律

  • ab=¬(¬a¬b)=Nand(¬a,¬b)a \lor b=\neg{(\neg{a} \land \neg{b})}=\text{Nand}(\neg{a}, \neg{b})

我们用真值表也可以证明上式 ⬇️ (aa,bb 两列是输入,其他列是计算结果)

aabbaba\lor b¬a\neg{a}¬b\neg{b}¬a¬b\neg{a} \land \neg{b}¬(¬a¬b)\neg{(\neg{a} \land \neg{b})}
00000011111100
00111111000011
11001100110011
11111100000011

所以已经找到了用 Nand 来实现 Or 的方式(因为之前已经通过 Nand 实现了 Not,所以这里可以使用 Not)。

填写我们的代码 ⬇️ 后,应该就可以通过仿真测试了。

CHIP Or {
    IN a, b;
    OUT out;

    PARTS:
    Not(in= a, out= notA);
    Not(in= b, out= notB);
    Nand(a= notA, b= notB, out= out);
}

效果如下图所示 ⬇️ image.png

其他方式(只用 Nand 来实现 Or

因为以下两式成立

  • ab=¬(¬a¬b)=Nand(¬a,¬b)a \lor b=\neg{(\neg{a} \land \neg{b})}=\text{Nand}(\neg{a}, \neg{b})
  • ¬x=Nand(x,true)\neg{x}=\text{Nand}(x, \text{true})

所以

  • ab=Nand(¬a,¬b)=Nand(Nand(a,true),Nand(b,true))a\lor b=\text{Nand}(\neg{a},\neg{b})=\text{Nand}(\text{Nand}(a,\text{true}),\text{Nand}(b,\text{true}))

基于上面的分析,可以写出如下的代码 ⬇️

CHIP Or {
    IN a, b;
    OUT out;

    PARTS:
    Nand(a= a, b= true, out= nota);
    Nand(a= b, b= true, out= notb);
    Nand(a= nota, b= notb, out= out);
}

这样的代码可以通过仿真测试 ⬇️

image.png

4. 实现 Xor

在前面 33 步中,我们已经实现了 Not/And/Or,那么对任意的布尔函数,我们只要先找到其对应的析取范式(DNF),然后就可以用 Not/And/Or 来实现它了。

Xor 的真值表如下

aabbXor(a,b)\text{Xor}(a, b)
000000
001111
110011
111100

基于以上真值表,可以将 Xor 转化成对应的析取范式(DNF)。

Xor(a,b)=(a¬b)(¬ab)\text{Xor}(a,b)=(a\land \neg{b}) \lor (\neg{a} \land b)

这样我们找到了一种用 Nand 来实现 Xor 的方式。

前往 Nand to Tetris Online IDE 后,选择 Project 1 里的 Xor,效果如下图所示 ⬇️

image.png

填写我们的代码 ⬇️ 后,应该就可以通过仿真测试了。

CHIP Xor {
    IN a, b;
    OUT out;

    PARTS:
    Not(in= a, out= notA);
    Not(in= b, out= notB);
    And(a= a, b= notB, out= out1);
    And(a= notA, b= b, out= out2);
    Or(a= out1, b= out2, out= out);
}

效果如下图所示 ⬇️

image.png

其他方式 1

也有其他方式可以实现 Xor。注意到 XorOr 的真值表只有一行不同 ⬇️

aabbXor(a,b)\text{Xor}(a, b)aba\lor b
00000000
00111111
11001111
11110011

所以我们可以对 Or 得到的结果进行微调,

Xor(a,b)\text{Xor}(a,b)
=(ab)(¬(ab))=(a\lor b) \land (\neg{(a \land b)})
=(ab)Nand(a,b)=(a\lor b) \land \text{Nand}(a,b)

对应的代码如下 ⬇️

CHIP Xor {
    IN a, b;
    OUT out;

    PARTS:
    Or(a= a, b= b, out= out1);
    Nand(a= a, b= b, out=out2);
    And(a= out1, b= out2, out= out);
}

这样的代码也可以通过仿真测试,效果如下图所示 ⬇️

image.png

其他方式 2(只用 Nand 来实现 Xor

还有有其他方式可以实现 Xor。注意到 XorNand 的真值表只有一行不同 ⬇️

aabbXor(a,b)\text{Xor}(a, b)Nand(a,b)\text{Nand}(a, b)
00000011
00111111
11001111
11110000

所以我们可以对 Nand 得到的结果进行微调 ⬇️

Xor(a,b)\text{Xor}(a,b)
=Nand(a,b)¬(¬a¬b)=\text{Nand}(a,b)\land \neg(\neg{a}\land\neg{b})
=Nand(a,b)Nand(¬a,¬b)=\text{Nand}(a,b)\land \text{Nand}(\neg{a},\neg{b})

考虑到 ¬x=Nand(x,true)\neg{x}=\text{Nand}(x,\text{true}) 成立,令

  • x=ax=a

¬a=Nand(a,true)\neg{a}=\text{Nand}(a,\text{true})

那么

Xor(a,b)\text{Xor}(a,b)
=Nand(a,b)Nand(¬a,¬b)=\text{Nand}(a,b)\land \text{Nand}(\neg{a},\neg{b})
=Nand(a,b)Nand(Nand(a,true),¬b)=\text{Nand}(a,b)\land \text{Nand}(\text{Nand}(a,\text{true}),\neg{b})

再令

  • x=bx=b

¬b=Nand(b,true)\neg{b}=\text{Nand}(b,\text{true})

那么

Xor(a,b)\text{Xor}(a,b)
=Nand(a,b)Nand(Nand(a,true),¬b)=\text{Nand}(a,b)\land \text{Nand}(\text{Nand}(a,\text{true}),\neg{b})
=Nand(a,b)Nand(Nand(a,true),Nand(b,true))=\text{Nand}(a,b)\land \text{Nand}(\text{Nand}(a,\text{true}),\text{Nand}(b,\text{true}))

考虑到 xy=¬Nand(x,y)=Nand(Nand(x,y),true)x\land y=\neg{\text{Nand}(x,y)}=\text{Nand}(\text{Nand}(x,y),\text{true}) 成立

  • x=Nand(a,b)x=\text{Nand}(a,b)
  • y=Nand(Nand(a,true),Nand(b,true))y=\text{Nand}(\text{Nand}(a,\text{true}),\text{Nand}(b,\text{true}))

可以得出

Xor(a,b)\text{Xor}(a,b)
=xy=x\land y
=Nand(Nand(x,y),true)=\text{Nand}(\text{Nand}(x,y),\text{true})
=Nand(Nand(Nand(a,b),Nand(Nand(a,true),Nand(b,true))),true)=\text{Nand}(\text{Nand}(\text{Nand}(a,b),\text{Nand}(\text{Nand}(a,\text{true}),\text{Nand}(b,\text{true}))),\text{true})
CHIP Xor {
    IN a, b;
    OUT out;

    PARTS:
    Nand(a= a, b= b, out= x);
    Nand(a= a, b= true, out= nota);
    Nand(a= b, b= true, out= notb);
    Nand(a= nota, b= notb, out= y);
    Nand(a= x, b= y, out= temp);
    Nand(a= temp, b= true, out= out);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

5. 实现 Mux

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Mux,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Multiplexer

  • 输入是
    • aa
    • bb
    • selsel
  • 输出是 outout

要实现的逻辑是这样的 ⬇️

if (sel = 0) out = a, else out = b

从题目的描述来看,应该可以这样实现 ⬇️

(¬sela)(selb)(\neg{sel}\land a) \lor (sel \land b)

如果一时想不出上面这个式子也无妨,我们也可以从真值表入手。这个 Mux 的真值表如下 ⬇️

selselaabboutout
00000000
00001100
00110011
00111111
11000000
11001111
11110000
11111111

从真值表入手,我们可以写出这个 Mux 的析取范式(DNF)如下

(¬selab)(¬sela¬b)(sel¬ab)(selab)(\neg{sel}\land a \land b) \lor (\neg{sel}\land a \land \neg{b}) \lor (sel \land \neg{a} \land b) \lor (sel \land a \land b)

下面对其进行化简 将前两个最小项(minterm)合并,可以得到

(¬selab)(¬sela¬b)(sel¬ab)(selab)(\neg{sel}\land a \land b) \lor (\neg{sel}\land a \land \neg{b}) \lor (sel \land \neg{a} \land b) \lor (sel \land a \land b)
=(¬sela)(sel¬ab)(selab)=(\neg{sel}\land a) \lor (sel \land \neg{a} \land b) \lor (sel \land a \land b)

再将最后两个最小项(minterm)合并,可以得到

(¬sela)(sel¬ab)(selab)(\neg{sel}\land a) \lor (sel \land \neg{a} \land b) \lor (sel \land a \land b)
=(¬sela)(selb)=(\neg{sel}\land a) \lor (sel \land b)

这和直接分析题意而得到的 (¬sela)(selb)(\neg{sel}\land a) \lor (sel \land b) 是一样的。于是可以写出对应的代码 ⬇️

CHIP Mux {
    IN a, b, sel;
    OUT out;

    PARTS:
    Not(in= sel, out= notSel);
    And(a= notSel, b= a, out= out1);
    And(a= sel, b= b, out= out2);
    Or(a= out1, b= out2, out= out);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

6. 实现 DMux

前往 Nand to Tetris Online IDE,选择 Project 1 里的 DMux,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Demultiplexer

  • 输入是
    • inin
    • selsel
  • 输出是
    • aa
    • bb

要实现的逻辑是这样的 ⬇️

[a, b] = [in, 0] if sel = 0
         [0, in] if sel = 1

我们可以把 aa, bb 分开来看。 先看 aa

if (sel = 0) a = in, else a = 0

所以

a=in¬sela=in \land \neg{sel}

再看 bb

if (sel = 0) b = 0, else b = in
b=inselb=in \land sel

将两个式子写在一起 ⬇️

  • a=in¬sela=in \land \neg{sel}
  • b=inselb=in \land sel

如果一时想不出上面这两个式子也无妨,我们也可以从真值表入手。这个 DMux 的真值表如下 ⬇️

ininselselaabb
00000000
00110000
11001100
11110011

先看 aa,从上面的真值表可以得出 aa 的析取范式(DNF)如下

a=in¬sela=in\land \neg{sel}

再看 bb,从上面的真值表可以得出 bb 的析取范式(DNF)如下

b=inselb=in \land sel

我们从真值表出发,得到的结果和刚才是一样的。现在可以写出对应的代码了 ⬇️

CHIP DMux {
    IN in, sel;
    OUT a, b;

    PARTS:
    Not(in= sel, out= notSel);
    And(a= in, b= notSel, out= a);
    And(a= in, b= sel, out= b);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

7. 实现 Not16

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Not16,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Not16

  • 输入是 in[16]in[16]
  • 输出是 out[16]out[16]

要实现的逻辑是这样的 ⬇️

16-bit Not gate:
for i = 0, ..., 15:
out[i] = Not(in[i])

在第 11 步中,我们已经实现了 Not,所以现在可以直接使用 Not。我们要写的代码会长这个样子 ⬇️

Not(in= in[0], out= out[0]);
Not(in= in[1], out= out[1]);
...

由于 Not(in= in[?], out= out[?]); 这样的代码一共要写 1616 遍,用程序来生成它比较好。我写了以下的 java 代码来生成对应的 hdl 代码。请将以下代码保存为 Not16HdlCodeGenerator.java ⬇️

import java.util.StringJoiner;

public class Not16HdlCodeGenerator {
    public static void main(String[] args) {
        String template = """
                CHIP Not16 {
                    IN in[16];
                    OUT out[16];
                    PARTS:
                %s
                }""";
        StringJoiner joiner = new StringJoiner(System.lineSeparator());
        for (int i = 0; i < 16; i++) {
            String line = String.format("    Not(in= in[%s], out= out[%s]);", i, i);
            joiner.add(line);
        }
        System.out.printf(template, joiner);
    }
}

用如下命令可以编译 Not16HdlCodeGenerator.java 并运行其中的 main 方法。

javac Not16HdlCodeGenerator.java
java Not16HdlCodeGenerator

运行结果如下

CHIP Not16 {
    IN in[16];
    OUT out[16];
    PARTS:
    Not(in= in[0], out= out[0]);
    Not(in= in[1], out= out[1]);
    Not(in= in[2], out= out[2]);
    Not(in= in[3], out= out[3]);
    Not(in= in[4], out= out[4]);
    Not(in= in[5], out= out[5]);
    Not(in= in[6], out= out[6]);
    Not(in= in[7], out= out[7]);
    Not(in= in[8], out= out[8]);
    Not(in= in[9], out= out[9]);
    Not(in= in[10], out= out[10]);
    Not(in= in[11], out= out[11]);
    Not(in= in[12], out= out[12]);
    Not(in= in[13], out= out[13]);
    Not(in= in[14], out= out[14]);
    Not(in= in[15], out= out[15]);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

8. 实现 And16

前往 Nand to Tetris Online IDE,选择 Project 1 里的 And16,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 And16

  • 输入是
    • a[16]a[16]
    • b[16]b[16]
  • 输出是 out[16]out[16]

要实现的逻辑是这样的 ⬇️

16-bit And gate:
for i = 0, ..., 15:
out[i] = a[i] And b[i] 

在第 22 步中,我们已经实现了 And,所以现在可以直接使用 And。我们要写的代码会长这个样子 ⬇️

And(a= a[0], b= b[0], out= out[0]);
And(a= a[1], b= b[1], out= out[1]);
...

由于 And(a= a[?], b= b[?], out= out[?]); 这样的代码一共要写 1616 遍,用程序来生成它比较好。我写了以下的 java 代码来生成对应的 hdl 代码。请将以下代码保存为 And16HdlCodeGenerator.java ⬇️

import java.util.StringJoiner;

public class And16HdlCodeGenerator {
    public static void main(String[] args) {
        String template = """
                CHIP And16 {
                    IN a[16], b[16];
                    OUT out[16];
                
                    PARTS:
                %s
                }
                """;
        StringJoiner joiner = new StringJoiner(System.lineSeparator());
        for (int i = 0; i < 16; i++) {
            String line = String.format("    And(a= a[%s], b= b[%s], out= out[%s]);", i, i, i);
            joiner.add(line);
        }
        System.out.printf(template, joiner);
    }
}

用如下命令可以编译 And16HdlCodeGenerator.java 并运行其中的 main 方法。

javac And16HdlCodeGenerator.java
java And16HdlCodeGenerator

运行结果如下

CHIP And16 {
    IN a[16], b[16];
    OUT out[16];

    PARTS:
    And(a= a[0], b= b[0], out= out[0]);
    And(a= a[1], b= b[1], out= out[1]);
    And(a= a[2], b= b[2], out= out[2]);
    And(a= a[3], b= b[3], out= out[3]);
    And(a= a[4], b= b[4], out= out[4]);
    And(a= a[5], b= b[5], out= out[5]);
    And(a= a[6], b= b[6], out= out[6]);
    And(a= a[7], b= b[7], out= out[7]);
    And(a= a[8], b= b[8], out= out[8]);
    And(a= a[9], b= b[9], out= out[9]);
    And(a= a[10], b= b[10], out= out[10]);
    And(a= a[11], b= b[11], out= out[11]);
    And(a= a[12], b= b[12], out= out[12]);
    And(a= a[13], b= b[13], out= out[13]);
    And(a= a[14], b= b[14], out= out[14]);
    And(a= a[15], b= b[15], out= out[15]);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

9. 实现 Or16

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Or16,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Or16

  • 输入是
    • a[16]a[16]
    • b[16]b[16]
  • 输出是 out[16]out[16]

要实现的逻辑是这样的 ⬇️

16-bit Or gate:
for i = 0, ..., 15:
out[i] = a[i] Or b[i] 

在第 33 步中,我们已经实现了 Or,所以现在可以直接使用 Or。我们要写的代码会长这个样子 ⬇️

Or(a= a[0], b= b[0], out= out[0]);
Or(a= a[1], b= b[1], out= out[1]);
...

由于 Or(a= a[?], b= b[?], out= out[?]); 这样的代码一共要写 1616 遍,用程序来生成它比较好。我写了以下的 java 代码来生成对应的 hdl 代码。请将以下代码保存为 Or16HdlCodeGenerator.java ⬇️

import java.util.StringJoiner;

public class Or16HdlCodeGenerator {
    public static void main(String[] args) {
        String template = """
                CHIP Or16 {
                    IN a[16], b[16];
                    OUT out[16];
                
                    PARTS:
                %s
                }
                """;
        StringJoiner joiner = new StringJoiner(System.lineSeparator());
        for (int i = 0; i < 16; i++) {
            String line = String.format("    Or(a= a[%s], b= b[%s], out= out[%s]);", i, i, i);
            joiner.add(line);
        }
        System.out.printf(template, joiner);
    }
}

用如下命令可以编译 Or16HdlCodeGenerator.java 并运行其中的 main 方法。

javac Or16HdlCodeGenerator.java
java Or16HdlCodeGenerator

运行结果如下

CHIP Or16 {
    IN a[16], b[16];
    OUT out[16];

    PARTS:
    Or(a= a[0], b= b[0], out= out[0]);
    Or(a= a[1], b= b[1], out= out[1]);
    Or(a= a[2], b= b[2], out= out[2]);
    Or(a= a[3], b= b[3], out= out[3]);
    Or(a= a[4], b= b[4], out= out[4]);
    Or(a= a[5], b= b[5], out= out[5]);
    Or(a= a[6], b= b[6], out= out[6]);
    Or(a= a[7], b= b[7], out= out[7]);
    Or(a= a[8], b= b[8], out= out[8]);
    Or(a= a[9], b= b[9], out= out[9]);
    Or(a= a[10], b= b[10], out= out[10]);
    Or(a= a[11], b= b[11], out= out[11]);
    Or(a= a[12], b= b[12], out= out[12]);
    Or(a= a[13], b= b[13], out= out[13]);
    Or(a= a[14], b= b[14], out= out[14]);
    Or(a= a[15], b= b[15], out= out[15]);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

10. 实现 Mux16

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Mux16,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Mux16

  • 输入是
    • a[16]a[16]
    • b[16]b[16]
    • selsel
  • 输出是 out[16]out[16]

要实现的逻辑是这样的 ⬇️

16-bit multiplexor: 
for i = 0, ..., 15:
if (sel = 0) out[i] = a[i], else out[i] = b[i]

在第 55 步中,我们已经实现了 Mux,所以现在可以直接使用 Mux。我们要写的代码会长这个样子 ⬇️

Mux(a= a[0], b= b[0], sel= sel, out= out[0]);
Mux(a= a[1], b= b[1], sel= sel, out= out[1]);
...

由于 Mux(a= a[?], b= b[?], sel= sel, out= out[?]); 这样的代码一共要写 1616 遍,用程序来生成它比较好。我写了以下的 java 代码来生成对应的 hdl 代码。请将以下代码保存为 Mux16HdlCodeGenerator.java ⬇️

import java.util.StringJoiner;

public class Mux16HdlCodeGenerator {
    public static void main(String[] args) {
        String template = """
                CHIP Mux16 {
                    IN a[16], b[16], sel;
                    OUT out[16];
                
                    PARTS:
                %s
                }
                """;
        StringJoiner joiner = new StringJoiner(System.lineSeparator());
        for (int i = 0; i < 16; i++) {
            String line = String.format("    Mux(a= a[%s], b= b[%s], sel= sel, out= out[%s]);", i, i, i);
            joiner.add(line);
        }
        System.out.printf(template, joiner);
    }
}

用如下命令可以编译 Mux16HdlCodeGenerator.java 并运行其中的 main 方法。

javac Mux16HdlCodeGenerator.java
java Mux16HdlCodeGenerator

运行结果如下

CHIP Mux16 {
    IN a[16], b[16], sel;
    OUT out[16];

    PARTS:
    Mux(a= a[0], b= b[0], sel= sel, out= out[0]);
    Mux(a= a[1], b= b[1], sel= sel, out= out[1]);
    Mux(a= a[2], b= b[2], sel= sel, out= out[2]);
    Mux(a= a[3], b= b[3], sel= sel, out= out[3]);
    Mux(a= a[4], b= b[4], sel= sel, out= out[4]);
    Mux(a= a[5], b= b[5], sel= sel, out= out[5]);
    Mux(a= a[6], b= b[6], sel= sel, out= out[6]);
    Mux(a= a[7], b= b[7], sel= sel, out= out[7]);
    Mux(a= a[8], b= b[8], sel= sel, out= out[8]);
    Mux(a= a[9], b= b[9], sel= sel, out= out[9]);
    Mux(a= a[10], b= b[10], sel= sel, out= out[10]);
    Mux(a= a[11], b= b[11], sel= sel, out= out[11]);
    Mux(a= a[12], b= b[12], sel= sel, out= out[12]);
    Mux(a= a[13], b= b[13], sel= sel, out= out[13]);
    Mux(a= a[14], b= b[14], sel= sel, out= out[14]);
    Mux(a= a[15], b= b[15], sel= sel, out= out[15]);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

11. 实现 Or8Way

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Or8Way,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Or8Way

  • 输入是 in[8]in[8]
  • 输出是 outout

要实现的逻辑是这样的 ⬇️

8-way Or gate: 
out = in[0] Or in[1] Or ... Or in[7]

在第 33 步中,我们已经实现了 Or,所以现在可以直接使用 Or。一个容易想到的做法是对 in[0]in[0], in[1]in[1] 进行 Or 运算,得到 out1out1,然后再对 out1out1, in[2]in[2] 进行 Or 运算,得到 out2out2,等等,直到将 in[8]in[8] 中的所有位都用上。

对应的代码会长这个样子 ⬇️

Or(a= in[0], b= in[1], out= out1);
Or(a= in1, b= in[2], out= out2);
...

由于 Or(a= ?, b= ?, out= ?); 这样的代码一共要写 77 遍,用程序来生成它比较好。我写了以下的 java 代码来生成对应的 hdl 代码。请将以下代码保存为 Or8WayHdlCodeGenerator.java ⬇️

import java.util.StringJoiner;

public class Or8WayHdlCodeGenerator {
    public static void main(String[] args) {
        String template = """
                CHIP Or8Way {
                    IN in[8];
                    OUT out;
                
                    PARTS:
                %s
                }
                """;
        StringJoiner joiner = new StringJoiner(System.lineSeparator());
        joiner.add("    Or(a= in[0], b= in[1], out= out1);");
        for (int i = 2; i < 7; i++) {
            String line = String.format("    Or(a= out%s, b= in[%s], out= out%s);", i - 1, i, i);
            joiner.add(line);
        }
        joiner.add("    Or(a= out6, b= in[7], out= out);");
        System.out.printf(template, joiner);
    }
}

用如下命令可以编译 Or8WayHdlCodeGenerator.java 并运行其中的 main 方法。

javac Or8WayHdlCodeGenerator.java
java Or8WayHdlCodeGenerator

运行结果如下

CHIP Or8Way {
    IN in[8];
    OUT out;

    PARTS:
    Or(a= in[0], b= in[1], out= out1);
    Or(a= out1, b= in[2], out= out2);
    Or(a= out2, b= in[3], out= out3);
    Or(a= out3, b= in[4], out= out4);
    Or(a= out4, b= in[5], out= out5);
    Or(a= out5, b= in[6], out= out6);
    Or(a= out6, b= in[7], out= out);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

其他方式

由于 Or 运算满足结合律和交换律,所以下面的等式成立(详细的说明请参考 当一个二元运算符同时满足 交换律 和 结合律 时 一文,或者从“只有 in[8]in[8] 中的 88 个数全都是 11 时,它们进行 Or 运算的结果才会是 11”这个角度来论证也可以)

in0in1in2in3in4in5in6in7in_0\lor in_1\lor in_2\lor in_3\lor in_4\lor in_5\lor in_6\lor in_7
=((in0in1)(in2in3))((in4in5)(in6in7))=((in_0\lor in_1)\lor (in_2\lor in_3))\lor ((in_4\lor in_5)\lor (in_6\lor in_7))

这样的转化可以用如下的代码来表示 ⬇️

CHIP Or8Way {
    IN in[8];
    OUT out;

    PARTS:
    Or(a= in[0], b= in[1], out= in01);
    Or(a= in[2], b= in[3], out= in23);
    Or(a= in[4], b= in[5], out= in45);
    Or(a= in[6], b= in[7], out= in67);
    Or(a= in01, b= in23, out= in03);
    Or(a= in45, b= in67, out= in47);
    Or(a= in03, b= in47, out= out);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

12. 实现 Mux4Way16

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Mux4Way16,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Mux4Way16

  • 输入是
    • a[16]a[16]
    • b[16]b[16]
    • c[16]c[16]
    • d[16]d[16]
    • sel[2]sel[2]
  • 输出是 out[16]out[16]

要实现的逻辑是这样的 ⬇️

4-way 16-bit multiplexor:
out = a if sel = 00
      b if sel = 01
      c if sel = 10
      d if sel = 11

在第 1010 步中,我们已经实现了 Mux16,所以现在可以直接使用 Mux16。一种做法是

  1. sel[0]sel[0] 决定从 a,ba,b 中选择谁(注意 a,ba,b 都是 1616 位的),选择结果为 abOut\text{abOut}
  2. sel[0]sel[0] 决定从 c,dc,d 中选择谁(注意 a,ba,b 都是 1616 位的),选择结果为 cdOut\text{cdOut}
  3. sel[1]sel[1] 决定从 abOut,cdOut\text{abOut},\text{cdOut} 中选择谁(注意 abOut,cdOut\text{abOut},\text{cdOut} 都是 1616 位的),选择结果为 outout

具体的代码如下 ⬇️

CHIP Mux4Way16 {
    IN a[16], b[16], c[16], d[16], sel[2];
    OUT out[16];
    
    PARTS:
    Mux16(a= a, b= b, sel= sel[0], out= abOut);
    Mux16(a= c, b= d, sel= sel[0], out= cdOut);
    Mux16(a= abOut, b= cdOut, sel= sel[1], out= out);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

13. 实现 Mux8Way16

前往 Nand to Tetris Online IDE,选择 Project 1 里的 Mux8Way16,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 Mux8Way16

  • 输入是
    • a[16]a[16]
    • b[16]b[16]
    • c[16]c[16]
    • d[16]d[16]
    • e[16]e[16]
    • f[16]f[16]
    • g[16]g[16]
    • h[16]h[16]
    • sel[3]sel[3]
  • 输出是 out[16]out[16]

要实现的逻辑是这样的 ⬇️

8-way 16-bit multiplexor:
out = a if sel = 000
      b if sel = 001
      c if sel = 010
      d if sel = 011
      e if sel = 100
      f if sel = 101
      g if sel = 110
      h if sel = 111
  • 在第 1010 步,我们实现了 Mux16
  • 在第 1212 步,我们实现了 Mux4Way16

所以现在可以直接使用 Mux16Mux4Way16。一种做法是

  1. sel[0]sel[0]sel[1]sel[1] 共同决定从 a,b,c,da,b,c,d 中选择谁(注意 a,b,c,da,b,c,d 都是 1616 位的),选择结果为 choice1choice1
  2. sel[0]sel[0]sel[1]sel[1] 共同决定从 e,f,g,he,f,g,h 中选择谁(注意 e,f,g,he,f,g,h 都是 1616 位的),选择结果为 choice2choice2
  3. sel[2]sel[2] 决定从 choice1,choice2choice1,choice2 中选择谁(注意 choice1,choice2choice1,choice2 都是 1616 位的),选择结果为 outout

具体的代码如下 ⬇️

CHIP Mux8Way16 {
    IN a[16], b[16], c[16], d[16],
       e[16], f[16], g[16], h[16],
       sel[3];
    OUT out[16];

    PARTS:
    Mux4Way16(a= a, b= b, c= c, d= d, sel= sel[0..1], out= choice1);
    Mux4Way16(a= e, b= f, c= g, d= h, sel= sel[0..1], out= choice2);
    Mux16(a= choice1, b= choice2, sel= sel[2], out= out);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

14. 实现 DMux4Way

前往 Nand to Tetris Online IDE,选择 Project 1 里的 DMux4Way,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 DMux4Way

  • 输入是
    • inin
    • sel[2]sel[2]
  • 输出是
    • aa
    • bb
    • cc
    • dd

要实现的逻辑是这样的 ⬇️

4-way demultiplexor:
[a, b, c, d] = [in, 0, 0, 0] if sel = 00
               [0, in, 0, 0] if sel = 01
               [0, 0, in, 0] if sel = 10
               [0, 0, 0, in] if sel = 11

我们先看看 a,b,c,da,b,c,d 的真值表 ⬇️

sel[2]sel[2]
(注意:
sel[1]sel[1] 在前,
sel[0]sel[0] 在后)
ininaabbccdd
00000000000000
00001111000000
01010000000000
01011100110000
10100000000000
10101100001100
11110000000000
11111100000011

可以得出

  • a=in¬sel[0]¬sel[1]a=in\land \neg{sel[0]} \land \neg{sel[1]}
  • b=insel[0]¬sel[1]b=in\land sel[0] \land \neg{sel[1]}
  • c=in¬sel[0]sel[1]c=in\land \neg{sel[0]} \land sel[1]
  • d=insel[0]sel[1]d=in\land sel[0] \land sel[1]

aabb 可以写成如下形式

  • a=(in¬sel[1])¬sel[0]a=(in\land \neg{sel[1]})\land \neg{sel[0]}
  • b=(in¬sel[1])sel[0]b=(in\land \neg{sel[1]})\land sel[0]

x=in¬sel[1]x=in\land \neg{sel[1]}

  • a=x¬sel[0]a=x\land \neg{sel[0]}
  • b=xsel[0]b=x\land sel[0]

所以如果我们能通过某种方式得到 xx,那么在 xx 的基础上,用一个 DMux 就能得到 aabb 了。 类似地

  • c=(insel[1])¬sel[0]c=(in\land sel[1])\land \neg{sel[0]}
  • b=(insel[1])sel[0]b=(in\land sel[1])\land sel[0]

y=insel[1]y=in\land sel[1]

  • c=y¬sel[0]c=y\land \neg{sel[0]}
  • d=ysel[0]d=y\land sel[0]

如果我们能通过某种方式得到 yy,那么在 yy 的基础上,用一个 DMux 就能得到 ccdd 了。仔细看看 xxyy,会发现它们可以用一个 DMux 来实现,具体的代码如下 ⬇️

CHIP DMux4Way {
    IN in, sel[2];
    OUT a, b, c, d;

    PARTS:
    DMux(in= in, sel= sel[1], a= x, b= y);
    DMux(in= x, sel= sel[0], a= a, b= b);
    DMux(in= y, sel= sel[0], a= c, b= d);
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

15. 实现 DMux8Way

前往 Nand to Tetris Online IDE,选择 Project 1 里的 DMux8Way,效果如下图所示 ⬇️

image.png

我们的目标是用 Nand(以及前文提到的可以用 Nand 实现的各种门)来实现一个 DMux8Way

  • 输入是
    • inin
    • sel[3]sel[3]
  • 输出是
    • aa
    • bb
    • cc
    • dd
    • ee
    • ff
    • gg
    • hh

要实现的逻辑是这样的 ⬇️

8-way demultiplexor:
[a, b, c, d, e, f, g, h] = [in, 0,  0,  0,  0,  0,  0,  0] if sel = 000
                           [0, in,  0,  0,  0,  0,  0,  0] if sel = 001
                           [0,  0, in,  0,  0,  0,  0,  0] if sel = 010
                           [0,  0,  0, in,  0,  0,  0,  0] if sel = 011
                           [0,  0,  0,  0, in,  0,  0,  0] if sel = 100
                           [0,  0,  0,  0,  0, in,  0,  0] if sel = 101
                           [0,  0,  0,  0,  0,  0, in,  0] if sel = 110
                           [0,  0,  0,  0,  0,  0,  0, in] if sel = 111
  • 在第 66 步中,我们已经实现了 DMux
  • 在第 1414 步中,我们已经实现了 DMux4Way

所以我们可以考虑用 DMuxDMux4Way 来实现 DMux8Way

CHIP DMux8Way {
    IN in, sel[3];
    OUT a, b, c, d, e, f, g, h;

    PARTS:
    DMux(in= in, sel= sel[2], a= x, b= y);
    DMux4Way(in= x, sel= sel[0..1], a= a, b= b, c= c, d= d);
    DMux4Way(in= y, sel= sel[0..1], a= e, b= f, c= g, d= h);    
}

这样的代码可以通过仿真测试,效果如下图所示 ⬇️

image.png

完成了 Project 1 之后,我们可以去做 Project 2。我在 From Nand to Tetris 里的 Project 2 一文中记录了自己对 Project 2 的理解。

参考资料