Errata - Software - FIG-Forth

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

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License