18

I gather the fastest way to implement memcpy (copy a certain number of bytes from one place in memory to another) on the Z80 is to use an instruction called LDIR. But how fast is the result, when fully optimized, in terms of clock cycles per byte?

2
  • 2
    see Breakeven number of bytes for programmable DMA I compare there CPU and DMA transfer rates. The most optimized memcopy I know of is 16T/Byte which is for 3.547 MHz a 221687.5 Byte/s without contention
    – Spektre
    Commented May 5, 2018 at 19:20
  • 1
    LDIR isn't the fastest memcpy (as demonstrated in the answers), but it certainly makes compact code. Commented Jul 13, 2018 at 9:45

4 Answers 4

27

There's no real optimisation — LDIR (or indeed LDDR, which goes downward instead of upwards) is the complete inner loop. It will always load from HL, store to DE, increment both and decrement BC. Then if BC is non-zero it will repeat.

Annoyingly it will repeat exactly by just decrementing the PC by 2. So it'll read the full instruction again. Which means that 50% of memory accesses are it reading the opcode for the entire loop. The advantage is that interrupts can be accommodated while a transfer is in progress.

Cost is 21 cycles for each time around the loop that does lead to a repetition, then 16 cycles on the final go. Four cycles opcode fetch, four cycles opcode fetch, three cycles reading from the address pointed to by HL, then a long five cycle write to the address pointed to by DE. Then five cycles without a bus access if repeating.

Unrolled LDIs will be faster if you have the space as they always cost the flat 16 cycles. E.g. put 16 of them in a row with a conditional jump at the end and if the number of bytes you want to copy is n then jump into the loop n mod 16 steps from the repetition test for the first iteration.

Alternatively, if you're writing for a computer with slower RAM access that ROM access (e.g. because RAM is shared with video) then a top tip is to look for the three-byte sequence LDIR RET anywhere in ROM and call it.

7
  • 6
    The 6502 uses only half of each cycle for memory access, which means that memory has to be twice as fast as teh clock rate would suggest. So a 1 MHz 6502 needs >2 MHz RAM (better than 500 ns), but uses only half the bandwith, leaving between each access enough time for another one to be used by video (or otherwise). The Z80 instead doesn't run with such a symetric access scheme offering bontifull time to insert 'hidden' video access. Here video needs to be priortized over CPU, claiming the RAM when needed, thus 'stealing' cycles. We're at the heart of the decade old 6502 MHz vs. Z80 MHz topic.
    – Raffzahn
    Commented Sep 16, 2017 at 8:34
  • 7
    Thats also the reason why (at least somewhat) professional sysems always include a Z80 DMA (8410). A 4MHz Z80 can transfers a bit less than 200 KiB/s (unrolling gives ~250 KiB/s), as only 4 of 21 (16) cycles are memory access (half of it being instruchtion (re)fetch) me while the Z80 DMA uses the full bandwith, offering up to 2 MiB/s - all cycles are now used for data transfer. Ofc, minus video cycles, if that unit is also accessing the RAM.
    – Raffzahn
    Commented Sep 16, 2017 at 8:54
  • 6
    If interrupts including NMI can be disabled, it's possible to write code that outperforms LDIR by using a sequence of stack manipulations, pops, register swaps, and pushes. LDIR is a lot easier, though. I do find myself curious how hard it would have been to make the microcode loop when no interrupt happens.
    – supercat
    Commented Sep 16, 2017 at 18:26
  • 2
    @rwallace My gut says right after the CPU. But I can't proof that. I don't have realy old documentation at hand. But from whats on my desk, it's already listed in the 1981 databook and a February 1980 datasheet - as well as the 1981 application reference. Furthermore I got a Z80 Family data book with no date (listing the 8410) which I assume to be eventually before 1979, as it refers to the 1979 databook in future form. Ao I go with anywhere between 1976 and 1978.
    – Raffzahn
    Commented Oct 7, 2017 at 14:04
  • 2
    @rwallace I found a high quality scan of the Feb 1980 data sheet online: bitsavers.org/components/zilog/z80/…
    – Raffzahn
    Commented Oct 7, 2017 at 14:06
