Nand2tetris Part1 lab04-lab06
概述
在前三个 lab 中,我们构建了 hack 计算机所需要的基础的芯片。后三个 lab 则是教我们如何使用这些芯片去搭建一个 hack 计算机并且使用汇编语言。
Week04 & Week05
把这两个 lab 放在一起是因为他们给我的感觉更像是知识的互补。 lab05 教会你汇编语言的规范是什么, lab06 则是如何搭建 CPU, 最后变成一个 hack 计算机,在这其中你会知道为什么汇编语言的规范是这样子的。
关于汇编语言
[Image]
寄存器
要知道汇编语言的逻辑,首先应该从 CPU 本身来看(当然,你现在不需要钻研,因为是下一讲的内容)。容易发现,我们现在有 A register ,D register , 还有一个 inM(M register)。他们的作用分别是:
- A register : 储存地址,使用 @address 召唤
- D register: 作为一个 temp 使用
- M register: 存放的是 A register 这个地址对应的 RAM 值
注意,在 hack 计算机架构中,RAM 用来存放值, ROM 用来存放指令(你可以在他给的 CPUEmulator 里面看见)
有了这三个寄存器,我们可以实现几乎所有功能。
首先回忆你构建的一个不起眼的芯片 PC。就是那个实现了自增/换值的寄存器。他在我们 CPU 架构的最低下。他实现了一个有趣的功能:掌控当前用到第几个 ROM 里面的指令了。也就是说,我们既可以逐步 +1 的读指令,也可以跳转。这就是 jump 的实现(也就是你诸如 循环等东西实现的法则)。
指令
在 hack 架构中,我们仅仅只有两种指令: - A instruction: 使用 @address 使用,作用就是把 A reg 的值变成你刚刚 @ 了的地址。
- C instruction: 使用 dest=comp;jump 来实现。 其中,dest 是你的要赋值的对象,comp 是你的ALU 计算式子,jump 是你的跳转规则。这三个的详细要求可以看下面的图或者自行查阅。
[Image]
值得一提的是你的 aa 那里是有顺序要求的(当你写到 lab06 就知道这个要求的好了)
这些就是组成汇编的核心部分,除此之外,我们还有变量(小写)和标签(大写)的说法
标签:
想象一下你想在你的汇编程序中频繁的使用各种 jump(这很常见),但你的指令读进 ROM 的时候并不是从 0 开始的,这时候,你的跳转(如果直接写地址的话),就很小丑了。所以我们引入了标签这个概念,让代码同时具有通用性和可读性。
使用方法很简单,通过 (LABEL) 来声明,以后直接 @LABEL 就可以正常使用了。(本质上就是一个 map)
变量:
同样的,我们也给 RAM 提供一些辅助,也就是变量。使用的时候直接 @variable 就可以了,如果这个变量还没用过,就会自己去 RAM 里面声明一个空间(通常是按照顺序的)。
顺便一提的是,为了方便使用,这里已经有被定义的值了:
[Image]
我们可以看几个简单的例子
[Image]
同样要注意的是,汇编程序没有一般意义上的终止,他只会遵循 PC 的值一直走下去,所以你如果不在末尾做出一些处理的话,容易刹不住车跑到其他 ROM 区域去。解决方法也很简单,你可以在末尾加个死循环:
[Image]
这样它就会一直停在这里了。
IO
最后是键盘和显示的事情。
关于键盘,在 hack 架构中,RAM 23576(已经有一个变量叫做 KBD 指向它了),改变它的值就意味着模拟输入。
关于显示器,我们的模拟器是一个 256*512 的黑白屏幕(注意你的值的位数),它内存的起始点是 16384(又叫 SCREEN),单个位的值为1是黑色,为0是白色。
Lab04
这里一共有两个任务
Mult.asm: RAM[2] = RAM[0]*RAM[1]
Fill.asm: 当键盘有输入的时候,把整个屏幕变为黑色,否则白色。
详细的请自行阅读程序中的要求。
当你成功写好上面两个程序以后,你应该对 hack 下的汇编有一定的了解,所以接下来让我们看看它的原理。
首先,下面是我们整个计算机的构造:
[Image]
里面很多东西你应该都是知道的,其中,ROM 是我们给你的(它就像 RAM 一样,只不过是 ReadOnly ),它为 CPU 提供 指令。CPU 接受 指令, reset 信号 和 inM(也就是 M register),然后输出图上结果给 RAM,其中, writeM 用来判断是否需要更改 M ,(毕竟 A,D Register 都在CPU里面)。
[Image]
首先,我们要了解输入的 instruction 是什么。按照你写汇编的经验,这里肯定也是 A/C 两种指令。
A 指令:0xxxxxxxxxxxxxxx 也就是0开头,后边跟一个地址,简单好懂。
C 指令:111accccccdddjjj a 代表用 A 还是 M 寄存器,cccccc 代表了 ALU 计算,ddd 代表你赋值给谁,jjj 就是你的 jump 操作。
[Image]
CPU 的架构如上图我们已经给你了,你需要做的就是选出架构中的每一个 c,让 CPU 符合我们的逻辑。注意,当你构建 C 指令的时候,永远不要忘记还有 A 指令。
(其他有关的请自行查看代码注释和图表)
在写好 CPU 后,其他事情就简单了。首先是一个 RAM 构建:
[Image]
其实挺好看的,就是当你的值是 RAM 16K 里面,改 RAM16K,在 Screen 区间就是改 Screen , 在 Keyboard 就是改Keyboard。你可以自行观察下数据选择如何正确的知道该选择哪里。(RAM16K 是你以前写过的,Screen 和 Keyboard 是我们给你的,你可以使用 vscode 的自动补充查看抽象后的芯片需要和输出什么)
最后就是计算机的构建了,其实照着上面把 3 部分拼一下就可以了,这里不打算赘述。
Lab05
完成你的 CPU,Memory,Computer 芯片。
Week06
这一讲比较简单,本质上是让你写一个 汇编器 (Assembler),实现从 汇编语言 转换为 机器语言(16位)。其实具体的转法当你完成了 lab04 和 lab05 后应该已经心中有数了。这里简单叙述下要求:// 为注释,你需要忽略 // 后面的内容,同时,你需要忽略掉所有的空格。注意,实现的是 .asm -> .hack ,我们建议加入文件操作,就像 tools 里面给你的一样。
Lab06
写一个汇编器。
Tips
假如你遇见了困难,可以试着使用下面的项目结构:
- 主体解决部分:实现两次 Pass,一次处理标签和变量,第二次实现转换。
- SymbolTable:符号表,以 hash 的方式存下标签和变量。
- IO:输入输出部分
- Main:主函数
当然,具体结构应该由你选择的语言规范和个人习惯来走。由于里面有一堆乱七八糟的转换,我们推荐你使用比较简单的语言快速实现(当然,c语言高手随意)。
最后,如果你还没有编程基础,我们推荐你简单写(画)出思路就可以了。或者你可以尝试速通一门语言(这个项目很简单,1-2天的速通绰绰有余了应该,然后就是 GPT 大法,面向项目学习)。