As a project to use my 6809 SBC and learn more about 6809 assembly language programming, I wrote a disassembler that can run on the board. The design was loosely based on the one I did for the 6502.
While straightforward in principle, a disassembler for the 6809 is a little more challenging that for the 6502 for a number of reasons:
- It supports many instructions.
- Some instructions have unique operand formats (e.g. TFR/EXG, PSH/PULS).
- There are many (24) different indexed addressing modes.
- The instruction length can change based on the indexed addressing mode being used.
- Some instructions are two bytes long, prefixed by $10 or $11 (the so-called page 2 and page 3 op codes).
It is mostly portable. It uses ASSIST09 monitor routines for i/o, but other than that could be used on other systems with minor changes.
I implemented over a few evenings, building it up in pieces. It took some time (but less than I expected) to handle all the index addressing modes and to correctly handle the differing instruction lengths and page 2/3 instructions.
My basic approaches for testing and debug were:
- Test low level functions, like PrintString, on their own with some test code.
- "Desk check" complex code on paper to try to verify the logic, use of registers, etc.
- Build it up in stages and confirm them working before adding on (e.g. initially just display hex bytes)
I initially ran it standalone, but at the end I was able to also support installing it as an external command in the ASSSIST09 monitor, so you can run it by typing U (for unassemble). ASSIST09 has calls to add new commands, so it can be done without changing the code in ROM.
Here is some sample output (disassembling the start of the ASSIST09 ROM code):
F800 30 8D 68 BE LEAX $68BE ,PCR
F804 1F 10 TFR X,D
F806 1F 8B TFR A,DP
F808 97 9D STA $9D
F80A 33 84 LEAU ,X
F80C 31 8C 35 LEAY $35 ,PCR
F80F EF 81 STU ,X++
F811 C6 16 LDB #$16
F813 34 04 PSHS B
F815 1F 20 TFR Y,D
F817 E3 A1 ADDD ,Y++
F819 ED 81 STD ,X++
F81B 6A E4 DEC ,S
F81D 26 F6 BNE $F815
F81F C6 0D LDB #$0D
F821 A6 A0 LDA ,Y+
F823 A7 80 STA ,X+
F825 5A DECB
F826 26 F9 BNE $F821
F828 31 8D F7 D4 LEAY $F7D4 ,PCR
F82C 8E 20 FE LDX #$20FE
F82F AC A1 CMPX ,Y++
F831 26 02 BNE $F835
F833 AD A4 JSR ,Y
With a little more 6809 assembly language programming experience under my belt, it is interesting to compare it to the other 8-bit processor I am most familiar with, the 6502. Let me list a few thoughts here.
Having two accumulators is very handy. Often one is being used as an accumulator for some form of data, but another may be needed for indexing or as a parameter to calling another routine. Surprisingly, I also found myself using the 16-bit D register (which uses A and B) because I needed 16-bit data. Some functions, like addition and subtraction, are supported on the D register, but many only have 8-bit versions which you need to combine using several instructions to work on 16 bits of data.
Two index registers is also handy (the 6502 has two, but the 6809's predecessor the 6800 only had one). But I found in practice that I rarely needed a second one. Having 16-bit index registers, unlike the 8-bit registers of the 6502, was much more convenient as you often find you are using them to store addresses.
The 6502 typically makes the programmer heavily use addresses in the first 256 bytes of memory (page 0). Some key instructions and addressing modes can only work with page zero. For that reason, these tend to become valuable real estate and you can run out of them or run into collisions between different uses of them. In contrast, the 6809 has a more orthogonal instruction set and all relevant instructions can work with full 16-bit addresses. It does have direct mode (8-bit addresses) but with the Direct Page register you can have them work with any page of memory, not just page zero. I did not use this feature (the ASSIST09 monitor does) but I can see it being useful when you wanted to optimize the size of your code.
In general the 6809 is more orthogonal than the 6502, with few limitations on the addressing modes or operands of instructions. Unlike the 6502 you can push, pull, transfer, or exchange any registers. The push and pull (PSHS/PSHU/PULS/PULU) instructions are particularly nice in that you can push or pull a set of registers in one instruction. The order of push and pull is defined, so that you do not have to ensure that the order is correct in your code (provided you push and pull the same list of registers). There are two stack pointers, but I only used one. And of course, the 16-bit stack pointer is much more reasonable than the 8-bit stack on the 6502 that is fixed in page 1 of memory.
Transfer (TFR) and Exchange (EXG) are two other instructions which are very handy for moving registers around without any restrictions. The 6502 had some limitations on what registers you could transfer and had no equivalent to EXG.
The 6809 introduced a MULtiply instruction, which sounded at the time like a luxury for assembly language programmers. I actually used it in an early version of my disassembler program when I needed to multiply the offset to a table by 4 and get a 16-bit result. I felt it was overkill using a multiply to do this, and later did it using two shifts (you can easily implement a 16-bit shift of the D register using two instructions: ASLB, ROLA).
The 6809 has many more addressing modes than the 6502, with 24 variations of indexed addressing. I think it is unlikely that assembly language programmers would use the majority of these as just a few of them generally suffice. I suspect the original intention may have been to help support compilers of high-level languages that could use these.
One quirk with indexed addressing that could confuse programmers is that the 5, 8, and 16 b-t offset modes consider the offset to be signed. So, for example, the 5 bit offsets are not 0 to 31 but rather -16 to 15. If you have a lookup table of 256 elements, for example, and expect the 8-bit offset indexed addressing mode to access all of them starting from an offset of zero, you will be surprised when values of $80 and higher don't read the table entries you expected. I ran into this, and solved it by using 16-bit offset in this case.
One instruction that you could easily overlook is LEA (Load Effective Address). It might not seem particularly useful, essentially removing one level of indirection. In fact it is very useful, and is the key to a lot of efficient programming as it can make use of all of the indexed addressing modes and make code position independent.
The 6809 supports position independent code (PIC). The ASSIST09 monitor, for example, is fully position independent. It is not particularly hard to write PIC code, and I made some effort to try this in the disassembler. Mostly it is a matter of using branches rather than jumps and using the PCR (program counter relative) addressing mode when working with addresses. I did leave the memory locations in RAM used by the program at fixed addresses. One annoyance is that as code gets larger during development 8-bit branches often fail and need to be converted to long branches. Unlike some assemblers, the one I was using did not automatically select short or long branches as needed.
Overall, the 6809 is quite a pleasant processor to program, with more instructions, more and larger registers than the 6502. Of course, it is not really a fair comparison as it came a generation later and could make use of advanced in technology that allowed a more complex chip. The 6800 was of the same vintage as the 6502. In fact, the decision to use the 6502 over the 6800 in a number of early computers was based on cost. Compared to the $300 price tag of the 6800, the 6502 sold for $25. The Apple 1 was originally designed to use a 6800, but was converted to the 6502 when Steve Wozniak learned about it. The schematic diagram for the Apple 1 still listed the circuit changes needed on the board to use a 6800.