10

I'm familiar with the Hitachi 6309's block transfer instructions (TFM). It can work as a "poor man's" DMA controller or Blitter, and is one of the things that makes the 6309 the best 8-bitter of all time (Just ask any crusty old TRS-80 CoCo user!)

I've seen it mentioned somewhere that the venerable Z80 also had instructions that could do memory block transfers and/or peripheral I/O transfers. What were these Z80 (or maybe even 8080) instructions, how do they work, and how do they compare with the 6309 abilities?

Maybe crusty CoCo users should be hammering a Z80 into their machines instead... :)

4 Answers 4

15

Unplug the soldering iron again. It's not really worth it. For one the Z80 operations are less flexible, but more important, they are also slower (clock by clock or cycle by cycle, as you prefer) than their 6309 counterparts.

Nonetheless, these Z80 additions over the 8080 core were nice additions to simplify programming in assembly as well as compacting code - the later especially with their non-repeating counterparts.


The Z80's operations are:

LDIR - LoaD Increment Repeat - Move block with increasing addresses (from start)
LDDR - LoaD Decrement Repeat - Move block with decreasing addresses (from end)
CPIR - ComPare Increment Repeat - Compare A using increasing addresses (from start)
CPDR - ComPare Decrement Repeat - Compare A using decreasing addresses (from end)
INIR - IN Increment Repeat - Input block with increasing addresses (from start)
INDR - IN Decrement Repeat - Input block with decreasing addresses (from end)
OTIR - OuT Increment Repeat - Output block with increasing addresses (from start)
OTDR - OuT Decrement Repeat - Output block with decreasing addresses (from end)

Unlike TFM they all operate with a fixed set of registers holding the needed information:

For memory (LDIR/LDDR/CPIR/CPDR):

H/L - Source address
D/E - Destination address (only LDxx)
B/C - Byte counter
A - Byte to be compared / searched (only CPxx)

For I/O (INIR/INDR/OTIR/OTDR):

H/L - Memory address (source or destination)
B - Byte counter
C - Port number


Stupid Part

They don't really offer increased speed. Just convenience in programming, as their execution timing is literally just the combined time of the individual instructions. In fact, unroling an LDIR into LDI can speed up execution :))

The speed implications have been already discussed in great detail in a question some time ago.


An interesting sidenote here might be a comparison with the 8086 string operations. While MOVS/CMPS/INS/OUTS look quite similar at first, the decrement of the byte counter is not included in the non-repeating basic version, making them more suitable as building blocks for string/stream processing loops. Even more so with the support of LODS/STOS (and XLAT) plus LOOPand JCXZ. Not to mention that they really just loop around the action with out wasting time for opcode fetch :))

To me these string operations have always been the most intrigue part about the 8086 (them and the strict 16-bit-ness). They feel more like building blocks for high level constructs than ordinary instructions. Like a bunch of MSI TTL perfect tuned to build some powerful circuitry.

So if stream operations are something you desire, maybe an 8088 would make a great 8 bit CPU to be added?

1
  • 1
    just need to add why we got ldi(r) and ldd(r) if the transfer destination area is overlapping the source area (like in scrolls) than we use either ldi(r) or ldd(r) depending on the overlap if it is in the beginning or at the end of block so the not yet copied data is not overwritten before their transfer ....
    – Spektre
    Commented Jan 12, 2018 at 11:59
6

Yes, the Z-80 has block move instructions for memory, I/O and searching. These were additions made by Zilog and were more definitely not available on the Intel 8080.

Nominally the Z-80 accesses devices though a different I/O address space using special IN and OUT instructions. There's nothing stopping the Z-80 from using memory mapped devices but the block move instructions continue this separation of memory and I/O.

The two block memory move instructions are LDIR and LDDR short for "LoaD, Increment and Repeat" and "LoaD, Decrement and Repeat" respectively. Like TFM a fixed register (BC) is used to track the count of bytes moved. Unlike TFM the source and destinations addresses always use HL and DE respectively and the instructions are safely interruptable.

Those familiar with TFM can read them instructions as follows:

LD    BC,256            LDW  #256
LDIR                    TFM  HL+,DE+
LDDR                    TFM  HL-,DE-

A zero count in BC will transfer 65536 bytes.

For I/O ports there is OTIR/OTDR for outputting data and INIR/INDR for inputting data. For those instructions C register is used as the I/O port address and B register tracks the byte count. So unlike TFM these instructions can only output 1 to 256 bytes at a time.

Their relationship to TFM is not exact because of the separate I/O address space but it is roughly as follows:

OTIR                    TFM  HL+,C
OTDR                    TFM  HL-,C
INIR                    TFM  C,HL+
INDR                    TFM  C,HL-

