The 68000/VM7 system supported two basic modes of operation:
- 68000 touches image/model pairs (read or write) using image and model address codes, M FIFO acts like FIFO
- 68000 touches image pixels (read or write) using image address codes, M FIFO acts like a ring.
The maximum rate at which the memory system can put image/model pairs on the bus is one pair (two bytes) per memory cycle, which is four clocks per pair. VM7 can easily keep up with that rate, so the bottleneck in pair mode is how efficiently code can use the bus.
The maximum rate at which the memory system can put image-only pixels on the bus is two per memory cycle, two clocks per pixel. VM7 needs a little over three clocks. If code can use the bus efficiently enough, VM7 would be the bottleneck in image-only mode.
On the 68000, there is exactly one instruction that uses the bus most efficiently: compare memory long:
cmpm.l (aMdl)+, (aImg)+ ; pair mode
cmpm.l (aImg)+, (aImg)+ ; image-only mode
cmpm.l compares two longwords fetched from memory, post-increments the address pointers, and sets the condition codes (which we don’t care about). aMdl is any address register with the address bits 23:22 set to 10, likewise for aImg with the address bits set to 01. With pair mode the model bytes are fetched first.
cmpm.l takes five memory cycles to fetch four image/model pairs, or eight image-only pixels, with one extra cycle to fetch the instruction. With pair mode that’s a theoretical max of 4/5 = 80% of the max memory bandwidth. Theoretical because there’s gonna be overhead. 80% is a target, not a guarantee. But it’s nice to have a target, because then you know when you’re done.
In image-only mode the best we could do is VM7 speed, three clocks per image-only pixel. cmpm.l can do eight bytes per 20 clocks = 2.5 clocks/image-only pixel. If we could keep overhead low so that 2.5/3 = 83% of the execution time is cmpm.l instructions, that’s the best the hardware will support.
A good simplicity-speed tradeoff was to have a subroutine that can be called to touch an entire pair of image/model rows, or an entire image-only row, with no loop overhead. All the loop overhead is per row, not per pixel. The challenge was that we did not know at compile time the row size, nor the image row alignment at a given image location. We could, however, guarantee that each row in a given image has the same alignment, and that model rows are even-aligned. Alignment was important because the 68000 cannot do a word bus transaction at an odd address, and even if it could it would still be extra bus transactions.
In the description of VM7 I forget to note that the model value 255 is a don’t care—the image/model pair is ignored. We padded odd-width models with a don’t care at the end of each row so that model rows are of even size.
There are eight cases to consider for designing row loops for use with image/model pairs, corresponding to odd/even row alignment and row size modulo four. The simplest row loop can handle three of the cases.
; d0 image address update
; d1 row count
; a1 model address + M pixel code
; a2 image address + I pixel code
;
loop1: cmpm.l (a1)+, (a2)+ ; 64 of these to handle row size <= 256
...
cmpm.l (a1)+, (a2)+
adda.l d0, a2
dbf d1, loop1 ; offset adjusted at run time for row size
| Alignment |
Size % 4 |
Operation |
| even |
0 |
Straightforward operation of the loop |
| even |
3 |
Round up row size to multiple of 4 for dbf offset. The model has been padded with don’t care for the image pixel beyond the end. |
| odd |
3 |
Round up row size to multiple of 4 for dbf offset, and decrement the starting row address to make it even. Preload a don’t care into the M FIFO so that the first pixel of the first row (even address) is ignored. At the end of the row, there is still one extra M in the FIFO, which is the padded don’t care from the model row, so the next row is ready to go. |
If we replace the final cmpm.l in the above loop with cmpm.w, it can handle the cases of even alignment, size % 4 = 2, and even or add alignment, size % 4 = 1, using the same strategy as above. Only two cases remain.
Here is the loop for odd alignment, size % 4 = 0:
; d0 image address update
; d1 row count
; a1 model address + M pixel code
; a2 image address + I pixel code
; a3 address of a don’t care + M pixel code
;
loop1: cmpm.l (a1)+, (a2)+ ; 64 of these to handle row size <= 256
...
cmpm.l (a1)+, (a2)+
tst.b (a2)+
adda.l d0, a2
loop2: tst.b (a3)
dbf d1, loop1 ; offset adjusted at run time for row size
We decremented the starting row address to make it even and start at loop2 to preload a don’t care into the M FIFO. Since the first image pixel (even address) is ignored, the tst.b does one more at the end of the row, which consumes the extra M. There is no padded don’t care at the end of the model row, so another tst.b preloads one for the next row. The dbf offset was adjusted to reflect the row size and the various adjustments.
The final case is odd alignment, size % 4 = 2. This is identical to the above loop except that the final cmpm.l is replaced with a cmpm.w.
At run time we looked at the image alignment and row size, choose the right loop, adjusted the dbf offset, and in one case preloaded a don’t care M pixel. The vast majority of bus transactions used cmpm.l, so we got pretty close to 80% of max memory bandwidth. Similar strategies were used for image-only operation.
For NC, the 68000/VM7 system processed image/model pairs at a rate that was very close to five clocks per pair, 400 ns/pair, 2.5 million pairs/sec. A 128x128 model would need about 6.6 ms. The cascaded accumulator loop needed 24 clocks per model graylevel and 34 clocks per image graylevel. If both used all 256 graylevels, that’s 14,848 clocks. Call it 15,000 with some overhead, or 1.2 ms, for a total of about 8 ms with some overhead. Remember that this was at one position in the image—clearly strategies were needed to drastically reduce the number of positions that had to be evaluated, but that’s beyond the scope of this thread.
Time could be reduced by reducing the number of graylevels used. In practice, 64 image levels and 24 model levels were usually quite sufficient. The model levels could be reduced by various means, and since that’s done only at training time it doesn’t add to the runtime. The image levels could be reduced at no runtime cost using the T array to implement any desired mapping. These reductions could shave a millisecond off the total time.
While computing NC was the most commercially valuable operation for the 68000/VM7 system, it was quite flexible and could do many other operations that were important in the early years of industrial machine vision. Next we’ll look at a completely different operation that uses both image/model pair and image-only operation, as well as the T array and moment generation by cascaded accumulators.