* * * * *

      A Motorola 6809 assembler—there are many like it, but this is mine

I think it's time I start talking about some of the software I write, and I
might as well start with my latest project that I've been having way too much
fun writing, a 6809 assembler written in C [1].

Yes, I could use an existing 6809 assembler, but most of the ones availble as
source seem to be based off one written in 1993 by L. C. Benschop. And the
code quality there is … of its time … which I think is the most charitable
thing I can say about it. Here's the code to convert text to a decimal
number:

-----[ C ]-----
short scandecimal()
{
char c;
short t=0;
c=*srcptr++;
while(isdigit(c)) {
 t=t*10+c-'0';
 c=*srcptr++;
}
srcptr--;
return t;
}
-----[ END OF LINE ]-----

Lots of globals, lots of “magic” numbers (at least they're described in
comments), and vwl mprd variable names. It's not a pleasant code base to work
in.

Besides, it's something I've been wanting to do since college. So why not?

So I have a standard two-pass assembler with a few features I haven't seen in
other 6809 assemblers. And that's what I'll be describing here. The first
feature is small, but decidedly nice—the ability to have underscores (“_”) in
numberic literals. It's more useful for binary literals, such as %10_00_01_11
or %000_01001_0_100_0010 but it can be used for decimal, octal or hexadecimal
numbers as well.

