Nand2tetris - 从布尔逻辑到算数运算

本文是 nand2tetris 课程的读书笔记,不会列举过多细节,仅精炼出有意思的概念,供日后查阅。


从布尔逻辑的视角来看,所谓加法器只不过是一种特殊的逻辑结构。但是,我们 人类 为其加上的新的 解释,即【定长二进制加法】,因此它变得 有用。名字与实际结构,共同构成了事物的完整概念,从此,这种电路被称为加法器,不再有其它意义。


加法器的级联,使其扩充至 n-bit。

....
HalfAdder(a=a[0], b=b[0], sum=out[0], carry=carry1);
FullAdder(a=a[1], b=b[1], c=carry1, sum=out[1], carry=carry2);
FullAdder(a=a[2], b=b[2], c=carry2, sum=out[2], carry=carry3);
FullAdder(a=a[3], b=b[3], c=carry3, sum=out[3], carry=carry4);
FullAdder(a=a[4], b=b[4], c=carry4, sum=out[4], carry=carry5);
FullAdder(a=a[5], b=b[5], c=carry5, sum=out[5], carry=carry6);
FullAdder(a=a[6], b=b[6], c=carry6, sum=out[6], carry=carry7);
....

二进制补码,是用来表示有符号整数的工具。
依然是【名字】的作用,同样的操作序列,一旦我们将其重新解释,它立刻从无符号加法,变为有符号加法。

但这里有个技术问题,为什么二进制补码能够按照这样的方式工作?

在上大学的时候,老师直接告诉我们二进制补码的计算方式,即按位取反加1,并用时钟的工作方式,说明它的正确性。

在时钟上,正着走 3 步,等于反着走 12 - 3 步。

屏幕快照 2019-01-09 上午12.35.24

我们是否有办法证明补码的正确性呢?具体而言:

a + b 的补码,是否等于 a 的补码 二进制加 b 的补码
我把无符号二进制加法写作 \(+_2\),并将 a 的补码定义为 \(comp2(a)\)
我们将命题改写为:
\(comp2(a+b)=comp2(a)+_2comp2(b)\)

证明:
将原始无符号二进制表示为 \(bin(a)\),且要求 \(a \ge 0\),它具有以下性质:

  1. \(bin(a)=bin(a + k * 2^n)\),其中 \(k \gt 0\),这个很显然,因为是定长二进制,所以高位都丢弃了。
  2. \(bin(x+y)=bin(x)+_2bin(y)\),这条代表二进制加法的正确性,如果不成立,我们就别用这个工具了。

此外,补码的定义为:

\(comp2(a)=\begin{equation}
\begin{cases}
bin(a)& 2^{n-1} \gt a \ge 0\\
bin(2^n + a)& -(2^{n-1}) \le a \lt 0
\end{cases}
\end{equation}\)

于是,可以改写命题为:
\(bin(k*2^n+a+b)=bin(a+m*2^n) +_2 bin(b+l*2^n)\),其中 k,m,l 都非负。

使用上面的性质1,可以进一步改写命题为:
\(bin((k+k+m+l)*2^n+a+b)=bin(a+(m+k)*2^n) +_2 bin(b+(l+k)*2^n)\)
\(bin(a+(m+k)*2^n+b+(l+k)*2^n)=bin(a+(m+k)*2^n) +_2 bin(b+(l+k)*2^n)\)

令 \(x=a+(m+k)*2^n,y=b+(l+k)*2^n\),可以得到:
\(bin(x+y)=bin(x) +_2 bin(y)\),即上面的性质2,故原命题成立。

对于补码的理解,有一些资料可以参考:

https://en.wikipedia.org/wiki/Modular_arithmetic
https://igoro.com/archive/why-computers-represent-signed-integers-using-twos-complement/
https://courses.engr.illinois.edu/ece199/fa2012/notes/twos-complement.pdf


ALU

将一系列算术和逻辑运算打包到一个芯片里,并用控制位进行操作选择,就是 ALU。

CHIP ALU {
    IN  
        x[16], y[16],  // 16-bit inputs        
        zx, // zero the x input?
        nx, // negate the x input?
        zy, // zero the y input?
        ny, // negate the y input?
        f,  // compute out = x + y (if 1) or x & y (if 0)
        no; // negate the out output?

    OUT 
        out[16], // 16-bit output
        zr, // 1 if (out == 0), 0 otherwise
        ng; // 1 if (out < 0),  0 otherwise

    PARTS:
    // x 的预处理
    Mux16(a=x, b=false, sel=zx, out=zeroedX);
    Not16(in=zeroedX, out=negateX);
    Mux16(a=zeroedX, b=negateX, sel=nx, out=finalX);
    // y 的预处理
    Mux16(a=y, b=false, sel=zy, out=zeroedY);
    Not16(in=zeroedY, out=negateY);
    Mux16(a=zeroedY, b=negateY, sel=ny, out=finalY);
    // 选择 and 或者 add
    And16(a=finalX, b=finalY, out=andXY);
    Add16(a=finalX, b=finalY, out=addXY);
    Mux16(a=andXY, b=addXY, sel=f, out=computed);
    Not16(in=computed, out=negatedComputed);
    Mux16(a=computed, b=negatedComputed, sel=no, out[15]=signBit, out[0..7]=lowBit, out[8..14]=hiBit);
    // 顺便输出状态位
    And(a=signBit, b=true, out=ng);
    And16(a[15]=signBit, a[8..14]=hiBit, a[0..7]=lowBit, b=true, out=out);
    Or8Way(in=lowBit, out=notZeroLow);
    Or8Way(in[0..6]=hiBit, in[7]=signBit, out=notZeroHi);
    Or(a=notZeroLow, b=notZeroHi, out=notZero);
    Not(in=notZero, out=zr);