Same hardware can run many different programs(Software)
文中提到的所有实现都可以参考:nand2tetris_sol,但是最好还是自己学习课程实现一遍,理解更深刻。
我们在之前的文章里,构建了 Register、RAM 和 ALU,使得我们有了存储和计算的能力,我们接着借助之前的组合逻辑单元和时序逻辑单元把 Register、RAM 和 ALU 就可以构建一个电脑架构(Hack),如下图:
这篇文章我们先介绍运行在这个架构上的汇编语言,用来编写在这个电脑架构上可以运行的程序,后面的文章会介绍这个电脑架构如何实现。我们了解汇编语言之后,也会实现自己的汇编器,把汇编语言编译成机器语言,如下图。
所以我们可以通过汇编语言的指令集来操作 Program、data 和 CPU 里的寄存器(具体是 A 和 D 两种寄存器),参考下图。
接下来我们来看看 Hack 的汇编语言里的指令集 A 指令和 C 指令,以及控制流、变量和标签。
A 指令
A 指令很简单,当我们写 @19 的时候就代表 Address register 被设置成 19(CPU 会帮我们干这件事,后面我们会实现这颗 CPU),而且 RAM[A] 和 ROM[A] 会被自动选中,Hack 汇编语言用 M 表示 RAM[A]。
C 指令
C 指令要复杂一些,有如下这些指令:
我们看一些例子,学习 C 指令的使用。
给 Data Register 赋值
给 RAM 赋值
控制流
这里用 A 指令选择存储在 ROM 里需要执行的指令,每条指令存储在 ROM 里的一个 Register 里。
变量
把 A 指令里的 const 替换成 symbol(变量名)即可,这里把变量名绑定到对应的 const 的工作是我们后面文章介绍的汇编器做的,这些 symbol 实际在 ROM 里不存在,汇编器帮我们实现了 symbol 和 地址的映射关系,我们来看一些例子:
汇编器还帮我们预定义了一些 Register,方便我们使用:
标签
sym 帮我们定义了数据的相对地址,这样我们不用记住数据(RAM)对应的绝对地址,就可以访问数据。同理,我们需要新的 sym 帮我们定义控制流的相对地址,我们用 (sym) 表示,这样我们不用记住程序段的绝对地址,就可以通过标签访问对应的程序段(ROM)。
我们需要 infinite loop 确保程序不会执行想要运行程序外的程序(用来表示程序结束):
有了控制流和标签,我们就相当于可以实现 for 循环:
指针
这里是这门课我最喜欢的部分之一,学习这门课之后你可以清清楚楚的知道指针是什么,而不是捧着《c++ primer》看大段的指针是什么的描述。
结合用 C++ 指针的写法写一下上面的汇编语言:*R0 = -1。我们对指针的理解更深刻了。来看一下更复杂点的例子:
数组
实际上就是通过指针访问的一连串数字,理解了指针,就可以理解数组了,我们看下例子:
我们可以看到程序定义了长度为 5,arr[1] = 100 的数组,并计算了数组所有元素相加的和。
A 指令和 C 指令总结
接下来我们来看二进制版本的指令集,我们编程还是用汇编语言,但是汇编器会把汇编程序翻译成下面的二进制版本加载到 ROM 里运行。
二进制版本指令集
I/O(输入和输出)
-
输入
可以看出 RAM 里的一个寄存器用来连接键盘,一个 Register 是 16-bit,足够表示键盘上的按键。例子:
-
输出
输出设备是一个 256(row) * 512(col) 像素的黑白屏幕。
我们可以看到 1 代表黑色,0 代表白色,每 32 个寄存器代表屏幕里的一行像素。
课程里有两个小项目帮助理解内容,一个是计算两个寄存器的乘积,一个是通过按键控制屏幕全黑或者全白,感兴趣的可以看文章开头的 github 实现,也可以自己学习课程实现。
接下来的文章会介绍如何构建 CPU 和如何写一个汇编语言的汇编器。