Пытаюсь понять ассемблерный код 65c816 — какова цель такого порядка инструкций?

Я новичок в сборке и пытаюсь понять, что делает этот фрагмент кода. Вот эталон Super NES 65c816, который я использую.

      01: $A4917E AD E5 05    LDA $05E5  [$A4:05E5]
02: $A49181 29 3F 00    AND #$003F
03: $A49184 AA          TAX
04: $A49185 AD E5 05    LDA $05E5  [$A4:05E5]
05: $A49188 89 02 00    BIT #$0002
06: $A4918B D0 05       BNE $05    [$9192]
07: $A4918D 8A          TXA
08: $A4918E 49 FF FF    EOR #$FFFF
09: $A49191 AA          TAX
10: $A49192 8A          TXA
11: $A49193 18          CLC
12: $A49194 6D 7A 0F    ADC $0F7A  [$A4:0F7A]
13: $A49197 85 12       STA $12    [$00:0012]
14: $A49199 AD E5 05    LDA $05E5  [$A4:05E5]
15: $A4919C 29 00 1F    AND #$1F00
16: $A4919F EB          XBA
17: $A491A0 85 14       STA $14    [$00:0014]

Я уже знаю, что $05E5 — это место хранения игрового генератора случайных чисел (ГСЧ). Это 16-битное значение. Я пытаюсь выяснить, какие математические операции пытается выполнить игра с этим случайным числом.

Вот более тривиальный пример для начала:

      $86AE1C AD E5 05    LDA $05E5  [$86:05E5]
$86AE1F 29 01 00    AND #$0001
$86AE22 F0 05       BEQ $05    [$AE29]

Здесь явно просто используется случайное число для подбрасывания монеты — самый высокий бит энтропии равен 0 или 1 — чтобы решить, переходить или нет.

Возвращаясь к исходному примеру, позвольте мне сказать, что (я думаю) я понимаю, по частям:

      01: $A4917E AD E5 05    LDA $05E5  [$A4:05E5]
02: $A49181 29 3F 00    AND #$003F
03: $A49184 AA          TAX

Это загрузка случайного числа в аккумулятор, скажем, и его И0000000000111111, уступая0000000000100010в аккумуляторе, который затем переносится в регистр X.

      04: $A49185 AD E5 05    LDA $05E5  [$A4:05E5]

Затем он загружает то же самое случайное число в аккумулятор — наверное, потому, что мы потеряли его во время операции И? (Случайное число генерируется в другом месте, выполняясь примерно раз в 1/60 секунды. Но здесь оно гарантированно будет таким же.)

      05: $A49188 89 02 00    BIT #$0002
06: $A4918B D0 05       BNE $05    [$9192]

Вот где это становится нечетким для меня. Я прочитал несколько ресурсов в Интернете (хотя и не конкретно для 65816), в которых говорилось, что BIT похож на AND, но без изменения ни одного операнда. Но, похоже, есть некоторые нюансы, связанные с флагами.

  1. Продолжая мой пример, правильно ли я считаю, чтоBIT #$0002на значение аккумулятора11010101001100010будет 1, потому что второй младший бит равен 1?

  2. Правильно ли я считаю, что в результате проверки битов значение флага z будет установлено на 0, что является полной противоположностью предыдущему результату?

  3. Правильно ли я считаю, что тогда он разветвится (к инструкциям по адресу $05), поскольку Z=0?

Хорошо, если я понял эти основы, то вот мои настоящие вопросы:

  • Почему разработчики (которые в то время все оптимизировали ) не поменяли местами строки 01-03 на 04-06?То есть, если есть шанс, что вы в конечном итоге перейдете к какому-то совершенно другому фрагменту кода, почему бы не протестировать и не выполнить сначала переход, и только если нет ветвления, выполнить операцию И? (Подождите, возможно ли, что регистр X может использоваться везде, куда переходит код?)

  • Может ли кто-нибудь помочь мне понять следующие несколько инструкций? В частности, какой смысл выполнять TAX, а затем TXA? Разве это не просто замена, а затем замена значений?

