nand2tetris Hack Assembly 与 VM
本文发布于 765 天前。

Assembly

CPU Emulator

语法

A指令

语法

@value #value为一个非负的十进制数

功能

A指令的主要功能:
– 将常数输入到计算机的唯一方式
– 将目标数据的内存单元地址存入A寄存器,为赋值C指令提供条件
– 将跳转地址放入A,为跳转C提供条件

二进制表示

0vvv vvvv vvvv vvvv
例如,0000 0000 0000 0111 代表 @7,即把7存入A寄存器.

使用例

-1存入地址为100的RAM中。

@100   #将目标地址设置为100
M=-1   #向目标地址中存入-1

C指令

语法

dest = comp; jump #dest和jump可为空
                  #若dest为空,则'='省略
                  #若jump为空,则';'省略

功能

C指令的主要功能:
– 计算什么
– 将计算后的值存到哪里
– 之后怎么跳转(做什么)

二进制表示

111a cccc ccdd djjj
其中,ac存储计算指令(compute),d存储目标位置(destination),j存储转移条件(jump)。
具体的功能如下

Compute域,表计算
(a=0)
comp
c1 c2 c3 c4 c5 c6 (a=1)
comp
0 1 0 1 0 1 0
1 1 1 1 1 1 1
-1 1 1 1 0 1 0
D 0 0 1 1 0 0
A 1 1 0 0 0 0 M
!D 0 0 1 1 0 1
!A 1 1 0 0 0 1 !M
-D 0 0 1 1 1 1
-A 1 1 0 0 1 1 -M
D+1 0 1 1 1 1 1
A+1 1 1 0 1 1 1 M+1
D-1 0 0 1 1 1 0
A-1 1 1 0 0 1 0 M-1
D+A 0 0 0 0 1 0 D+M
D-A 0 1 0 0 1 1 D-M
A-D 0 0 0 1 1 1 M-D
D&A 0 0 0 0 0 0 D&M
D|A 0 1 0 1 0 1 D|M

符号表记说明:
– A、D为寄存器的名字。(A寄存器通过@来管理)
– M代表地址为A的内存单元,即Memory[A]RAM[A]

例如,对于Comp域a c1c2c3c4 c5c6
1 1101 11表示计算M+1,即Memory[A]+1
0 0101 01表示计算D|A,即D寄存器 或 A寄存器

Compute D+M时,标准的写法是D+M,但在.asm文件中书写时,也可以写作M+D,默认的编译器会自动将其转为D+M
但是:M+1不能写作1+M,会编译错误。

Destination域,表存储
d1 d2 d3 助记符 目的地(被赋值)
0 0 0 Null 不存储计算结果
0 0 1 M 将计算结果存入M,也就是Memory[A]RAM[A]
0 1 0 D 将计算结果存入寄存器D
0 1 1 MD 将计算结果存入Memory[A]寄存器D
1 0 0 A 将计算结果存入寄存器A
1 0 1 AM 将计算结果存入寄存器AMemory[A]
1 1 0 AD 将计算结果存入寄存器A寄存器D
1 1 1 AMD 将计算结果存入寄存器AMemory[A]寄存器D

例如,对于Dest域d1d2 d3
01 0表示将Comp域的计算结果存入寄存器D

Jump域,表跳转
j1
(out < 0)
j2
(out = 0)
j3
(out > 0)
助记符 作用
0 0 0 null 不跳转,正常执行下一行
0 0 1 JGT if comp>0 jump
0 1 0 JEQ if comp=0 jump
0 1 1 JGE if comp>=0 jump
1 0 0 JLT if comp<0 jump
1 0 1 JNE if comp≠0 jump
1 1 0 JLE if comp<=0 jump
1 1 1 JMP always jump

教材中对跳转作用的说明是形如out > 0,但我觉得compout更易于理解。

例如,对于Dest域j1j2j3
110表示在comp<=0时进行跳转。
跳转的目标为寄存器A的地址,即跳转到第ROM[A]行代码

Hack规定ROM为代码存储空间,RAM为数据存取空间。

使用例

赋值

不跳转的赋值语句,jump域均为0
111a cccc ccdd d000
例如,将Memory[A]+1的值存入Memory[A]寄存器D,可写为

@7                # 设定A寄存器的地址为7
MD = M + 1        # Memory[7] = Memory[7] + 1
                  # D = Memory[7] + 1

用二进制即为1111 1101 1101 1000

若你想给某个地址赋值,你不能直接写类似@15; M=233这样的代码,因为C指令comp域只有1,0-1这三个常数。正确的写法是

