计算机系统要素,从零开始构建现代计算机(nand2tetris)
如果完成了本书所有的项目 你将会获得以下成就
- 构建出一台计算机(在模拟器上运行)
- 实现一门语言和相应的语言标准库
- 实现一个简单的编译器
而且,这本书的门槛非常低,只要你能熟练运用一门编程语言即可。本课程综合了数字电路,计算机组成原理,计算机体系架构,操作系统,编译原理,数据结构等的主要内容,搭建了计算机平台的构建的框架,并未深入细节,如果需要了解细节,可由本书作为主线,逐步完善的知识体系。
QQ交流群(含资料):39014053
课程连接
项目地址Github
本章要完成的内容
- 乘法程序:该程序的输入值存储在R0和R1中。程序计算R0*R1的值并将其存入R2。
- I/O处理程序:这个程序是个无限循环程序,它侦测键盘的输入。当按下任一键时,程序将屏幕变黑,即将“black”写入每个像素。当没有键按下时,屏幕应该被清屏。你以任何空间顺序来选择屏幕的变黑和清屏,只要连续地按一个键足够长时间,屏幕就会全黑,长时间不按键就会清屏。
内容详解
实现乘法操作
比如:2*3操作,在底层实现上,将其作为3个2相加来实现,因为我们前面已经实现了加法操作。可以通过伪代码来描述 一下这个过程,这个过程比较简单。
R0,R1;
initialize sum = 0;
while(R1>0){
R1--;
sum=sum+a;
}
实现一个简单的I/O处理程序
这个I/O程序比较简单,只需要在屏幕上打印黑色像素即可。项目中关于本程序的描述是这样的: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; the screen should remain fully black as long as the key is pressed. When no key is pressed, the program clears the screen, i.e. writes “white” in every pixel; the screen should remain fully clear as long as no key is pressed.
书中关于本程序的描述:这个程序是个无限循环程序,它侦测键盘的输入。当按下任一键时,程序将屏幕变黑,即将“black”写入每个像素。当没有键按下时,屏幕应该被清屏。你以任何空间顺序来选择屏幕的变黑和清屏,只要连续地按一个键足够长时间,屏幕就会全黑,长时间不按键就会清屏。
看完两端,便会有一个几个问题:
1、如何定义无限循环?
2、什么意味着监听到按键?
3、监听到按键后,如何将屏幕变黑?
4、如何清屏?
实际上这些问题并不难,重点理解这个过程。首先需要注意的是SCREEN和KBD被预定义为表示屏幕内存映像和键盘内存映像的基地址。
接下来我们逐个解决这些问题:
如何定义无限循环?
定义无限循环很简单,重点在于结束时候的跳转,只要我们保证,它总是跳转到起始位置即可,代码如下:
(LOOP)
...
...
@LOOP
0;JMP
什么意味着监听到按键?
如果有按键,那么键盘内存映像中一定不能为0,所以只要判断基地址KBD中的内容是否大于零即可。由于程序是在无限循环执行,所以只要按键不停,那么KBD的内容就不为零,程序就会一直检测到有按键,然后会一直在屏幕上打印。如果键盘内存映像中的值为0,说明没有按键,此时,应该去执行清屏操作(跳转过去的方式有多种,这里只列出了一种)。所以这个功能的代码如下:
@KBD
D=M // get the basic address of th keyboard memory-map
@FILL
D;JGT // if D is greater than zero, jump to FILL
@CLEAR // if D is not greater than zero, jump to CLEAR
0;JMP
如何将屏幕变黑?
实际上这就是前面程序代码中FILL实现的功能。首先我么得确定,将那个单元变黑,实际上就是将该单元存储的值置为-1(参考补码的内容理解细节)。因此,我们要判断屏幕内存映像中第一个可用单元(当前单元)是什么,因此,我们需要一个单元来存储当前单元的地址,不妨存储在M[0]中,接着,如何判断屏幕内存映像是否已满,即屏幕已经全部置黑。这可以通过将当前单元地址与屏幕内存映像的最大单元地址进行比较,因此,我们需要来存储最大单元地址,不妨用M[1]。需要注意的是,当前单元表示的是第一个可用单元。如果没有可用单元的话,那么M[1]-M[0]<0,如果等于零,则表示,最后一个单元可用。现在完成了是否已满的判断,如果满,那么我们无法写入黑色像素,直接返回LOOP,如果未满,则继续在当前单元写入黑色像素,写完之后返回LOOP。如果按键不停的话,那么循环会一直检测到按键,也就会一直执行FILL模块,每执行一次会填充一各单元的黑色像素,因此,在屏幕上看到的像素显示应该是有时间差距的,而不是连续的。本模块的代码如下:
/* 这段代码应该定义在LOOP之前 */
@SCREEN
D=A // get the basic address of the screen memory-map
@0
M=D // store the basic address in the M[0]
@24575
D=A // get the max address of the screen memory-map
@1
M=D // store the max address in the M[1]
/**************************/
(FILL)
@1
D=M // get the max address of the screen memory-map
@0
D=D-M // get the current address of the screen memory-map, and let the max address minus the current address
@LOOP
D;JLT // if the result of this minus operation is lower then zero, jump to LOOP
@0
D=M // get the current address of the screen memory-map
A=D // store the current address in the A register
M=-1 // let the value of the current address is "black"
@0
D=M // get the current address of the screen memory-map
D=D+1 // add the current address
M=D
@LOOP
0;JMP // return to the LOOP
如何清屏?
这个问题类似于第三个问题的反过程。当程序检测到没有按键时,就会进入清屏的子程序。然后进行清屏操作。如果要清屏,我们只需要获得程序当前地址,对它减一,然后与SCREEN比较是否小与SCREEN(因为小与SCREEN意味着清屏结束),如果大于或等于那么我们就将当前地址中的内容置零,也就是清屏。那么结束条件,前面已经提到了,就是小与SCREEN一位置结束。在这种汇编程序中,通过是将两数相减,然后跟零比较来判断两数的大小。代码如下:
(CLEAR)
@0
D=M // get the current address
D=D-1 // do the minus operation
@SCREEN // get the basic address
D=D-A // let current address minus basic address
@LOOP
D;JLT // do just, if D is lower than zero, jump to LOOP
@0
D=M // get the current address
A=D // store the current address in the A register
M=0 // clear the "black", in another word
@LOOP
0;JMP // return to LOOP