16

我正在阅读和研究计算系统的元素,但我被困在某一点上。示例章节跳过接下来的 5 条指令可以在这里找到。

无论如何,我正在尝试实现一个虚拟机(或一个字节码到汇编翻译器),但我被困在跳过接下来的 5 条指令。

您可以在此处找到汇编符号。

目标是实现一个翻译器,将特定的字节码翻译成这个汇编代码。

我成功完成的一个例子是字节码

push constant 5

翻译为:

@5
D=A
@256
M=D

正如我所说,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]

嗯,这很简单。根据定义,存储器位置 256 是堆栈的顶部。所以

push constant 5
push constant 98

将被翻译成:

@5
D=A
@256
M=D
@98
D=A
@257
M=D

这都很好..

我还想再举一个例子:

push constant 5
push constant 98
add

被翻译成:

@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

我认为这很清楚。

但是我不知道如何翻译字节码

eq

到大会。eq 的定义如下:

其中三个命令(eq、gt、lt)返回布尔值。VM 将真假分别表示为 -1(减一,0xFFFF)和 0(零,0x0000)。

所以我需要分别向寄存器 A 和 D 弹出两个值,这很容易。但是我应该如何创建一个汇编代码来检查值并在结果为真时压入 1,如果结果为假则压入 0?

Hack Computer 支持的汇编代码如下:

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

我可以做类似的事情:

push constant 5
push constant 6
sub

如果推入堆栈的 2 个值相等,它将保持值 0 或 !0 如果不相等,但这有什么帮助?我尝试使用 D&A 或 D&M,但这也无济于事..

我也可以引入条件跳转,但我怎么知道要跳转到哪条指令?哈克汇编代码没有“跳过接下来的 5 条指令”之类的东西。

[由 Spektre 编辑] 我看到的目标平台摘要

  • 16 位冯诺依曼架构(地址为 15 位,16 位字访问)
  • 数据存储器 32KW(读/写)
  • 指令(程序)内存 32KW(只读)
  • 本机 16 位寄存器 A,D
  • 通用 16 位寄存器 R0-R15 映射到数据存储器的 0x0000 - 0x000F
  • 这些很可能也用于:SP(R0),LCL(R1),ARG(R2),This(R3),That(R4)
  • 屏幕映射到 0x4000-0x5FFF 的数据存储器(512x256 B/W 像素 8KW)
  • 键盘在 0x6000 处映射到数据存储器(如果最后一次按键,则为 ASCII 码?)

在此处输入图像描述

4

2 回答 2

10

似乎还有另一章更明确地定义了 Hack CPU。它说:

Hack CPU 由第 2 章中指定的 ALU 和三个寄存器组成,分别称为数据寄存器 (D)、地址寄存器 (A) 和程序计数器 (PC)。D 和 A 是通用 16 位寄存器,可以按照第 4 章中指定的 Hack 机器语言,通过算术和逻辑指令(如 A=D-1 、 D=D|A 等)进行操作。而 D -register 仅用于存储数据值,A 寄存器的内容可以用三种不同的方式解释,具体取决于指令的上下文:作为数据值、作为 RAM 地址或作为 ROM 地址

因此,显然“M”访问是对由 A 控制的 RAM 位置。 这是我缺少的间接寻址。 现在一切都点击了。

消除了这种困惑,现在我们可以处理 OP 的问题(更容易)。

让我们从使用堆栈实现子程序调用开始。

     ; 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 ****

“push constant”指令可以很容易地转换为存储到堆栈中的动态位置:

     @<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

如果我们想创建一个子程序来推送常量:

   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

并调用“推送常量”例程:

     @<constant>
     D=A
     @R2
     M=D
     @returnaddress   ; sets the A register
     D=A
     @pushR2
     0 ; jmp
  returnaddress:

要推送变量值 X:

     @X
     D=M
     @R2
     M=D
     @returnaddress   ; sets the A register
     D=A
     @pushR2
     0 ; jmp
  returnaddress:

将堆栈中的值弹出到 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

现在,执行 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

把它们放在一起:

     @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:

此时,OP 可以生成代码将 D 压入堆栈:

     @R2                ; push D onto stack
     M=D
     @returnaddress4 
     D=A
     @pushR2
     0 ; jmp
  returnaddress4:

或者他可以生成代码以根据 D 的值进行分支:

     @jmptarget
     EQ ; jmp
于 2015-05-16T10:39:16.960 回答
1

正如我在上一条评论中所写,有一种无分支方式,因此您需要直接从操作数计算返回值

让我们像eq现在这样简单的操作

  • 如果我做对eq a,d了,就像a=(a==d)
  • 0xFFFF和假是0x0000
  • so this if a==dthen a-d==0this 可以直接使用

    1. 计算a=a-d
    2. 计算OR所有位的级联a

      • 如果结果为 0 返回 0
      • 如果结果为 1,则返回 0xFFFF
      • 这可以通过表格或通过0-OR_Cascade(a)
    3. OR级联_

      • 我在您的描述中没有看到任何位移操作
      • 所以你需要使用a+a而不是a<<1
      • 如果需要右移,那么你需要实现除以 2

所以当我总结这eq a,d可能看起来像这样:

  • a=a-d;
  • a=(a|(a>>1)|(a>>2)|...|(a>>15))&1
  • a=0-a;
  • 您只需要将其编码到您的程序集中
  • 因为您没有直接支持的部门或班次,这可能会更好
  • a=a-d;
  • a=(a|(a<<1)|(a<<2)|...|(a<<15))&0x8000
  • a=0-(a>>15);

较低和较大的比较要复杂得多

  • 您需要计算减法的进位标志
  • 或使用结果的符号(结果的 MSB)
  • 如果您将操作数限制为 15 位,那么它只是第 15 位
  • 对于完整的 16 位操作数,您需要计算结果的第 16 位
  • 为此,您需要了解相当多的逻辑电路和ALU求和原理
  • 或将值划分为 8 位对并执行 2x8 位减法级联
  • 所以a=a-d会变成:
  • sub al,dl
  • sbc ah,dh
  • 并且进位/符号位于结果的第 8 位,可访问
于 2015-05-12T20:56:26.187 回答