Another simple feature is the ability to generate a dependency list for make.
Since I support the inclusion of multiple assembly files, it makes sense to
support this feature as well. I'm not trying to make an assembler that works
on the 6809 system (I think it's way too small a system for that), but an
assembler that makes it nice to write code for a 6809 system.

I also have local labels that work similarly to NASM (Netwide Assembler) [2].
As an example:

-----[ Assembly ]-----
clear_bytes     clra
loop            sta     ,x+
               decb
               bne     .loop
               rts

clear_words     stb     ,-s
               clra
               clrb
loop            std     ,x++
               dec     ,s
               bne     .loop
               rts
-----[ END OF LINE ]-----

Internally, the assembler will merge the local labels with the previous non-
local label, and thus, we get the labels clear_bytes, clear_bytes.loop,
clear_words and clear_words.loop. I find it makes for cleaner code. What is
easier to understand, this?

-----[ Assembly ]-----
;********************************************************************
;       Music Synthesizer
;Entry: $3FF0   Freq delay count
;       $3FF1   Envelope table address
;       $3FF3   Envelope delay count
;       $3FF5   Volume, 1 to 255
; NOTE: from _TRS_80 Color Computer Assembly Lanauge Programming_,
;       page 252
;********************************************************************

               org     $3F00

mussyn          lda     $FF01           ; select sound out
               anda    #$F7            ; reset MUX bit
               sta     $FF01
               lda     $FF03           ; select sound out
               anda    #$F7            ; reset MUX bit
               sta     $FF03
               lda     $FF23           ; get PIA
               ora     #8              ; set 6-bit sound enable
               sta     $FF23
               ldu     #$3FF0          ; point to block
               ldx     1,u             ; get envelope address
               stx     envptr          ; save in envptr
               ldx     3,u             ; get envelope delay
mus005          lda     [envptr]        ; get value
               beq     mus090          ; if 0, done
               ldb     5,u             ; get volume
               mul                     ; adjust volume
               anda    #$FC            ; reset RS-232-C (?)
               sta     $FF20           ; set on
               ldb     ,u              ; get frequency delay count
mus010          leax    -1,x            ; decrement envelope count
               bne     mus020          ; go if not 0
               ldy     envptr          ; increment evelope ptr
               leay    1,y
               sty     envptr
               ldx     3,u             ; get envrolope delay
mus020          decb                    ; decrement frequency count
               bne     mus010          ; go if not 0
               lda     [envptr]        ; DUMMY
               brn     *+2             ; DUMMY
               ldb     5,u             ; DUMMY
               mul                     ; DUMMY
               clr     $FF20           ; set off
               ldb     ,u              ; get frequency delay
mus030          leax    -1,x            ; decrement envelope count
               bne     mus040          ; go if not 0
               ldy     envptr          ; increment envelope ptr
               leay    1,y
               sty     envptr
               ldx     3,u             ; get envelope delay
mus040          decb                    ; decrement frequency count
               bne     mus030          ; go if not 0
               bra     mus005          ; keep on playing
mus090          rts
envptr          fdb     0

               end     mussyn
-----[ END OF LINE ]-----

Or this?

-----[ Assembly ]-----
;********************************************************************
;       Music Synthesizer
;Entry: $3FF0   Freq delay count
;       $3FF1   Envelope table address
;       $3FF3   Envelope delay count
;       $3FF5   Volume, 1 to 255
; NOTE: from _TRS_80 Color Computer Assembly Lanauge Programming_,
;       page 252
;********************************************************************

               org     $3F00

mussyn          lda     $FF01           ; select sound out
               anda    #$F7            ; reset MUX bit
               sta     $FF01
               lda     $FF03           ; select sound out
               anda    #$F7            ; reset MUX bit
               sta     $FF03
               lda     $FF23           ; get PIA
               ora     #8              ; set 6-bit sound enable
               sta     $FF23
               ldu     #$3FF0          ; point to block
               ldx     1,u             ; get envelope address
               stx     .envptr         ; save in envptr
               ldx     3,u             ; get envelope delay
next_byte       lda     [.envptr]       ; get value
               beq     .exit           ; if 0, done
               ldb     5,u             ; get volume
               mul                     ; adjust volume
               anda    #$FC            ; reset RS-232-C (?)
               sta     $FF20           ; set on
               ldb     ,u              ; get frequency delay count
sound_on        leax    -1,x            ; decrement envelope count
               bne     .check_freq_on  ; go if not 0
               ldy     .envptr         ; increment evelope ptr
               leay    1,y
               sty     .envptr
               ldx     3,u             ; get envrolope delay
check_freq_on   decb                    ; decrement frequency count
               bne     .sound_on       ; go if not 0
               lda     [.envptr]       ; DUMMY
               brn     *+2             ; DUMMY
               ldb     5,u             ; DUMMY
               mul                     ; DUMMY
               clr     $FF20           ; set off
               ldb     ,u              ; get frequency delay
sound_off       leax    -1,x            ; decrement envelope count
               bne     .check_freq_off ; go if not 0
               ldy     .envptr         ; increment envelope ptr
               leay    1,y
               sty     .envptr
               ldx     3,u             ; get envelope delay
check_freq_off  decb                    ; decrement frequency count
               bne     .sound_off      ; go if not 0
               bra     .next_byte      ; keep on playing
exit            rts
envptr          fdb     0

               end     mussyn
-----[ END OF LINE ]-----

It helps that I allow 63 characters for a label, which is way more than any
6809 assembler I've ever used.

The last feature I have are warnings. Given the following code:

-----[ Assembly ]-----
start           lda     <<b16,x
               ldb     #$FF12
               std     foobar
               lda     b5,u
               ldb     b8,s
               tfr     a,x
               lbsr    a_really_long_label_that_exceeds_the_internal_limit_its_quite_long

               sta     [<<b5,y]
               bra     another_long_label_that_is_good

a_really_long_label_that_exceeds_the_internal_limit_its_quite_long
               rts

another_long_label_that_is_good
               clra
but_this_makes_it_too_long_to_use
               decb
               bne     .but_this_makes_it_too_long_to_use

               bra     next8
next8           lbra    next1
next16          brn     next8b
next8b          lbrn    next16b
next16b         rts

foobar          equ     $20
b16             equ     $8080
b5              equ     3
b8              equ     25
-----[ END OF LINE ]-----

The assembler will generate the following warnings (yes, this code is used to
test all the warnings in the assembler):

-----[ data ]-----
warn.asm:1: warning: W0010: missing initial label
warn.asm:6: warning: W0008: ext/tfr mixed sized registers
warn.asm:7: warning: W0001: label 'a_really_long_label_that_exceeds_the_internal_limit_its_quite_l' exceeds 63 characters
warn.asm:12: warning: W0001: label 'a_really_long_label_that_exceeds_the_internal_limit_its_quite_l' exceeds 63 characters
warn.asm:17: warning: W0001: label 'another_long_label_that_is_good.but_this_makes_it_too_long_to_u' exceeds 63 characters
warn.asm:19: warning: W0001: label 'another_long_label_that_is_good.but_this_makes_it_too_long_to_u' exceeds 63 characters
warn.asm:1: warning: W0003: 16-bit value truncated to 5 bits
warn.asm:2: warning: W0004: 16-bit value truncated to 8 bits
warn.asm:3: warning: W0005: address could be 8-bits, maybe use '<'?
warn.asm:4: warning: W0006: offset could be 5-bits, maybe use '<<'?
warn.asm:5: warning: W0007: offset could be 8-bits, maybe use '<'?
warn.asm:7: warning: W0009: offset could be 8-bits, maybe use short branch?
warn.asm:9: warning: W0011: 5-bit offset upped to 8 bits for indirect mode
warn.asm:21: warning: W0012: branch to next location, maybe remove?
warn.asm:22: warning: W0012: branch to next location, maybe remove?
warn.asm:1: warning: W0002: symbol '.start' defined but not used
-----[ END OF LINE ]-----

So, in order of appearance:

W0010

       What happens if you give a local label sans a non-lobal label? Well,
       I decided to allow it, but at least warn about it. The result label
       is just .start but it could be hard to reference. I could see making
       this an error, but for now, it's just a warning.
W0008

       This is the only warning about undefined behavior. The 6809 doesn't
       specify what happens when you transfer (or exchange) an 8-bit
       register with a 16-bit register [3] (or vice versa). The CPU (Central
       Processing Unit) just keeps running, but the results are just that—
       undefined. Again, this could be an error, but for now, I'm letting it
       slide as a warning.
W0001

       Internally, the assembler just truncates labels to 63 characters, but
       otherwise, it just keeps going.
W0003

       This is related to the nature of a two-pass assembler and forward
       references. Here, I'm forcing the given index to a 5-bit index (which
       doesn't take an additional byte of space, unlike an 8-bit (one
       additional byte) or a 16-bit (two additional bytes) offset), but the
       assembler has to assume it's okay on pass one. By the time pass two
       comes around, b16 is defined but it's value exceeds that 5-bits
       (which is -16 to 15 for the record). This warning is just letting the
       user know the value doesn't fit into 5-bits.
