Skip to content

The Maths Library

Julie Montoya edited this page Oct 17, 2020 · 8 revisions

A collection of mathematical and fundamental display routines which are called from elsewhere in the program.

Many of the routines take parameters in X and Y, or just a single parameter in X. These indicate the offsets from wkspace, the bottom of the workspace (=&A00 normally) of operands. Some routines take a zero-page address in X and an offset from wkspace in Y. The multiply and divide routines expect their parameters, and place their results, in fixed locations.

GENERAL MATHEMATICAL FUNCTIONS

The multiplication and division routines have been rewritten in a faster form.

mult16 -- 16-bit unsigned multiply. Multiplies the 16-bit multiplicand in &74, &75 (which will not be altered) by the 16-bit multiplier in &70, &71 and leaves the 32-bit product in &70-&73.

mult16_signed -- Signed version. First "positivify" the multiplier and the multiplicand by examining their sign bits and if negative, twos-couplementing. We EOR together the original sign bits by EORing the original high bytes and if the high bit of the result is 1, we know that the product should be negative. We perform an ASL on the accumulator to shift this sign bit from the high bit of the accumulator into the carry before we begin the multiplication. This bit gets shifted into the multiplier when we shift out its units bit, will work its way through the multiplier as the product replaces it, and eventually fall out of the end back into the carry when the units bit of the product is shifted into place. After the multiplication, if the carry is set then we need to twos-complement all 32 bits of the product.

mult16_preload -- Multiplies as above, but adds a "preload" given in &72, &73 to the product. (This actually happens anyway, as a useful side-effect of the multiplication process. This entry point just skips over code to zero out the preload when not required.) This is useful when a record size needs to be multiplied by a table index and added to a base address.

mult_AX -- Multiplies the 8-bit value in A, with sign extension, by the 8-bit value in X. Returns the low byte in A and the high byte in X.

set_multiplicand, set_divisor -- stores A in the low byte of the multiplicand / divisor (they share the same memory location, so this subroutine is known by two labels), and zero in the high byte.

divide -- Divides a 32-bit unsigned dividend in &70-&73 by a 16-bit unsigned divisor in &74, &75, leaving the quotient in &70, &71 and the remainder in &72, &73.


mult16 -- 16-bit multiply. Multiplies 16-bit multiplicand at mulA by 16-bit multiplier at mulB. Places 32-bit product at product. Preserves X on stack, does not touch Y.

mult_div_16 -- This subroutine, which performs a 16-bit multiply then divides the product by the contents of divr, essentially forms the heart of the scale-to-screen routine. The multiplier is ordinarily kept in mulB and the divisor in divr; this state is indicated by bit 6 of scale_mode being set. If we have interfered with either mulB or divr, we can simply clear this flag to force scale_to_screen to restore the scale factors; otherwise we can save a few clock cycles each time around.

mult8 -- This is a quick 8-bit multiply routine which multiplies the 8-bit multiplicand in mulA by an 8-bit multiplier in mul8B, and returns a 16-bit product in product.


csign -- This extends the sign from C. It returns A=&FF if C=1, A=0 if C=0.

nsign -- This extends the sign from N. It returns A=&FF if N=1, A=0 if N=0.

add16 -- 16-bit clear carry and add. The address given by wkspace+X contains the augend initially, which will be replaced by the sum. The address given by wkspace+Y contains the addend. On exit, both X and Y will have been increased by 2 from their initial values. The Z, C, N and V flags will be correct for the addition.

adc16 -- alternative entry point immediately after the initial CLC, in case it be desired to preserve the carry flag e.g. for the upper 16 bits of a 32-bit value.

sub16 -- 16-bit set carry and subtract. The address given by wkspace+X contains the minuend initially, which will be replaced by the difference. The address given by wkspace+Y contains the subtrahend. On exit, both X and Y will have been increased by 2 from their initial values. Z, C, N and V will be correct for the subtraction.

sbc16 -- alternative entry point after the initial SEC.

add8_16 -- clear carry and add 8-bit to 16-bit. The 8-bit addend is in A, the 16-bit augend in the address given by wkspace+X.

adc8_16 -- alternative entry point after the initial CLC.

sub8_16 -- set carry and subtract 8-bit from 16-bit. The 8-bit subtrahend is in A, the 16-bit minuend in the address given by wkspace+X. Note that due to the order of presentation, we have to take the twos complement "manually" and add. If you remember the mantra "flip the bits and add one", we actually perform the flipping of the bits with EOR &FF and force a one to be added by setting the carry flag before the addition. If the carry flag is set after the low byte, this means no borrowing was required; otherwise, we need to decrease the high byte to account for the 256 borrowed.

sbc8_16 -- alternative entry point after the initial SEC.

twc16 -- replaces the 16-bit value at wkspace+X by its twos complement. X is unaltered.

