Режимы адресации

Вернуться на главную страницу

Содержание

Режимы адресации

Режим адресации (адресация памяти) - способ обращения процессора к своей памяти.

Любая инструкция процессора состоит как минимум из опкода инструкции. В зависимости от выбранного режима адресации, после опкода может быть дополнительно записан операнд (аргумент) этой инструкции размером 1 или 2 байта. В итоге получается инструкция размером от 1-го до 3-х байтов.

Разные инструкции могут использовать один и тот же режим адресации. У одних инструкций может быть больше выбора режимов адресации, чем у других.

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

Immediate

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

Операнд инструкции размером 1 байт, в нем указывается константа, с которым будет работать эта инструкция.

00:8100: A0 0F LDY #$0F ; загрузить байт #$0F в регистр Y
00:8102: E0 33 CPX #$33 ; сравнить регистр X с байтом #$33
00:8104: 69 1C ADC #$1C ; сложить регистр A с байтом #$1C

Чтобы инструкция LDY использовала режим адресации Immediate, она записывается с помощью опкода A0. После опкода ставится операнд 0F для загрузки в регистр Y байта #$0F.

Zero Page

Адресация нулевой страницы. Нулевой страницей являются адреса RAM в диапазоне $0000-$00FF. Старший байт адресов этого диапазона = #$00, поэтому страница называется "нулевая".

8-битная адресация Zero Page служит для экономии ресурсов процессора, поскольку затрачивает меньше тактов на выполнение инструкции по сравнению с режимом адресации Absolute.

По этой причине данные, которые будут использованы кодом игры чаще, чем другие, обычно находятся именно в нулевой странице для снижения нагрузки на процессор при регулярной работе с этими данными. За счет сэкономленных тактов можно успеть выполнить больше кода за кадр.

Операнд инструкции размером 1 байт, которым указывается младший байт адреса. А старший байт адреса всегда будет = #$00.

00:8100: 85 4D STA $4D ; записать байт из регистра A в адрес $004D
00:8102: C6 03 DEC $03 ; уменьшить на #$01 байт в адресе $0003
00:8104: 45 A1 EOR $A1 ; исключающее ИЛИ байта из регистра A и байта из адреса $00A1

Чтобы инструкция STA использовала режим адресации Zero Page, она записывается с помощью опкода 85. После опкода ставится операнд 4D для указания адреса $4D, в который будет записан байт из регистра A.

Absolute

Абсолютная адресация. Полноценная 16-битная адресация, позволяет инструкциям работать с любым адресом памяти процессора (NES Memory) в диапазоне $0000-$FFFF.

Операнд инструкции размером 2 байта, ими указывается 16-битный адрес. Из-за особенностей архитектуры процессора, в операнде старший и младший байты адреса всегда записываются в обратном порядке, такой формат называется little-endian.

00:8100: AE 23 01 LDX $0123 ; загрузить в регистр X байт из адреса $0123
00:8103: 0E 45 03 ASL $0345 ; арифметический сдвиг битов влево у байта в адресе $0345
00:8106: ED AB 89 SBC $89AB ; вычесть из регистра A байт из адреса $89AB

Чтобы инструкция LDX использовала режим адресации Absolute, она записывается с помощью опкода AE. После опкода ставится операнд 23 01 для указания адреса $0123, из которого будет загружен байт в регистр X.

Сравнение с Zero Page

Поскольку адресация Absolute затрагивает весь возможный диапазон $0000-$FFFF, ей можно указывать и адреса из нулевой страницы. Однако так делать нежелательно, ведь выполнение таких инструкций затрачивает больше тактов, чем с адресацией Zero Page, и сама инструкция по размеру на 1 байт больше, а результат будет точно такой же.

00:8100: AD 11 00 LDA $0011 ; требует 4 такта на выполнение инструкции, размер 3 байта
00:8103: A5 11 LDA $11 ; требует 3 такта на выполнение инструкции, размер 2 байта

Необходимость прибегнуть к режиму адресации Absolute для указания адресов нулевой страницы есть только в том случае, когда у выбранной инструкции не существует варианта с адресацией Zero Page Indexed, а есть только Absolute Indexed. Например, у инструкции LDA,Y.

Indexed

Индексная адресация. При вычислении адреса учитывается значение индексного регистра X или Y. К адресу, указанному в операнде инструкции, добавляется текущее значение индексного регистра во время выполнения инструкции. После сложения операнда и индексного регистра получается итоговый адрес, с которым будет работать инструкция.

