From Nand to Tetris 里的 Project 2

背景

让我们按照 From Nand to TetrisProject 2 的要求,来完成下列的设计。

  1. 实现半加器(HalfAdder)
  2. 实现全加器(FullAdder)
  3. 实现 16 16 16 位加法器(Add16)
  4. 实现 16 16 16 位自增器(Inc16)
  5. 实现算术逻辑单元(ALU) (todo)

正文

1. 实现 HalfAdder

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

我们的目标是用 Project 1 里已经实现的各种 chip 实现一个 HalfAdder

  • 输入是
    • a a a
    • b b b
  • 输出是
    • s u m sum sum
    • c a r r y carry carry

我们可以从它的真值表入手 ⬇️

a a a b b b s u m sum sum c a r r y carry carry
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 1 1 0 0 0
1 1 1 0 0 0 1 1 1 0 0 0
1 1 1 1 1 1 0 0 0 1 1 1

可见

  • s u m = ( ¬ a ∧ b ) ∨ ( a ∧ ¬ b ) = a ⊕ b sum=(\neg{a}\land b) \lor (a\land \neg{b})=a\oplus b sum=(¬a∧b)∨(a∧¬b)=a⊕b
  • c a r r y = a ∧ b carry=a\land b carry=a∧b

所以可以这样实现 ⬇️

hdl 复制代码
CHIP HalfAdder {
    IN a, b;    // 1-bit inputs
    OUT sum,    // Right bit of a + b 
        carry;  // Left bit of a + b

    PARTS:
    And(a= a, b= b, out= carry);
    Xor(a = a, b = b, out = sum);
}

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

2. 实现 FullAdder

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

我们的目标是用 Project 1 里已经实现的各种 chip,以及刚才实现的 HalfAdder 来实现一个 FullAdder

  • 输入是
    • a a a
    • b b b
    • c c c
  • 输出是
    • s u m sum sum
    • c a r r y carry carry

我们还是从真值表入手 ⬇️

a a a b b b c c c s u m sum sum c a r r y carry carry
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1 0 0 0
0 0 0 1 1 1 0 0 0 1 1 1 0 0 0
0 0 0 1 1 1 1 1 1 0 0 0 1 1 1
1 1 1 0 0 0 0 0 0 1 1 1 0 0 0
1 1 1 0 0 0 1 1 1 0 0 0 1 1 1
1 1 1 1 1 1 0 0 0 0 0 0 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

通过观察真值表,可以将 s u m sum sum 和 c a r r y carry carry 写成对应的析取范式 DNF(Disjunctive Normal Form) 的形式

  • s u m = ( ¬ a ∧ ¬ b ∧ c ) ∨ ( ¬ a ∧ b ∧ ¬ c ) ∨ ( a ∧ ¬ b ∧ ¬ c ) ∨ ( a ∧ b ∧ c ) sum=(\neg{a}\land \neg{b}\land c)\lor (\neg{a}\land b\land \neg{c})\lor(a\land\neg{b}\land \neg{c})\lor(a\land b\land c) sum=(¬a∧¬b∧c)∨(¬a∧b∧¬c)∨(a∧¬b∧¬c)∨(a∧b∧c)
  • c a r r y = ( ¬ a ∧ b ∧ c ) ∨ ( a ∧ ¬ b ∧ c ) ∨ ( a ∧ b ∧ ¬ c ) ∨ ( a ∧ b ∧ c ) carry=(\neg{a}\land b\land c)\lor(a\land \neg{b}\land c)\lor(a\land b\land \neg{c})\lor(a\land b\land c) carry=(¬a∧b∧c)∨(a∧¬b∧c)∨(a∧b∧¬c)∨(a∧b∧c)

从上面的析取范式出发,我们可以只用 And/Or/Not 来实现 FullAdder。但有没有更简洁的实现方式呢? 我们回忆一下 HalfAdder 中的 s u m sum sum 和 c a r r y carry carry 是怎样的 ⬇️ (为了和 FullAdder s u m sum sum 和 c a r r y carry carry 进行区分,我把 HalfAdder s u m sum sum 和 c a r r y carry carry 分别改写为的 s u m h a sum_{ha} sumha 和 c a r r y h a carry_{ha} carryha)

  • s u m h a = ( ¬ a ∧ b ) ∨ ( a ∧ ¬ b ) = a ⊕ b sum_{ha}=(\neg{a}\land b) \lor (a\land \neg{b})=a\oplus b sumha=(¬a∧b)∨(a∧¬b)=a⊕b
  • c a r r y h a = a ∧ b carry_{ha}=a\land b carryha=a∧b

