MOS 6502 to Motorola 6809 Assembly Language Transcoding

I’ve started looking at some MOS 6502 assembly code and I’m in the process of transcoding it to Motorola 6809 assembly. I’ve found an excellent PDF online describing the process of translating programs from the 6502 to the 6809 starting on page 77 of the July, 1982 edition of MICRO – The 6502/6809 Journal magazine there is a similar PDF on the Color Computer archive found here, but it’s not laid out as nicely as the magazine version. The article discusses the theory of doing a 6502 to 6809 translation and possible work arounds. I found that actually doing the translation in the real world that there are a few more things that need to be handled and some things that can be done in the process that may help speed up the 6809 code without actually re-writing the routine.

I won’t go over the same info from the PDF linked above such as the 6502 is little-endian and the 6809 is big-endian and tons more.

Once you managed to go through the 6502 disassembled code and generally converted it to a 6809 version the first thing I would suggest doing (especially if it’s a big program) would be to figure out in each routine if the accumulator and registers are used & changed and put that info in the comment for each JSR to that routine in the entire program. That way when you are doing your translation you know if the A,X or Y are used or changed in a routine so you know you can or can’t modify them while translating it to 6809 code. Also you can see if you can get away with using the B accumulator instead of X or Y in the code before the JSR.

Below are some things I’ve learned so far in my transcoding journey. The first thing that will bite you in the butt is that after a compare (CMP) on the 6502 and a compare (CMP) on the 6809 the carry bit will be the opposite.

Example of 6502 code:

CMP  #$41
BCC  Jumpahead

Example of 6809 version to execute the same way:

CMPA  #$41
BCS   Jumpahead

Similarly, 6502 code that does:

CMP  #$41
BCS  Jumpsomeplace

Example of 6809 version to execute the same way:

CMPA #$41
BCC  Jumpsomeplace

I’ve also found there are times in 6502 code where a CMP is done then immediately after an ADC is done, for example the 6502 code:

CMP  RamAddress
ADC  #$34

You will have to work around this with a few more lines of code on the 6809 as:

  CMPA  RamAddress
  BCS   >            ; carry is the opposite on 6809 so we don't add carry bit
  INCA
! ADDA  #$34

Sometimes the CMPA is used before a ROL or ROR command, 6502 code:

    CMP #$80             * test and set/clear the carry bit
    ROR A                * Use the carry bit in the ROR instruction

6809 version (kills accumulator B’s value):

    CMPA    #$80         * Carry bit will be reversed on the 6809
    RORB                 * Move the carry bit into bit 7 of B
    COMB                 * Flip the bits
    ROLB                 * Move bit 7 of B to the carry bit

    RORA                 * Use the carry bit in the ROR instruction

I’ve found that the carry bit is very important in 6502 code since the 6502 doesn’t have an ADDA instruction but only has an ADC (Add with Carry) instruction that it’s very important to follow the code and make sure the carry after a compare instruction is what you expect since it will be added to the accumulator.

I’ve found many places in 6502 code where the carry is cleared with the SEC instruction just before the ADC instruction.

6502 example:

CLC          ; Clear the carry flag
ADC  #$20    ; A = A + $20 + carry (carry is zero)

The 6809 version can skip the clearing of the carry flag and simply use the ADDA instruction:

ADDA  #$20   ; A = A + $20

If the carry flag is set before and ADC instruction on the 6502:

SEC           ; Set the carry flag
ADC   #$20    ; A = A + $20 + carry (carry is 1)

You can do this on the 6809 without setting the carry flag with:

ADDA  #$20+1   ; A = A + $20 + 1

The 6502 SBC instruction is a little different than the 6809. The 6502 does a subtraction of a memory location (or direct value) – the complement of the carry bit. The 6809 SBCA instruction does a subtraction from a memory location (or direct value) – the value of the carry bit. Here’s an example and a 6809 workaround.

6502 code:

 SBC  #$52     ; A = A - $52 - the complement of the Carry bit value

6809 code version:

  BCS   >            * if the carry is set skip ahead
  DECA               * otherwise if the carry is clear then A = A - 1
! SUBA  #$52         * A = A - $52

6809 version of the same code (this kills the B accumulator value):

 RORB                 * Move the carry bit into bit 7 of B
 COMB                 * Flip the bits
 ROLB                 * Move bit 7 of B to the carry bit
 SBCA   #$52          * A = A - $52 - the carry bit value

Darren Atkinson posted a comment below with an even better way to handle the 6502 SBC (Thanks Darren) translation as:

  ADCA  #$00        * If the Carry is set A=A+1, which will cancel the +1 below 
SUBA #$52+1 * A = A - $53