12

You can do better in particular cases. Here is an inner loop (REPT 11 [...] ENDM makes 11 inline repetitions) for scrolling by 16 bits in a screen area I used in a game of mine:

        LD      B,16            ;29
SCRL11: EX      DE,HL           ;4
        LD      E,(HL)          ;11
        INC     HL              ;17
        LD      D,(HL)          ;24
        INC     HL              ;30
        EX      DE,HL           ;34
        REPT    11
        EX      (SP),HL         ;35
        DEC     SP              ;41
        DEC     SP              ;47
        ENDM                    ;551
        EX      (SP),HL         ;586
        LD      HL,22+40H       ;596
        ADD     HL,SP           ;607
        LD      SP,HL           ;611
        DJNZ    SCRL11          ;10008

Numbers are accumulated clock cycles (obviously the first line does not start with zero: this is part of something quite longer). I am not actually sure why EX (SP),HL clocks in with 35 cycles here: the nominal count is 19 cycles. I'm assuming that the 16 additional cycles are wait states because access is in video memory.

Looks like I'm a bit fuzzy after somewhat more than a quarter century.

In general, putting the stack pointer in the data area can improve speed for some operations since LDI and its ilk only work by bytes and have extended Z80 opcodes (where the opcode fetch alone is good for something like 8 cycles) while many stack operations work by words and are part of the original 8080 opcode set. Of course, you cannot have interrupts arriving while doing that kind of thing.

2
  • 3
    Welcome to Retrocomputing Stack Exchange. If you ever want to reclaim this account (and therefore ownership of this answer) you can use the contact form linked to at the bottom of the page.
    – wizzwizz4
    Commented Sep 16, 2017 at 8:49
  • I wonder how much silicon it would have cost to have an internal flag that would skip instruction fetch cycles,would be set when LDIR etc. reset the program counter, and would be cleared when either LDIR didn't go to reload the program counter or when an interrupt occurred? Even if getting LDIR down to 6 cycles/byte would be expensive, its present form seems really unnecessarily slow.
    – supercat
    Commented May 6, 2018 at 17:06
12

The heaven of memcpy-like optimization in Z80 is the stack. If you have destination fixed, for example, you do like:

ld   sp,src
pop  hl
ld   [dest+0],hl
pop  hl
ld   [dest+2],hl
...

thus getting 14 clocks per byte transferred (ldir/lddr is just 21 clocks per byte).

If both source and destination are fixed, you can do:

ld   sp,src
pop  af,bc,de,hl
exx
ex   af,af`
pop  af,bc,de,hl
ld   sp,dest+16
push hl,de,bc,af
exx
ex   af,af`
push hl,de,bc,af
...

thus getting ~13 clocks per byte.

The main disadvantage of such stack methods is the need to disable interrupts (in general case, of course there could be cases when interrupt is acceptable).

1
  • 6
    With no loop test in your first snippet, the correct comparison is LDI, not LDIR. So having a fixed destination saves 2 cycles, not 7.
    – Tommy
    Commented Sep 16, 2017 at 20:21
11

Perhaps I should just edit the contradiction out of the accepted answer that starts by claiming you can do no better than LDIR then mentions that unrolling it to a series of LDI instructions is faster.

But for the nonce I'll put an explicit example of the LDI unroll here.

; Copy BC bytes from HL to DE.
copy_mem:
    ld      a,b
    or      c
    ret     z

    ld      a,16
    sub     c
    and     15
    add     a,a
    ld      (lp_entry),a
    jr      $
lp_entry    equ $-1
copy_lp:
    ldi
    ldi
    ldi
    ldi

    ldi
    ldi
    ldi
    ldi

    ldi
    ldi
    ldi
    ldi

    ldi
    ldi
    ldi
    ldi

    jp      v,copy_lp

    ret