W0004

       Pretty much the same as W0003 except for an 8-bit value.
W0005

       Again, due to the nature of a two-pass assembler. This time, no hint
       is given to the size of the label, and on pass one, the assembler
       assumes the worst—a 16 bit value. It's only on pass two does it have
       enough information to know it could be an 8-bit address, but it can't
       use an 8-bit address as it would throw all the other addresses off
       (ask me how I know).
W0006

       Similar to W0005, but for an offset that can fit in 5-bits.
W0007

       Similar to W0006 but for an 8-bit value.
W0009

       This time, the assembler has determined that the target instruction
       falls within an 8-bit relative branch instruction, but was given a 16
       bit relative branch instruction. This can happen because of code
       refactorings that shrinks the distance between the branch instruction
       and the target.
W0011

       One of the features of the 6809 is its support of indirect indexing.
       Instead of the index having the data directly, the index contains the
       address of the data (in C parlance, LDA ,X is A = *X and LDA [,X] is
       A = **X). The 6809 doesn't support this mode for 5-bit offsets, but
       it does for 8-bit and 16-bit offsets. This is just a warning that you
       can't use a 5-bit offset for this. I'm on the fence about keeping or
       removing this, and I'm keeping it for now.
W0012

       This detects when you branch to the following instruction, except if
       the instruction is BRN which is “branch never” (or the long branch
       version LBRN). The 6809 is unique for an 8-bit CPU with such an
       instruction. And despite it's apparent uselessness (why would you
       have a branch that is never taken) it is useful to pad out timing
       loops when talking to hardware.
W0002

       The label wasn't referenced by any other code. And if the label is
       not referenced, why have the label in the first place? It could also
       mean an unused variable whose removal could save some space.

As you can see, most of the warnings are about code sequences that could be
shorter, and I'm not aware of any assembler that gives such warnings. I could
be wrong, but of the 6809 assmemblers I've used, I haven't seen anything like
this.

I also have a way to supress a given warning (they're all enabled by default—
I'm opinionated about this, and your stuck with my opinion if you want to use
this assembler).

So that's it about the unique features I have in my assembler. I don't expect
many people to use this, but I don't care, I'm having fun developing it. And
that's what counts.

[1] https://github.com/spc476/a09
[2] https://nasm.us/
[3] https://tlindner.macmess.org/?p=945

Email author at [email protected]