assemblymotorola68000

68000 Assembly – Passing Parameters via Stack for String Concatenation


I'm working on a Motorola 68000 assembly program that concatenates two strings using a subroutine. The challenge was to implement parameter passing via the stack for both input and output, so I focused on properly setting up and restoring the stack.

I developed the program logic with the help of Sep Roland and Erik Eidt. Afterward, I studied how to pass parameters using the stack, which is why my code is heavily commented.

Task Requirements:

My Implementation:

          ORG $8000
   
;DATA
StringA DC.B 'Hello',0    ; First string with a null terminator
StringB DC.B 'World',0    ; Second string with a null terminator
StringC DS.B 256          ; Buffer for the concatenated string 

START: 

; The stack pointer (A7) starts at address $8000. 
; In the 68000 architecture, A7 always points to the memory address where 
; the next value will be saved (push operation).

      pea.l StringC ; Equivalent to [move.l #StringC, -(a7)]
                    ; The stack pointer (A7) is decremented by 4 (pushing a longword = 4 bytes)
                    ; Initial A7 = $8000, now A7 = $7FFC
      
      pea.l StringB  ; A7 = $7FF8
      pea.l StringA  ; A7 = $7FF4

; Therefore, the stack (from lowest to highest address) contains:
; A7 = $7FF4  |StringA address| 
; A7 = $7FF8  |StringB address| 
; A7 = $7FFC  |StringC address| 
; A7 = $8000 (original SP value before the push operations)

      bsr.s CopyStrings     ; Call the first subroutine, saving the PC (Program Counter)
                            ; onto the stack
                                
; When executing bsr.s, the processor:
; - Saves the return address (PC) on the stack (another 4 bytes subtracted from A7).
; - Then branches to CopyStrings.

; Upon returning from the subroutine (rts), the stack pointer A7 will remain 
; where the subroutine left it. However, we need to clean up the three parameters 
; (StringA, StringB, StringC) that we previously pushed.

      addq.l #8,a7  ; Restore 8 bytes of the stack
      addq.l #4,a7  ; Restore the remaining 4 bytes (total 12 bytes)

      SIMHALT 

CopyStrings:
      ; At the entry of the subroutine, the stack looks like this:
      ; A7    |Return Address | 
      ; A7+4  |StringA Address| 
      ; A7+8  |StringB Address| 
      ; A7+12 |StringC Address|
      
      move.l 4(a7),a0  ; Retrieve the address of StringA 
      move.l 8(a7),a1  ; Retrieve the address of StringB
      move.l 12(a7),a2 ; Retrieve the address of StringC 
      
CopyA: 
      move.b (a0)+,(a2)+  ; Load a character from StringA into StringC
                          
      bne.s CopyA         ; If the character is not null, continue copying
      subq.l #1,a2        ; Move back 1 byte to overwrite the null terminator

CopyB:
      move.b (a1)+,(a2)+  ; Load a character from StringB into StringC
      bne.s CopyB         ; If the character is not null, continue copying
      rts                 ; Return from subroutine
    
     END START

Questions:

  1. Is my approach to passing parameters via the stack correct?
  2. Are there any optimizations or best practices I should consider?

Any feedback would be greatly appreciated!


Solution

    1. Is my approach to passing parameters via the stack correct?

    That's fine, but you could consolidate the two addq.l #.., a7 instructions into a single adda.l #12, a7. On 68000 this will run in 14 clocks which is 2 clocks less than what you wrote.

    1. Are there any optimizations or best practices I should consider?

    Now that the addresses for your arrays are on the stack, you have the perfect opportunity to clobber one address register less in your CopyStrings subroutine. This could come handy in a bigger program. Please note that you should not be able to load an address register using a simple move. The instruction set has a movea instruction just for that.

    CopyStrings:
          ; At the entry of the subroutine, the stack looks like this:
          ; A7    |Return Address | 
          ; A7+4  |StringA Address| 
          ; A7+8  |StringB Address| 
          ; A7+12 |StringC Address|
          
          movea.l 4(a7), a0  ; Retrieve the address of StringA
          movea.l 12(a7), a1 ; Retrieve the address of StringC
    CopyA:
          move.b  (a0)+, (a1)+
          bne.s   CopyA
          subq.l  #1, a1     ; Move back 1 byte to overwrite the null terminator
    
          movea.l 8(a7), a0  ; Retrieve the address of StringB
    CopyB:
          move.b  (a0)+, (a1)+
          bne.s   CopyB
          rts