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.
"Hello"
"World"
"HelloWorld"
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
Any feedback would be greatly appreciated!
- 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.
- 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