Инструкция, работающая с индексным регистром, не может использовать этот же регистр с режимом адресации Indexed. То есть не существует инструкций вроде LDX,X или LDY,Y.

Поскольку значение индексного регистра может быть в диапазоне от #$00 до #$FF, при помощи одной-единственной инструкции можно обратиться к целому диапазону размером 256 адресов, изменяя значение индексного регистра перед выполнением этой инструкции.

Этот подход очень часто применяется при создании циклов и подпрограмм. А также адресация Indexed - одна из наиболее важных причин, почему однотипные адреса у различных игровых объектов, например их координаты, очень часто находятся в RAM по соседству. Ведь достаточно загрузить порядковый номер объекта в нужный индексный регистр, и можно работать с адресом именно этого объекта через ту же самую инструкуцию.

Адресация Indexed может быть использована как с режимом адресации Zero Page, так и Absolute. У некоторых инструкций есть ограничение по выбору этих разновидностей адресации Indexed, в основном ограничения по использованию регистра Y.

Также адресация Indexed может использоваться с адресацией Indirect, которая описана отдельно.

Zero Page Indexed

Используется вместе с адресацией Zero Page (диапазон $0000-$00FF). Поскольку указывается адрес нулевой страницы, операнд инструкции должен быть размером 1 байт.