I won't go into the CPIR and CPDR instructions here as they are only similar to TFM in that they repeat.

There are a few details I glossed over that I'll cover now.

Technically the I/O instructions work on port BC, not just C. This is a bit odd as the changing byte count in B means the port number is changing as data is output. Some Z-80 systems simply ignore the upper 8 bits of the port address and act as if there are only 256 ports. In other words, the effective operation of the instructions depends on system architecture and not just the Z-80 itself.

All the Z-80 block move instructions come in non-repeating form. They do exactly the same operation and the repeating forms including decrementing BC (or B) register but do not repeat themselves until BC (or B) is zero.

Repeating           Single
LDIR                LDI
LDDR                LDD
INIR                INI
INDR                IND
OTIR                OUTI
OTDR                OUTD

They do set flags to indicate if BC or B has reached zero so are useful when you wish to do the block copy operation but with some extra checks along the way. As they are all 5 T-States faster than the repeat forms they can also be used to unroll the operation for faster execution.

Finally it should be noted that these instructions are optimized for program compactness and programmer convenience. At 21 T-States per byte copied they're not particularly fast.

1
  • Ah. That's disappointing, as the 6309 TFM instruction is both for compactness AND performance.
    – Brian H
    Commented Jan 12, 2018 at 3:06
6

Despite being believed as too slow, LDI- and LDIR-like commands in Z80 actually do their best in moving bytes.

LDI takes 16 cycles in total, specifically 4+4 cycles to read the opcode, then 3+3 cycles to read and write memory and finally 2 cycles to house-keep, which includes incrementing HL and DE and decrementing BC, as well as setting several flags according to the resulting BC. This is actually not bad when we take into account that INC BC/DE/HL solely take 6 cycles, that is 4 cycles for opcode read and then 2 extra cycles.

LDIR is similar to LDI with the addition of looping. The loop was made in the simplest way by conditionally retarding PC back 2 bytes and executing LDIR again, which takes additional 5 cycles. If we look at the simplest JR command we can notice that it takes same 5 cycles to add signed byte to PC: 4 cycles for opcode fetch, 3 cycles for operand fetch and the rest is exactly the same 5 cycles. (IX+offset) addressing again takes takes the same 5 cycles for the same task.

Comparing Z80 with 6809 we should take into account difference in first production date which leads to technology difference (feature size) and thus to difference in the number of transistors that were available to the designers, as well as the fact that Z80 traditionally has higher clock rates than 6502-likes and 6809-likes, thus making the direct comparison like 'this CPU is better because it makes more work per clock' irrelevant.

4
  • 2
    My only complaint with LDIR/LDDR is that it rereads the opcode every time around the loop. So of the five unavoidable memory accesses at each step — two bytes for the opcode, with a refresh cycle, then a read and a write — 40% are unnecessary. It's better than not providing them at all though; they are useful.
    – Tommy
    Commented Jan 12, 2018 at 14:46
  • 2
    Yes, but... I believe that it was the decision not made voluntarily. I can imagine that adding capability of not repeatedly reading the opcode to ***R commands would incur significant changes in instruction decoder and control logic (that is, being able to repeatedly execute internal microcode as well as being able to correctly interrupt ***R execution for an interrupt response, leaving contents of registers and flags intact), as well as overall increase in transistor count. The creators of Z80 probably had deadlines or limited transistor budget hence didn't do that.
    – lvd
    Commented Jan 13, 2018 at 14:18
  • 1
    The Z80 already had quite a high transistor count for the time. Implementing the ***R instructions the efficient way would have required implementing looping and/or branching logic in the instruction decode circuitry making it much more complex. In any case, as the answer says, the existing implementation is already a win compared to an explicit loop.
    – JeremyP
    Commented Jan 15, 2018 at 10:24
  • 2
    @lvd: There would be three reasonable ways for LDIR to work: 1. Rewind the PC each iteration except the last, allowing for interrupts to be processed normally; 2. Run the entire block without re-fetching instructions or handling interrupts; 3. Run the block without re-fetching instructions, but if an interrupt occurs rewind the PC and then service it. Of those choices, I think #2 might have been easier and faster than #1, but the people at Zilog may not have wanted to have interrupts disabled for so long.
    – supercat
    Commented Jan 15, 2018 at 20:03
5

IIRC, LDIR (et al.) is a 2-byte opcode. The repeat was implemented as a decrement of the PC rather than an increment to the next instruction. Dirt simple, but inefficient on the bus, and not terribly speedy. However, this also had some value, both in the processor implementation and in the system: 1) DRAM refresh was unaffected, as that is done at the end of the opcode fetch, which continues to happen; 2) Interrupt behavior was unaffected.

You must log in to answer this question.

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