Question

I am trying to understand control flow in 6502 assembly.

Say I have the following code:

    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

Personally, I would love a switch statement or some other control flow structure. The JMP above concerns me as well. It seems like there is a better way to handle multiple cases without this type of spaghetti code.

Was it helpful?

Solution 2

There is no better way actually but there might be an improvement like calling the FlowControl as a subroutine and returning with RTS.

This is the main flow.

  jsr ControlFlow
  ; main routine continues here

Here is the sub routine.

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

if sub routines are too long, you need to use JMPs as mentioned before.

.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

That's how it is done and unfortunately, there is no better way. This applies to many assembly languages if not all.

OTHER TIPS

Jump tables can be useful, if the number of cases is large enough. On the left, there's a template (untested) for jump to label that pushes the correct address to stack and returns. On the right there's a diff to jsr based routine, that will continue at the label _out: after returning from each subroutine. The carry logic is inverted on 6502, meaning that carry will be set if (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 provides the following to control program flow, i.e. modify the PC register.

  • JMP absolute
  • JMP indirect
  • Bxx relative instructions
  • JSR absolute with a later RTS
  • BRK or other IRQ with a later RTI (or RTS if you pull .P off the stack)
  • Pushing two values to the stack and then RTS/RTI
  • Hardware reset causes jump through the reset vector

That's it. If you want anything more complicated you need to create it using one or more of the above.

One way to implement a switch statement is to first create a table of pointers to all routines involved in the switch statement. Split them according to the low bytes of the routines and then the high bytes:

switchtab_lo .db >routine1, >routine2, >routine3

switchtab_hi .db <routine1, <routine2, <routine3

(I can never remember if > means low byte or high byte and different assemblers may have different syntax)

and then, assuming the value you want to switch upon is in .X, and that vector is two bytes that don't start at the end of the page (to avoid the JMP indirect bug) and you've made sure it's a valid value:

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

It's tedious to do this each time you need to switch but that's why high level languages were invented.

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

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

if the list of vectors is not aligned, on needs to add the index*2 to the whole vector address, slower, but more memory efficient.

other options would be:

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

The CMOS 6502 (ie, 65c02) also has the JMP(abs,X) addressing mode, so you can take the input in A, shift left 1 bit with ASL A, (because each address in the table takes two locations), then transfer it to X and do the JMP(Addr_Table,X). Much simpler. This is one of many op-code additions made in the CMOS version. (The CMOS version also fixed all the bugs of the NMOS version).

I don't know how you'd do this on a 6502, but switches are often compiled into jump tables.

I have an article on using macros to make program structures in 6502 assembly, at http://wilsonminesco.com/StructureMacros/index.html . I've done it for PIC also, and the links are there for the source code for both. Further additions are coming when I finish a project I'm working on.

The OP is a perfect situation for IF...ELSE...END_IF. When more cases are desired, the jump table works well if the numbers are consecutive and you don't have to test for limits to avoid jumping indirect from outside the table and crashing. Otherwise a CASE statement works nicely. http://forum.6502.org/viewtopic.php?f=2&t=2311&start=15 is the second page of such a discussion, and I show there how you could test for individual cases, a range of numbers, or a scattering of numbers, all in the same CASE statement. I don't have RANGE_OF and SET_OF written yet as 6502 assembly macros. Those are two I only have in Forth.

The goal of these macros of course is to get better control of code by making it much more clear what you're doing, and getting rid of the mass of labels and jumps that commonly characterize assembly code. The macros let you keep full control of every speck of code laid down by the assembler, but you don't have to keep looking at the internal details. In most cases there is absolutely zero penalty in program memory taken or in execution speed. You get the performance of assembly with a lot of the benefits of higher-level languages. Code becomes quicker to develop, more bug-free, and easier to maintain, meaning that it's easier to come back later and figure out what you did when you decide to add a feature or change something.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top