00:8100: A9 10 LDA #$10 ; загрузить байт #$10 в регистр A
00:8102: A2 00 LDX #$00 ; загрузить байт #$00 в регистр X
00:8104: 95 99 STA $99,X ; записать байт из регистра A в адрес $0099 ($0099 + #$00)
00:8106: E8 INX ; увеличить байт в регистре X на #$01 (X = #$01)
00:8107: 95 99 STA $99,X ; записать байт из регистра A в адрес $009A ($0099 + #$01)
00:8109: E8 INX ; увеличить байт в регистре X на #$01 (X = #$02)
00:810A: 95 99 STA $99,X ; записать байт из регистра A в адрес $009B ($0099 + #$02)

Чтобы инструкция STA использовала режим адресации Zero Page,X, она записывается с помощью опкода 95. После опкода ставится операнд 99 для указания адреса $99,X, в который будет записан байт из регистра A. Изменяя байт в регистре X, при выполнении инструкций STA $99,X меняется итоговый адрес, в который произойдет запись байта из регистра A.

Нельзя выйти за пределы нулевой страницы, используя адресацию Zero Page Indexed. Например, выполняя инструкцию STA $FF,X с текущим значением X = #$01, байт из регистра A будет записан в адрес $0000, а не $0100.

Absolute Indexed

Используется вместе с адресацией Absolute (диапазон $0000-$FFFF). Операнд будет размером 2 байта, старший и младший байты адреса записываются в обратном порядке.

В примере показан цикл, с помощью которого происходит поочередное копирование байтов из адресов $E000-$E005 в адреса $0120-$0125.

00:8100: A0 00 LDY #$00 ; загрузить байт #$00 в регистр Y
00:8102: B9 00 E0 LDA $E000,Y ; загрузить байт из адреса $E000,Y в регистр A
00:8105: 99 20 01 STA $0120,Y ; записать байт из регистра A в адрес $0120,Y
00:8108: C8 INY ; увеличить байт в регистре Y на #$01
00:8109: C0 06 CPY #$06 ; сравнить Y с байтом #$06 (обновление состояния флага Z)
00:810B: D0 F5 BNE $8102 ; перейти на адрес $8102, если Z = 0 (продолжить цикл копирования)

Чтобы инструкция LDA использовала режим адресации Absolute,Y, она записывается с помощью опкода B9. После опкода ставится операнд 00 E0 для указания адреса $E000,Y, из которого будет загружен байт в регистр A. Изменяя байт в регистре Y, изменятся итоговые адреса в инструкциях LDA $E000,Y и STA $0120,Y при их выполнении.

Нельзя выйти за пределы диапазона $0000-$FFFF, используя адресацию Absolute Indexed. Например, выполняя инструкцию LDA $FFFF,Y с текущим значением Y = #$01, байт в регистр A будет загружен из адреса $0000, а не $10000.

Увеличение затрачиваемых тактов

У некоторых инструкций при использовании адресации Absolute Indexed будет затрачен +1 дополнительный такт. Это происходит в случае перехода на новую страницу адресов, то есть когда при сложении операнда и значения индексного регистра увеличивается старший байт адреса, указанного в операнде инструкции.

00:8100: A2 0F LDX #$0F ; загрузить байт #$0F в регистр X
00:8102: BC F0 06 LDY $06F0,X ; $06F0 + #$0F = $06FF, 4 такта на выполнение инструкции
00:8105: E8 INX ; увеличить байт в регистре X на #$01
00:8106: BC F0 06 LDY $06F0,X ; $06F0 + #$10 = $0700, 5 тактов на выполнение инструкции
00:8109: E8 INX ; увеличить байт в регистре X на #$01
00:810A: BC F0 06 LDY $06F0,X ; $06F0 + #$11 = $0701, 5 тактов на выполнение инструкции

Indirect

Косвенная (непрямая) адресация. Итоговый адрес вычисляется исходя из байтов, находящихся в двух соседних адресах. В первом адресе находится младший байт итогового адреса, а во втором - старший байт.

Эти 2 соседних адреса могут находиться в любом месте диапазона $0000-$FFFF, но в подавляющем большинстве случаев это будут адреса RAM $0000-$07FF, так как в RAM можно без труда менять байты на нужные, чтобы контролировать косвенный адрес.

Indirect Jump

Единственная инструкция, использующая чистый режим адресации Indirect - JMP. Благодаря ей можно совершать прыжки на различные участки кода, подготавливая соответствующие байты для адреса прыжка.

Операнд инструкции размером 2 байта, ими указывается 16-битный адрес первого соседнего адреса. Старший и младший байты этого адреса записываются в обратном порядке.

00:8100: 6C 20 01 JMP ($0120) ; непрямой прыжок

Адрес $0120 содержит младший байт итогового адреса, а соседний адрес $0121 - старший байт. Чтобы инструкция JMP ($0120) совершила прыжок на адрес $89AB, надо записать байт #$AB в адрес $0120 и байт #$89 в адрес $0121. Порядок записи не имеет значения, но первым принято записывать младший байт.

00:8100: A9 AB LDA #$AB ; загрузка младшего байта
00:8102: 8D 20 01 STA $0120 = #$AB ; запись младшего байта
00:8105: A9 89 LDA #$89 ; загрузка старшего байта
00:8107: 8D 21 01 STA $0121 = #$89 ; запись старшего байта
00:810A: 6C 20 01 JMP ($0120) = $89AB ; итоговый адрес $89AB

Если подготовить таблицу с младшими и старшими байтами, и записывать их в 2 соседних адреса RAM, выбранных в операнде инструкции Indirect JMP, можно создавать любое количество развилок в своем коде.

Технические особенности

В процессоре 6502 существует баг, из-за которого нельзя выйти за пределы страницы памяти, если в операнде инструкции указать младший байт адреса #$FF.

00:8100: 6C FF 02 JMP ($02FF) ; младший байт считывается из $02FF, а старший байт из $0200

Однако эмулятор FCEUX не эмулирует данную особенность, в нем соседним адресом будет считаться адрес $0300.

Post-Indexed Indirect

Косвенно-индексная адресация. В отличие от обычной индексной адресации, итоговый адрес контролируется при помощи регистра Y. Этот режим адресации используется для более гибкого обращения к памяти, чем обычная индексная адресация, а также при работе с таблицами, которые по размеру больше, чем 256 байтов.

Для инструкции, использующей косвенно-индексную адресацию, требуется 2 соседних адреса в нулевой странице. Операнд инструкции размером 1 байт, которым указывается младший байт первого адреса нулевой страницы.

00:8100: B1 DE LDA ($DE),Y ; соседние адреса $00DE и $00DF

Если в операнде инструкции указать младший байт адреса #$FF, то соседним адресом будет считаться $0000, а не $0100.

Как и в случае с обычной индексной адресацией, по соседним адресам записываются младший и старший байты итогового адреса.

00:8100: A9 80 LDA #$80 ; загрузка младшего байта
00:8102: 85 DE STA $DE = #$80 ; запись младшего байта
00:8104: A9 F0 LDA #$F0 ; загрузка старшего байта
00:8106: 85 DF STA $DF = #$F0 ; запись старшего байта

Получив итоговый адрес $F080, можно добавлять к нему значение регистра Y, который нужно подготовить перед выполнением инструкции LDA ($DE),Y.

00:8108: A0 00 LDY #$00 ; Y = #$00
00:810A: B1 DE LDA ($DE),Y @ $F080 ; $F080 + #$00
00:810C: C8 INY ; Y = #$01
00:810D: B1 DE LDA ($DE),Y @ $F081 ; $F080 + #$01
00:810F: C8 INY ; Y = #$02
00:8110: B1 DE LDA ($DE),Y @ $F082 ; $F080 + #$02

Это позволит работать с диапазоном памяти размером 256 адресов. Но в отличие от обычной индексной адресации, ты можешь изменять начальный адрес этого диапазона когда это небходимо.

Увеличение затрачиваемых тактов

У некоторых инструкций при использовании адресации Post-Indexed Indirect будет затрачен +1 дополнительный такт. Это происходит в случае перехода на новую страницу адресов, то есть когда к адресу, вычисляемому по младшему и старшему байтам в соседних адресах, прибавляется значения индексного регистра, после чего увеличивается старший байт итогового адреса.

00:8100: A0 7F LDY #$7F ; Y = #$7F
00:8102: B1 DE LDA ($DE),Y @ $F0FF ; $F080 + #$7F, 5 тактов на выполнение инструкции
00:8104: C8 INY ; Y = #$80
00:8105: B1 DE LDA ($DE),Y @ $F100 ; $F080 + #$80, 6 тактов на выполнение инструкции
00:8107: C8 INY ; Y = #$81
00:8108: B1 DE LDA ($DE),Y @ $F101 ; $F080 + #$81, 6 тактов на выполнение инструкции

Pre-Indexed Indirect

Индексно-косвенная адресация. Встречается в коде довольно редко, так как использование этого режима адресации слишком затратно и неэффективно.

Отличается от косвенно-индексной тем, что здесь используется индексный регистр X. Этот регистр влияет не на итоговый адрес, а на тот адрес, откуда будут считываться младший и старший байты для итогового адреса.

В примере подготавливаются младшие и старшие байты сразу в двух парах соседних адресов нулевой страницы.

00:8100: A9 CD LDA #$CD ; загрузка младшего байта
00:8102: 85 DE STA $DE = #$AB ; запись младшего байта
00:8104: A9 AB LDA #$AB ; загрузка старшего байта
00:8106: 85 DF STA $DF = #$CD ; запись старшего байта
00:8108: A9 25 LDA #$25 ; загрузка младшего байта
00:810A: 85 E0 STA $E0 = #$25 ; запись младшего байта
00:810C: A9 05 LDA #$05 ; загрузка старшего байта
00:810E: 85 E1 STA $E1 = #$05 ; запись старшего байта
00:8110: A2 00 LDX #$00 ; вычисление адреса из байтов в $00DE и $00DF ($00DE + #$00)
00:8112: A1 DE LDA ($DE,X) @ $ABCD ; загрузка байта в A из $ABCD
00:8114: A2 02 LDX #$02 ; вычисление адреса из байтов в $00E0 и $00E1 ($00DE + #$02)
00:8116: A1 DE LDA ($DE,X) @ $0525 ; загрузка байта в A из $0525

По итогу всех этих действий удалось получить лишь 2 адреса - $ABCD и $0525. Слишком дорогое удовольствие с учетом того, что ради этого было выделено целых 4 адреса в нулевой странице. Чтобы повлиять на итоговые адреса, надо изменять байты в соседних адресах нулевой страницы.

Указав в операнде инструкции с индексно-косвенным режимом адресации младший байт адреса #$FF, либо попытавшись увеличить старший байт адреса при сложении значения регистра X, соседние адреса все равно будут находиться в пределах нулевой страницы.

Relative

Относительный режим адресации. Используется только в инструкциях условного перехода, которые проверяют состояние флагов и совершают относительный переход при выполнении условия инструкции.

Режим называется "относительный", потому что он не имеет конкретного адреса перехода. В операнде, который размером 1 байт, указывается дистанция того, сколько байтов нужно "перепрыгнуть" относительно адреса нижестоящей инструкции.

Относительный переход вперед

В операнде инструкции условного перехода ставится положительный байт #$00-#$7F. Начальным адресом перехода является адрес, в котором находится следующая по списку инструкция. Например, если инструкция BEQ находится по адресу $8102, значит следующая инструкция будет по адресу $8104, это и есть начальный адрес для перехода.

00:8100: A6 DE LDX $DE
00:8102: F0 00 BEQ $8104 ; операнд #$00, значит адрес перехода $8104
00:8104: A0 01 LDY #$01
00:8106: 84 DF STY $DF
00:8108: 60 RTS

К начальному адресу перехода добавляется операнд инструкции. Если операнд #$01, адрес будет $8105, если #$02, то адрес $8106, и так далее. Если нужно при выполнении условия перейти на инструкцию RTS по адресу $8108, требуется поменять операнд на #$04.

00:8100: A6 DE LDX $DE
00:8102: F0 04 BEQ $8108 ; $8104 + #$04
00:8104: A0 01 LDY #$01
00:8106: 84 DF STY $DF
00:8108: 60 RTS

Относительный переход назад

Операндом ставится отрицательный байт #$80-#$FF. Если байт #$00 это начальный адрес перехода, то #$FF - смещение адреса перехода на 1 байт назад, #$FE - смещение на 2 байта назад, и так далее.

Очень часто обратный переход используется при создании циклов, чтобы повторно выполнить его тело, пока не будет выполнено условие выхода из цикла.

00:8100: A2 03 LDX #$03
00:8102: AD FF 01 LDA $01FF
00:8105: 9D 00 01 STA $0100,X
00:8108: CA DEX
00:8109: 10 FA BPL $8105 ; переход назад на $8105

Если операндом записать байт #$FE, то при выполнении своего условия инструкция перейдет сама на себя, повторно выполнит свое условие и снова перейдет на себя. Это приведет к созданию бесконечного цикла.

Увеличение затрачиваемых тактов

Если условие инструкции условного перехода не выполняется, будет затрачено 2 такта. Если же условие выполняется, затрачивается 3 такта.

00:8100: A9 00 LDA #$00 ; Z = 0
00:8102: D0 07 BNE $810B ; условие не выполнено, 2 такта
00:8104: F0 05 BEQ $810B ; условие выполнено, 3 такта

А если условие выполняется, и при вычислении адреса перехода старший байт этого адреса будет увеличен/уменьшен на #$01 относительно начального адреса, затрачивается 4 такта.

00:8100: A9 00 LDA #$00
00:80FC: F0 02 BEQ $8100 ; начальный адрес $80FE, старший байт адреса перехода = #$81, 4 такта
00:80FE: EA NOP
00:80FF: EA NOP
00:8100: F0 EE BEQ $80F0 ; начальный адрес $8102, старший байт адреса перехода = #$80, 4 такта

Рекомендации по вычислению операнда

С опытом короткие дистанции можно будет вычислить на глаз, а иногда даже заранее будешь ставить нужный байт, когда уже знаешь какой именно код тебе нужно прописать. Но лучше все же не пытаться вычислять операнды до тех пор, пока код не будет написан полностью. Временно оставляй операнд #$00.

Универсальный способ

Чтобы правильно подобрать дистанцию условного перехода, нужно в Hex Editor'е изменить байт операнда инструкции на тот, который ты считаешь приблизительно правильным. Затем кликни на Debugger, который обновит адрес перехода с учетом операнда. При необходимости скорректируй байт в ту или иную сторону и повторно сверься с Debugger'ом.

Для большей наглядности создай временный комментарий для адреса, на который нужно перейти. Если операнд записан правильно, этот комментарий должен отобразиться еще и в адресе перехода у инструкции Bxx.

Дистанция перехода вперед

В Hex Editor'е выделяешь байты, которые требуется перепрыгнуть. Возьмем код из первого примера, где в операнде инструкции BEQ записано #$00.

00:8100: A6 DE LDX $DE
00:8102: F0 00 BEQ $8104
00:8104: A0 01 LDY #$01
00:8106: 84 DF STY $DF
00:8108: 60 RTS

Чтобы вычислить операнд для перехода на адрес $8108, нужно начать выделение с байта опкода инструкции, находящейся сразу после BEQ, а закончить выделение прямо перед опкодом инструкции, на которую должен быть совершен переход. То есть надо выделить байты в диапазоне $8104-$8107.

В названии окна будет отображено количество выделенных байтов HEX-числом (#$04), этот байт нужно записать в опкоде BEQ для относительного перехода на $8108.

Дистанция перехода назад

Рекомендуется использовать окно Inline Assembler, в котором можно указать конкретный адрес условного перехода. Операнд будет вычислен автоматически. Этот же способ походит и для указания адреса перехода вперед.

Альтернативно можно придумать свою формулу, например "операнд = #$100 + желаемый адрес перехода - базовый адрес перехода". #$100 + $8105 - $810B = #$FA. С этим поможет калькулятор.

Прочие режимы адресации

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

Accumulator

Инструкции ASL, LSR, ROL и ROR, использующие данный режим адресации, работают непосредственно с аккумулятором (регистром A).

Implied

"Подразумеваемый" (неявный) режим адресации. Инструкция уже содержит информацию о том, с каким регистром нужно работать. Таких инструкций довольно много, к примеру INX, TYA, SEC, RTS.