A little cleaner way to do the same thing and leave the SUBA be the same as the SBC you could:

  ADCA  #$FF        * If the Carry is set A=A+(-1)+1, if carry is clear the A=A+(-1)+0 
  SUBA  #$52        * A = A - $52

To do subtraction the 6502 only has the SBC (Subtract with carry) instruction, the carry flag afterwards is the opposite of the 6809. Just as a compare is done using subtraction but the resulting values are thrown away and only the condition code flags are kept.

Another thing the 6502 has is the SED instruction that puts the 6502 in decimal mode so that ADC and SBC are done with binary coded decimal versions of numbers. The 6809 does it a little differently as you use the DAA instruction after the ADDA or ADCA instruction.

A 6502 example of decimal addition:

CLC          ; Clear the carry flag
SED          ; Set Decimal Mode - put the processor in decimal mode
LDA   #$25   ; A = $25
ADCA  #$27   ; A = $25 + $27 resulting in $52
CLD          ; Clear Decimal Mode - put it in binary mode

On the 6809 the same could be done with:

LDA   #$25
ADDA  #$27   ; Produces binary result of $4C
DAA          ; Adjusts A to $52 (BCD result of $25 + $27)

Since the 6502 X & Y registers are 8 bits there are many times when a transfer command from A to Y or A to X or X to A or Y to A are used. Since the X & Y registers on the 6809 are 16 bit registers the TFR command will store the 8 bit accumulator to the LSB of the X or Y registers but the MSB of X & Y will always be $FF. To get around this we can add $100 to the register after the TFR command to fix the value.

A transfer from A to X on the 6502:

LDA  #$23  ; A = $23
TAX        ; X = $23  [2 cycles]

A similar transfer from A to X on the 6809:

LDA  #$23      ; A = $23
TFR  A,X       ; 8 bit accumulator to 16 bit register, X = $FF23  [6 cycles]
LEAX $0100,X   ; X=$23 [8 cycles]

Or

LDA  #$23
TFR  A,B       ; B = $23 [6 cycles]
CLRA           ; A = 0, D=$0023  [2 cycles]
TFR  D,X       ; X = $0023 [6 cycles]

It is faster and simpler to do a CLRA at the start of your 6809 translation of the program and use B as the accumulator in place of A throughout your entire 6809 translated code. Then you could simply do a TFR D,X or TFR D,Y as a direct substitute for 6502 TAX and TAY instructions. A TFR D,X or TFR D,Y is 6 cycles as the other method is 6 cycles + 8 cycles (for the LEAX. $100,X) = 14 cycles.

Example of what I mean would be:

CLRA        ; A = 0  [2 cycles]
...
LDB  #$23   ; B = $23
TFR  D,X    ; X = $0023  [6 cycles]

One other thing you could do (under certain circumstances) to speed up your translation but keep using A as the main accumulator would be change 6502 code such as this:

6 cycles:
   TAX                     ; Transfer A to X
   LDA  VelocityTable,X    ; Get velocity value from lookup table

Versions of the 6809 code:

27 Cycle version:
   TFR      A,X       ; Transfer A to X, X = $FF00 + unsigned value of A
   LEAX $0100,X       ; Fix X so now X = A
   LDA  VelocityTable,X    ; Get velocity value from lookup table

23 Cycle version:
   TFR      A,X       ; Transfer A to X, X = $FF00 + unsigned value of A
   LEAX $0100+VelocityTable,X ; Fix X and add the pointer table vale to X
                      ; X = VelocityTable + unsigned value of A
   LDA  ,X            ; Get velocity value from lookup table

16 or 13 cycle version:
    LDX    #VelocityTable       ; X = VelocityTable
    TSTA
    BPL    >
    LDX    #VelocityTable+$100  ; X = VelocityTable+$100
!   LDA    A,X                  ;Get velocity value from lookup table

If you know the value of A is always going to be between 0 and 127 then use    8 cycles as:
    LDX    #VelocityTable       ; X = VelocityTable
    LDA    A,X                  ;Get velocity value from lookup table

I’m getting a little into optimizations here but as usual you can make it even faster if you start dealing with the game logic yourself and use B instead of A for this routine. We can use the super fast ABX instruction which adds the unsigned value of B to X as:

10 Cycle version:
If B was used in the code instead of A (no TFR is needed)
   LDX   #VelocityTable  ; X = VelocityTable
   ABX                   ; X = X + unsigned value of B
   LDA   ,X              ; Get velocity value from lookup table

6502 – Indirect Indexed (I found this excellent description from here)

This mode is only used with the Y register. It differs in the order that Y is applied to the indirectly fetched address. An example instruction that uses indirect index addressing is LDA ($86),Y . To calculate the target address, the CPU will first fetch the address stored at zero page location $86. That address will be added to register Y to get the final target address. For LDA ($86),Y, if the address stored at $86 is $4028 (memory is 0086: 28 40, remember little endian) and register Y contains $10, then the final target address would be $4038. Register A will be loaded with the contents of memory at $4038.

