目录
1. 项目概况
1.1 项目背景
上一章实现了基本逻辑门,本章在此基础上设计和构建具有完整功能的算术逻辑单元ALU,实现对数字的算数操作。ALU是整个计算机的核心单元,它执行所有的算数和逻辑操作,因此构建ALU模块是很重要的一步。
1.2 需求清单
使用上一章构建的芯片,依次实现以下芯片:
加法器 | 芯片名 | 功能 |
---|---|---|
半加器 | HalfAdder | 实现两个2位二进制数加法 |
全加器 | FullAdder | 实现三个3位二进制数加法 |
16位加法器 | Adder | 实现两个16位二进制数加法 |
16位增量器 | Inc16 | 输出16位二进制数+1 |
算术逻辑单元 | ALU | 实现两个16位二进制数置零、取反、相加(Adder)以及布尔运算 |
2. 芯片实现过程
2.1 HalfAdder
用于实现半加器HalfAdder的芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | HalfAdder |
输入 | a, b |
输出 | sum, carry |
功能 | sum = LSB of a + b, carry = MSB of a + b |
可以使用And和Xor实现:
carry
=
a
And
b
\text{carry}=a\text{ And }b
carry=a And b
sum
=
a
Xor
b
\text{sum}=a\text{ Xor }b
sum=a Xor b
用HDL实现HalfAdder(直接在已有文件中修改,位置为 .\nand2tetris\projects\02\HalfAdder.hdl):
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/02/HalfAdder.hdl
/**
* 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:
Xor (a = a, b = b, out = sum);
And (a = a, b = b, out = carry);
}
2.2 FullAdder
用于实现全加器FullAdder的芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | FullAdder |
输入 | a, b, c |
输出 | sum, carry |
功能 | sum = LSB of a + b + c, carry = MSB of a + b + c |
可以使用基本逻辑门实现:
carry
=
(
a
And
b
)
Or
(
b
And
c
)
Or
(
a
And
c
)
\text{carry}=(a\text{ And }b)\text{ Or }(b\text{ And }c)\text{ Or }(a\text{ And }c)
carry=(a And b) Or (b And c) Or (a And c)
sum
=
(
a
Xor
b
)
Xor
c
\text{sum}=(a\text{ Xor }b)\text{ Xor }c
sum=(a Xor b) Xor c
也可以使用半加器实现(这种方法更简单,参考文章):
carry
=
HalfAdder
(
a
,
b
)
[
c
a
r
r
y
]
Or HalfAdder
(
HalfAdder
(
a
,
b
)
,
c
)
[
c
a
r
r
y
]
\text{carry}=\text{HalfAdder}(a, b)[carry]\text{ Or }\text{HalfAdder}(\text{HalfAdder}(a, b), c)[carry]
carry=HalfAdder(a,b)[carry] Or HalfAdder(HalfAdder(a,b),c)[carry]
sum
=
HalfAdder
(
HalfAdder
(
a
,
b
)
,
c
)
[
s
u
m
]
\text{sum}=\text{HalfAdder}(\text{HalfAdder}(a, b), c)[sum]
sum=HalfAdder(HalfAdder(a,b),c)[sum]
用HDL实现FullAdder(直接在已有文件中修改,位置为 .\nand2tetris\projects\02\FullAdder.hdl):
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/02/FullAdder.hdl
/**
* 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:
// 使用基本逻辑门实现:
// And (a = a, b = b, out = aAndb);
// And (a = b, b = c, out = bAndc);
// And (a = a, b = c, out = aAndc);
// Or (a = aAndb, b = bAndc, out = abOrbc);
// Or (a = abOrbc, b = aAndc, out = carry);
// Xor (a = a, b = b, out = aXorb);
// Xor (a = aXorb, b = c, out = sum);
// 使用半加器实现:
HalfAdder (a = a, b = b, sum = tempsum, carry = tempcarry);
HalfAdder (a = tempsum, b = c, sum = sum, carry = tempcarry2);
Or (a = tempcarry, b = tempcarry2, out = carry);
}
2.3 Add16
用于实现16位加法器Add16的芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | Add16 |
输入 | a[16], b[16] |
输出 | out[16] |
功能 | out = a + b |
说明 | 2补码的整数加法,不处理溢出的情况 |
可以使用半加器和全加器实现:
out[0]
=
HalfAdder
(
a
[
0
]
,
b
[
0
]
)
[
s
u
m
]
\text{out[0]}=\text{HalfAdder}(a[0], b[0])[sum]
out[0]=HalfAdder(a[0],b[0])[sum]
out[1]
=
FullAdder
(
a
[
1
]
,
b
[
1
]
,
HalfAdder
(
a
[
0
]
,
b
[
0
]
)
[
c
a
r
r
y
]
)
[
s
u
m
]
\text{out[1]}=\text{FullAdder}(a[1], b[1], \text{HalfAdder}(a[0], b[0])[carry])[sum]
out[1]=FullAdder(a[1],b[1],HalfAdder(a[0],b[0])[carry])[sum]
…
out[15]
=
FullAdder
(
a
[
15
]
,
b
[
15
]
,
HalfAdder
(
a
[
14
]
,
b
[
14
]
)
[
c
a
r
r
y
]
)
[
s
u
m
]
\text{out[15]}=\text{FullAdder}(a[15], b[15], \text{HalfAdder}(a[14], b[14])[carry])[sum]
out[15]=FullAdder(a[15],b[15],HalfAdder(a[14],b[14])[carry])[sum]
用HDL实现Add16(直接在已有文件中修改,位置为 .\nand2tetris\projects\02\Add16.hdl):
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/02/Adder16.hdl
/**
* 16-bit adder: Adds two 16-bit two's complement values.
* The most significant carry bit is ignored.
*/
CHIP Add16 {
IN a[16], b[16];
OUT out[16];
PARTS:
// 使用半加器实现:
HalfAdder (a = a[0], b = b[0], sum = out[0], carry = carry0);
FullAdder (a = a[1], b = b[1], c = carry0, sum = out[1], carry = carry1);
FullAdder (a = a[2], b = b[2], c = carry1, sum = out[2], carry = carry2);
FullAdder (a = a[3], b = b[3], c = carry2, sum = out[3], carry = carry3);
FullAdder (a = a[4], b = b[4], c = carry3, sum = out[4], carry = carry4);
FullAdder (a = a[5], b = b[5], c = carry4, sum = out[5], carry = carry5);
FullAdder (a = a[6], b = b[6], c = carry5, sum = out[6], carry = carry6);
FullAdder (a = a[7], b = b[7], c = carry6, sum = out[7], carry = carry7);
FullAdder (a = a[8], b = b[8], c = carry7, sum = out[8], carry = carry8);
FullAdder (a = a[9], b = b[9], c = carry8, sum = out[9], carry = carry9);
FullAdder (a = a[10], b = b[10], c = carry9, sum = out[10], carry = carry10);
FullAdder (a = a[11], b = b[11], c = carry10, sum = out[11], carry = carry11);
FullAdder (a = a[12], b = b[12], c = carry11, sum = out[12], carry = carry12);
FullAdder (a = a[13], b = b[13], c = carry12, sum = out[13], carry = carry13);
FullAdder (a = a[14], b = b[14], c = carry13, sum = out[14], carry = carry14);
FullAdder (a = a[15], b = b[15], c = carry14, sum = out[15], carry = carry);
}
2.4 Inc16
用于实现16位增量器Inc16的芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | Inc16 |
输入 | in[16] |
输出 | out[16] |
功能 | out = in + 1 |
说明 | 2补码的整数加法,不处理溢出的情况 |
可以使用16位加法器实现:
out
=
Add16
(
i
n
,
0000000000000001
)
\text{out}=\text{Add16}(in,0000000000000001)
out=Add16(in,0000000000000001)
用HDL实现Inc16(直接在已有文件中修改,位置为 .\nand2tetris\projects\02\Inc16.hdl):
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/02/Inc16.hdl
/**
* 16-bit incrementer:
* out = in + 1
*/
CHIP Inc16 {
IN in[16];
OUT out[16];
PARTS:
// 使用16位加法器实现
Add16 (a = in, b[0] = true, b[1..15] = false, out = out);
// Add16 (a = in, b[0] = 1, b[1..15] = 0, out = out); // 错误,只能用true/false表示,不能用1/0
}
2.5 ALU
用于实现算术逻辑单元ALU的芯片描述如下:
信息项 | 描述 |
---|---|
芯片名 | ALU |
输入 | x[16], y[16], //两个16位数据输入 |
… | zx, // x输入置零 |
… | nx, // x输入取反 |
… | zy, // y输入置零 |
… | ny, // y输入取反 |
… | f, // 功能码:为1则代表Add,为0则代表And |
… | no // out输出取反 |
输出 | out[16], // 16位输出 |
… | zr, //若out = 0则为True,否则False |
… | ng, //若out < 0则为True,否则False |
功能 | if zx then x = 0 // 16位常量0 |
… | if nx then x = !x // 按位取反 |
… | if zy then y = 0 // 16位常量0 |
… | if ny then y = !y // 按位取反 |
… | if f then out = x + y // 2补码的整数加法 |
… | else out = x & y // 按位与运算(And) |
… | if no then out = !out // 按位取反 |
… | if out = 0 then zr = 1 else zr = 0 // 16位eq.比较 |
… | if out < 0 then ng = 1 else ng = 0 // 16位neg.比较 |
说明 | 不处理溢出的情况 |
用HDL实现ALU(直接在已有文件中修改,位置为 .\nand2tetris\projects\02\ALU.hdl):
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/02/ALU.hdl
/**
* ALU (Arithmetic Logic Unit):
* Computes out = one of the following functions:
* 0, 1, -1,
* x, y, !x, !y, -x, -y,
* x + 1, y + 1, x - 1, y - 1,
* x + y, x - y, y - x,
* x & y, x | y
* on the 16-bit inputs x, y,
* according to the input bits zx, nx, zy, ny, f, no.
* In addition, computes the output bits:
* zr = (out == 0, 1, 0)
* ng = (out < 0, 1, 0)
*/
// Implementation: Manipulates the x and y inputs
// and operates on the resulting values, as follows:
// if (zx == 1) sets x = 0 // 16-bit constant
// if (nx == 1) sets x = !x // bitwise not
// if (zy == 1) sets y = 0 // 16-bit constant
// if (ny == 1) sets y = !y // bitwise not
// if (f == 1) sets out = x + y // integer 2's complement addition
// if (f == 0) sets out = x & y // bitwise and
// if (no == 1) sets out = !out // bitwise not
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) or (out = x & y)?
no; // negate the out output?
OUT
out[16], // 16-bit output
zr, // (out == 0, 1, 0)
ng; // (out < 0, 1, 0)
PARTS:
// if (zx == 1) sets x = 0
Mux16 (a = x, b = false, sel = zx, out = outzx);
// if (nx == 1) sets x = !x
Not16 (in = outzx, out = notx);
Mux16 (a = outzx, b = notx, sel = nx, out = outnx);
// if (zy == 1) sets y = 0
Mux16 (a = y, b = false, sel = zy, out = outzy);
// if (ny == 1) sets y = !y
Not16 (in = outzy, out = noty);
Mux16 (a = outzy, b = noty, sel = ny, out = outny);
// if (f == 1) sets out = x + y
Add16 (a = outnx, b = outny, out = xAddy);
And16 (a = outnx, b = outny, out = xAndy);
Mux16 (a = xAndy, b = xAddy, sel = f, out = outf);
// if (no == 1) sets out = !out
//注意: 16位的out不能通过out[0..7]形式来获取片段,只能事先保存在单独的变量中,才能调用
Not16 (in = outf, out = notout);
Mux16 (a = outf, b = notout, sel = no, out = out, out = out0);
And16 (a = out0, b = true, out[0..7] = outPart1, out[8..15] = outPart2, out[15] = flag);
// if (out = 0) sets zr = True
Or8Way (in = outPart1, out = sel01);
Or8Way (in = outPart2, out = sel02);
Or (a = sel01, b = sel02, out = sel0);
Mux (a = true, b = false, sel = sel0, out = zr);
// if (out < 0) sets ng = True
Mux (a = false, b = true, sel = flag, out = ng);
}
3. 总结
3.1 遇到的问题和解决办法
(1)构建全加器芯片时,采用的方法不够简洁。
一开始想使用半加器实现,觉得这样应该会比较快,但苦于没有想出具体的实现形式,所以转而用基本逻辑门实现了,写出了自己认为有些笨重的代码。
后来找到别人写过的代码,发现果然用半加器才是最快的!只是自己并没有想到,可以通过比较两次半加运算的输出结果carry位,来巧妙地确定最终输出结果的carry位。
(2)编写完ALU的HDL文件,总是读取失败。
根据Project1的经验,文件载入失败是因为代码词法/语法错误,因此打开文件仔细检查,没有发现因为大意写错的地方。
通过网络搜索找到其他人写的代码,经比较发现,我的代码在获取数组片段时,直接使用了out[0…7]形式,而这种方式在此hdl语言中不允许。参考文章1、文章2
因此,借助And门,将数组片段事先保存在单独的变量中,之后调用,终于成功。
3.2 心得体会
ALU对小白来说有些难度,但是自己多看几遍书,尝试一下,也能写对大部分代码,不必过于担心。