There’s probably another CPU meeting your criteria which beats this, but as a data-point, a number of x86 CPUs fit the bill. One is the 80C188 (introduced in 1987) which was available at frequencies up to 25MHz, and the compatible Am188ER CPU was available at frequencies up to 50MHz. These have an 8-bit data bus, but a 16-bit ALU. Another is the 80486, introduced just before your cut-off; see below. Like all x86 CPUs these support the following minimal 16-to-32-bit multiplication:
MUL BX
This multiplies AX
and BX
, unsigned, and stores the result in DX
:AX
. If you want to read the operands from memory and write them back:
MOV AX, [op1]
MOV BX, [op2]
MUL BX
MOV [resh], DX
MOV [resl], AX
where op1
and op2
point to the operands, resh
points to the high word of the result, and resl
to the low word.
The timings are as follows (MOV
is slightly slower on the Am188ER):
MUL r16
: 35–37 cycles
MOV
: 12 cycles on the 80188, 13 on the Am188ER for a 16-bit transfer between a register and memory (in either direction)
resulting in a total of at most 89 cycles, i.e. 1.8µs at 50Mhz.
A complete multiply-accumulate would look like this, courtesy of supercat, multiplying ranges of words starting at ES
:SI
and DS
:BX+SI+2
respectively, over CX
words:
ES LODSW
lp: IMUL [BX+SI]
ADD DI, AX
ADC BP, DX
ES LODSW
LOOP lp
The result is accumulated in BP
(high word) and DI
(low word).
This takes advantage of prefetching to shave cycles off the overall execution time. The reference 80188 timings are 18 cycles per LODSW
, 3 per addition, 34–37 per IMUL
, 15 per loop taken (including instruction queue reinitialisation and target instruction prefetch). The effective address calculations would add a lot more cycles on an 8086/8088 but are handled by a dedicated hardware unit on the 80188, with no additional (visible) cost.
Raffzahn pointed out that the 80486 can also run with an 8-bit data bus; while it has a 32-bit data bus natively, if BS8 and BS16 are pulled low, it will split up data transfers into 8-bit units. The 486 greatly reduces the base number of cycles required for all the instructions above: 1 cycle per register-to-memory MOV
, 13–26 cycles per 16-bit register-based multiplication, 1 cycle per register-based addition, 5 cycles per LODSW
, 7 per loop taken. I imagine the transfer splits add a number of additional cycles, although those might be hidden by the cache. The 486’s clock maxed out at 50MHz externally (in 1989), but the CPU was clock-doubled and then tripled during the 90s, up to 100MHz in Intel variants and 160MHz in AMD variants.