SEGA Genesis: Structure

I haven't made a lot of complex assembly code previously, and haven't read a lot of other people's code, so I'm fairly new to structuring the code.

Assembly being lower level than other languagues makes it very structurally open. It makes for an interesting language for a computer scientist to work with, enabling the implementation of every kind of programming paradigm.

However, it's also easy to make a lot of stupid mistakes.

Variables

Contra: Hard Corps ROM

In assembly code, a variable can be represented by a register or a memory location. Registers are normally temporary, so for something like a global variable, you need to use memory.

Here is one way to declare a variable in memory:

myVar:      dc.b    7

Here, the variable is located next to the code. However, in a SEGA Genesis ROM, this variable will be constant. Fairly obvious, since the code and the variable will be in ROM, not RAM, and thus, cannot be changed.

If you want a mutable variable, you need to place it in RAM instead. My first naive attempt was to place it in address 0:

myVar   = $00000000
    move.w  #7,myVar  ; myVar = 7

This would have worked, if address 0 was in RAM, but it isn't. From https://emu-docs.org/Genesis/gen-hw.txt :

000000-3FFFFFh : ROM
400000-7FFFFFh : Unused (1)
800000-9FFFFFh : Unused (2)
A00000-A0FFFFh : Z80 address space (3)
A10000-A1001Fh : I/O
A10020-BFFFFFh : Internal registers and expansion (4)
C00000-DFFFFFh : VDP (5)
E00000-FFFFFFh : RAM (6)

So our work RAM is $E00000-$FFFFFF, so this works:

myVar   = $E00000
    move.w  #7,myVar  ; myVar = 7

This is an example set of global variables:

; WORK RAM: $E00000 and up
mode         = $E00000    ; 1B 0 = FSScroll, 1 = line scroll
xoff         = $E00002    ; 2B offset x
yoff         = $E00004    ; 2B offset y
dx           = $E00006    ; 2B scroll speed
dy           = $E00008    ; 2B scroll speed

Function Calling Conventions

The other guys just don't stack up

In many programming languages, parsing parameters into and out of functions and methods is hidden by the language implementation. For example, the formal parameters may be pushed onto the stack by the calling code before giving executing to the function body itself. In assembly code, you have to make these decisions yourself.

The simplest implementation would be defining input and output registers for a function:

      result = Add(parameterA, parameterB)
        D0             D0          D1

This function could be implemented like this:

Add:
    add.w   D1,D0
    rts

And called like this:

    ; D0 = Add(5,7)
    move.w  #5,D0   ; parameterA = 5
    move.w  #7,D1   ; parameterA = 7
    jsr     Add

This works, as long as you're careful with which registers are overwritten by functions.

However, when you have multiple levels of function calls, it goes bad almost immediately:

Multiply:
    move.w  d0,d2   ; <- Multiply destroys D2
.loop:
    add.w   d2,d0
    dbra    d1,.loop
    rts
    ; result = Power( x, power )
    ;   D0            D0   D1
Power:
    move.w  d1,d2  ; <- But D2 is also used by Power... Uh oh
.loop:
    move.w  d2,d0
    jsr     Multiply
    dbra    d2,.loop
    rts

To avoid problems, our function can save the state of the registers that will be overwritten onto the stack:

Multiply:
    move.l  d2,-(sp)  ; push D2 onto stack
    move.w  d0,d2     ; destroys D2, but we already saved it
.loop:
    add.w   d2,d0
    dbra    d1,.loop
    move.l  (sp)+,d2  ; restore D2 from the stack
    rts

We can use the 'movem' instruction to save multiple registers:

    movem d0-d7/a0-a6,-(sp) ;    push all registers onto the stack
    movem (sp)+,d0-d7/a0-a6 ; restore all registers from the stack

State Pattern

My menu

I wanted to implement a State Pattern in 68000 assembly to make a menu structure. My first implementation is based on the way I would implement it in C#, like this:

  delegate void StateMethod();

  void Test() {
      StateMethod state = StateA;
      state();
  }
  void StateA() { }
  void StateB() { }

The 68000 assembly version could look like this:

 state = $E00000            ; function ptr location
    move.l  #StateA,state   ; state = StateA
 .loop;
    move.l  state,a0        ; a0 = *(state)
    jsr     (a0)            ; jumps to a0, call StateA
    bra     .loop
 StateA:
    rts
 StateA:
    rts

After looking at the Sonic the Hedgehog disassembly, I learned how to do an equivalent implementation based on a jump table:

     move.w  #0,d0             ; function index = 0 (StateA)
     lsl.w   #2,d0             ; d0 *= sizeof( function pointer ) = 4
     jsr     DoState(pc,d0.w)  ; invoke jump table

 DoState:              ; Jump table
     bra.w     StateA  ; offset 0
     bra.w     StateB  ; offset 4
     rts               ; this should never be reached
 StateA:
     rts
 StateB:
     rts

I ended up using the jump table, as adding new functions was trivial compared to assigning new function pointers.

References

http://meseec.ce.rit.edu/eecc250-winter99/250-final-review.pdf