nand2tetris_hack汇编语言

计算机

我接触的第一台电脑是winXP系统,我拥有的第一台电脑是win7,也就说一开始我理解的计算机就有着好看的界面,灵活的操作性方式,拥有许多软件,可以做很多事情。
我们可曾想过,大部分机器都有其专属用途,比如榨汁机只能用来榨汁、削皮刀只能用来削皮,而计算机,他可以播放视频、浏览网页等等,这是一件多么神奇的事情!那这是如何做到的
开始的理论模型图灵机,至后来真正实现了的计算机基础架构冯洛伊曼体系。详细的先不讨论,我们看一下,这门课程将要实现的计算机基本架构图

图中省略了总线和控制单元,大体思路是程序和数据放在内存中的两块,ALU通过从内存和寄存器中取值完成运算,输出至外部设备或内存和寄存器中。架构图已经有了,那第一步怎么做

指令集

可以发现,此刻我们对组装的计算机并没有一个具象化的概念。他需要实现哪些功能,他的内部数据如何传输等等,我们一无所知。课程老师在这一节,先假定我们已拥有了这样一个hack计算机,我们学习hack-CPU提供的指令集,进行简单程序开发。这样在实现CPU之前,我们大概知道了我们想做的是什么
计算机中,用户输入的指令,将会被编译器翻译为二进制编码(后面需要实现的),继而交给CPU执行。指令需要实现的功能有三

  1. 指令需要计算机要做什么,比如取数还是运算
  2. 指令需要能够控制硬件的执行,比如正常指令都是逐行执行的,有时需要跳转
  3. 指令需要告诉硬件如何做,比如去哪里取值,输出放在哪里

实现指令集的过程中,存在硬件和软件之间的权衡。这门课里就没有在指令集层次支持乘法,但是我们通过软件算法实现。所以,硬件设计和机器语言往往是同步进行的

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指令集

这个略复杂些,分为了以下几种情况

  1. A/D/M可以赋值立即数(常数),不过可选值只有0,-1,1三种
  2. A/D/M可以互相赋值,支持相反数
  3. A/D/M支持+-算数运算,也支持&|逻辑运算,特别的支持立即数1,可以理解为第二种情况的扩展
  4. 跳转指令,课程选择了在下文介绍,用于控制程序执行流程

    如果现在和你说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实现!!

posted @ 2024-04-05 23:51  柠檬水请加冰  阅读(153)  评论(0编辑  收藏  举报