为了便于区分,我把 FullAdder s u m sum sum 和 c a r r y carry carry 分别改写为 s u m f a sum_{fa} sumfa 和 c a r r y f a carry_{fa} carryfa。 先看 s u m f a sum_{fa} sumfa 和 s u m h a sum_{ha} sumha 是否有关联。

  • s u m f a = ( ¬ a ∧ ¬ b ∧ c ) ∨ ( ¬ a ∧ b ∧ ¬ c ) ∨ ( a ∧ ¬ b ∧ ¬ c ) ∨ ( a ∧ b ∧ c ) sum_{fa}=(\neg{a}\land \neg{b}\land c)\lor (\neg{a}\land b\land \neg{c})\lor(a\land\neg{b}\land \neg{c})\lor(a\land b\land c) sumfa=(¬a∧¬b∧c)∨(¬a∧b∧¬c)∨(a∧¬b∧¬c)∨(a∧b∧c)
  • s u m h a = a ⊕ b sum_{ha}=a\oplus b sumha=a⊕b

如何表示 s u m sum sum

一个思路是看看能否将 s u m f a sum_{fa} sumfa 用 s u m h a sum_{ha} sumha 表示出来,我试了试,觉得有些繁琐,而且不容易想到。另一个思路是,我们从 s u m f a sum_{fa} sumfa 和 s u m h a sum_{ha} sumha 背后的含义入手。 s u m h a sum_{ha} sumha 表示 a , b a,b a,b 的和(忽略进位),所以 s u m h a = a ⊕ b sum_{ha}=a\oplus b sumha=a⊕b。

  • s u m h a = 0 sum_{ha}=0 sumha=0 时,只有 c = 1 c=1 c=1, s u m f a = 1 sum_{fa}=1 sumfa=1 才会成立
  • s u m h a = 1 sum_{ha}=1 sumha=1 时,只有 c = 0 c=0 c=0, s u m f a = 1 sum_{fa}=1 sumfa=1 才会成立

所以 s u m f a = s u m h a ⊕ c sum_{fa}=sum_{ha}\oplus c sumfa=sumha⊕c

HalfAdder 的作用就是对输入 a , b a,b a,b 分别执行 ⊕ \oplus ⊕ 和 ∧ \land ∧ 运算,所以 s u m f a sum_{fa} sumfa 可以通过拼接两个 HalfAdder 来实现 ⬇️

  • 第一个 HalfAdder a , b a,b a,b 为输入,其 s u m h a 1 = a ⊕ b sum_{ha1}=a\oplus b sumha1=a⊕b
  • 第二个 HalfAdder s u m f a 1 sum_{fa1} sumfa1 和 c c c 为输入,其 s u m h a 2 = s u m f a 1 ⊕ c = sum_{ha2}=sum_{fa1}\oplus c= sumha2=sumfa1⊕c=

如何表示 c a r r y carry carry

