Multiplying (and dividing) by powers of 2 has always been trivial and fast even for 8-bit processors like Z80 or 6502, with shifting instructions (commonly arithmetic shift left aka ASL
).
But those processors didn't have a MUL
instruction so when it came to non-power of 2 multiplication, it always involved shifting, testing bit and adding shifted result if bit is set, exactly like we do manually in base 10, if I may say.
So in the ROM, when one piece of coded needed to multiply by 2 or 4 or whatever, it used explicit ASL
, ROL
or whatever shifting instruction available, even when a generic shift-and-add multiply routine was available.
Sometimes when the number to multiply with was known, a special routine was used, like in the oric atmos ROM, when the ROM needed to multiply by 40 which is the number of bytes per row.
F731 A0 00 LDY #$00 This routine multiplies the
F733 8C 63 02 STY $0263 content of the accumulator by
F736 8D 64 02 STA $0264 #28 (40). Y holds the high
F739 0A ASL A byte of the result. The page
F73A 2E 63 02 ROL $0263 2 locations store temporary
F73D 0A ASL A results.
F73E 2E 63 02 ROL $0263
F741 18 CLC
F742 6D 64 02 ADC $0264 The result is calculated by
F745 90 03 BCC $F74A adding 4 x A to A and then
F747 EE 63 02 INC $0263 double the result.
F74A 0A ASL A
F74B 2E 63 02 ROL $0263
F74E 0A ASL A
F74F 2E 63 02 ROL $0263
F752 0A ASL A
F753 2E 63 02 ROL $0263
F756 AC 63 02 LDY $0263
F759 60 RTS
For other cases, it used the generic multiply routine. As you see, multiplying by a known number such as 40 is already a long, time consuming routine. The generic integer routine takes even more cycles.
Games didn't call ROM multiply directly but often defined their own when they needed it, with the same principle. L'Aigle d'Or (1984) has one for instance. When I converted the game to C, I "optimized" it by using multiplication. You can see the C & asm equivalent below
C version: performs (0x70)*(0x71), returns result in r.a,r.y
label_multiply_3E00:
{
int a=m[0x70];
int b=m[0x71];
int c=a*b;
r.a = c >> 8;
r.y = c & 0xFF;
rts;
}
original asm 6502 code, same interface, returns result in A, Y
lda #0
sta 0x72
ldx #8
label_0x3E06:
lsr 0x71
bcc 0x3E0D
clc ; clear carry
adc 0x70
label_0x3E0D:
ror A
ror 0x72
dex
bne label_0x3E06
sta 0x73
ldy 0x72
rts
A 8/16 bit developper (I think it was Simon Phipps) once said how much he was relieved when working on 16 bit processors because of the multiply and divide native instructions.
To be perfectly honest and transparent, I didn't find the generic integer multiply routine in the Oric ROM and I'm not going to find it since it probably only exists as floating point (that one can be found). This follow-up question Is integer arithmetic really slower than float with (early) MS-BASIC? is the reason for that final edit.