As was said in the comments, this is the binary long division algorithm. The long division is performed by carefully juggling the bits between the registers and the carry flag.
The algorithm is probably best analysed by looking at each half of the loop as a whole.
MOV D, A
MOV A, E
RAL
MOV E, A
DCR C
JZ exit
The first half of the loop shifts the result bit held in the carry flag (computed in the other half of the loop) into the bottom-most bit of E and simultaneously shifts the top-most bit of E into the carry flag. At the start of the loop, the carry flag is in an indeterminate state, but because the loop iterates nine times, at the end that indeterminate bit is shifted out and does not contribute to the result.
MOV A, D
RAL
SUB B
JNC loop_1
ADD B
JMP loop_1
The other half of the loop shifts the carry flag (what was the top-most bit of E) into the bottom-most bit of D and then performs subtraction D − B. If this computation underflows, the SUB
instruction sets the carry flag; the subtraction is then undone, keeping the carry flag set. Otherwise, if the computation succeeds, the carry flag is cleared. Either way, the value of the carry flag is passed along to be shifted into E in the first half of the loop.
After the loop exits, the bits of E are complemented. This is because the value of the carry flag obtained in the second half of the loop is actually the opposite of the corresponding bit of the result. This could have been addressed by executing the CMC
instruction before shifting the bit into the E register, but, as per the 8080 manual, that would cost 4 CPU cycles per iteration (36 cycles overall), instead of 4 cycles to complement all the bits in bulk and 10 cycles to move them between the E register and the accumulator.
Thus, as the loop iterates, the D register contains the running remainder of the long division; meanwhile, the remaining bits of the dividend in the E register are gradually replaced by bits of the quotient. The overall register state can be summarised with a diagram:
D at entry E at entry B
┌─────┴┐ ┌┴─────┐ │
│ low bits of E (inverted)
high bits of E │ │
┌─┴┐ ┌────┴─┐ ┌─┤┌─── the carry bit (inverted)
00000001_00110000 ÷ 00001100 = 0001
0001_0011┆◁╌╌┌──────────────────── D
− 0000_1100┆ │
───────────┆ │ (not shown: the indeterminate initial
0000_0111▽◀──┘ value of the carry bit, also held in E)
000_01110◁╌╌┘
Below is a similar algorithm in C. It is not a literal translation (there is no explicit carry bit), but it does reflect how bits of the dividend are gradually replaced by bits of the results.
#include <stdint.h>
uint16_t divide(uint16_t dividend, uint8_t divisor) {
uint8_t d = dividend >> 8, e = dividend;
uint8_t b = divisor;
uint8_t c = 8;
do {
d = (d << 1) | (e >> 7);
e = (e << 1);
if (d >= b) {
d -= b;
e |= 1;
}
} while (--c);
/* result in lower byte, remainder in upper byte */
return (d << 8) | e;
}
For those who would rather watch a video, Ben Eater has made one where he works through an example, and then implements (mostly) the same binary long division on the 6502.
RAL
) in the third iteration of the loop the accumulator is zero?