; A library of math subroutines for SplitBit. #Program int16add: ; Add two two-byte unsigned integers. ; Operands are stored in a four-byte array in the following order: ; Operand A, high byte ; Operand A, low byte ; Operand B, high byte ; Operand B, low byte ; The result is stored by overwriting Operand A. ; This subroutine expects the Data Pointer to be set to the start of the array. ; After execution, this routine leaves the data pointer set to the high byte of the result. ; If the addtion results in a carry, the carry flag will be set. CCF ; Clear the Carry Flag, just in case. DPUP 0d03 ; Move to Operand B's low byte. LDB ; Load it to B. DPDN 0d02 ; Move to Operand A's low byte. LDA ; Load it to A. ADD ; Add them together. STQ ; Store the result. INCD ; Go to Operand B's high byte. LDB ; Load it to B. DPDN 0d02 ; Go to Operand A's high byte. LDA ; Load it to A. ADD ; Add them together. STQ ; Store the result. RET ; Return to the caller. int16sub: ; Subtract two two-byte unsigned integers. ; Operands are stored in a four-byte array in the following order: ; Operand A, high byte ; Operand A, low byte ; Operand B, high byte ; Operand B, low byte ; Operand B is subtracted from Operand A and the result is stored by overwriting Operand A. ; This subroutine expects the Data Pointer to be set to the start of the array. ; After execution, this routine leaves the data pointer set to the high byte of the result. ; If the addtion results in a carry, the carry flag will be set. CCF ; Clear the Carry Flag, just in case. DPUP 0d03 ; Move to Operand B's low byte. LDB ; Load it to B. DPDN 0d02 ; Move to Operand A's low byte. LDA ; Load it to A. SUB ; Subtract B from A. STQ ; Store the result. INCD ; Go to Operand B's high byte. LDB ; Load it to B. DPDN 0d02 ; Go to Operand A's high byte. LDA ; Load it to A. SUB ; Subtract B from A. STQ ; Store the result. RET ; Return to the caller. ; ---- Multiplying, which this machine cannot do ---- ; ; There is no MUL. What there is instead is an identity: ; ; a * b = qs[a + b] - qs[|a - b|] where qs[n] is n squared over four ; ; because (a+b)^2/4 - (a-b)^2/4 is exactly a*b, and the halves that the flooring throws away ; cancel between the two terms. So a multiply is TWO LOOKUPS AND A SUBTRACT. ; ; ---- And the table is built by adding ---- ; ; Which is the part that makes this fit a machine with no multiplier at all. A table of ; squares would need squaring to fill, and this one does not: ; ; qs[n] = qs[n-1] + n/2 ; ; and n/2 goes 0, 1, 1, 2, 2, 3, 3 - a number that steps up on every even n. So the whole ; table is one running total and a counter, and nothing harder than an add appears anywhere in ; building the thing that does the multiplying. ; ; 511 entries of two bytes, because a and b are bytes and a+b reaches 510. That is 1,022 bytes ; of Data Memory, which is the price: this is a routine that trades a kilobyte for an ; operation the hardware has not got. ; Fills the table. Call once, before the first multiply, and never again. mulReady: SETD.0 MulTable RSTA STA.0 INCD.0 STA.0 ; qs[0] is nought. INCD.0 SETD.1 MulTotalLow STA.1 INCD.1 STA.1 ; And so is the running total. SETD.1 MulStep STA.1 SETD.1 MulToggle STA.1 ; Five hundred and ten more entries to write. INIA 0xFE SETD.1 MulLeftLow STA.1 INIA 0x01 SETD.1 MulLeftHigh STA.1 mulEntry: ; ---- The step goes up on every even index ---- ; ; n/2 for n = 1, 2, 3, 4 is 0, 1, 1, 2: it rises at the even ones. A toggle says which this ; is, which is cheaper than halving a sixteen bit counter every time round. SETD.1 MulToggle LDA.1 INIB 0x01 XOR STQ.1 BNQ mulNoStep ; The toggle is one, so this index is odd and the step stands. SETD.1 MulStep LDA.1 INCA STA.1 mulNoStep: ; The running total, plus the step, sixteen bits. CCF SETD.1 MulTotalLow LDA.1 SETD.2 MulStep LDB.2 ADD STQ.1 SETD.1 MulTotalHigh LDA.1 RSTB ADD ; Nothing but the carry out of the half below. STQ.1 ; And into the table, where DP0 has been walking all along. SETD.1 MulTotalLow LDA.1 STA.0 INCD.0 SETD.1 MulTotalHigh LDA.1 STA.0 INCD.0 ; One fewer to do, counted down sixteen bits. SETD.1 MulLeftLow LDA.1 BNA mulCountLow SETD.1 MulLeftHigh LDA.1 DECA STA.1 INIA 0xFF SETD.1 MulLeftLow STA.1 BRI mulCounted mulCountLow: DECA STA.1 mulCounted: SETD.1 MulLeftLow LDA.1 SETD.2 MulLeftHigh LDB.2 OR BNQ mulEntry RET ; ---- One multiply ---- ; ; The operands go in MulA and MulB and the sixteen bit product comes back in MulLow and ; MulHigh. IN MEMORY RATHER THAN IN REGISTERS, because B cannot be stored and a product does ; not fit in one byte anyway - two registers in and two out would spend more instructions ; shuffling than the multiply costs. mul8: ; The sum, which is nine bits: two bytes can reach 510 between them. SETD.0 MulA LDA.0 SETD.1 MulB LDB.1 CCF ADD BRC mulSumOver RSTA BRI mulSumHigh mulSumOver: INIA 0x01 mulSumHigh: SETD.0 MulSumHigh STA.0 MVQA ; Q is still the sum's low half; nothing above touched it. SETD.0 MulSumLow STA.0 ; The difference, without its sign, which is what the identity wants. SETD.0 MulA LDA.0 SETD.1 MulB LDB.1 CCF SUB BRC mulOtherWay ; Borrowed, so B is the larger and the sum goes the other way. MVQA BRI mulDiffGot mulOtherWay: SETD.0 MulB LDA.0 SETD.1 MulA LDB.1 CCF SUB MVQA mulDiffGot: SETD.0 MulDiff STA.0 ; qs[sum]. An entry is two bytes, so the index is doubled - one turn of the shift register, ; where A is the high half and B the low, which is the way DPUW wants them too. SETD.0 MulSumHigh LDA.0 SETD.0 MulSumLow LDB.0 SHL SETD.0 MulTable DPUW.0 LDA.0 SETD.1 MulLow STA.1 INCD.0 LDA.0 SETD.1 MulHigh STA.1 ; Less qs[difference], sixteen bits, the borrow running from one half to the other. RSTA SETD.0 MulDiff LDB.0 SHL SETD.0 MulTable DPUW.0 CCF SETD.1 MulLow LDA.1 LDB.0 SUB STQ.1 INCD.0 SETD.1 MulHigh LDA.1 LDB.0 SUB STQ.1 RET #Data ; ---- The multiply's working ---- ; ; MulLow and MulHigh are next to each other on purpose: the product is read as a pair. MulA: 0x00 MulB: 0x00 MulLow: 0x00 MulHigh: 0x00 MulSumLow: 0x00 MulSumHigh: 0x00 MulDiff: 0x00 MulStep: 0x00 MulToggle: 0x00 MulTotalLow: 0x00 MulTotalHigh: 0x00 MulLeftLow: 0x00 MulLeftHigh: 0x00 ; 511 entries of two bytes: n from nought to 510, which is as far as a byte plus a byte goes. MulTable: #Reserve 0d1022 Int16operands: 0x00 ; Operand A high byte. 0x00 ; Operand A low byte. 0x00 ; Operand B high byte. 0x00 ; Operand B low byte.