32

The Z80 has an instruction RLD, which apparently treats the lower 4 bits in the accumulator and the full 8 bits in (HL) as a twelve bit integer which it then rotates left by 4 bits. The carry flag does not participate in the rotation and the rest of the accumulator is left alone.

Correspondingly, there's an RRD.

Why would the designers put something like that in? I don't imagine it's a simple by-product of some other part of the design, but I can't think of a use-case which the other shift/rotate instructions wouldn't do a reasonably good job at.

1
  • 12
    I think because of BCD math like you might use to implement a decimal calculator, but that's just a guess. Great question! Commented May 29, 2018 at 15:38

6 Answers 6

39

Per Zilog's Z80 user manual:

Two BCD digit rotate instructions (RRD and RLD) allow a digit in the accumulator to be rotated with the two digits in a memory location pointed to by register pair HL (See Figure 10). These instructions allow for efficient BCD arithmetic.

Given that DAA exists for addition and subtraction, I'm going to hazard a guess that the motivation was long multiplication and division, permitting the shift-a-digit step with A acting as carry.

E.g. to derive long multiplication from first principles:

3 * 29
= 3 * (2 * 10 + 9)
= (3 * 2) * 10 + 3 * 9

RLD will give you the * 10 step.

If it were 3 * 2983471 there'd be six * 10 steps, manipulating what becomes a four-byte answer.

If it were 23 * 2983471 then that'd be twice as much work, to get 2 * 2983471, multiply that by ten, then calculate and add in 3 * 2983471. So twelve * 10 steps.

I'd also be likely to have obtained the 2 in isolation by a divide-by-ten step. Which RRD would help with.

17

RLD and RRD are simple 4-bit shifts of BCD-coded values. As they are shifting by one decimal digit right or left, they effectively result in a multiplication (or division) by 10 (assumed A is 0) when used repeatedly on a multi-digit BCD value or a multiplication by 10 plus an addition of the lower digit in A. A acts as a sort of "decimal carry" in this operation.

You can use this in a multiplication (or division) routine for decimals parallel to the pen-and-paper method you learned at school.

Multiplying a multi-digit packed BCD number at DATA by 10, with the byte count (half the number of decimal digits) in COUNT, would look like the below:

        LD      HL, DATA
        LD      B, COUNT
        XOR     A
ROTAT:  RLD
        INC     HL
        DJNZ    ROTAT

Outside of BCD-coded decimals, you could be using these commands to scroll a ZX Spectrum screen left or right by 4 pixels, for example.

1
  • 2
    Probably worth mentioning that your example assumes little-endian BCD. IOW, RLD should start at the right-hand end, using A to hold the intermediate carries as HL moves leftwards through the number; conversely, a RRD loop starts at the left-hand end and works rightwards. Commented Mar 27, 2019 at 15:39
11

The other answers all mentioned indeed that it is for BCD operations. What they all forgot to mention is why it is worthwhile to implement these complex instructions. Simply for multiplying/dividing by 10 is not sufficient. The real use comes up when one implements BCD floating point arithmetic, which was much more used prior to IEEE-754.

Floating point operations add and subtract need that mantissas be aligned which requires shifting one of the mantissas by a multiple of 4 bits. Dedicated instructions can in that case be quickly worth it.

Some implementations of the Z80 in FPGA (Z80N) add a slew of instructions of which SWAPNIB (EC23) is related to this operation, it swaps the high and the low nibble of the accumulator.

Z80 was not the only CPU to implement these instructions.

  • NEC added to the V20/V30 instructions (80186 compatible) a whole group of BCD instructions of which ROL4 (0F28) and ROR4 (0F2A) are notable.

  • Sharp's ESR-H microcontroller SC61860 used in a lot of their pocket computers have SRW and SLW instruction that shift by 4 in a loop (it also has SWP instruction that swaps low and high nibble of the accumulator). The instruction set is optimized to handle floating point BCD arithmetic.

  • Sharp's LH-5801 CPU of the PC-1500 pocket computer has similar instructions DRL, DRR and AEX (nibble swap) for the exact same reason of handling BCD floating point format.

8

These instructions appear to be useful for converting between BCD numbers and their printed representation. They would also be useful for printing numbers in hexadecimal format. accessing a BCD or hex value 4 bits at a time corresponds to one input or output character.

3
  • 1
    I think their main usefulness would be when shifting multi-byte quantities, both in the case where one wants a shift by four, and also in the cases where one wants a shift by 3 or 5 [as might occur fairly often when working with floating-point numbers], since shifting by 4 and then shifting one bit forward or backward would be faster than shifting left 3 times or moving data forward a byte and shifting backward 3 times.
    – supercat
    Commented May 29, 2018 at 15:48
  • @Ken Would you be able to put some code together to illustrate what you mean? Commented May 29, 2018 at 15:52
  • 1
    @Wilson I am unfortunately not a Z80 programmer, but the idea is similar to code given in other answers: loop through a number 4 bits at a time and do something with the 'current' 4-bit 'digit'. to convert a BCD digit to ASCII you would add 48, so that 0 becomes ASCII '0' (48+0 = 0) and 9 becomes ASCII '9' (48+9 = 57). Converting ASCII to BSD you would do the reverse, subtracting 48.
    – Ken Gober
    Commented May 30, 2018 at 15:28
5

I think the primary usage case for something like that would be shifting a multi-byte quantity left or right by four bits. One could rotate a 56-bit value (e.g. the mantissa of a floating-point value) left by 4 bits by doing something like:

    ld h,myThing >> 8  ; Assume it doesn't cross a page boundary
    ld bc,4
    and a ; Clear carry
loop:
    ld l,myThing & 255
    rl (hl)
    inc l
    rl (hl)
    inc l
    rl (hl)
    inc l
    rl (hl)
    inc l
    rl (hl)
    inc l
    rl (hl)
    inc l
    rl (hl)
    djnz loop

but it could be done much faster as:

    ld h,myThing >> 8  ; Assume it doesn't cross a page boundary
    xor a  ; Could use "ld a,0" if carry was important for some reason

    ld l,myThing & 255
    rld (hl)
    inc l
    rld (hl)
    inc l
    rld (hl)
    inc l
    rld (hl)
    inc l
    rld (hl)
    inc l
    rld (hl)
    inc l
    rld (hl)

I'm not sure the value of that is greater than the value of other features that could have been provided for the same amount of effort and silicon(*), but I think something like the above would be the intended usage pattern. Still, the feature would have value either in cases where things are being shifted by exactly four bits, or in performance-sensitive code (like floating-point math) where values may need to be shifted by N bits and it would not be uncommon for N to be 3, 4, or 5. I'm not sure exactly how big the thing to be shifted would need to be to make such optimizations worthwhile, but the requirement that objects be stored in RAM might limit the value of such optimizations when shifting smaller things. If a 32-bit value needed to be shifted, the first approach could be changed to:

    ld hl,(myThing)
    ld de,(myThing+2)
    ld bc,4
    and a ; Clear carry
loop:
    add hl,hl
    rl  e
    rl  d
    djnz loop
    ld (myThing),hl
    ld (myThing+2),de

greatly reducing the cost of the loop. The fact that the rld form doesn't need the loop would likely make it more efficient than running the loop four times, but if "rld" had allowed code to specify a register rather than (HL) it could have been much more useful in the small-objects case.

(*) Since writing the above, I've discovered that the Z80 uses a 4-bit ALU, and has steering logic to load/store its value from/two the upper or lower half of either half of any 16-bit register pair, including the temporary register, which would suggest the amount of circuitry required for these instructions is probably less than I would have expected. This likely contributes to their inclusion in the instruction set.

3

One particular example of the use and usefulness of these is in certain Z80 versions of Microsoft BASIC, such as MSX-BASIC from 1.0 onwards.

MSX BASIC uses decimal floating point arithmetic with 6 digits (4-byte single-precision ! format) or 14 digits (8-byte double-precision # format) of significand along with one byte for the exponent. (The sign is stored separately.)

To normalise numbers in this format you need to shift all of the digits to the left or the right as you decrement or increment the exponent. Clearly, RLD and RRD are quite useful for this, since each digit takes up 4 bits, and you can see them actually used in the code in the file f4.mac.

You must log in to answer this question.

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