“Make everything as simple as possible, but no simpler.” – Albert Einstein
文中提到的所有实现都可以参考:nand2tetris_sol,但是最好还是自己学习课程实现一遍,理解更深刻。
之前的文章里我们介绍了 Register、PC、RAM 和 ALU,这节课我们来介绍如何把这些芯片组合起来成一个 CPU 和一个能运行上篇文章介绍的汇编语言的电脑 —— Hacker。
我们来看一看 Hacker 的架构。
总线
上图里的各个模块通过总线连接起来,什么是总线呢,看下图:
总线就是把数据、地址、程序在各个模块之间传递的“公交车”(buses)总线的英文太形象了,01010101 就是车上的“人”。
Fetch-Excute Cycle
那么 CPU 干了什么事呢,就是不断执行 fetch-excute cycle 的循环,fetch 阶段去获取指令,excute 阶段去执行指令。比如现代电脑的开机,打开开机键后执行 fetch 阶段,获取操作系统程序的地址,然后 excute 第一行指令后,再去 fetch 第二行指令,如此往复,直到开机成功。
上篇文章我们知道 A 指令可以同时访问 RAM[A] 和 ROM[A],把指令和数据放在不同的 Memory 里,那么我们可不可以把他们放在同一个 Memory 里,然后根据需要把 RAM[A] 解释成数据或者指令。当然可以,第一种是空间换时间的方法(Harvard architecture),第二种是时间换空间的方法,所以这两种方法的优缺点很明显。我们来看看这两种不同的 Fetch-Excute Cycle:
课程里选择了 single cycle,two-memory machine 这种实现,接下来我们继续来看 Hacker 里各个模块的实现。
CPU
CPU 的作用是读取指令,执行指令,读取数据,写入数据。我们可以对应着看上面两张图,「inM、outM、writeM(bool)、addressM」对应着「write、read、address(data memory)」,「instruction、pc」对应着「insturction、address(instruction memory)」, reset 的作用就是相当于重启按钮。接下来我们来看看具体实现。
这里出现的都是之前文章里提到过的元器件,接下来我们逐一解释这些元器件是如何实现上述的 CPU 的。图中,“c”表示“控制位”,控制位来自指令;他们告诉各种芯片部件该做什么。
A 指令的实现很简单,看下图:
我们来看 C 指令的实现:
和 A 指令一样,C 指令的最高位决定第一个 Mux16 的输入:
acccccc 用来决定的 cccccc ALU 的 zx nx zy ny f no 的引脚。a 来决定输入 ALU 的是 A 还是 M。
ddd 用来决定 ALU 的输出是否保存到相应的位置:
jjj 用来决定执行哪一条指令:
这里 jjj 可以通过 f(zr, ng) 来获得。PC 的输出就是下一条要执行指令的地址。这里一个可以执行汇编程序的 CPU 就实现了,我只想说,太优雅了!!!
接下来我们把剩余的模块和 CPU 连接起来组成 Hacker。
Data Memory
这里用到了我们之前实现的 RAM16K,屏幕是 RAM8K 连接着显示屏。(为了简便,Keyboard 和 Screen 都是内置的模块,直接调用即可)。
Instruction Memory
/** Read-Only memory (ROM),
acts as the Hack computer instruction memory. */
CHIP ROM32K {IN address[15];OUT out[16];BUILTIN ROM32K;
}
Instruction Memory 也是内置的模块,和 RAM32K 的唯一区别就是 ROM32K 是只读的,所以每次载入新程序,相当于把之前的程序删除掉,再载入新程序。就和单片机下载程序一样。
Hacker
到这里 Hacker 就实现完成了,下篇文章会介绍如何实现汇编器。
缓存
最后解释下 Hacker 没有外接存储设备,也没有多级缓存的原因。