Skip to content

lashtear/816asm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 

Repository files navigation

816asm

A Forth-based assembler for 65c816 and other 6502-family processors

Current features

  • Works for generating small binary objects with no linking or object format
  • Forth assembler-style loop constructs remove (most of) the need for symbols
  • Nice hexadecimal debug output showing loop and conditional backpatching
  • Mostly portable Forth code
  • Easy to add instructions from other processor variants
  • Instead of macros, the full power of the hosting Forth environment is avaliable

Current limitations

  • Mostly 816-centric; originally the branches used BRA ($80) which is not present on NMOS varieties. (mitigated for now with clc, bcc)
  • (Mis-)uses the Forth BLOCK words for assembling arbitrarily large target data. In standard forth, block zero should not be used, but here it is.
  • Non-long-addresses (i.e. all 6502 style addresses) are limited to the wordsize of the host Forth system.
    • On a 16bit forth, that limits things to 64k, but code is bank-wrapped on the 65816 anyway so that should be fine.
    • Explicitly 24bit address forms are split into (low16, bank) form; this maps nicely to the Forth doubleword syntax on 16bit forths but is easier specified separately on larger (32, 64bit, etc) host systems.

To-do next

  • Replace the BLOCK access words with equivalent file words to improve portability
  • Split instruction table handling by cpu-type, so that N6502, 65C02, 65SC02, 65CE02, Hudson C6280, etc., and 65816/65802 activate different sets of accepted words.
  • Likewise make loop operators use appropriate features for the active processor (offset bra:r vs clc:. offset bcc:r)
  • Restructure those tables for more useful disassembly with timing information

Similar prior work

  • Forth in general, especially early FIGForth. The FIGForth model 6502 implementation had a wickedly clever little assembler, though that one specified addressing-mode in stateful prefix words.
  • Brad Rodriguez's excellent articles on writing assemblers in Forth: https://www.bradrodriguez.com/papers/tcjassem.txt etc.
  • Scot Stevenson's excellent "A Typist's 65816 Assembler" https://github.com/scotws/tasm65816/ - very similar goals and design choices, strongest inspiration for doing this

Relative to scotws's assembler, this one uses colon (:) to separate instruction mnemonic and addressing mode; Forth likes to parse unknown symbols as numbers in whatever number base is in use, so things like ADC.A rightfully generate warnings in better forth compilers.

Additionally the modes are shuffled a bit:

tasm65816-style 816asm-style
(blank absolute) :a
.a (accumulator) :. (into implied)
.x :ax
.y :ay
.il :ail

A variety of instructions are spelled a bit different where convenient, but most forms are accepted. (NMOS, CMOS, and 816 are all active at once, which is usually not desirable)

This assembler uses a unified address-mode word for implementing the instructions, with two-stage or "nested" DOES> forms, but that probably makes no difference for end users, should they ever exist.

Examples

  • detect-sim.fs - An implementation of Chromatix's CPU detection method; see this forum post; demonstrates assembling some tricky code and using conditional forms. Outputs a binary image suitable for llvm-mos-sdk's simulator; when run this emits a letter for the CPU it found:
\ O - old NMOS 6502 rev A (no ROR insn)
\ N - NMOS 6502 post rev A (ROR working)
\ n - NMOS simulator with 1-byte NOPs
\ d - NMOS without Decimal mode (eg. Ricoh 2A03)
\ S - 65SC02
\ C - 65C02
\ E - 65CE02
\ H - HuC6280
\ 8 - 65C816 or 65C802

Example invocation, with debug dump showing the back-patching as addresses are determined:

$ gforth examples/detect-sim.fs 

1FFC:                                    00 20 00 00
2000:A9 00 85 84 85 85 A9 1D 85 83 A9 6B 85 1D A9 4E
2010:47 83 45 83 C9 4E D0 FE 58 F8 A9 90 69 10 D8 78
2020:90 FE A9 4E 2A 6A B0 FE A9 4F
2027:                     02
202A:                              18 90 FE
2021:   0B
202D:                                       A9 64
202C:                                    02
2017:                     17
202F:                                             C9
2030:53 D0 04 80 02 A9 6E C9 43 D0 FE A0 01 C0 01 C2
2040:08 D0 FE C0 00 D0 FE 7A A9 48
2046:                  03
204A:                              18 90 FE
2042:      0A
204D:                                       A9 45
204C:                                    02
203A:                              14
204F:                                             C9
2050:38 D0 FE 38 FB B0 FE 7A
2056:                  01
2052:      05
2058:                        8D F9 FF A9 0A 8D F9 FF
2060:A9 00 8D F8 FF 00 00
1FFE:                                          67 00
2067:                     FA FF 06 00 00 20 00 20 F7
2070:FF
  • dos33-bootsector.fs - Assembles to Apple DOS 3.3's boot sector (minus the patch area) in a 5.25" .dsk image (with only that boot sector in it).