@233    # 向寄存求A中存入值233
D = A   # 向寄存器D中存入A的值(即233)
@15     # 向寄存器A中存入地址15
M = D   # 向地址15填入D的值
判断值并跳转
if Memory[3]=5 then
    goto 100
else
    goto 200

以上代码的Hack实现为:

@3              # 寄存器A设定地址3
D=M             # 寄存器D存入Memory[3]的值

@5              # 寄存器A设定值5
D=D-A           # 给寄存器D赋值, D=D-A=D-5
                # 这一步是为了进行计算D-5的值。
                # D-5=0 ↔ D=5

@100            # 寄存器A设定跳转行100
D;JEQ           # 如果D为0(即实际上D-5=0, D=5)
                # 则跳转到ROM[A],即ROM[100]

@200            # 寄存器A设定跳转行200
0;JMP           # 如果上一步goto 100未执行,那么执行这一步
                # 即跳转到ROM[A],即ROM[200]

预定义符号

虚拟寄存器

R0~R15表示Memory[0]Memory[15]的地址(而不是值?)

预定义指针

SP   == Memory[0]
LCL  == Memory[1]
ARG  == Memory[2]
THIS == Memory[3]
THAT == Memory[4]

以上均为RAM地址。

I/O指针

SCREEN == Memory[16384] # (0x4000) 屏幕的基地址
KBD    == Memory[24576] # (0x6000) 键盘的基地址

以上均为RAM地址。

屏幕内存为16384-24574

标签符号

写在句首,单独一行的标签,例如(LOOP)那么后面可以引用这个标签,跳转到这一行的所在位置。可理解为标记循环的读档点。如下

    ...
(LOOP)          # ←─┐
    ...         #   |
    ...         #   |
    @LOOP       #   |
    0;JMP       # ──┘  跳转到(LOOP)所在位置
(END)           # ←─┐
    @END        #   |
    0;JMP       # ──┘  跳转到(END)所在位置
                #  这个无限循环是用来结束程序的(定义)

变量符号

配合A指令使用,直接在RAM中自动开辟一个地址并用设定的变量名指代这个地址,如下

@i      # 在`RAM`中开辟了一个地址,并把此地址存入寄存器A中。
        # 此地址和变量名`i`关联
M = 1   # 向被"i"关联的地址的RAM中存入1

@sum    # 在`RAM`中开辟了一个地址,并把此地址存入寄存器A中。
        # 此地址和变量名`sum`关联
M = 0   # 向被"sum"关联的地址的RAM中存入1

值得注意的是:
1. 这只能在代码中使用,CPU Emulator会自动为其改为分配的地址。
例如,代码为

@i
M = 1
@sum
M = 0

但用CPU Emulator打开后,ROM区显示的是:

  1. 变量符号的地址自动分配是从0x0010Memory[16]开始的。例如上图中,第一个变量i被自动分配到了Memory[16],第二个变量sum被自动分配到了Memory[17]
特别提醒
自动分配(从16开始)的地址并不会考虑之前是否人为地用过这个地址。例如:

@16
M = -1
@i
M = 1
@sum
M = 0

会被解读为

@16
M = -1
@16
M = 1
@17
M = 0


在这里Memory[16]被(可能是意料外的)重复使用了。

推荐的用法是,当你需要存入数值时,直接使用@数字

@15
M = A

而想要引用地址时,使用替代的变量名

D = 1
@R15
M = D

文件规定

二进制文件

.hack为二进制文件,即一连串的机器语言指令。每行16个值,表示一个指令。

汇编文件

.asm为汇编文件,即上文提到的A指令C指令等。
编译说明:
1. 空格和空行会被忽略
2. 常数必须非负,且用十进制表示
3. 用户自定变量名可以包含任何字母、数字、_、.、Dollar符、冒号等,但不能以数字开头
4. 注释行用双斜线//开头,不能换行。
5. 所有助记符必须大写。用户自定义标签区分大小写。
6. 一般规定标签大写((LOOP)),变量名小写(@sum)。

宏命令

A指令的缩写

对于上面提到的赋值命令,Memory[15]=233,其基础写法为

@233
D=A
@15
M=D

但可以用[]来表示@,例如

D = A[233]
M[15] = D

值得注意的是,对于编译器而言,[]放在哪里都是一样的,即以下三条是等价的:

D = [233]A 
D = A[233]
D[233] = A

它们的编译结果都是

@233
D=A

GOTO的缩写

警告
在2.5版本中已经不能用了。以下仅为个人理解,实际不能这么写。

