Какие методы использования потока управления есть в 6502?

Я пытаюсь понять поток управления в сборке 6502.

Скажем, у меня есть следующий код:

    ControlFlow:
      lda mem
      cmp #1
      bne .sub_one

      cmp #2
      bne .sub_two

      .sub_one:
        ; sub routine one goes here
        jmp .done ; <-------------- without this jmp, .sub_two will execute

      .sub_two:
       ; sub routine two goes here

      .done:

      rts

Лично мне бы понравился оператор switch или какая-либо другая структура потока управления. Приведенный выше JMP также касается меня. Кажется, что есть лучший способ обрабатывать несколько случаев без такого спагетти-кода.


person JohnnyStarr    schedule 19.01.2013    source источник
comment
Вы программируете на ассемблере. Вы должны построить все причудливые структуры управления самостоятельно. Если вам нужна управляющая структура, написанная кем-то другим, используйте язык высокого уровня.   -  person Raymond Chen    schedule 19.01.2013


Ответы (7)


На самом деле нет лучшего способа, но может быть улучшение, такое как вызов FlowControl как подпрограммы и возврат с RTS.

Это основной поток.

  jsr ControlFlow
  ; main routine continues here

Вот подпрограмма.

ControlFlow:
  lda mem
  cmp #1
  bne .sub_one
  cmp #2
  bne .sub_two
  cmp #3
  bne .sub_three
    ; case else here
  rts

  .sub_one:
    ; sub routine one goes here
  rts

  .sub_two:
   ; sub routine two goes here
  rts

  .sub_three:
   ; sub routine three goes here
  rts

если подпрограммы слишком длинные, вам нужно использовать JMP, как упоминалось ранее.

.jump_to_sub_one
  jmp .sub_one
.jump_to_sub_two
  jmp .sub_two
.jump_to_sub_three
  jmp .sub_three

ControlFlow:
  lda mem
  cmp #1
  bne .jump_to_sub_one
  cmp #2
  bne .jump_to_sub_two
  cmp #3
  bne .jump_to_sub_three
    ; case else here
  rts

  .sub_one:
    ; sub routine one goes here
  rts

  .sub_two:
   ; sub routine two goes here
  rts

  .sub_three:
   ; sub routine three goes here
  rts

Вот как это делается, и, к сожалению, нет лучшего способа. Это относится ко многим языкам ассемблера, если не ко всем.

