Motorola 68000 Assembly Language

How-Tos

Set up a 68K LSP for NeoVim with CoC

If you're using CoC with NeoVim, you can enable a 68k Language Server Protocol server running in Node: https://github.com/grahambates/m68k-lsp

# install the language server
npm i -g m68k-lsp-server

# configure CoC to use the LSP
:CocConfig

# add to the JSON file:

  "languageserver": {
    "m68k": {
      "command": "m68k-lsp-server",
      "args": ["--stdio"],
      "filetypes": ["asm"],
      "rootPatterns": ["*.s"]
    }

# if you're using ALE as well as CoC, ALE may complain, like a lot about the file.
# I think ALE thinks it's x86 assembler. Disable ALE on that buffer:

:ALEDisableBuffer

Now you can get docstrings and instruction reference:

Set memory breakpoints for debugging within WinUAE/FS-UAE

You can set a “breakpoint” in code by triggering a memory write to a safe area of memory (https://eab.abime.net/showpost.php?p=899516&postcount=6). This will trigger the debugger at that point in code.

In your code:

snippet.asm
  ; code before breakpoint
  CLR.W $100 ; write two zero bytes to $100
  ; code you want to verify

Then, in WinUAE/FS-UAE, set a memory watchpoint. This creates watchpoint 0, looking at $100, waiting for two bytes to be written. In your user code (at least in AMOS extensions), nothing else will be writing here.

w 0 100 2

Start the code executing again with g and UAE will break when it hits your CLR.W.

For actually debugging:

  • z for stepping forward one instruction

Remove the memory watchpoint with w 0.

This also works in C:

snippet.c
int main(void) {
  static volatile short *debug = (volatile short*) 0x100;
  int a;
 
  a = 1;
 
  *(debug) = 0;
 
  a = 2;
 
  return 0;
}

Debugging checklist

  • Ensure things are constants that should be constants!
snippet.asm
CUSTOM EQU $DFF000
 
; bad, better reboot the Amiga
  MOVE.L CUSTOM,A0
  MOVE.L CopperlistPointer,cop1lc(A0)
; good
  MOVE.L #CUSTOM,A0
  MOVE.L CopperlistPointer,cop1lc(A0)    

* Watch for address register usage when looking at code from other sources!

snippet.asm
  MOVE.W #$2c21,diwstrt(A0)
  MOVE.W #$2cc1,diwstop(A0)
  MOVE.W #$0038,ddfstrt(A1) ; uh oh
  MOVE.W #$00d0,ddfstop(A1)

References

Addressing Modes

Registers

Try it out!

Processor versions

68020

Instructions

MOVE

The default size seems to be word, so MOVE and MOVE.W seem to be equivalent.

ASL and ROL

If you want to shift a high bit from one thing onto the low bit of another, ASL, will shift off the top bit into the carry flag without wrapping it back around. Unlike 6502 assembler, ROL will not use the carry flag. You have to do that yourself:

snippet.asm
    MOVE.L #$ffffffff,D0
    MOVE.L #$1,D1
    ASL.L #1,D0
    SCS D2
    AND.L #$1,D2
    ROL.L #1,D1 ; D1 is now 2
    ADD.L D2,D1 ; D1 is now 3

Algorithms

Divide two long numbers and get two long numbers back

A bit more work than DIVU but it allows you to work with longs. Based on finally figuring out multiplying and dividing on the 6502.

snippet.asm
START:
    MOVE.L #31,D4
 
loop:
    ASL.L #1,number
    SCS D2
    AND.L #1,D2
    ROL.L #1,remainder
    ADD.L D2,remainder
 
    MOVE.L remainder,D3
    SUB.L divisor,D3
 
    BMI keep_going
    ADD.L #1,number
    MOVE.L D3,remainder
keep_going:
    DBRA D4, loop
    move.l number,d4 ; 2
    move.l remainder,d5 ; 500000
    RTS
 
number: dc.l 2500000
divisor: dc.l 1000000
remainder: dc.l 0

DBcc

Remember that DBcc checks for the condition both before and after the decrement! This is a very likely source of off-by-one errors if you get it wrong, so be mindful!

Macros

  • If you want to use a macro over and over and have labels that come along with it, use \@ in the macro code to expand to _<current macro usage number>.
  • If you're accepting a register as a parameter, make sure you're not using that same register in the code. At that point, a subroutine might be better.

Alignment

  • If something needs to be long-word aligned, used CNOP 0,4.