Indirect Indexed instructions are 2 bytes – the second byte is the zero-page address – $86 in the example. (So the fetched address has to be stored in the zero page.)

While indexed indirect addressing will only generate a zero-page address, this mode’s target address is not wrapped – it can be anywhere in the 16-bit address space.

6502 instruction:

 STA (VecRamPtr),y

6809 versions:

Without changing any of the original 6502 source code:
 PSHS  A		; Save A
 LDD   VecRamPtr	; Get the address in D
 EXG   A,B		; change 6502 little endian to 6809 big endian
 LEAU  D,Y		; U = D + Y
 PULS  A		; Restore A
 STA   ,U		; Store A at U

If you fix the little endian of the 6502 source code a head of time:
 PSHS  A
 LDD   VecRamPtr
 LEAU  D,Y
 PULS  A
 STA   ,U

Another way with self mod code:
 LDU  VecRamPtr
 STU  Fix+1
Fix:
 LEAU $FFFF,Y
 STA  ,U

Fastest way, if you fixed little endian ahead of time and re-arranged the code so that you use B instead of Y in the 6502 source code you could then use:
 LDX  VecRamPtr
 ABX
 STA  ,X

6502 – Indexed Indirect (I found this excellent description from here)

This mode is only used with the X register. Consider a situation where the instruction is LDA ($20,X), X contains $04, and memory at $24 contains 0024: 74 20, First, X is added to $20 to get $24. The target address will be fetched from $24 resulting in a target address of $2074. Register A will be loaded with the contents of memory at $2074.

If X + the immediate byte will wrap around to a zero-page address. So you could code that like targetAddress = (X + opcode[1]) & 0xFF.

Indexed Indirect instructions are 2 bytes – the second byte is the zero-page address – $20 in the example. Obviously the fetched address has to be stored in the zero page.

6502 version:

LDA ($20,X)

6809 version:

LDA [$20,X]

That’s about all I’ve come across so far, if I find more as I look at more code I’ll add it above. If you know of anything else that the 6502 does that might bite me in the butt please comment below and I’ll add it to this list.

See you in the next post,

Glen

This entry was posted in CoCo Programming. Bookmark the permalink.

7 Responses to MOS 6502 to Motorola 6809 Assembly Language Transcoding

  1. Daniel says:

    So the goal is to create a basic 6502 to 6809 transcoder as a starting point to bringing in new and exciting games and software?

    I think it would be hilarious to transcode the commodore 64 kernel over šŸ™‚

    • nowhereman999 says:

      Yes you definately need a tool to help you get started, then you will have to hand tweak the code with some of the exceptions I noted above and probably plenty more, depending on the size of the original program. The Commodore 64 BASIC ROM would be a BIG under taking, but there’s probably tons of great resources out there on the hardware, which would help a lot. I would enjoy seeing that, but the Commodore 64 wasn’t my machine when I was a kid so it doesn’t really mean much to me. But maybe some other coders would like the challenge.

      • Daniel says:

        LOL! You don’t have to complete it! Just set to 40 columns, set the colors and the title screen… then anything that follows gives an error. šŸ™‚ Nah I’m with ya… wouldn’t be worth the work and based on all other things, nothing would work in it because they way they wrote code on c64, it was almost nothing but hacks and completely unreadable.

        So what would you want to bring over from 6502 land?

  2. Phillip Eaton (D-Type) says:

    Good article, thanks for publishing it. My interest is in bringing 6502 games to Vectrex, such as from the VIC-20, although Vectrex conversion poses other challenges!

    • nowhereman999 says:

      Sounds like a great project! Iā€™m sure the vectored part will make things more difficult, but it will be cool in the end and once you do one project future ones should go faster. Good luck with it.

  3. Darren A says:

    Be careful with TFR or EXG when using different sized registers, as the actual behaviour is different on 6309 vs 6809. The TFR A,X instruction will set both the MSB and LSB of X to the value in A on a 6309 (instead of setting the MSB to $FF).

    For the subtraction code, how about:
    adca #0
    suba #$52+1

    • nowhereman999 says:

      Hi Darren, thanks for the comment. I was aware of the differences with the TFR and EXG with the 6809 and 6309 while I was working through the code above. In my mind it was going to be only for a 6809. But now that I have 6309’s in all my CoCo’s I’m going to have to tweak the code to be more 6809/6309 friendly.

      As for the subtraction code, that is awesome! Your version doesn’t require a check of the carry and the code is nice and tight. I’ll edit this post and include your version for others to see.

      Cheers,
      Glen

Leave a comment