c a r r y f a = ( ¬ a ∧ b ∧ c ) ∨ ( a ∧ ¬ b ∧ c ) ∨ ( a ∧ b ∧ ¬ c ) ∨ ( a ∧ b ∧ c ) carry_{fa}=(\neg{a}\land b\land c)\lor(a\land \neg{b}\land c)\lor(a\land b\land \neg{c})\lor(a\land b\land c) carryfa=(¬a∧b∧c)∨(a∧¬b∧c)∨(a∧b∧¬c)∨(a∧b∧c)
= ( ¬ a ∧ b ∧ c ) ∨ ( a ∧ ¬ b ∧ c ) ∨ ( ( a ∧ b ∧ ¬ c ) ∨ ( a ∧ b ∧ c ) ) =(\neg{a}\land b\land c)\lor(a\land \neg{b}\land c)\lor((a\land b\land \neg{c})\lor(a\land b\land c)) =(¬a∧b∧c)∨(a∧¬b∧c)∨((a∧b∧¬c)∨(a∧b∧c))
= ( ¬ a ∧ b ∧ c ) ∨ ( a ∧ ¬ b ∧ c ) ∨ ( a ∧ b ) =(\neg{a}\land b\land c)\lor(a\land \neg{b}\land c)\lor(a\land b) =(¬a∧b∧c)∨(a∧¬b∧c)∨(a∧b)
= ( ¬ a ∧ b ∧ c ) ∨ ( a ∧ ¬ b ∧ c ) ∨ c a r r y h a =(\neg{a}\land b\land c)\lor(a\land \neg{b}\land c)\lor carry_{ha} =(¬a∧b∧c)∨(a∧¬b∧c)∨carryha
= ( ( a ⊕ b ) ∧ c ) ∨ c a r r y h a =((a\oplus b)\land c)\lor carry_{ha} =((a⊕b)∧c)∨carryha
= ( s u m h a ∧ c ) ∨ c a r r y h a =(sum_{ha}\land c)\lor carry_{ha} =(sumha∧c)∨carryha

考虑到 HalfAdder 的作用就是对输入 a , b a,b a,b 分别执行 ⊕ \oplus ⊕ 和 ∧ \land ∧ 运算,所以 c a r r y f a carry_{fa} carryfa 可以通过拼接两个 HalfAdder 再加上一个 Or 运算 来实现 ⬇️

  • 第一个 HalfAdder a , b a,b a,b 为输入,它的
    • s u m f a 1 = a ⊕ b sum_{fa1}=a\oplus b sumfa1=a⊕b
    • c a r r y h a 1 = a ∧ b carry_{ha1}=a\land b carryha1=a∧b
  • 第二个 HalfAdder s u m f a 1 sum_{fa1} sumfa1 和 c c c 为输入,它的
    • s u m h a 2 = s u m f a 1 ⊕ c sum_{ha2}=sum_{fa1}\oplus c sumha2=sumfa1⊕c
    • c a r r y h a 2 = s u m f a 1 ∧ c carry_{ha2}=sum_{fa1}\land c carryha2=sumfa1∧c

那么
c a r r y f a = ( s u m h a 1 ∧ c ) ∨ c a r r y h a 1 = c a r r y h a 2 ∨ c a r r y h a 1 carry_{fa}=(sum_{ha1}\land c)\lor carry_{ha1}=carry_{ha2}\lor carry_{ha1} carryfa=(sumha1∧c)∨carryha1=carryha2∨carryha1

至此我们可以用 HalfAdderOr 来实现 FullAdder。具体的代码如下 ⬇️

hdl 复制代码
CHIP FullAdder {
    IN a, b, c;  // 1-bit inputs
    OUT sum,     // Right bit of a + b + c
        carry;   // Left bit of a + b + c

    PARTS:
    HalfAdder(a= a, b= b, sum= sumHa, carry= carryHa);
    HalfAdder(a= sumHa, b= c, sum= sum, carry= temp);
    Or(a= carryHa, b= temp, out= carry);
}

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

3. 实现 16 16 16 位加法器(Add16)

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

我们的目标是用 Project 1 里已经实现的各种 chip,以及刚才实现的 HalfAdder/FullAdder 来实现一个 Add16

  • 输入是
    • a 16 a16 a16
    • b 16 b16 b16
  • 输出是
    • o u t 16 out16 out16

要实现的逻辑如下 ⬇️

text 复制代码
16-bit adder: Adds two 16-bit two's complement values.
The most significant carry bit is ignored.

我写了如下的 java 程序来生成对应的 HDL 代码

java 复制代码
import java.util.StringJoiner;

public class Adder16HdlCodeGenerator {
    public static void main(String[] args) {
        String template = """
                CHIP Add16 {
                    IN a[16], b[16];
                    OUT out[16];
                
                    PARTS:
                %s
                }
                """;
        StringJoiner joiner = new StringJoiner(System.lineSeparator());
        joiner.add("    FullAdder(a= a[0], b= b[0], c= false, sum= out[0], carry= c0);");
        for (int i = 1; i < 16; i++) {
            String line = String.format("    FullAdder(a= a[%s], b= b[%s], c= c%s, sum= out[%s], carry= c%s);", i, i, i - 1, i, i);
            joiner.add(line);
        }
        System.out.printf(template, joiner);
    }
}

