第二周
二进制加法
两个二进制数能够从右至左一位一位相加,跟十进制加法一样。首先,把两个数的最右边的一位(也称为LSB,Least Significant Bits)加起来。然后,将两个位相加后的进位(0或者1)再与两个数的右起第二位相加。就按照这种方式计算下去,知道两个数最左边的一位(MSB,Most Significant Bits)为止。如果最后的两位数相加后产生了进位1,那么就说它产生了溢出;否则就说加法运算成功执行;
可以看到用于两个n-位数字二进制加法的计算机硬件可以由三位(两个计算位加上一个进位)加法的逻辑门构建而成。将进位转到下两个位上的加法是很容易实现的,只需要通过三位加法器门电路的正确引线即可实现。
有符号二进制数
n位二进制系统可以产生2的n次方个不同的组合。如果必须用二进制码表示有符号数,有个简单的方法就是,将这个空间分成两个相同的子集,一个子集用来表示正数,另一个表示负数。
如今几乎所有计算机都采用称为2-补码(2·s complement)的编码方式,也称为基补码(radix complement)。在n-位的二进制系统中,数x的2-补码定义如下:
举例来说,在一个5-位二进制系统中,-2的补码表示为2的五次方-00010=11110,也就是说只要00010+11110=00000即可。因为我们是在5-位二进制系统中,所以最左边的第6位被忽略掉了。通常,当补码表示法应用在n-位数字时,x+(-x)总是加到2的n次方(即1后面跟n个0)。下图显示了用补码表示的4-位二进制系统。
- 系统能对所有2的n次方个有符号数进行编码,最大的数和最小的数分别为2的n-1次方-1和-2的n-1次方。
- 所有正整数的编码的首位是0。
- 所有负整数的编码的首位是1。
- 为了通过x的编码获得-x的编码,所有最右边的0和左起的第一个1保持不变,然后将剩余的位取反。等价的捷径就是,对x的所有的位取反,然后再加上1,这个方案更容易在硬件中实现。
这种表示法有个特征:任何两个用补码表示的有符号数的加法和与整数的加法完全相同。例如-2+(-3),使用补码(用一个4-位表示),则要表示成1110+1101。我们不用关心这些代码表示的数字(正数还是负数),这个加法会得到结果1011(丢掉溢出位后)。结果正好是-5的补码表示法。
加法器
我们介绍三个加法器,并由此引出多位加法器芯片:
- 半加器(Half-adder):用来进行两位加法。
- 全加器(Full-adder):用来进行三位加法。
- 加法器(Adder):用来进行两个n-位加法。
Half-adder(半加器)
半加器 进行二进制数加法的第一步就是要能够对两个二进制位进行相加。我们把结果的LSB位称为sum,MSB位称为carry。
/**
* Computes the sum of two bits.
*/
CHIP HalfAdder {
IN a, b; // 1-bit inputs
OUT sum, // Right bit of a + b
carry; // Left bit of a + b
PARTS:
// Put you code here:
And(a=a,b=b,out=carry);
Xor(a=a,b=b,out=sum);
}
Full-adder(全加器)
现在已经知道了如何对两个位进行相加,下图显示了全加器,用来对三个位相加。跟半加器一样,全加器电路也会产生两个输出:加法的LSB位和进位。
/**
* Computes the sum of three bits.
*/
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:
// Put you code here:
HalfAdder(a=a,b=b,sum=s1,carry=c1);
HalfAdder(a=c,b=s1,sum=sum,carry=c2);
Or(a=c1,b=c2,out=carry);
}
Adder(加法器)
存储器和寄存器电路用n-位的形式来表示整数,n可以是16、32、64等等——这依赖于所在的计算机平台。进行n-位加法的芯片称为多位加法器(multi-bit adder),或者简称为加法器。下图展示了一个16-位加法器,对于任何n-位加法器,相同的逻辑和表示按比例增加。
/**
* Adds two 16-bit values.
* The most significant carry bit is ignored.
*/
CHIP Add16 {
IN a[16], b[16];
OUT out[16];
PARTS:
// Put you code here:
HalfAdder(a=a[0],b=b[0],sum=out[0],carry=c1);
FullAdder(a=a[1],b=b[1],c=c1,sum=out[1],carry=c2);
FullAdder(a=a[2],b=b[2],c=c2,sum=out[2],carry=c3);
FullAdder(a=a[3],b=b[3],c=c3,sum=out[3],carry=c4);
FullAdder(a=a[4],b=b[4],c=c4,sum=out[4],carry=c5);
FullAdder(a=a[5],b=b[5],c=c5,sum=out[5],carry=c6);
FullAdder(a=a[6],b=b[6],c=c6,sum=out[6],carry=c7);
FullAdder(a=a[7],b=b[7],c=c7,sum=out[7],carry=c8);
FullAdder(a=a[8],b=b[8],c=c8,sum=out[8],carry=c9);
FullAdder(a=a[9],b=b[9],c=c9,sum=out[9],carry=c10);
FullAdder(a=a[10],b=b[10],c=c10,sum=out[10],carry=c11);
FullAdder(a=a[11],b=b[11],c=c11,sum=out[11],carry=c12);
FullAdder(a=a[12],b=b[12],c=c12,sum=out[12],carry=c13);
FullAdder(a=a[13],b=b[13],c=c13,sum=out[13],carry=c14);
FullAdder(a=a[14],b=b[14],c=c14,sum=out[14],carry=c15);
FullAdder(a=a[15],b=b[15],c=c15,sum=out[15],carry=c16);
}
Inc16(增量器)
增量器 专门构建一个“对指定数字加1”的电路,这样做会带来很多便利。这里给出了一个16-位增量器的描述。
/**
* 16-bit incrementer:
* out = in + 1 (arithmetic addition)
*/
CHIP Inc16 {
IN in[16];
OUT out[16];
PARTS:
// Put you code here:
Add16(a=in,b[0]=true,out=out);
}
ALU(算数逻辑单元)
Hack的ALU计算一组固定的函数out=f(x,y),这里x和y是芯片的两个16-位输入,out是芯片的16-位输出,f是位于一个函数表中的函数,该寒暑表由18个固定函数组成。我们通过设置六个称为控制位(control bits)的输入位来告诉ALU用哪一个函数来进行何种函数计算。下图给出了用伪代码表示的详细的输入/输出规范。
要注意的是,这六个控制位的每一位指示ALU来执行某个基本操作。这些操作的各种组合可以让ALU计算多种有用的函数。因为全部操作都是由六个控制位引起的,那么ALU可以对2的六次方=64个不同的函数进行操作。
/**
* The ALU (Arithmetic Logic Unit).
* Computes one of the following functions:
* x+y, x-y, y-x, 0, 1, -1, x, y, -x, -y, !x, !y,
* x+1, y+1, x-1, y-1, x&y, x|y on two 16-bit inputs,
* according to 6 input bits denoted zx,nx,zy,ny,f,no.
* In addition, the ALU computes two 1-bit outputs:
* if the ALU output == 0, zr is set to 1; otherwise zr is set to 0;
* if the ALU output < 0, ng is set to 1; otherwise ng is set to 0.
*/
// Implementation: the ALU logic manipulates the x and y inputs
// and operates on the resulting values, as follows:
// if (zx == 1) set x = 0 // 16-bit constant
// if (nx == 1) set x = !x // bitwise not
// if (zy == 1) set y = 0 // 16-bit constant
// if (ny == 1) set y = !y // bitwise not
// if (f == 1) set out = x + y // integer 2's complement addition
// if (f == 0) set out = x & y // bitwise and
// if (no == 1) set out = !out // bitwise not
// if (out == 0) set zr = 1
// if (out < 0) set ng = 1
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:
// Put you code here:
Not16(in=x,out=notx);
And16(a=x,b=notx,out=allzerox);
Or16(a=x,b=notx,out=allonex);
Mux4Way16(a=x,b=allzerox,c=notx,d=allonex,sel[0]=zx,sel[1]=nx,out=outx);
Not16(in=y,out=noty);
And16(a=y,b=noty,out=allzeroy);
Or16(a=y,b=noty,out=alloney);
Mux4Way16(a=y,b=allzeroy,c=noty,d=alloney,sel[0]=zy,sel[1]=ny,out=outy);
And16(a=outx,b=outy,out=xandy);
Add16(a=outx,b=outy,out=xaddy);
Mux16(a=xandy,b=xaddy,sel=f,out=out1);
Not16(in=out1,out=notout1);
Mux16(a=out1,b=notout1,sel=no,out=out2);
And16(a=out2,b[0..15]=true,out[0..7]=headout,out[8..15]=tailout);
Or8Way(in=headout,out=headout1);
Or8Way(in=tailout,out=tailout1);
Not(in=headout1,out=notheadout1);
Not(in=tailout1,out=nottailout1);
And(a=notheadout1,b=nottailout1,out=zr);
//Or(a=headout1,b=tailout1,out=outzr);
//Not(in=outzr,out=zr);
And16(a=out2,b[0..15]=true,out[0..14]=out14,out[15]=out15);
And(a=out15,b=true,out=ng);
And16(a=out2,b=true,out=out);
}