Practical Memory Move Routines by Bruce Clark
[Up to Source Code Repository]
Here are some reasonably fast general-purpose routines for moving blocks of memory. You simply specify the address to move from, the address to move to, and the size of the block. When SIZE is zero, no bytes are moved. SIZEL and SIZEH do not need to be consecutive memory locations, or even on the zero page for that matter. These routines only take one additional cycle if SIZEL is not on the zero page. Likewise, they only take one additional cycle if SIZEH is not on the zero page. Note that this adds only to the total number of cycles, not to the number of cycles per byte, since neither SIZEL nor SIZEH is inside a loop anywhere.
These routines are intended to be both flexible and practical, without being excessively lengthy or excessively slow. To that end, they can be placed in ROM or in RAM.
There are three routines moving memory upward (i.e. to a higher address), each of which is tailored to a slightly different set of input parameters.
; Move memory down
;
; FROM = source start address
; TO = destination start address
; SIZE = number of bytes to move
;
MOVEDOWN LDY #0
LDX SIZEH
BEQ MD2
MD1 LDA (FROM),Y ; move a page at a time
STA (TO),Y
INY
BNE MD1
INC FROM+1
INC TO+1
DEX
BNE MD1
MD2 LDX SIZEL
BEQ MD4
MD3 LDA (FROM),Y ; move the remaining bytes
STA (TO),Y
INY
DEX
BNE MD3
MD4 RTS
; Move memory up
;
; FROM = source start address
; TO = destination start address
; SIZE = number of bytes to move
;
MOVEUP LDX SIZEH ; the last byte must be moved first
CLC ; start at the final pages of FROM and TO
TXA
ADC FROM+1
STA FROM+1
CLC
TXA
ADC TO+1
STA TO+1
INX ; allows the use of BNE after the DEX below
LDY SIZEL
BEQ MU3
DEY ; move bytes on the last page first
BEQ MU2
MU1 LDA (FROM),Y
STA (TO),Y
DEY
BNE MU1
MU2 LDA (FROM),Y ; handle Y = 0 separately
STA (TO),Y
MU3 DEY
DEC FROM+1 ; move the next page (if any)
DEC TO+1
DEX
BNE MU1
RTS
; Move memory up
;
; FROM = 1 + source end address
; TO = 1 + destination end address
; SIZE = number of bytes to move
;
MOVEUP LDY #$FF
LDX SIZEH
BEQ MU3
MU1 DEC FROM+1
DEC TO+1
MU2 LDA (FROM),Y ; move a page at a time
STA (TO),Y
DEY
BNE MU2
LDA (FROM),Y ; handle Y = 0 separately
STA (TO),Y
DEY
DEX
BNE MU1
MU3 LDX SIZEL
BEQ MU5
DEC FROM+1
DEC TO+1
MU4 LDA (FROM),Y ; move the remaining bytes
STA (TO),Y
DEY
DEX
BNE MU4
MU5 RTS
; Move memory up
;
; FROM = source end address
; TO = destination end address
; SIZE = number of bytes to move
;
MOVEUP LDY #0
LDX SIZEH
BEQ MU3
MU1 LDA (FROM),Y ; handle Y = 0 separately
STA (TO),Y
DEY
DEC FROM+1
DEC TO+1
MU2 LDA (FROM),Y ; move a page at a time
STA (TO),Y
DEY
BNE MU2
DEX
BNE MU1
MU3 LDX SIZEL
BEQ MU5
LDA (FROM),Y ; handle Y = 0 separately
STA (TO),Y
DEY
DEX
BEQ MU5
DEC FROM+1
DEC TO+1
MU4 LDA (FROM),Y ; move the remaining bytes
STA (TO),Y
DEY
DEX
BNE MU4
MU5 RTS
Even more speed can be gained by using self-modifying code, i.e. replacing
the (ZeroPage),Y addressing mode with the Absolute,Y addressing mode. This
will take 2 fewer cycles per byte. There will be some additional cycles from
the added instructions that self-modify the code, but the self-modification
occurs only once, and therefore adds these cycles to total number of cycles,
rather than the number of cycles per byte moved. As always, the instructions
that are self-modified can't be located in ROM.
Last page update: April 3, 2004.