The code does check for the byte count == 0 case. However, it does use self-modifying code so it won't work if run from ROM nor will it be viable in a multi-threaded environment. Replacing the unrolled loop entry with a jump table is left as an exercise for the reader.

Note that while LDI does not loop it does decrement BC and sets the overflow flag to indicate that BC != 0. That helps make the loop faster and by unrolling by 16 the cost per byte is 16 + 10/16 cycles: 16 cycles per LDI which copy a byte and a 10 cycle cost every 16 bytes to account for the JP V.

Can We Do Better?

Well, Z-80 memory access is fastest through the PUSH and POP instructions. PUSH can write 2 bytes in 11 cycles and POP can read 2 bytes in 10 cycles. Thus the theoretical maximum speed write is 5.5 cycles/byte and maximum speed read is 5 cycles/byte. This means that 10.5 cycles/byte is the best we could possibly do. Loop overhead will cut that number down, of course.

The main issue with using PUSH and POP is that you cannot have any interrupts occur while the copy takes place. That may be a deal breaker, but if acceptable wrapping the function in DI/EI will take care of maskable interrupts and we'll assume that specific platform knowledge can rule out non-maskable interrupts (NMI).

The secondary problem is that between loading the stack pointer register twice for each block copied and some counter loop overhead it is hard if not impossible to run faster than 16 cycles/byte so LDI wins.

If your source and destination are fixed a fully unrolled stack-based copy can get as low as 12.5 cycles per byte. And the unrolling costs 1.57 program bytes per byte moved. Those are pretty heavy restrictions but there are applications. For example, moving bytes from an off-screen frame buffer to the hardware buffer.

Here's one iteration of such an unroll that moves 14 bytes:

    ld      sp,src  ; 10
    pop     af      ; 10
    pop     bc      ; 10
    pop     de      ; 10
    pop     hl      ; 10
    exx             ; 4
    pop     bc      ; 10
    pop     de      ; 10
    pop     hl      ; 10
    ld      sp,dst  ; 10
    push    hl      ; 11
    push    de      ; 11
    push    bc      ; 11
    exx             ; 4
    push    hl      ; 11
    push    de      ; 11
    push    bc      ; 11
    push    af      ; 11

Those numbers add up to 175 cycles. 175 cycles / 14 bytes = 12.5 cycles/byte.

Note that it is perfectly safe to use AF in this fashion; the data will be copied correctly. Also note that trying to speed it up by using IX or IY won't work as their PUSH/POP is slower and can only manage 14.5 cycles/byte.

3
  • 1
    George, you might want to add that above is only true for fixed adresses of src and dst (and dst being destination+14) and only works if only 14 bytes are to be transfered. With variable addresses LD SP,(x) counts 20 clocks (may be overcome with self modifying code but only in RAM) and any transer of more than 14 bytes adds an overhead of at least 61 cycles for pointer update plus 20 for the loop, for a total of 256 cycles, or ~18,3 cycles, thus beeing slower than an unroaled LDI
    – Raffzahn
    Commented Sep 16, 2017 at 21:28
  • @George your first paragraph shows that you've misunderstood the question. Directly to quote it: "I gather the fastest way to implement memcpy (copy a certain number of bytes from one place in memory to another) on the Z80 is to use an instruction called LDIR. But how fast is the result, when fully optimized, in terms of clock cycles per byte?" So it asks about optimising an LDIR implementation. It is correct to say that an LDIR implementation doesn't need optimising. It is then an extension to discuss alternatives, but I felt it helped more fully to answer "But how fast is the result?"
    – Tommy
    Commented Sep 17, 2017 at 0:44
  • 2
    I think the question was just poorly worded. Taking your interpretation it is a non-sequitur. You can't optimize a single instruction. Commented Sep 17, 2017 at 1:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .