Sunday, January 5, 2020

The PDP-11 DIV instruction and how to avoid it (sometimes)


  In a MACRO-11 program I've been working on, I have occasion to emulate tabbed text when composing a line for output. Part of that task involves figuring out how many spaces to insert after a text item, to get to the next "virtual" tab stop.

  Easy enough - if I divide the length of the line, including the new item, by 8, then take the remainder from the division and subtract it from 8, that tells me how many spaces I have to append to it to take me to the next tab stop in the output line. The PDP-11 instruction DIV provides the remainder from the division in the higher of the two registers used, so it seems like an obvious choice here.

  Easy enough to describe, but, distasteful for several reasons. The DIV instruction on PDP-11s is one of my least favorite instructions. Actually, all of the EIS instructions (MUL, DIV, ASH, ASHC and XOR) are pains in the sitz-platz. MUL, DIV and ASHC all use two registers, and it's hard for me  to remember which ones hold the high and low words, for arguments and results. When you're trying to make all of your code reentrant and PIC, wasting registers is a real problem. XOR and ASH aren't  too bad - they have single word arguments, in the logical order, but they  aren't completely orthogonal - ASH only works on registers, and you can't XOR two memory locations, for instance. Additionally, EIS instructions aren't available on all PDP11 processors - so as soon as you manage to learn how to use them, you'll find yourself working on an 11/05, and you'll have to fall back on repeated adds, subtracts, and shifts, to get the job done.

  OK, so you get the picture - I'd rather eat a spoonful of dirt than use DIV. But, I have a job to get done here, and it's always easier to dig out the PDP11 handbook and look up how it works again than it is to stop and come up with a good alternative. And that's what I did this time - again. But, I came back a couple days later and realized that, since the divisor in this case is a power of two, I can get the remainder I need practically by inspection.

  To get the remainder of a division by 8, all you have to do is clear  bits 3 and above in the argument (it's bit 3, because 2 to the 3rd power is 8). Logically enough, since by doing that, you clear away everything in the number but the bits that make up a number less than 8 - and that's the definition of the remainder. Have a look at this example...



A:     .word   37.

         bic      #177770,A         ;clear bits 3 to 15

  37 decimal is 100101 binary. Clearing all but the bits 0, 1 and 2 (that is clearing bits 3 and up) leaves us with 101, which is 5 decimal - and that's equal to the remainder of 37/8.

  In like wise, for other powers of two, mask from the appropriate bit up to bit 15, For remainders of 16, for example, bit 4 and up (bit 4, since 2 to the 4th power = 16).

A:     .word   41.

        bic       #177760,A     ;clear bits 4 to 15

  41 decimal is 101001. Clearing all bits except for 0, 1, 2 and 3 leaves us with 1001, which leaves a remainder of 9 in word A - and that's the remainder of 41/16.

  OK, it's not the most profound bit of MACRO-11  math, but, it did save use of an extra register, which means it also most likely saved a push and a pop to save the register before I used it.... And I didn't have to use DIV...