Извините, это много вопросов в одном, но, надеюсь, все в порядке, потому что я имею в виду один и тот же набор инструкций. Спасибо заранее за любую помощь.

PS Вот страница о BIT в учебнике, который у меня есть о 65816, на случай, если это поможет.

Я не мог понять абзац, начинающийся со слов «BIT обычно используется непосредственно перед инструкцией условного перехода:» , хотя это кажется уместным...

3 ответа

Отвечая на один из вопросов об этих строках

      06: $A4918B D0 05       BNE $05    [$9192]
07: $A4918D 8A          TXA
08: $A4918E 49 FF FF    EOR #$FFFF
09: $A49191 AA          TAX
10: $A49192 8A          TXA

Почему следует ?

Это из-за ветки в строке 6, которая, если ее взять, будет выполняться из строки 10.

The TAXпоказанное соответствует предыдущему TXA(не следующий) как в

      07: $A4918D 8A          TXA
08: $A4918E 49 FF FF    EOR #$FFFF
09: $A49191 AA          TAX

Некоторые пробелы или метки в коде сделали бы эту «фразировку» более понятной, хотя ветвь действительно указывает адрес назначения.


Вопросы 1-3 о флаге: вы правы, говоря, чтоZфлаг устанавливается , если тестируемый результат0.

  1. Правильно ли я считаю, что тогда он разветвится (к инструкциям по адресу $05), поскольку Z=0?

Да, но$05это не адрес, это смещение со знаком (из инструкции, которая должна была следовать, потому чтоPCсчетчик программ (он жеIPуказатель инструкции) уже был бы продвинут к моменту принятия решения).

Может ли кто-нибудь помочь мне понять следующие несколько инструкций? В частности, какой смысл выполнять TAX, а затем TXA? Разве это не просто замена, а затем замена значений?

  • Это вообще не обмен ! ЭтиTxxинструкции копируют значение из одного регистра в другой, оставляя оба регистра равными друг другу.
  • Инструкция в строке 6 вызвала два разных пути выполнения, которые в конечном итоге должны были снова сойтись. Программист мог бы использовать для этого безусловный переход, но в конечном итоге этого не сделал, потому что предпочел размер кода скорости выполнения:

The BNEинструкция либо проваливается по первому пути, либо переходит ко второму пути. В конце первого пути,BRAпрыгает в точку схода. Это решение требует на 1 байт больше (3 вместо 2), но первый путь выполняется на 1 цикл меньше (3 вместо 4, если нет пересечения границы страницы, тогда это также будет 4 цикла):

      06: $A4918B D0 06       BNE $06    [$9193]
    *** first path
07: $A4918D 8A          TXA
08: $A4918E 49 FF FF    EOR #$FFFF
09: $A49191 .. 01       BRA $01    [$9194]
    *** second path
10: $A49193 8A          TXA
    *** come together point
11: $A49194 18          CLC

The TAXИнструкция используется, чтобы избежать использования более дорогостоящей инструкции перехода. Он устанавливает регистр X, чтобы код мог провалиться вTXAинструкция по второму пути. Это решение требует на 1 байт меньше (2 вместо 3), но первый путь выполняется на 1 такт больше (4 вместо 3):

      06: $A4918B D0 05       BNE $05    [$9192]
    *** first path
07: $A4918D 8A          TXA
08: $A4918E 49 FF FF    EOR #$FFFF
09: $A49191 AA          TAX
    *** second path
10: $A49192 8A          TXA
    *** come together point
11: $A49193 18          CLC

то же самое случайное число в аккумулятор... Но здесь гарантированно будет то же самое.

Почему этот код такой сложный?

