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
其中,a
和c
存储计算指令(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 | 将计算结果存入寄存器A 和Memory[A] |
1 | 1 | 0 | AD | 将计算结果存入寄存器A 和寄存器D |
1 | 1 | 1 | AMD | 将计算结果存入寄存器A ,Memory[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
,但我觉得comp
比out
更易于理解。
例如,对于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
区显示的是:
- 变量符号的地址自动分配是从
0x0010
即Memory[16]
开始的。例如上图中,第一个变量i
被自动分配到了Memory[16]
,第二个变量sum
被自动分配到了Memory[17]
。
@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
的缩写
例如,一个求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
中执行的。
以下的x
和y
分别表示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