I am reading and studying The Elements of Computing Systems but I am stuck at one point. Sample chapter skip the next 5 instruction s can be found here.
我正在阅读和研究计算系统的元素,但我一度陷入困境。示例章节跳过下面的5条指令可以在这里找到。
Anyway, I am trying to implement a Virtual Machine (or a byte code to assembly translator) but I am stuck at skip the next 5 instruction one point.
无论如何,我正在尝试实现一个虚拟机(或一个字节代码到汇编转换器),但我坚持跳过下一个5指令一点。
You can find the assembly notation here.
您可以在此处找到装配符号。
The goal is to implement a translator that will translate a specific byte code to this assembly code.
目标是实现将特定字节代码转换为此汇编代码的转换器。
An example I have done successfully is for the byte code
我成功完成的一个例子是字节码
push constant 5
which is translated to:
这被翻译成:
@5
D=A
@256
M=D
As I said, the assembly language for Hack is found in the link I provided but basically:
正如我所说,Hack的汇编语言可以在我提供的链接中找到,但基本上是:
@5 // Load constant 5 to Register A
D=A // Assign the value in Reg A to Reg D
@256// Load constant 256 to Register A
M=D // Store the value found in Register D to Memory Location[A]
Well this was pretty straight forward. By definition memory location 256 is the top of the stack. So
嗯这很直接。根据定义,存储器位置256是堆栈的顶部。所以
push constant 5
push constant 98
will be translated to:
将被翻译为:
@5
D=A
@256
M=D
@98
D=A
@257
M=D
which is all fine..
这一切都很好..
I also want to give one more example:
我还想再举一个例子:
push constant 5
push constant 98
add
is translated to:
被翻译成:
@5
D=A
@256
M=D
@98
D=A
@257
M=D
@257 // Here starts the translation for 'add' // Load top of stack to A
D=M // D = M[A]
@256 // Load top of stack to A
A=M // A = M[A]
D=D+A
@256
M=D
I think it is pretty clear.
我认为很清楚。
However I have no idea how I can translate the byte code
但是我不知道如何翻译字节码
eq
to Assembly. Definition for eq is as follows:
到大会。 eq的定义如下:
Three of the commands (eq, gt, lt) return Boolean values. The VM represents true and false as ????-1 (minus one, 0xFFFF) and 0 (zero, 0x0000), respectively.
其中三个命令(eq,gt,lt)返回布尔值。 VM表示真和假,分别为????-1(减1,0xFFFF)和0(0,0x0000)。
So I need to pop two values to registers A and D respectively, which is quite easy. But how am I supposed to create an Assembly code that will check against the values and push 1 if the result is true or 0 if the result is false?
所以我需要分别向寄存器A和D弹出两个值,这很容易。但是我应该如何创建一个将检查值的汇编代码,如果结果为true则按1,如果结果为false则按0?
The assembly code supported for Hack Computer is as follows:
Hack Computer支持的汇编代码如下:
I can do something like:
我可以这样做:
push constant 5
push constant 6
sub
which will hold the value 0 if 2 values pushed to the stack are equal or !0 if not but how does that help? I tried using D&A or D&M but that did not help much either..
如果推送到堆栈的2个值相等,它将保持值0;如果不是,则保持为0,但这有什么帮助?我尝试使用D&A或D&M,但这也没有多大帮助..
I can also introduce a conditional jump but how am I supposed to know what instruction to jump to? Hack Assembly code does not have something like "skip the next 5 instructions" or etc..
我也可以引入一个条件跳转,但我怎么知道要跳转到什么指令? Hack汇编代码没有“跳过下5条指令”等等。
[edit by Spektre] target platform summary as I see it
[Spektre编辑]我看到的目标平台摘要
- 16bit Von Neumann architecture (address is 15 bits with 16 bit Word access)
- Data memory 32KW (Read/Write)
- Instruction (Program) memory 32KW (Read only)
- native 16 bit registers A,D
- general purpose 16 bit registers R0-R15 mapped to Data memory at 0x0000 - 0x000F
- these are most likely used also for:
SP(R0),LCL(R1),ARG(R2),This(R3),That(R4)
- Screen is mapped to Data memory at 0x4000-0x5FFF (512x256 B/W pixels 8KW)
- Keyboard is mapped to Data memory at 0x6000 (ASCII code if last hit key?)
16位冯·诺依曼架构(地址为15位,16位字访问)
数据存储器32KW(读/写)
指令(程序)存储器32KW(只读)
本机16位寄存器A,D
通用16位寄存器R0-R15映射到0x0000 - 0x000F的数据存储器
这些最有可能用于:SP(R0),LCL(R1),ARG(R2),这(R3),那(R4)
屏幕映射到数据存储器0x4000-0x5FFF(512x256 B / W像素8KW)
键盘映射到0x6000的数据存储器(如果是最后一个键,则为ASCII码?)
2 个解决方案
#1
It appears there is another chapter which more definitively defines the Hack CPU. It says:
看来还有另一章更明确地定义了Hack CPU。它说:
The Hack CPU consists of the ALU specified in chapter 2 and three registers called data register (D), address register (A), and program counter (PC). D and A are general-purpose 16-bit registers that can be manipulated by arithmetic and logical instructions like A=D-1 , D=D|A , and so on, following the Hack machine language specified in chapter 4. While the D-register is used solely to store data values, the contents of the A-register can be interpreted in three different ways, depending on the instruction’s context: as a data value, as a RAM address, or as a ROM address
Hack CPU由第2章中指定的ALU和称为数据寄存器(D),地址寄存器(A)和程序计数器(PC)的三个寄存器组成。 D和A是通用的16位寄存器,可以通过算法和逻辑指令操作,如A = D-1,D = D | A等,遵循第4章中规定的Hack机器语言。 -register仅用于存储数据值,A寄存器的内容可以用三种不同的方式解释,具体取决于指令的上下文:作为数据值,作为RAM地址或作为ROM地址
So apparently "M" accesses are to RAM locations controlled by A. There's the indirect addressing I was missing. Now everything clicks.
显然“M”访问是由A控制的RAM位置。我缺少间接寻址。现在一切都点击了
With that confusion cleared up, now we can handle OP's question (a lot more easily).
随着这种混乱被清除,现在我们可以处理OP的问题(更容易)。
Let's start with implementing subroutine calls with the stack.
让我们从使用堆栈实现子程序调用开始。
; subroutine calling sequence
@returnaddress ; sets the A register
D=A
@subroutine
0 ; jmp
returnaddress:
...
subroutine: ; D contains return address
; all parameters must be passed in memory locations, e.g, R1-R15
; ***** subroutine entry code *****
@STK
AM=M+1 ; bump stack pointer; also set A to new SP value
M=D ; write the return address into the stack
; **** subroutine entry code end ***
<do subroutine work using any or all registers>
; **** subroutine exit code ****
@STK
AM=M-1 ; move stack pointer back
A=M ; fetch entry from stack
0; jmp ; jmp to return address
; **** subroutine exit code end ****
The "push constant" instruction can easily be translated to store into a dynamic location in the stack:
“推送常量”指令可以轻松转换为存储到堆栈中的动态位置:
@<constant> ; sets A register
D=A ; save the constant someplace safe
@STK
AM=M+1 ; bump stack pointer; also set A to new SP value
M=D ; write the constant into the stack
If we wanted to make a subroutine to push constants:
如果我们想要一个子程序来推动常量:
pushR2: ; value to push in R2
@R15 ; save return address in R15
M=D ; we can't really use the stack,...
@R2 ; because we are pushing on it
D=M
@STK
AM=M+1 ; bump stack pointer; also set A to new SP value
M=D ; write the return address into the stack
@R15
A=M
0 ; jmp
And to call the "push constant" routine:
并称为“推动常量”例程:
@<constant>
D=A
@R2
M=D
@returnaddress ; sets the A register
D=A
@pushR2
0 ; jmp
returnaddress:
To push a variable value X:
要推送变量值X:
@X
D=M
@R2
M=D
@returnaddress ; sets the A register
D=A
@pushR2
0 ; jmp
returnaddress:
A subroutine to pop a value from the stack into the D register:
一个子程序,用于将值从堆栈弹出到D寄存器:
popD:
@R15 ; save return address in R15
M=D ; we can't really use the stack,...
@STK
AM=M-1 ; decrement stack pointer; also set A to new SP value
D=M ; fetch the popped value
@R15
A=M
0 ; jmp
Now, to do the "EQ" computation that was OP's original request:
现在,要执行OP的原始请求的“EQ”计算:
EQ: ; compare values on top of stack, return boolean in D
@R15 ; save return address
M=D
@EQReturn1
D=A
@PopD
0; jmp
@EQReturn1:
@R2
M=D ; save first popped value
@EQReturn2
D=A
@PopD
0; jmp
@EQReturn2:
; here D has 2nd popped value, R2 has first
@R2
D=D-M
@EQDone
equal; jmp
@AddressOfXFFFF
D=M
EQDone: ; D contains 0 or FFFF here
@R15
A=M ; fetch return address
0; jmp
Putting it all together:
把它们放在一起:
@5 ; push constant 5
D=A
@R2
M=D
@returnaddress1
D=A
@pushR2
0 ; jmp
returnaddress1:
@X ; now push X
D=M
@R2
M=D
@returnaddress2
D=A
@pushR2
0 ; jmp
returnaddress2:
@returnaddress3 ; pop and compare the values
D=A
@EQ
0 ; jmp
returnaddress3:
At this point, OP can generate code to push D onto the stack:
此时,OP可以生成将D推入堆栈的代码:
@R2 ; push D onto stack
M=D
@returnaddress4
D=A
@pushR2
0 ; jmp
returnaddress4:
or he can generate code to branch on the value of D:
或者他可以生成代码来分支D的值:
@jmptarget
EQ ; jmp
#2
As I wrote in last comment there is a branch less way so you need to compute the return value from operands directly
正如我在上一篇评论中写的那样,有一个分支更少的方式,所以你需要直接从操作数计算返回值
Lets take the easy operation like eq
for now
让我们像eq一样轻松操作
- if I get it right
eq a,d
is something likea=(a==d)
- true is
0xFFFF
and false is0x0000
-
So this if
a==d
thena-d==0
this can be used directly所以这个如果a == d然后a-d == 0这可以直接使用
- compute
a=a-d
-
compute
OR
cascade of all bits ofa
计算a的所有位的OR级联
- if the result is 0 return 0
- if the result is 1 return 0xFFFF
- this can be achieved by table or by
0-OR_Cascade(a)
如果结果为0则返回0
如果结果为1则返回0xFFFF
这可以通过表格或0-OR_Cascade(a)来实现
-
the
OR
cascadeOR级联
- I do not see any bit shift operations in your description
- so you need to use
a+a
instead ofa<<1
- and if shift right is needed then you need to implement divide by 2
我没有在您的描述中看到任何位移操作
所以你需要使用+ a而不是<< 1
如果需要右移,那么你需要实现除以2
- compute
如果我把它做对了eq a,d就像a =(a == d)
true为0xFFFF,false为0x0000
So when I summarize this eq a,d
could look like this:
因此,当我总结这个方程式时,d看起来像这样:
a=a-d;
a=(a|(a>>1)|(a>>2)|...|(a>>15))&1
a=0-a;
- you just need to encode this into your assembly
- as you do not have division or shift directly supported may be this may be better
a=a-d;
a=(a|(a<<1)|(a<<2)|...|(a<<15))&0x8000
a=0-(a>>15);
你只需要将它编码到你的程序集中
因为你没有直接支持分裂或转移可能这可能会更好
the lower and greater comparison are much more complicated
较低和较大的比较要复杂得多
- you need to compute the carry flag of the substraction
- or use sign of the result (MSB of result)
- if you limit the operands to 15 bit then it is just the 15th bit
- for full 16 bit operands you need to compute the 16th bit of result
- for that you need to know quite a bit of logic circuits and ALU summation principles
- or divide the values to 8 bit pairs and do 2x8 bit substraction cascade
- so
a=a-d
will became: sub al,dl
sbc ah,dh
- and the carry/sign is in the 8th bit of result which is accessible
你需要计算减法的进位标志
或使用结果的符号(结果的MSB)
如果将操作数限制为15位,那么它只是第15位
对于完整的16位操作数,您需要计算结果的第16位
为此你需要了解相当多的逻辑电路和ALU求和原理
或将值除以8位对并进行2x8位减法级联
所以a = a-d将成为:
并且进位/符号位于可访问的结果的第8位
#1
It appears there is another chapter which more definitively defines the Hack CPU. It says:
看来还有另一章更明确地定义了Hack CPU。它说:
The Hack CPU consists of the ALU specified in chapter 2 and three registers called data register (D), address register (A), and program counter (PC). D and A are general-purpose 16-bit registers that can be manipulated by arithmetic and logical instructions like A=D-1 , D=D|A , and so on, following the Hack machine language specified in chapter 4. While the D-register is used solely to store data values, the contents of the A-register can be interpreted in three different ways, depending on the instruction’s context: as a data value, as a RAM address, or as a ROM address
Hack CPU由第2章中指定的ALU和称为数据寄存器(D),地址寄存器(A)和程序计数器(PC)的三个寄存器组成。 D和A是通用的16位寄存器,可以通过算法和逻辑指令操作,如A = D-1,D = D | A等,遵循第4章中规定的Hack机器语言。 -register仅用于存储数据值,A寄存器的内容可以用三种不同的方式解释,具体取决于指令的上下文:作为数据值,作为RAM地址或作为ROM地址
So apparently "M" accesses are to RAM locations controlled by A. There's the indirect addressing I was missing. Now everything clicks.
显然“M”访问是由A控制的RAM位置。我缺少间接寻址。现在一切都点击了
With that confusion cleared up, now we can handle OP's question (a lot more easily).
随着这种混乱被清除,现在我们可以处理OP的问题(更容易)。
Let's start with implementing subroutine calls with the stack.
让我们从使用堆栈实现子程序调用开始。
; subroutine calling sequence
@returnaddress ; sets the A register
D=A
@subroutine
0 ; jmp
returnaddress:
...
subroutine: ; D contains return address
; all parameters must be passed in memory locations, e.g, R1-R15
; ***** subroutine entry code *****
@STK
AM=M+1 ; bump stack pointer; also set A to new SP value
M=D ; write the return address into the stack
; **** subroutine entry code end ***
<do subroutine work using any or all registers>
; **** subroutine exit code ****
@STK
AM=M-1 ; move stack pointer back
A=M ; fetch entry from stack
0; jmp ; jmp to return address
; **** subroutine exit code end ****
The "push constant" instruction can easily be translated to store into a dynamic location in the stack:
“推送常量”指令可以轻松转换为存储到堆栈中的动态位置:
@<constant> ; sets A register
D=A ; save the constant someplace safe
@STK
AM=M+1 ; bump stack pointer; also set A to new SP value
M=D ; write the constant into the stack
If we wanted to make a subroutine to push constants:
如果我们想要一个子程序来推动常量:
pushR2: ; value to push in R2
@R15 ; save return address in R15
M=D ; we can't really use the stack,...
@R2 ; because we are pushing on it
D=M
@STK
AM=M+1 ; bump stack pointer; also set A to new SP value
M=D ; write the return address into the stack
@R15
A=M
0 ; jmp
And to call the "push constant" routine:
并称为“推动常量”例程:
@<constant>
D=A
@R2
M=D
@returnaddress ; sets the A register
D=A
@pushR2
0 ; jmp
returnaddress:
To push a variable value X:
要推送变量值X:
@X
D=M
@R2
M=D
@returnaddress ; sets the A register
D=A
@pushR2
0 ; jmp
returnaddress:
A subroutine to pop a value from the stack into the D register:
一个子程序,用于将值从堆栈弹出到D寄存器:
popD:
@R15 ; save return address in R15
M=D ; we can't really use the stack,...
@STK
AM=M-1 ; decrement stack pointer; also set A to new SP value
D=M ; fetch the popped value
@R15
A=M
0 ; jmp
Now, to do the "EQ" computation that was OP's original request:
现在,要执行OP的原始请求的“EQ”计算:
EQ: ; compare values on top of stack, return boolean in D
@R15 ; save return address
M=D
@EQReturn1
D=A
@PopD
0; jmp
@EQReturn1:
@R2
M=D ; save first popped value
@EQReturn2
D=A
@PopD
0; jmp
@EQReturn2:
; here D has 2nd popped value, R2 has first
@R2
D=D-M
@EQDone
equal; jmp
@AddressOfXFFFF
D=M
EQDone: ; D contains 0 or FFFF here
@R15
A=M ; fetch return address
0; jmp
Putting it all together:
把它们放在一起:
@5 ; push constant 5
D=A
@R2
M=D
@returnaddress1
D=A
@pushR2
0 ; jmp
returnaddress1:
@X ; now push X
D=M
@R2
M=D
@returnaddress2
D=A
@pushR2
0 ; jmp
returnaddress2:
@returnaddress3 ; pop and compare the values
D=A
@EQ
0 ; jmp
returnaddress3:
At this point, OP can generate code to push D onto the stack:
此时,OP可以生成将D推入堆栈的代码:
@R2 ; push D onto stack
M=D
@returnaddress4
D=A
@pushR2
0 ; jmp
returnaddress4:
or he can generate code to branch on the value of D:
或者他可以生成代码来分支D的值:
@jmptarget
EQ ; jmp
#2
As I wrote in last comment there is a branch less way so you need to compute the return value from operands directly
正如我在上一篇评论中写的那样,有一个分支更少的方式,所以你需要直接从操作数计算返回值
Lets take the easy operation like eq
for now
让我们像eq一样轻松操作
- if I get it right
eq a,d
is something likea=(a==d)
- true is
0xFFFF
and false is0x0000
-
So this if
a==d
thena-d==0
this can be used directly所以这个如果a == d然后a-d == 0这可以直接使用
- compute
a=a-d
-
compute
OR
cascade of all bits ofa
计算a的所有位的OR级联
- if the result is 0 return 0
- if the result is 1 return 0xFFFF
- this can be achieved by table or by
0-OR_Cascade(a)
如果结果为0则返回0
如果结果为1则返回0xFFFF
这可以通过表格或0-OR_Cascade(a)来实现
-
the
OR
cascadeOR级联
- I do not see any bit shift operations in your description
- so you need to use
a+a
instead ofa<<1
- and if shift right is needed then you need to implement divide by 2
我没有在您的描述中看到任何位移操作
所以你需要使用+ a而不是<< 1
如果需要右移,那么你需要实现除以2
- compute
如果我把它做对了eq a,d就像a =(a == d)
true为0xFFFF,false为0x0000
So when I summarize this eq a,d
could look like this:
因此,当我总结这个方程式时,d看起来像这样:
a=a-d;
a=(a|(a>>1)|(a>>2)|...|(a>>15))&1
a=0-a;
- you just need to encode this into your assembly
- as you do not have division or shift directly supported may be this may be better
a=a-d;
a=(a|(a<<1)|(a<<2)|...|(a<<15))&0x8000
a=0-(a>>15);
你只需要将它编码到你的程序集中
因为你没有直接支持分裂或转移可能这可能会更好
the lower and greater comparison are much more complicated
较低和较大的比较要复杂得多
- you need to compute the carry flag of the substraction
- or use sign of the result (MSB of result)
- if you limit the operands to 15 bit then it is just the 15th bit
- for full 16 bit operands you need to compute the 16th bit of result
- for that you need to know quite a bit of logic circuits and ALU summation principles
- or divide the values to 8 bit pairs and do 2x8 bit substraction cascade
- so
a=a-d
will became: sub al,dl
sbc ah,dh
- and the carry/sign is in the 8th bit of result which is accessible
你需要计算减法的进位标志
或使用结果的符号(结果的MSB)
如果将操作数限制为15位,那么它只是第15位
对于完整的16位操作数,您需要计算结果的第16位
为此你需要了解相当多的逻辑电路和ALU求和原理
或将值除以8位对并进行2x8位减法级联
所以a = a-d将成为:
并且进位/符号位于可访问的结果的第8位