6809
A 6809 assembler, simulator and debugger written in rust.
Install / Use
/learn @gorsat/6809README
6809 Assembler, Simulator, Debugger (in rust)
The venerable Motorola 6809 was the first microprocessor I knew. I have great fondness for it and when I was looking for a project through which to learn Rust, I figured a 6809 assembler might be just the ticket. But then I wanted to be able to run the output on something, so I decided to write a simple simulator, too. And then I needed to debug both the assembler and the simulator so I bolted on a simple debugger as well.
I ended up with this little 6809 swiss army knife and the world got yet another piece of software focused on a processor from 40+ years ago. It's not optimal, or cycle-accurate, or a good example of Rust code. But it will build and run some substantial assembly language programs (like Microsoft Extended Basic), and run them much faster than they ever ran on the real processor. It might be useful for someone doing some retro work and it's definitely entertaining for those who want to relive the magic of early 1980s computing.
Note: If you're looking for a TRS-80 Color Computer emulator then check out gorsat/coco. Using the coco emulator, you can load the EDTASM+ cartridge and directly edit/build/run 6809 assembler in the old coco console. It's arguably not as developer friendly as this 6809 simulator, but it's lots of fun :grin:
Getting Started
This program runs on Mac, Windows and Linux (only tested on Ubuntu). To sanity check it, you can run a quick test:
cargo test
...and after several lines of output it should say something like:
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
To assemble and run a program:
cargo run -- -r /path/to/program.asm
...or if you've already built the binary then just...
6809 -r /path/to/program.asm
Options
Help for command line options is available using -h or --help. There are a lot of options and I won't cover all of them here. Hopefully most of them are sufficiently self explanatory.
MC6809 assembler, simulator and debugger written in Rust.
Usage: 6809 [OPTIONS] <FILE>
Arguments:
<FILE> Assembly (.asm, .s) or Hex (.hex) file to assemble/run/debug
Options:
--acia-disable
Disable ACIA emulation
--acia-addr <ACIA_ADDR>
Address at which to map the ACIA (hex ok with '0x') [default: 65488]
--acia-port <ACIA_PORT>
TCP port on which to expose ACIA [default: 6809]
--acia-debug
Print ACIA debug information
--acia-case
Swap the case of alpha ASCII characters received via ACIA (a->A;A->a)
-b, --break-start
Break into the debugger before running the program (only if debugger enabled)
-c, --code-only
Remove blank and comment-only lines from program listing
-d, --debug
Run the program with debugger enabled (if both -r and -d are specified then -d wins)
--history <HISTORY>
The number of instructions to keep in the execution history when debugging [default: 200]
-l, --list
If there is a program listing then dump it to stdout
--lbr-disable
Disable automatic branch->long_branch conversion
-n, --no-auto-sym
No automatic loading of symbols
-p, --pemdas
Automatically evaluate expressions using PEMDAS rather than left-to-right
--perf
Display perf data (only interesting for longer-running programs)
--ram-top <RAM_TOP>
Set the top RAM address [default: 32767]
--reset-vector <RESET_VECTOR>
Override the reset vector
-r, --run
Run the program with debugger disabled and evaluate any test criteria
-t, --trace
Trace each machine instruction as it is executed
-v, --verbose
Enable verbose output
-w, --write-files
Write output files after assembly (.lst, .sym, .hex)
-h, --help
Print help information
-V, --version
Print version information
NOTE: The program can load/run/debug a hex file (in I8HEX format) rather than an assembly language file but it requires the file to have the .hex extension. You still have to specify that you want to
--runthe program, i.e.,6809 -r my_program.hex
Assembler
The assembler supports a number of features including:
- simple macros - See below. Macros are expanded prior to the build process and parameters are supported.
- test criteria - See below. Test criteria are evaluated after the completion of a
--run. - direct mode addressing - you can force direct mode by prepending a '<' to an 8-bit address
- current location reference - you can use '*' to refer to the "current location" in the program, so that, for example, the statement
jmp *is an infinite loop. - automatic branch extension - if you use a Bxx instruction but the destination is too far away then it will be automatically converted to an LBxx instruction (and you can turn this off with the
--lbr-disableoption) - multiple literal types - in addition to decimal you can use hex values (
$ff) and binary (%0101) and character literals ('Z) in operands - left-to-right and pemdas -
Assembly language programs (like Microsoft Extended Basic) relied on the fact that
expressions in operands would be evaluated left-to-right rather than following the
regular PEMDAS rule. Thus, the assembler uses left-to-right as the default order
of operations. In other words, the expression
2+8/2evaluates to 5 rather than 6. However, you can just specify-por--pemdasto change this. Also, whether in PEMDAS mode or not, the assembler supports parentheses so that2+(8/2)will always evaluate to 6.
Macros
There are a few examples of assembly language programs in the test directory that show several features of the assembler. The program mbadd.asm includes the following trivial macro definition:
.macro my_bne
bne @0
.endm
Once defined, this macro can be used anywhere you'd use a regular instruction and it will be expanded with parameters, so that, for example, the statement
my_bne LOOP
would be replaced with
bne LOOP
prior to the build process. Macros are not limited in terms of number of lines or parameters. I will caveat this by saying that I have not got around to adding signficant macro tests to the test suite.
Including Files
A simple .include <asm_file_path> statement enables code reuse and basic modularity by literally inserting the contents of the referenced .asm file within the current file. So, for example, if this is file1.asm:
lda #1
.include include/file2.asm
swi
...and ./include/file2.asm is the following:
; this is file2
ldb #2
...loading file1.asm results in the following combined listing:
lda #1
; this is file2
ldb #2
swi
...which is then assembled. You can include at multiple levels of depth, but not recursively (so, for example, file2.asm could include a file3.asm, but it would cause an error if file2.asm or file3.asm included file1.asm).
Directives
The following assembler directives are suppported:
- EQU
<br>Syntax:
Label EQU <expression><br>Example:ANSWER EQU 21*2<br>Sets Label equal to a constant. The constant can be provided as an expression. - FCB
<br>Syntax:
[Label] FCB <expression> [,<expression>...]<br>Example:hex_ans fcb $42<br>Sets a byte (or sequence of bytes) in the program binary equal to the evaluated expression(s). Each evaluated expression must fit into 8 bits. The list of expressions must not contain spaces. The optional Label will be equal to the address where the first byte is stored in the binary. - FDB
<br>Syntax:
[Label] FDB <expression> [,<expression>...]<br>Example:Magic FDB 378+ANSWER,$face,$45,'E<br>Sets a 16-bit word (or sequence of words) in the program binary equal to the evaluated expression(s). Each evaluated expression must fit into 16 bits. The list of expressions must not contain spaces. The optional Label will be equal to the address where the first word is stored in the binary. - FCC
<br>Syntax:
[Label] FCC <delim><ascii_string><delim><br>Example:Hello FCC "Hello, world!"<br>Stores an ascii string as bytes within the program binary. The deliminators can be any character but they must match one another. Any characters following the second deliminator are ignored. The optional Label will be equal to the address where the first byte of the string is stored in the binary. - ORG
<br>Syntax:
[Label] ORG <expression><br>Example:Start org $1000<br>Sets the current address within the program binary. The next instruction or data directive (e.g. FCB) will begin at the address defined by the expression. The optional Label will be equal to this address. - RMB
<br>Syntax:
[Label] RMB <expression><br>Example:Table rmb 13*14<br>Reserves a block of bytes whose size is given by expression. The current program address is increased by this number of bytes, thus the next instruction or data directive will begin at the address just after the reserved bytes. The optional Label will be equal to the address of the first byte in the reserved block. - SETDP
<br>Syntax:
SETDP [<expression>]<br>Example:setdp Table/256<br>Tells the assembler to assume that the DP register will be set to the value given by expression at runtime. This allows the assembler to optimize for direct mode addressing. By default, DP is assumed to be 0. You can turn off direct mode optimization by using SETDP without an expression. If the expression evaluates to an 8-bit number then DP is assumed to be equal to that number. If expression evaluates to a 16-bit number then the LSB must be equal to 0 and DP is assumed t
