nand2tetris_hack汇编语言
计算机
我接触的第一台电脑是winXP系统,我拥有的第一台电脑是win7,也就说一开始我理解的计算机就有着好看的界面,灵活的操作性方式,拥有许多软件,可以做很多事情。
我们可曾想过,大部分机器都有其专属用途,比如榨汁机只能用来榨汁、削皮刀只能用来削皮,而计算机,他可以播放视频、浏览网页等等,这是一件多么神奇的事情!那这是如何做到的
开始的理论模型图灵机,至后来真正实现了的计算机基础架构冯洛伊曼体系。详细的先不讨论,我们看一下,这门课程将要实现的计算机基本架构图
图中省略了总线和控制单元,大体思路是程序和数据放在内存中的两块,ALU通过从内存和寄存器中取值完成运算,输出至外部设备或内存和寄存器中。架构图已经有了,那第一步怎么做
指令集
可以发现,此刻我们对组装的计算机并没有一个具象化的概念。他需要实现哪些功能,他的内部数据如何传输等等,我们一无所知。课程老师在这一节,先假定我们已拥有了这样一个hack计算机,我们学习hack-CPU提供的指令集,进行简单程序开发。这样在实现CPU之前,我们大概知道了我们想做的是什么
计算机中,用户输入的指令,将会被编译器翻译为二进制编码(后面需要实现的),继而交给CPU执行。指令需要实现的功能有三
- 指令需要计算机要做什么,比如取数还是运算
- 指令需要能够控制硬件的执行,比如正常指令都是逐行执行的,有时需要跳转
- 指令需要告诉硬件如何做,比如去哪里取值,输出放在哪里
实现指令集的过程中,存在硬件和软件之间的权衡。这门课里就没有在指令集层次支持乘法,但是我们通过软件算法实现。所以,硬件设计和机器语言往往是同步进行的
hack汇编
介绍课程hack汇编语法之前,先引入几段大众认知的标准汇编语言
我的前几篇博客里,简单的介绍了如何编写8086汇编代码。相比于高级语言里复杂的语法,汇编语言更接近编程的实质,只提供基础的算数运算和逻辑运算。写汇编代码其实很受限,脑子里只能有加加减减、选址赋值这些操作,不过课程老师也说了,“simple people are impressed by sophisticated thing, sophisticated people are impressed by simple thing”
hack计算机内存和寄存器架构
在通用计算机中,代码其实也是数据,只是我们将他放在了代码段内,这里hack计算机为了简便处理,不在一整块内存上划分代码与数据,而是设计了两块内存,分别存放代码和数据
hack计算机有两个寄存器,存放地址的A寄存器和存放数据的D寄存器。我们可以设置A寄存器的值,去内存中取值,然后将值保存在D寄存器中,同时提供了一个变量M,M代表此刻数据区选中的值,可以对他进行赋值和运算
hack计算机指令集
有了内存和寄存器存放数据/代码之后,此时需要一套指令来读取/写入数据,并交给CPU运算。hack计算机提供了两种指令集,依据指令集编写而成的代码被加载入ROM中,而后逐行执行;至于指令怎么被加载,ROM又是如何执行的,暂且先不讨论。现在可以理解就是有了这样一个hack计算机,提供了这样的指令集,请为他编写代码。其实这和我们学一门新的语言一样,学语法,写功能,再去了解其本质
A指令集
这个比较简单A-address取址,并自动赋值A寄存器;至于图中提示的如何确认,随后而来的取值是到数据区还是代码区,这是后面实现CPU时考虑的事情
C指令集
这个略复杂些,分为了以下几种情况
- A/D/M可以赋值立即数(常数),不过可选值只有0,-1,1三种
- A/D/M可以互相赋值,支持相反数
- A/D/M支持+-算数运算,也支持&|逻辑运算,特别的支持立即数1,可以理解为第二种情况的扩展
- 跳转指令,课程选择了在下文介绍,用于控制程序执行流程
如果现在和你说hack计算机就是由这两种指令集做为基石搭建而成,你肯定不相信。其实两种指令集配合起来使用,可以实现对任何一个内存单元的取值和赋值,看例子会很容易理解了
这里直接将A寄存器当作立即数来使用了
这里是通过M这个变量,方便的实现内存地址赋值操作,并总结了一种基础范式,A指令取值,C指令赋值
来一个稍难一点的例子,这里可以实际去CPU模拟器中执行,看一看内存和寄存器在执行过程中,值的变化
hack计算机 Symbolic programming
标题我不知道怎么翻译,实际这里是介绍了使用一些人类可读的方式来影响程序执行
分支
高级语言里的if else以及for循环,需要通过这种方式实现,不知道为什么,习惯了前几节使用选择器来判断,感觉这种jump方式,也不是那么难受了。跳转指令实际上是包含在C指令集内的,下文会再对C指令集综述
再贴一个稍微复杂一点的,一般是这样配合使用的
变量
之前在介绍A指令集的时候,@x 这里x可以用来表示立即数,也可以用来表示实际内存地址,当混在一起的时候,就看不懂这里x是代表什么的了;所以我们需要一种区分,约定,如果这里是表示实际内存地址的,我们可以使用一个小写英文来代替,相当于高级语言里申请了一个变量,内存地址自动从16号开始,前面被内置变量名R0..R15占据了
这有一个立即数和实际内存地址混合使用的例子,很容易区分这两者
标签
这个主要是为了配合跳转指令来使用的,毕竟跳转的时候还要数在第几行,修改代码之后都要调整一下受影响的跳转代码,这也太痛苦了
实战
这里主要介绍了一种思路,编写汇编代码时,先尝试思考伪代码,再将伪代码翻译为汇编语言。执行时汇编器再翻译为机器指令,可以发现变量和标签这些语法糖都被抹去了
这里提到了一个问题,因为计算机通电之后是不会停止的(这里我不明白CPU占有率是怎么一回事),程序会一直往下执行的。就像C语言里的数组越界,我们需要这个程序执行结束之后,“停”在最后位置
顺便说一句,上面的代码可以这样优化。我在平时写代码,也会尽量写if 不写else
hack计算机低级编程
迭代
指针
实战
其实学到这里,基本已经把计算机程序里的三种结构理解了,大部分的功能都可以参考于此
hack计算机指令集-补充
标题有点奇怪,主要开始的时候如果介绍完整,很难解释,现在学习了这么多例子,已经对hack汇编语言有了完整的认识,再来谈一谈指令集
A指令集的两种用法
C指令集的完整概述
这里贴两张图感受一些,C指令集的组成规则
稍复杂些的跳转指令,也被容纳在内了,是不是有种统一美
注意事项
这里的意思是,不要混用jump指令和M赋值,因为二者都依赖A指令
输入与输出
这里hack计算机内置了显示器和键盘芯片,并将他们映射到内存的某一块区域;可以通过读取/写入这一块区域内的值,来显示内容或读取当前正在操作的键值
显示器
@SCREEN为内置变量
可以将一个16位数设置为-1,快速设置一大块像素点为黑色
键盘
键盘简单一点,这里的值来自于被按下键的Unicode编码值,没有键被按下时,0
使用时,也内置了一个变量@KBD
课程作业
这节课程的基础知识到这里就结束了,留下了两个作业,贴一下代码加深理解
实现乘法
这里实现的很简单,简单的++,而且没有判断R0和R1谁大谁小寻找更少的迭代次数
// 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/4/Mult.asm
// Multiplies R0 and R1 and stores the result in R2.
// (R0, R1, R2 refer to RAM[0], RAM[1], and RAM[2], respectively.)
// The algorithm is based on repetitive addition.
@R2
M=0
@R0
D=M
@END
D;JEQ
@R1
D=M
@END
D;JEQ
@i
M=0
(LOOP)
@i
D=M
@R1
D=D-M
@END
D;JEQ
@R0
D=M
@R2
M=M+D
@i
M=M+1
@LOOP
0;JMP
(END)
@END
0;JMP
白屏/黑屏
这个程序里就能看出hack计算机的局限性了,因为只有两个寄存器,想把黑屏和白屏代码封装起来,就很有难度了。最后是黑屏写了一套代码,白屏写了一套代码,二者就在大循环里跑,也没有设置子循环,让程序完成渲染后处于监听键盘的状态。不过,至多就是按键的时间长一点就好了..
// Runs an infinite loop that listens to the keyboard input.
// When a key is pressed (any key), the program blackens the screen,
// i.e. writes "black" in every pixel. When no key is pressed,
// the screen should be cleared.
(MAIN)
@KBD
D=M
@BLACK
D;JNE
@WHITE
0;JMP
@MAIN
0;JMP
(BLACK)
@i
M=0
@j
M=0
@head
M=0
(BLACK-LOOP1)
@i
D=M
@255
D=D-A
@MAIN
D;JGT
@j
M=0
(BLACK-LOOP2)
@j
D=M
@31
D=D-A
@BLACK-LOOP1-END
D;JGT
@head
D=M
@j
D=D+M
@SCREEN
A=A+D
M=-1
@j
M=M+1
@BLACK-LOOP2
0;JMP
(BLACK-LOOP1-END)
@32
D=A
@head
M=M+D
@i
M=M+1
@BLACK-LOOP1
0;JMP
(WHITE)
...
M=0
...
(END)
@END
0;JMP
总结
这一节贴了好多图,不过想想还是很有成就感的,我在学会一门语言之后,还可以把他表述出来。这节看懂了课程上的例子之后,难度就不大了,最后的白屏/黑屏作业也只是复杂,而不是困难。期待下节课的CPU实现!!