请将以上 java 代码保存为 Adder16HdlCodeGenerator.java。用以下命令可以编译 Adder16HdlCodeGenerator.java 并运行其中的 main 方法。

bash 复制代码
javac Adder16HdlCodeGenerator.java
java Adder16HdlCodeGenerator

运行结果如下

hdl 复制代码
CHIP Add16 {
    IN a[16], b[16];
    OUT out[16];

    PARTS:
    FullAdder(a= a[0], b= b[0], c= false, sum= out[0], carry= c0);
    FullAdder(a= a[1], b= b[1], c= c0, sum= out[1], carry= c1);
    FullAdder(a= a[2], b= b[2], c= c1, sum= out[2], carry= c2);
    FullAdder(a= a[3], b= b[3], c= c2, sum= out[3], carry= c3);
    FullAdder(a= a[4], b= b[4], c= c3, sum= out[4], carry= c4);
    FullAdder(a= a[5], b= b[5], c= c4, sum= out[5], carry= c5);
    FullAdder(a= a[6], b= b[6], c= c5, sum= out[6], carry= c6);
    FullAdder(a= a[7], b= b[7], c= c6, sum= out[7], carry= c7);
    FullAdder(a= a[8], b= b[8], c= c7, sum= out[8], carry= c8);
    FullAdder(a= a[9], b= b[9], c= c8, sum= out[9], carry= c9);
    FullAdder(a= a[10], b= b[10], c= c9, sum= out[10], carry= c10);
    FullAdder(a= a[11], b= b[11], c= c10, sum= out[11], carry= c11);
    FullAdder(a= a[12], b= b[12], c= c11, sum= out[12], carry= c12);
    FullAdder(a= a[13], b= b[13], c= c12, sum= out[13], carry= c13);
    FullAdder(a= a[14], b= b[14], c= c13, sum= out[14], carry= c14);
    FullAdder(a= a[15], b= b[15], c= c14, sum= out[15], carry= c15);
}

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

4. 实现 16 16 16 位自增器(Inc16)

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

我们的目标是用 Project 1 里已经实现的各种 chip,以及刚才实现的 HalfAdder/FullAdder/Add16 来实现一个 Inc16

  • 输入是
    • i n 16 in16 in16
  • 输出是
    • o u t 16 out16 out16

要实现的逻辑如下 ⬇️

text 复制代码
16-bit incrementer:
out = in + 1

一个直观的做法是使用上一步实现的 Add16,具体的代码如下 ⬇️

hdl 复制代码
CHIP Inc16 {
    IN in[16];
    OUT out[16];

    PARTS:
    Add16(a = in, b[0]= true, b[1..15] = false, out = out);
}

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

5. 实现算术逻辑单元(ALU)

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

参考资料

相关推荐
广州智造1 天前
如何在HyperMesh的两片相邻体单元间批量创建RBE3实现载荷传递
人工智能·设计·建模·网格·网格划分·hypermesh·前处理
_code_bear_4 天前
如何设计 Agent 场景下的 Prompt
程序员·开源·设计
湖南精循科技5 天前
Ansys 案例研究 | 刹车片应力变形仿真
设计·仿真·ansys·机械·cae·大变形
bryant_meng6 天前
【Design Patterns】23 Design Patterns: The Ultimate Developer‘s Toolkit
设计模式·编程·计算机科学·设计·工程
用户5812441541579 天前
产品经理用AI画原型,代码怎么交付?GemDesign MCP vs Claude Design Handoff 技术对比
设计
等一场雾13 天前
升级一时爽,修 Bug 火葬场:2026 年主流框架升级兼容问题血泪全记录
设计
Yeats_Liao14 天前
5:Servlet程序-Java Web
java·后端·设计
Yunzenn15 天前
深度分析字节最新研究cola-DLM 第 08 章:工程实现评析 —— 优秀实践与改进空间
算法·架构·设计
Ailrid23 天前
设计模式——创建型设计模式:阅读笔记与个人思考
架构·设计
星栈1 个月前
事件写进去了但查不到?CQRS 投影层的坑我都替你踩了
开源·设计