Подтвердить что ты не робот

Как я могу написать интерпретатор для "eq" для языка сборки Hack?

Я читаю и изучаю Элементы вычислительных систем, но я застрял в какой-то момент. Пример главы пропустить следующие 5 инструкций s можно найти здесь.

В любом случае, я пытаюсь реализовать виртуальную машину (или байтовый код для переводчика сборки), но я застрял, чтобы пропустить следующую 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) возвращают логические значения. ВМ представляет true и false как 􏰁-1 (минус один, 0xFFFF) и 0 (ноль, 0x0000) соответственно.

Поэтому мне нужно поместить два значения в регистры A и D соответственно, что довольно просто. Но как я должен создать код сборки, который будет проверять значения и нажимать 1, если результат равен true или 0, если результат является ложным?

Код сборки, поддерживаемый для Hack Computer, выглядит следующим образом:

enter image description hereenter image description hereenter image description here

Я могу сделать что-то вроде:

push constant 5
push constant 6
sub

который будет удерживать значение 0, если 2 значения, нажатые в стек, равны или! 0, если нет, но как это помогает? Я попытался использовать D & A или D & M, но это тоже не помогло.

Я также могу ввести условный скачок, но как я должен знать, с какой инструкцией перейти? Код сборки Hack не имеет чего-то вроде "пропустить следующие 5 инструкций" или т.д.

[edit by Spektre] сводка целевой платформы, как я ее вижу

  • 16-битная архитектура фон Неймана (адрес - 15 бит с 16-битным доступом к Word)
  • Память данных 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, если последний нажал клавишу?)

enter image description here

4b9b3361

Ответ 1

Кажется, есть еще одна глава, которая более точно определяет процессор Hack. В нем говорится:

Процессор Hack состоит из ALU, указанного в главе 2 и трех регистры, называемые регистром данных (D), адресным регистром (A) и программой счетчик (ПК). D и A - 16-разрядные регистры общего назначения, которые могут быть управляются арифметическими и логическими инструкциями, такими как A = D-1, D = D | A, и т.д., следуя за машинным языком Hack, указанным в главе 4. Хотя D-регистр используется исключительно для хранения данных, содержимое A-регистра можно интерпретировать тремя различными способами, в зависимости от контекста команд: как значение данных, как ОЗУ адрес или адрес ROM

Таким образом, по-видимому, "M" обращаются к ячейкам RAM, контролируемым A. Там косвенная адресация, которую я отсутствовал. Теперь все нажимает.

С устранением этой путаницы, теперь мы можем справиться с вопросом 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

И для вызова подпрограммы "push constant":

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

Теперь, чтобы выполнить вычисление "EQ", которое было исходным запросом OP:

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

Ответ 2

Как я писал в последнем комментарии, существует меньше ветки, поэтому вам нужно непосредственно вычислить возвращаемое значение из операндов

Позволяет выполнить легкую операцию, например eq

  • если я правильно понял, eq a,d это что-то вроде a=(a==d)
  • true 0xFFFF, а false 0x0000
  • Итак, если a==d then a-d==0 это можно использовать напрямую

    • вычислить a=a-d
    • вычислить OR каскад всех бит a

      • если результат равен 0 return 0
      • если результат равен 1 return 0xFFFF
      • это может быть достигнуто таблицей или 0-OR_Cascade(a)
    • каскад 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-битные пары и выполните каскад субкадров 2х8 бит
  • поэтому a=a-d станет:
  • sub al,dl
  • sbc ah,dh
  • и знак переноса/находится в 8-м бите результата, который доступен