Исходя из того, что случайное число из 05E5 остается прежним, и зная, чтоBITне изменяет ни одного из своих операндов, и что бит, который мы проверяем, находится среди битов, оставшихся отAND, мы можем написать этот код, даже не касаясь X:

      01: $A4917E AD E5 05    LDA $05E5  [$A4:05E5]
02: $A49181 29 3F 00    AND #$003F
05: $A49184 89 02 00    BIT #$0002
06: $A49187 D0 03       BNE $03    [$918C]
08: $A49189 49 FF FF    EOR #$FFFF
11: $A4918C 18          CLC

Что касается этого вопроса и ответа Weather Vane, я думаю, в зависимости от значения RNG (0x05E5) вы можете догадаться, почему это делается именно так:

  • Если значение равно 0x3F (важные биты взяты здесь для демонстрационных целей)
      01: $A4917E AD E5 05    LDA $05E5  [$A4:05E5] ;  A=0x3F, X=?
02: $A49181 29 3F 00    AND #$003F ; A=0x3F, X=?, Y=?, flags: Z=0
03: $A49184 AA          TAX ; A=0x3F X=0x3F
04: $A49185 AD E5 05    LDA $05E5  [$A4:05E5] ; A=0x3F, X=0x3F
05: $A49188 89 02 00    BIT #$0002; A=0x3F, X=0x3F, flags: Z=0
06: $A4918B D0 05       BNE $05    [$9192] ; Branch !
10: $A49192 8A          TXA A=0x3F, X=0x3F
11: $A49193 18          CLC ; Continue code
  • Если значение ВRNG & 0x3F == 0 && RNG & 0x2 == 0, например 0x40:
      01: $A4917E AD E5 05    LDA $05E5  [$A4:05E5] ; A=0x40
02: $A49181 29 3F 00    AND #$003F ; Z=0
03: $A49184 AA          TAX ; A=0x40, X=0x40
04: $A49185 AD E5 05    LDA $05E5  [$A4:05E5] ; A=0x40
05: $A49188 89 02 00    BIT #$0002 ; A=0x40 Z=1
06: $A4918B D0 05       BNE $05    [$9192] ; No branching here
07: $A4918D 8A          TXA ; A=0x40, X=0x40
08: $A4918E 49 FF FF    EOR #$FFFF ; A=0xBF, X=0x40 , assuming 8 bits mode but I'll talk about that later
09: $A49191 AA          TAX ; A=0xBF, X=0xBF
10: $A49192 8A          TXA ; A=0xBF, X=0xBF
11: $A49193 18          CLC ; c=0

Глядя на это, да, возникает вопрос: почему разработчик не поменял местами и не сделал TAX / TXA?

Мое лучшее предположение по первому вопросу такое же, как у флюгера, потому что ветка. Я думаю, что у него есть еще одна цель: очистить флаги.

До сих пор мы не говорили о 8/16-битном режиме (флаг m) или других флагах. Я думаю, что это точка CLC после того, как также очистить флаги.

При использовании инструкций передачи, в зависимости от флага m (который определяет режим ширины памяти), вы копируете флаги, некоторые очищаете.

В моем источнике http://6502.org/tutorials/65c816opcodes.html#6.10.1 они приводят следующий пример:

Пример: если аккумулятор равен $1234, регистр X равен $ABCD, а флаг m равен 1, то после TXA:

  • аккумулятор будет $12CD
  • флаг n будет равен 1 (поскольку фактически был передан только $CD)
  • флаг z будет равен 0

Я предполагаю, что это только сбрасывает флаги до известного состояния.


О вашем последнем вопросе:

Я не мог понять начало абзаца: «БИТ обычно используется непосредственно перед инструкцией условного перехода:», хотя это кажется уместным...

Возможность выполнить побитовую проверку без изменения ваших значений позволяет выполнять дополнительные инструкции. В противном случае вам снова понадобится LDA, как это сделано в строке 4. Вот как я бы это использовал.

Другие вопросы по тегам