Bugs in 6502 FIG-Forth:
U/ (a.k.a. UM/MOD in ANS Forth)
U/ divides a 32-bit unsigned integer dividend by a 16-bit unsigned integer divisor to produce a 16-bit unsigned integer quotient and a 16-bit unsigned integer remainder. Note that:
dividend = divisor * quotient + remainder
- Inputs:
- 0,X = bits 7-0 of the divisor
- 1,X = bits 15-8 of the divisor
- 4,X = bits 7-0 of the dividend
- 5,X = bits 15-8 of the dividend
- 2,X = bits 23-16 of the dividend
- 3,X = bits 31-24 of the dividend
- Output (when it JuMPs to POP):
- 2,X = bits 7-0 of the quotient
- 3,X = bits 15-8 of the quotient
- 4,X = bits 7-0 of the remainder
- 5,X = bits 15-8 of the remainder
The original (buggy) code follows. With this routine, dividing $80000000 by $8001 returns the quotient
$0000 and the remainder $0000, which is incorrect. The correct quotient is $FFFE and the correct remainder is $0002.
LDA 4,X
LDY 2,X
STY 4,X
ASL A
STA 2,X
LDA 5,X
LDY 3,X
STY 5,X
ROL A
STA 3,X
LDA #16
STA N
L433 ROL 4,X
ROL 5,X
SEC
LDA 4,X
SBC 0,X
TAY
LDA 5,X
SBC 1,X
BCC L444
STY 4,X
STA 5,X
L444 ROL 2,X
ROL 3,X
DEC N
BNE L433
JMP POP
The problem is that when a one is shifted into the carry by the ROL 5,X instruction, it must be accounted for and the routine above does not do so.
The corrected routine follows.
;
LDA 4,X
LDY 2,X
STY 4,X
ASL A
STA 2,X
LDA 5,X
LDY 3,X
STY 5,X
ROL A
STA 3,X
LDA #16
STA N
L433 ROL 4,X
ROL 5,X
LDA 4,X
BCS L0446
SEC
SBC 0,X
TAY
LDA 5,X
SBC 1,X
BCC L444
STY 4,X
L0442 STA 5,X
L444 ROL 2,X
ROL 3,X
DEC N
BNE L433
JMP POP
L0446 SBC 0,X
STA 4,X
LDA 5,X
SBC 1,X
SEC
BCS L0442 ; branch always
With this routine, dividing $80000000 by $8001 returns the quotient $FFFE and the remainder $0002, which is correct.
U* (a.k.a. UM* in ANS Forth)
U* mulitplies two 16-bit unsigned integers (the mulitplicand and the multiplier) to produce a 32-bit result.
- Inputs:
- 0,X = bits 7-0 of the multiplier (TOS)
- 1,X = bits 15-8 of the multiplier (TOS)
- 2,X = bits 7-0 of the multiplicand (second-on-stack)
- 3,X = bits 15-8 of the multiplicand (second-on-stack)
- Output:
- 2,X = bits 7-0 of the product
- 3,X = bits 15-8 of the product
- 0,X = bits 23-16 of the product
- 1,X = bits 31-24 of the product
The original (buggy) routine follows; note that FIG-Forth calls this code with Y=$00. With this routine, multiplying $16A1 by $16A1 returns the product $01001141, which is incorrect. The correct product is $02001141.
;
LDA 2,X
STA N
STY 2,X
LDA 3,X
STA N+1
STY 3,X
LDY #16 ; for 16 bits
L396 ASL 2,X
ROL 3,X
ROL 0,X
ROL 1,X
BCC L411
CLC
LDA N
ADC 2,X
STA 2,X
LDA N+1
ADC 3,X
STA 3,X
LDA #0
ADC 0,X
STA 0,X
L411 DEY
BNE L396
The problem is that when there is a carry from the ADC 0,X instruction, the carry must be added to 1,X (to update bits 31-23 of the product), but the routine above does not do this.
A simple solution is to insert the necessary instructions between the STA 0,X and DEY instructions. However, the following multiplication can be used instead; it shifts the product right (rather than left, as the original routine does), is slightly smaller, and is faster for most cases.
;
TYA
STA N
LDY #16
LSR 3,X
ROR 2,X
L396 BCC L411
CLC
PHA
LDA N
ADC 0,X
STA N
PLA
ADC 1,X
L411 ROR
ROR N
ROR 3,X
ROR 2,X
DEY
BNE L396
STA 1,X
LDA N
STA 0,X
With this routine, multiplying $16A1 by $16A1 returns the product $02001141 which is correct.
For Forths where Y will not be $00 when U* is called, the TYA instruction can be replaced by LDA #0