例如,一个求1-100的和如下:

    @i
    M = 1
    @sum
    M = 0
(LOOP)
    @i
    D = M
    @100
    D = D-A
    @END
    D; JGT

    @i
    D = M
    @sum
    M = D+M

    @i
    M = M+1

    @LOOP
    0; JMP

(END)
    @END
    0; JMP

可以写为

    @i
    M = 1
    @sum
    M = 0
(LOOP)
    @i
    D = M
    @100
    D = D-A
    @END
    D; JGT

    @i
    D = M
    @sum
    M = D+M

    @i
    M = M+1

    GOTO LOOP

(END)
    GOTO END

一些基本结构的转译

以c语法为例

赋值

C

tmp = 666;

Hack

D = A[666]   # 向寄存器D中存入A的值(即233)
M[tmp] = D   # 向地址15填入D的值

复制值

C

*p1 = 5;
*p2 = *p1;

Hack

# *p1 = 5;
@5
D = A
@p1
M = D

# *p2 = *p1;
@p1
D = M
@p2
M = D

while循环

C

while (i<10)
{
    AAAA
}
BBBB

Hack

(LOOP)
    @i
    D = M       // 将i存入寄存器D
    @10         // 将10存入寄存器A
    D = D-A     // 计算D-A=D-10=i-10
    @BBBB
    D; JGT      // 如果i-10>0 则退出这个LOOP,到BBBB

    //AAAA Codes         // 如果未跳转,则继续执行代码AAAA

    @LOOP       // 回到循环头
    0; JMP

(BBBB)

if选择

if…

C

if (i>10)
{
    AAAA
}
BBBB

Hack

    @i
    D = M       // 将i存入寄存器D
    @10         // 将10存入寄存器A
    D = D-A     // 计算D-A=D-10=i-10
    @AAAA        
    D; JGT      // 如果i-10>0 ,AAAA
    @BBBB
    D; JMP      // 如果上面未跳转,则跳到BBBB

(AAAA)
    // AAAA Codes

(BBBB)
    // BBBB Codes

if…else…

C

if (i>10)
{
    AAAA
}
else
{
    BBBB
}
CCCC

Hack

    @i
    D = M       // 将i存入寄存器D
    @10         // 将10存入寄存器A
    D = D-A     // 计算D-A=D-10=i-10
    @AAAA        
    D; JGT      // 如果i-10>0 ,AAAA
    @BBBB
    D; JMP      // 如果上面未跳转,则跳到BBBB



(AAAA)
    // AAAA Codes

(CCCC)
    // CCCC Codes

(END)
    @END
    0; JMP      // 程序结束了

(BBBB)
    // BBBB Codes

    @CCCC
    0; JMP      // 执行完BBBB之后回到CCCC

VM

语法

VM语法对stack执行操作,包括

堆栈和内存命令

sp为堆栈指针

pop

pop a
Stack | 2 3 5 SP
Memory … a=6 … b=10 …
After pop a:
Stack | 2 3 SP
Memory … a=5 … b=10 …
即:把stack最上的元素pop out,存入a

push

Stack | 2 3 5 SP
Memory … a=6 … b=10 …
After push b:
Stack | 2 3 5 10 SP
Memory … a=6 … b=10 …
即:把b放到stack最上

添加常数时,要写push constant 10

算数和逻辑命令

这些操作都是在stack中执行的。
以下的xy分别表示stack-2-1元素。
Stack | … x y SP

add

整数加法
Stack | 12 3 7 SP
After add:
Stack | 12 10 SP

sub

整数减法
Stack | 12 3 7 SP
After sub:
Stack | 12 -4 SP

neg

算数求反
Stack | 12 3 7 SP
After neg:
Stack | 12 3 -7 SP

eq

x=y
Stack | 12 3 7 SP
After eq:
Stack | 12 0 SP

gt

x>y
Stack | 12 3 7 SP
After gt:
Stack | 12 0 SP

lt

x\<y
Stack | 12 3 7 SP
After gt:
Stack | 12 -1 SP

-1 = 11111111

and

位操作:x And y
Stack | 12 5 11 SP
After and:
Stack | 12 1 SP

or

位操作:x Or y
Stack | 12 5 11 SP
After or:
Stack | 12 15 SP

not

位操作:Not y
Stack | 12 5 11 SP
After not:
Stack | 12 5 -12 SP

标题:nand2tetris Hack Assembly 与 VM
作者:IKK
除转载和特殊声明外,所有文章采用 CC BY-NC-SA 4.0协议
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