teq16 -- test for equality, the 16-bit numbers at wkspace+X and wkspace+Y. Neither X nor Y is modified. On return, Z=1 if the two values were equal, 0 if they were different.

cmp16 -- perform a signed comparison (i.e., subtract but do not store the result) between the 16-bit numbers at wkspace+X and wkspace+Y. Neither X nor Y is modified. On return, N=1 if the value at wkspace+X was smaller than the value at wkspace+Y, 0 if the value at wkspace+X was equal to or larger than the value at wkspace+Y. Note that if the subtraction caused a false change of sign setting V, N is automatically corrected within the subroutine.

All the main working variables are organised together in page &A. Many operations take a "source" and "destination" address within this workspace in X and Y, or sometimes an address of a single argument.

CO-ORDINATE MANIPULATION

copy_coords -- copies the X,Y co-ordinate pair pointed to by X in wkspace to the location pointed to by Y in wkspace.

set_scale -- sets the multiplier to scaleM and the divisor to scaleD, and sets scalemode to indicate that co-ordinate scaling is in operation.

set_div5 -- sets the divisor to 5, and sets scalemode to indicate that divide-by-5 is active.

forget_scale -- sets scalemode to indicate that the multiplier and divisor values are not correct either for co-ordinate scaling or for division by 5.

div_coords -- sets the divisor to 5, divides the X,Y co-ordinate pair pointed to by X in wkspace by 5 and stores the results in the location pointed to by Y in wkspace.

test_pivp -- tests whether the current part (whose boundary is given by bdyL, bdyB, bdyR and bdyT) falls within the viewport (vptL, vptB, vptR and vptT). This calls the following routine twice in succession; first to test whether the bottom left corner is within the viewport, and again to test whether the top right corner is within the viewport.

test_pt -- tests whether a point (whose co-ordinates are pointed to by X in wkspace) falls within a box (whose co-ordinates, as an L,B,R,T quadruplet, are pointed to by Y in wkspace. This may be used variously to test whether a point is within the boundary of a component, or within the viewport, for example. N.B. All co-ordinates are assumed to be in the same system!

We first test if X (that is to say, the co-ordinate at wkspace,X, not the processor register itself) is less than L (at wkspace,Y), then if R (at wkspace+4,Y) is less than X (i.e., X is greater than R); then if Y is less than B, and lastly if Y is greater than T. Each time we shift a "1" leftwards into a byte in zero page if the co-ordinate is outside the range. We can AND this with &F and if the point was in the box, Z will be set.

On exit, X will have been increased by 4 (so possibly pointing to the next co-ordinate to test) and Y will be unchanged overall (so still pointing to the same box) after being increased twice while testing the Y co-ordinates, then decreased twice to restore it.

mult_coords -- Multiplies the X,Y co-ordinate pair pointed to by Y in wkspace by 5, overwriting with the products. On exit, Y will have been increased by 4.

mult5 -- multiplies the 16-bit value pointed to by Y in wkspace by 5. On exit, Y will have been increased by 2. This does not use the multiply routine (and therefore, does not affect scaling), but instead takes advantage of there being only two ones in the binary representation of five.

asl7071 -- doubles the 16-bit value in &70 and &71 (literally performs ASL &70 followed by ROL &71).

PACKED CO-ORDINATE HANDING

The locations pcb and pcb+1 contain the base address of a list of packed co-ordinates, which are accessed using LDA (pcb),Y; and are unpacked to standard addresses pinX and pinY.

unpack_KXY -- unpacks a K,X,Y plot mode and co-ordinate pair from (pcb),Y to pinX and pinY, and places the plot mode K in A.

unpack_KXY0 -- alternative entry point which clears Y first.

unpack_XY -- unpacks an X,Y co-ordinate pair from (pcb),Y to pinX and pinY.

unpack_XY0 -- alternative entry point which clears Y first.

DISPLAY AND PLOTTING ROUTINES

do_plot_XA -- plots the point whose co-ordinates are pointed to in wkspace by X, using A for the plot mode.

do_plot_X -- plots the point whose co-ordinates are pointed to in wkspace by X, in the plot mode given in plotmode.

begin_plot -- clears X and sends VDU25 to begin a PLOT instruction, followed by the byte in plotmode to specify the plot mode. The MOS will interpret the next four bytes sent to the VDU as the low and high bytes of the X co-ordinate and the low and high bytes of the Y co-ordinate respectively.

begin_plot0 -- alternative entry point which bypasses the LDX #0 and so preserves the contents of X.

begin_plotA -- Initiates a PLOT instruction using A for the plot mode, by sending VDU25 followed by the previous contents of the accumulator.

dispA -- displays an 8-bit unsigned number in A, in decimal notation.

disp_dec_X -- as below, but displays the number pointed to in wkspace by X. It does this by first copying the number to decnum as it will get spoiled if negative.

disp_decnum -- displays a 16-bit signed number at decnum (low byte) and decnum+1 (high byte), in decimal notation. Note that if the number is negative, it will be overwritten with its twos complement.

decode_decnum -- decodes a 16-bit number (assumed positive) pointed to in wkspace by X, and stores the ASCII codes for the digits of its decimal representation, units-first, at plotbuf. On return, Y contains the offset from plotbuf of the most significant digit.

os -- displays the character whose ASCII code is in A, then displays a space.

s -- displays a space.

b -- sounds a beep.

spcX -- displays the number of spaces in the X register.

ZERO-PAGE POINTERS

Zero-page pointers are used heavily in BCP. These routines allow them to be initialised and an offset added. Note that here, X is used for the zero-page address and Y indicates an offset from wkspace; this is because the short zp,Y addressing mode is not available for all instructions.

seed_zpp -- seed a zero-page pointer pair at the address given in X, with the number pointed to in wkspace by Y.

add_zpp -- add the 16-bit value at wkspace+Y to the zero-page pointer pair at X and save the total there in zero-page. On exit, both X and Y will have been increased by 2.

adc_zpp -- alternative entry point after the initial CLC.

addfp_zpp -- add the 16-bit value at (fpb),Y to the zero-page pointer pair at X and save the total there in zero-page. On exit, both X and Y will have been increased by 2.

adcfp_zpp -- alternative entry point after the initial CLC.

TABLES

The maths library contains some tables as follows:

cosines -- a table of cosines for x = 0 to x = 2 * pi, in steps of pi / 8. (17 entries including x = 2 * pi; actually, there are an extra four "unofficial" entries, due to some weird quirk of mathematics ;) ), as signed values from -127 to +127. Used for drawing circles (well, hexadecagons ;) )

