Какие методы использования потока управления существуют в 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 выше касается и меня. Кажется, что есть лучший способ обработки нескольких случаев без такого типа кода спагетти.

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

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

Таблицы переходов могут быть полезны, если количество дел достаточно велико. Слева есть шаблон (непроверенный) для перехода к метке, который помещает правильный адрес в стек и возвращает. Справа есть подпрограмма на основе diff to 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:  

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

  • JMP абсолют
  • 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)

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

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

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

У меня есть статья об использовании макросов для создания структур программы в сборке 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 макросов сборки. Это те два, которые у меня есть только в Форт.

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

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

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