person Emir Akaydın    schedule 19.01.2013
comment
Это довольно неэффективный способ построения таблицы переходов с использованием множества пар тестов и ветвей. Есть определенно лучшие способы, чем это. - person JasonD; 20.01.2013
comment
JasonD, пожалуйста, поделитесь своим альтернативным решением, тогда мы сможем обсудить. Автор пытался избавиться от переходов в конце рутины, чтобы избежать спагетти-кода, и мое решение показывает, как вы это делаете. Если бы он пытался выполнить ветвление за меньшее количество циклов или более короткий код, я бы дал другой совет. Какой, по-твоему, лучший способ для такого сценария? - person Emir Akaydın; 22.01.2013
comment
Есть по крайней мере два улучшения, которые я могу придумать навскидку; во-первых, использование косвенной адресации ZP со значением «mem» в качестве смещения в таблице подпрограмм устраняет серию CMP. Во-вторых, дополнить этот метод таблицей, содержащей начальные адреса подпрограммы, и использовать стек для подделки адреса возврата перед использованием RTS и позволить ЦП «вернуться» к соответствующей точке входа подпрограммы. В обоих случаях для таблицы переходов требуется дополнительная память, и для выполнения условного перехода требуется некоторый код настройки, но в результате получается более быстрый и чистый метод. - person Eight-Bit Guru; 22.01.2013
comment
скажем, вы пишете процедуру управления джойстиком. вы читаете адрес ввода джойстика (скажем, $dc00 для commodore 64) и начинаете сравнивать значения для левого, правого, вверх, вниз, диагонального направлений и огня. состояния не являются инкрементными, как 1,2,3... поэтому вам нужна таблица размером 256 * 2 = 512 байт для возможных случаев, подобных этому, или несколько таблиц поиска, что делает вещи еще более сложными, чем просил автор. ZP ограничен 256 байтами и даже меньше для некоторых машин, таких как Commodore 64 (вам не разрешено использовать $01). так что ваше предложение не может быть принято как общее решение, а как частный случай - person Emir Akaydın; 22.01.2013
comment
@EmirAkaydın Это не решение «особого случая»; вы придумали пример особого случая, чтобы попытаться изобразить его как таковой, но на самом деле эти концепции обычно применимы к широкому спектру сценариев условного перехода на всех машинах на базе 6502. Конкретный выбор реализации зависит от проблемы, но для вашего примера я бы, скорее всего, использовал операции логической маски, чтобы сопоставить возвращаемые джойстиком значения с чем-то, чем можно управлять без сложной логики декодирования. Если вам действительно нужен особый случай, как насчет нажатия на вектор BRK и прослушивания стека в поисках адреса возврата, на который нужно перейти...? - person Eight-Bit Guru; 23.01.2013
comment
@Jonners, это не по теме, но, пожалуйста, посмотрите мой профиль c64 здесь. csdb.dk/scener/?id=8375. как вы можете видеть, я работал над многими 256-байтовыми заставками и другими продуктами, использующими системы на базе 6502. ваш совет - это сценарий особого случая и очень хорошая оптимизация для последовательных случаев. Автор сказал: «Лично мне бы понравился оператор switch или какая-либо другая структура потока управления. Оператор switch не гарантирует последовательных случаев, не так ли? Итак, мой ответ - правильный ответ на этот вопрос. если вы хотите расширить его с помощью особых случаев, продолжайте. - person Emir Akaydın; 24.01.2013
comment
@EmirAkaydın Действительно, я не ожидал бы, что кто-либо, отвечая на этот вопрос, будет иметь меньше, чем, по крайней мере, прочную основу в практических приложениях 6502 (я отсылаю уважаемого джентльмена к моему собственному профилю здесь). Проблема в том, что ваш ответ заключен в скобки словами «лучшего пути нет» и «к сожалению, лучшего пути нет». Это заведомо ошибочно, поскольку есть несколько лучших способов в зависимости от конкретной решаемой задачи. - person Eight-Bit Guru; 24.01.2013

Таблицы переходов могут быть полезны, если количество случаев достаточно велико. Слева есть шаблон (непроверенный) для перехода к метке, который помещает правильный адрес в стек и возвращается. Справа есть подпрограмма diff на основе jsr, которая будет продолжаться с метки _out: после возврата из каждой подпрограммы. Логика переноса инвертирована на 6502, что означает, что перенос будет установлен, если (Acc >= Imm).

; goto  label[n]   vs.         call label[n]

lda variable
cmp #MAX_PLUS_ONE                          
bcs _out
tax
lda table_hi, X
pha                vs.         sta jsrcmd+2
lda table_lo, X
pha                vs.         sta jsrcmd+1
rts                vs. jsrcmd: jsr 1000        ; self modify

_out:  
person Aki Suihkonen    schedule 20.01.2013
comment
Конечно, вы также можете расширить таблицу переходов, чтобы недопустимые индексы переходили непосредственно к out. Если ваши случаи достаточно редки, вы можете записать индекс непосредственно в ветку, переходя в тщательно созданную таблицу отправки. - person JasonD; 20.01.2013
comment
Да, если известно, что количество случаев ограничено. Дальнейшие улучшения позволят выделить 2 байта от нулевой страницы для смещения таблицы и гарантировать, что каждая метка регистра переключения находится на одной и той же странице. Также в этом случае возможен самоизменяющийся относительный адрес. - person Aki Suihkonen; 20.01.2013
comment
+1 (Хех, прочитайте этот ответ после добавления моего комментария к предыдущему.) - person Eight-Bit Guru; 22.01.2013
comment
table_hi и table_lo в приведенном выше примере должны содержать адрес перехода минус один. Это связано с тем, что jsr подталкивает не сам адрес возврата, а адрес возврата минус один, следовательно, rts добавляет один к извлекаемому адресу перед переходом туда. - person lvd; 23.10.2015

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

  • абсолютный JMP
  • непрямой СПМ
  • Относительные инструкции Bxx
  • Абсолют JSR с более поздней RTS
  • BRK или другой IRQ с более поздним RTI (или RTS, если вы вытащите .P из стека)
  • Помещение двух значений в стек, а затем RTS/RTI
  • Аппаратный сброс вызывает скачок по вектору сброса

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

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

switchtab_lo .db >routine1, >routine2, >routine3

switchtab_hi .db <routine1, <routine2, <routine3

(Я никогда не могу вспомнить, означает ли> младший байт или старший байт, и разные ассемблеры могут иметь разный синтаксис)

а затем, предполагая, что значение, на которое вы хотите переключиться, находится в .X, и что vector - это два байта, которые не начинаются в конце страницы (чтобы избежать косвенной ошибки JMP), и вы убедились, что это допустимый ценность:

lda switchtab_lo,X
sta vector
lda switchtab_hi,X
sta vector+1
jmp (vector)

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

person LawrenceC    schedule 18.08.2013

        lda mem
        asl
        sta jump+1
jump    jmp (vector)

;should be page aligned
vector
        !word func1, func2, func3, func4

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

другие варианты:

         lda mem
         asl
         clc
         adc mem        ;we assume it does not overflow, so carry stays cleared
         sta branch+1   ;mem * 3
branch   bcc *
         jmp func1
         jmp func2
         jmp func3
         jmp ...
person bb_oxy    schedule 21.10.2015
comment
Здесь, во втором вашем примере, если mem содержит значение меньше 128 (а должно содержать еще меньше, так как мы умножаем его на 3), clc становится избыточным и может быть удален. - person lvd; 23.10.2015

CMOS 6502 (т. е. 65c02) также имеет режим адресации JMP(abs,X), так что вы можете взять ввод в A, сдвинуть влево на 1 бит с помощью ASL A (поскольку каждый адрес в table занимает два места), затем перенесите ее в X и выполните JMP(Addr_Table,X). Гораздо проще. Это одно из многих дополнений кода операции, сделанных в версии CMOS. (В версии CMOS также исправлены все ошибки версии NMOS).

person Garth Wilson    schedule 22.11.2013

Я не знаю, как бы вы сделали это на 6502, но switches часто компилируются в таблицы переходов.

person icktoofay    schedule 19.01.2013

У меня есть статья об использовании макросов для создания программных структур на ассемблере 6502 по адресу http://wilsonminesco.com/StructureMacros/index.html . Я сделал это и для PIC, и для обоих есть ссылки на исходный код. Дальнейшие дополнения появятся, когда я закончу проект, над которым работаю.

OP — идеальная ситуация для IF...ELSE...END_IF. Когда требуется больше случаев, таблица переходов работает хорошо, если числа идут последовательно, и вам не нужно проверять ограничения, чтобы избежать косвенного перехода из-за пределов таблицы и сбоя. В противном случае оператор CASE работает прекрасно. http://forum.6502.org/viewtopic.php?f=2&t=2311&start=15 — это вторая страница такого обсуждения, и я показываю там, как вы можете тестировать отдельные случаи, диапазон чисел или разброс чисел в одном и том же операторе CASE. У меня еще нет RANGE_OF и SET_OF, записанных как макросы сборки 6502. Эти два у меня есть только в Форте.

Цель этих макросов, конечно же, состоит в том, чтобы получить лучший контроль над кодом, делая более понятным то, что вы делаете, и избавляясь от массы меток и переходов, которые обычно характеризуют ассемблерный код. Макросы позволяют вам полностью контролировать каждую частицу кода, заложенного ассемблером, но вам не нужно постоянно смотреть на внутренние детали. В большинстве случаев абсолютно нулевая потеря памяти программы или скорость выполнения. Вы получаете производительность ассемблера с множеством преимуществ языков более высокого уровня. Код становится быстрее в разработке, без ошибок и легче в обслуживании, а это означает, что легче вернуться позже и выяснить, что вы сделали, когда решили добавить функцию или что-то изменить.

person Garth Wilson    schedule 04.03.2013