【问题标题】:What methods for using control flow are there in 6502?6502中有哪些使用控制流的方法?
【发布时间】:2013-01-19 04:12:02
【问题描述】:

我正在尝试了解 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 也与我有关。似乎有更好的处理方式 很多情况下都没有这种意大利面条代码。

【问题讨论】:

  • 嗯,你正在用汇编语言编程。您必须自己构建所有花哨的控制结构。如果您想要其他人编写的控制结构,请使用高级语言。

标签: assembly 6502 retro-computing


【解决方案1】:

如果事例数量足够大,跳转表会很有用。 在左侧,有一个模板(未经测试)用于跳转到标签,它将正确的地址推送到堆栈并返回。右侧有一个 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:  

【讨论】:

  • 当然你也可以扩展跳转表,让无效索引直接跳转到out。如果您的案例足够稀疏,您可以将索引直接写入分支,分支到精心编写的调度表中。
  • 是的,如果已知案件数量有限。进一步的改进将把从零页开始的 2 个字节用于表偏移,并确保每个 switch case 标签驻留在同一页上。在这种情况下,也可以使用自我修改的相对地址。
  • +1 (嘿,将我的评论添加到上面的评论之后阅读此答案。)
  • 上例中的table_hitable_lo必须包含跳转地址一个。这是因为jsr 推送的不是返回地址本身,而是返回地址减一,因此rts 在弹出的地址上加一。
【解决方案2】:

实际上没有更好的方法,但可能会有改进,例如将 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

就是这样完成的,不幸的是,没有更好的方法。这适用于许多汇编语言,如果不是全部的话。

【讨论】:

  • 使用许多测试+分支对来构建跳转表是一种非常低效的方法。肯定有比这更好的方法。
  • JasonD,请分享您的替代解决方案,然后我们可以讨论。作者试图摆脱常规跳转的结束以避免意大利面条代码,我的解决方案展示了你是如何做到的。如果他尝试以更少的周期或更短的代码进行分支,我会给出不同的建议。对于这样的场景,你的“绝对更好的方法”是什么?
  • 我能想到的至少有两个改进;首先,使用 ZP 间接寻址并将“mem”值作为子程序表的偏移量消除了 CMP 系列。其次,使用包含子程序起始地址的表来增强该技术,并在使用 RTS 之前使用堆栈伪造返回地址,并允许 CPU “返回”到适当的子程序入口点。在这两种情况下,跳转表都有存储开销,并且需要一些设置代码来执行条件跳转,但结果是一种更快、更简洁的技术。
  • @Jonners,这不是主题,但请在此处查看我的 c64 个人资料。 csdb.dk/scener/?id=8375。如您所见,我使用基于 6502 的系统开发了许多 256 字节的介绍和其他产品。您的建议是一种特殊情况,对顺序情况进行了非常好的优化。作者说“就我个人而言,我会喜欢 switch 语句或其他一些控制流结构。”。 switch 语句不能保证连续的情况,是吗?所以,我的答案是这个问题的正确答案。如果你想用特殊情况扩展它,请继续。
  • @EmirAkaydın 事实上,我不希望任何回答这个问题的人在实际 6502 应用程序中至少有扎实的基础(我在此将这位尊贵的先生介绍给我自己的个人资料)。问题是您的答案被“没有更好的方法”和“不幸的是,没有更好的方法”这两个词括起来。这显然是错误的,因为根据要解决的具体问题,有几种更好的方法。
【解决方案3】:

6502提供以下控制程序流程,即修改PC寄存器。

  • JMP 绝对值
  • JMP 间接
  • Bxx 相关说明
  • 具有更高 RTS 的绝对 JSR
  • BRK 或其他具有更高 RTI 的 IRQ(或 RTS,如果您将 .P 从堆栈中拉出)
  • 将两个值推入堆栈,然后 RTS/
  • 硬件复位导致跳过复位向量

就是这样。如果您想要更复杂的东西,您需要使用上述一种或多种方法来创建它。

实现 switch 语句的一种方法是首先创建一个指向 switch 语句中涉及的所有例程的指针表。按照例程的低字节再高字节进行拆分:

switchtab_lo .db &gt;routine1, &gt;routine2, &gt;routine3

switchtab_hi .db &lt;routine1, &lt;routine2, &lt;routine3

(我不记得 > 是指低字节还是高字节,不同的汇编程序可能有不同的语法)

然后,假设您要切换的值在 .X 中,并且 vector 是两个不从页面末尾开始的字节(以避免 JMP 间接错误)并且您已经确保它是一个有效值:

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

每次需要切换时都这样做很乏味,但这就是发明高级语言的原因。

【讨论】:

    【解决方案4】:
            lda mem
            asl
            sta jump+1
    jump    jmp (vector)
    
    ;should be page aligned
    vector
            !word func1, func2, func3, func4
    

    如果向量列表没有对齐,on需要将索引*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 ...
    

    【讨论】:

    • 在第二个示例中,如果 mem 恰好包含小于 128 的值(并且它应该包含更少,因为我们将它乘以 3),clc 变得多余并且可以被删除。
    【解决方案5】:

    CMOS 6502(即 65c02)也有 JMP(abs,X) 寻址模式,所以你可以在 A 中取输入,用 ASL A 左移 1 位,(因为每个表中的地址占用两个位置),然后将其传输到 X 并执行 JMP(Addr_Table,X)。简单得多。这是 CMOS 版本中添加的许多操作码之一。 (CMOS版本也修复了NMOS版本的所有bug)。

    【讨论】:

      【解决方案6】:

      我不知道你会如何在 6502 上执行此操作,但 switches 通常会编译为 jump tables

      【讨论】:

        【解决方案7】:

        我在http://wilsonminesco.com/StructureMacros/index.html 有一篇关于使用宏在 6502 汇编中制作程序结构的文章。我也为 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 汇编宏。这些是我只在 Forth 中拥有的两个。

        当然,这些宏的目标是通过更清楚地了解您在做什么,并摆脱通常表征汇编代码的大量标签和跳转,从而更好地控制代码。宏让您可以完全控制汇编程序编写的每一段代码,但您不必一直查看内部细节。在大多数情况下,占用的程序内存或执行速度绝对为零。您可以获得汇编的性能以及高级语言的许多好处。代码开发得更快、更无错误且更易于维护,这意味着当您决定添加功能或更改某些内容时,您可以更轻松地稍后返回并弄清楚您做了什么。

        【讨论】:

          猜你喜欢
          • 2010-09-21
          • 2019-02-04
          • 1970-01-01
          • 2013-08-05
          • 1970-01-01
          • 1970-01-01
          • 2021-09-21
          • 2010-09-15
          • 2019-01-24
          相关资源
          最近更新 更多