sines -- a table of sines for x=0 to x=2 * pi, in steps of pi/8. (17 entries including x=2 * pi), as signed values from -127 to +127.

bitsLH -- a table with single-bit values in ascending order 1, 2, 4 .....

bitsHL -- a table with single-bit values in descending order 128, 64, 32 .....

PROGRAM OUTPUT

When the program is RUN, it assembles the maths library code in memory. After a silent first pass, the assembler code is displayed on screen; press the SHIFT key to allow it to scroll. When the code is finished assembling, the program displays (but does not execute) a *SAVE statement with the correct starting address, length, execution address (aimed at the first RTS instruction) and reload address, and another similar statement for the variable workspace; and a PROCedure call which will export the variables generated by the program, for use as entry points in the next program. Use the cursor up and COPY keys to copy the *SAVE commands. Then copy the PROC statement.

(N.B. If as a result of editing the program you have caused the machine code to overflow the available space, the program will STOP prematurely and print an error message. Adjust the values of S% (where the program is to be run from in actual use, not necessarily where it is actually assembled in memory) and/or B% (first unavailable address; =&5800 for MODE4 and MODE5 on a Model B, can be &8000 on a Master 128) at the beginning, SAVE the modified program and run it again.)

THE VARIABLE EXPORT FILE

Exporting the variables generates a *SPOOL file on disk containing BASIC statements to initialise variables corresponding to the entry points within the machine code we just assembled. This can then be read with *EXEC into any subsequent programs which make use of the maths subroutines, and those entry points used as normal.

MAGIC NUMBERS

25802 = &64CA : Multiply a number of hundredths of a millimetre by this to get a 32-bit product; the upper 16 bits of the product are the equivalent amount in thousandths of an inch.

The important maths is: 25802 * 254 (hundredths) DIV 65536 = 100 (thousandths). It's accurate (for multiples of 2.54mm. / 0.1in.) up to 327.66mm., which is almost as high as BCP's maths routines can go.

AIDE MEMOIRE PLEASE IGNORE


 5490\ DECODE decnum TO plotbuf
 5500.decode_decnum
 5580.dec_dn1
 5730.dec_dn2
 5760\  SEED ZERO-PAGE POINTERS
 5770.seed_zpp
 5830\  ADD wkspace,Y to 0,X
 5840.add_zpp
 5860.adc_zpp
 5880.adc_zpp1
 5900.adc_zpp2
 5960\  LIKEWISE FROM (fpb),Y
 5970.addfp_zpp
 5990.adcfp_zpp
 6010.adcfp_zpp1
 6040\
 6050\  MULTIPLY CO-ORDS BY 5
 6060\
 6070.mult_coords
 6090.mult5
 6200.asl7071


\ TRIG TABLES
.cosines EQUD&3059757F
.sines EQUD&8BA7D000
\  BITS IN ASCENDING, DESCENDING ORDERS
.bitsLH EQUD&8040201
.bitsHL EQUD&10204080
.end