$ gforth examples/dos33-bootsector.fs

0800:01 A5 27 C9 09 D0 FE A5 2B 4A 4A 4A 4A 09 C0 85
0810:3F A9 5C 85 3E 18 AD FE 08 6D FF 08 8D FE 08
0806:                  18
081F:                                             AE
0820:FF 08 30 FE BD 4D 08 85 3D CE FF 08 AD FE 08 85
0830:27 CE FE 08 A6 2B 6C 3E 00
0823:         15
0839:                           EE FE 08 EE FE 08 20
0840:89 FE 20 93 FE A6 2B 6C FD 08 00 0D 0B 09 07 05
0850:03 01 0E 0C 0A 08 06 04 02 0F EA EA EA EA EA EA
0860:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
0870:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
0880:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
0890:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
08A0:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
08B0:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
08C0:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
08D0:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
08E0:EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA
08F0:EA EA EA EA EA EA EA EA EA EA EA EA EA 00 B6 09

Documentation

Mnemonics are suffixed by their address mode:

Mode name Insn Width 816asm style Traditional style
absolute 3 :a 0000
absolute-indexed-x 3 :ax 0000,X
absolute-indexed-y 3 :ay 0000,Y
long-absolute 4 :l 000000
long-absolute-indexed-x 4 :lx 000000,X
absolute-indirect 3 :ai (0000)
absolute-indexed-x-indirect 3 :axi (0000,X)
absolute-indirect-long 3 :ail [0000]
direct 2 :d 00
direct-indexed-x 2 :dx 00,X
direct-indexed-y 2 :dy 00,Y
direct-indirect 2 :di (00)
direct-indexed-x-indirect 2 :dxi (00,X)
direct-indirect-indexed-y 2 :diy (00),Y
direct-indirect-long 2 :dil [00]
direct-indirect-long-indexex-y 2 :dily [00],y
immediate-8 2 :# #00
immediate-16 3 :## #0000
implied 1 :. (empty or A)
stack-relative 2 :s 00,S
stack-relative-indirect-indexed-y 2 :siy (00,s),y
block-move 3 := 00,00
immediate-m-size 2 or 3 :#m #00 or #0000
immediate-x-size 2 or 3 :#x #00 or #0000
relative-8 2 :r (usually by label, but +/- 00)
relative-16 3 :rr (usually by label, but +/- 0000)
rockwell-bit-op 2 :rw or 0:d 0 00
rockwell-branch 2 :rwb or 0:d 0 00

Control flow

6502 condition codes are mapped to flag labels:

Name Flag
plus pl/
minus mi/
oVerflow clear vc/
oVerflow set vs/
carry clear cc/
carry set cs/
not equal ne/
equal eq/
less than lt/
greater equal ge/

With the condbranch word selecting the right branch instruction for the flag. Normally, condbranch is instead called by the structured condition words if,, else,, then, which append the appropriate instructions with necessary backpatching:

'A' cmp:#m eq/ if,
    ...code run when accumulator held 'A'...
then,

Or

'B' cmp:#m eq/ if,
    ...code run when accumulator held 'B'...
else,
    ...code run otherwise...
then,

Or even arbitrary nesting, as in the detect-sim.fs example:

'N' cmp:#m eq/ if,             \ Quacks like NMOS so far
    cli:. sed:.                \ Check for broken decimal
    90 lda:#m 10 adc:#m
    cld:. sei:. cs/ if,
        'N' lda:#m             \ Decimal works
        rol:. ror:.            \ Check for broken ROR
        cc/ if,
            'O' lda:#m         \ No ROR implemented!
        then,
    else,
        'd' lda:#m             \ Decimal missing like 2A03
    then,
then,

Looping is similar, with begin,, until,, again,, while,, and repeat,, which work in the traditional Forth way. Back-references for these are held on the host-Forth's parameter stack along with matching sentinels for structure error detection.

Infinite loop:

begin,
again,

Loop until condition met:

begin,
    ...code that leaves flags register set...
cc/ until,

More conventional while-loops:

begin,
    ...code that leaves flags register set...
cc/ while,
    ...
repeat,

On 816 there are long-branch equivalents ifl,, elsel,, and thenl,.

Processor M/X bit status is tracked where possible (constant rep:#, sep:#, and xce:.). set-emulation returns the assembler to 6502 mode. If necessary, the internal assembler variables asm-m, asm-x, and asm-e hold the assembler's understanding of the current mode for future assembly.

THERE is the current target memory address (analogous to Forth HERE). There-based primitives for memory manipulation include:

Word Name
tc@ there character fetch
tc! there character store
tc, there character append
t@ there word (16bit LE) fetch
t! there word store
t, there word append
talign zeropad at there to requested alignment
t," append an inline forth string there
ta>p convert there-address to pointer
origin! update there data pointer offset like ORG

About

A Forth-based assembler for 65816

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages