Go to the Boostrap/Initialization routine at 3015H (which just jumps to 3455H which is the BOOTSTRAP (sets interrupts, clears ports, checks for a BREAK key and tries to work with the disk controller).
0008 (RST 8H) Jumps to 4000H. 4000H passes control to 1C96H. This routine is used for scanning strings. It compares the character pointed to by the HL Register Pair with the character pointed to by the return address on the top of the STACK (Note that a RST instruction is in effect a CALL and places a return address on the STACK) formula: (HL)=((SP))? If they are not equal an SN ERROR will result; if they are equal then the return address on the STACK will be incremented to bypass the test character and control will be passed to RST 10H logic. RST 8H is used to look for expected characters in a string and then return with (HL) pointing to the next non-blank character. (see RST l0H) (BC and DE registers unaffected.). This routine can be used by CALLing 1C96H or RST 8H
Jumps to 4000H. 4000H passes control to 1C96H. This routine is used for scanning strings. It compares the character pointed to by the HL Register Pair with the character pointed to by the return address on the top of the STACK (Note that a RST instruction is in effect a CALL and places a return address on the STACK) formula: (HL)=((SP))? If they are not equal an SN ERROR will result; if they are equal then the return address on the STACK will be incremented to bypass the test character and control will be passed to RST 10H logic. RST 8H is used to look for expected characters in a string and then return with (HL) pointing to the next non-blank character. (see RST l0H) (BC and DE registers unaffected.). This routine can be used by CALLing 1C96H or RST 8H. This is the COMPARE SYMBOL routine which comparess the symbol in the input string pointed to by HL Register to the value in the location following the RST 08 call. If there is a match, control is returned to address of the RST 08 instruction 2 with the next symbol in in Register A and HL incremented by one. If the two characters do not match, a syntax error message is given and control returns to the Input Phase)
000BH-000CH – DISK ROUTINE – “WHERE”
000B ↳ WHERE
POP HLE1
Get the address from the STACK and put it in Register Pair HL
000DH-000FH – DISK BOOTSTRAP – “$BOOT”
000C
JP (HL)E9
Jump to the location of the address in Register Pair HL
JUMP to the disk load and run sector routine at 069FH.
0010H-0012H – RST 10 – GET A CHARACTER FROM THE BUFFER
0010 (RST 10H) jumps to 1D78H through 4003H. This routine INCrements HL and tests the characters pointed to by the HL Register Pair. It will bypass any spaces and CHAR’S 9 and 10 (shifted left and down arrows respectively). Upon return from this routine HL will point to the next non-blank character; the carry flag will be SET if HL is pointing to a numeric ASCII character and the Z flag will be SET if the character pointed to happens to be zero (ASCII 30H) or 3AH (“:”). (BC and DE registers are unaffected) This routine can be used by CALLing 1D78H or RST l0H
This routine INCrements HL and tests the characters pointed to by the HL Register Pair. It will bypass any spaces and CHAR’S 9 and 10 (shifted left and down arrows respectively). Upon return from this routine HL will point to the next non-blank character; the carry flag will be SET (C=1) if HL is pointing to a number and the Z flag will be SET if the character pointed to happens to be zero (ASCII 30H) or 3AH (“:”). The carry flag will be RESET (0) if the character is non-numeric. (BC and DE registers are unaffected) This routine can be used by CALLing 1D78H or RST l0H
0013H-0017H – INPUT ROUTINE – $GET
This routine Inputs a byte from an input device. When calling, DE = starting address of DCB of device. On exit, A = byte received from device, Z set if device ready. Uses AF.
0013 ↳ $GET
PUSH BCC5
Save the value in Register Pair BC on the STACK
0014-0015
LD B,01H06 01
Load Register B with the device type entry code of 01H
Jump to the Level II BASIC driver entry routine at 0046H
0018 (RST 18H) Jumps to 1C90H through 4006H. This routine can be called by using RST 18H or CALL 1C90H. It compares two 16 bit values in HL and DE and sets the S and Z flags accordingly (they are set in the same way as for a normal 8 bit CP). All registers are unchanged except for A
Jumps to 1C90H through 4006H. This routine can be called by using RST 18H or CALL lC90H. It compares two 16 bit values in HL and DE and sets the S and Z flags accordingly (they are set in the same way as for a normal 8 bit CP). All registers are unchanged except for A. This is the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status Register as: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal)
001BH-001EH – DRIVER ENTRY ROUTINE – Part 1 – “PUT”
This routine outputs a byte to a device. When calling, A = output byte, DE = starting address of DCB of device. On exit, Z set if device ready. Uses AF.
001B ↳ PUT
PUSH BCC5
Save the value in Register Pair BC on the STACK
001C-001D
LD B,02H06 02
Load Register B with the device type entry code of 02H
Jump to the Level II BASIC driver entry routine at 0046H
0020 (RST 20H) This routine jumps to 25D9H through 4009H. If the NTF=8 then C=RESET or else C=SET, Z flag will be SET if NTF=3 (S flag is valid also.). After execution of RST 20H or CALL 25D9H, A will contain the value NTF-3, all other registers are unchanged. (The NTF will be discussed in the arithmetic section.)
This routine jumps to 25D9H through 4009H. If the NTF=8 then C=RESET or else C=SET, Z flag will be SET if NTF=3 (S flag is valid also.). After execution of RST 20H or CALL 25D9H, A will contain the value NTF-3, all other registers are unchanged. Returns a combination of STATUS flags and unique numeric values in the A Register according to the data mode flag (40AFH). Integer = NZ/C/M/E and A is -1; String = Z/C/P/E and A is 0; Single Precision = NZ/C/P/O and A is 1; and Double Precision is NZ/NC/P/E and A is 5. This CALL is usually made to determine the type of the current value in the ACCumulator (i.e., 4121H-4122H). It should be used with caution, however since the mode flag and ACCumulator can get out of phase particularly if some of the CALLS described here are used to load ACCumulator
0023H-0027H – DISK ROUTINE – “$CTL”
0023 ↳ $CTL
PUSH BCC5
Save the value in Register Pair BC on the STACK
0024-0025
LD B,04H06 04
Load Register B with the device type entry code of 04H
Jump to the Level II BASIC driver entry routine at 0046H
0028 (RST 28H) Jumps to 400CH which contains C9H (RET) under Level II BASIC. This vector is only used by Disk BASIC. It is called by the BREAK key routine, and can be used to intercept the BREAK key logic
Jumps to 400CH which contains C9H (RET) under Level II BASIC. This vector is only used by Disk BASIC. It is called by the BREAK key routine, and can be used to intercept the BREAK key logic. This is the DOS FUNCTION CALL routine at RST 28 (which passes request code in A-register to DOS for processing. Returns for non-disk system. For disk systems, the A Register must contain a legitimate DOS function code. If the code is positive, the CALL is ignored and control returns to the caller. Note that the DOS routine discards the return address stored on the STACK by the RST instruction. After processing control will be returned to the previous address on the STACK)
002BH-002FH – KEYBOARD ROUTINE – “$KBD”
Keyboard scanning routine. After CALLing 2BH, the A Register will contain the ASCII value for the key that was pressed. The A Register will contain 00H if no key was pressed at the time. Apart from the AF Register Pair the DE Register Pair is also used by the routine. This Routine Performs an instantaneous scan of the keyboard. If no key is depressed control is returned to the caller with the A Register and status Register set to zero. If any key (except the BREAK key) is active the ASCII value for that character is returned in the A-register. If the BREAK key is active, a RST 28 with a system request code of 01 is executed. The RST instruction results in a JUMP to the DOS Exit 400C. On non-disk systems the Exit returns, on disk systems control is passed to SYS0 where the request code will be inspected and ignored, because system request codes must have bit 8 on. After inspection of the code, control is returned to the caller of 002B. Characters detected at 002B are not displayed. Uses DE, status, and A register
Of the 3 keyboard scanning routines, this is the most fundamental one. If no key is pressed when the CALL is executed, the code falls through with A = 00H. If you want to wait for a key to be pressed, you would use CALL 0049 or you would write a routine that jumps back to the call if A is 0.
This routine loads DE with address of keyboard DCB and scans keyboard. On exit, if no key pressed the A Register will contain a zero byte, else the character input from the keyboard wi 11 be returned in A. Character is not echoed to video. Uses AF,DE (to save DE use routine at 03588).
Scan Keyboard: Performs an instantaneous scan of the keyboard. If no key is depressed control is returned to the caller with in Register A and status Register set to zero. If any key (except the BREAK key) is active the ASCII value for that character is returned in the A-register. If the BREAK key is active, a RST 28 with a system request code of 01 is executed. The RST instruction results in a JUMP to the DOS Exit 400C. On non-disk Systems the Exit returns, on disk systems control is passed to SYS0 where the request code will be inspected and ignored, because system request codes must have bit 8 on. After inspection of the code, control is returned to the caller of 002B. Characters detected at 002B are not displayed. Uses DE, status, and A register
This location passes control to 400FH which contains a RET (C9H) under Level II. This location is only used by a Disk system. This is the LOAD DEBUG routine, and loads the DEBUG program and transfers control to it. When DEBUG processing is complete, control is returned to the orginal caller. For non-disk systems control is returned immediately
0033H-0037H – VIDEO ROUTINE – “$DSP”
Character print routine. A CALL 33H will print a character at the current cursor position. The A Register must contain the ASCII code for the character or graphics figure that is to be printed before CALLing this routine. The DE Register Pair is used by the routine. A call to 0033H displays the character in the A-register on the video. Control codes are permitted. All registers are used.
This location will pass control to 4012H. This location is only used by a Disk system. This is the INTERRUPT ENTRY POINT routine at RST 38H which is the system entry point for all interrupts. It contains a jump to a section of code in the Communications Region designed to field interrupts. That section of code consists of a DI (disables further interrupts) followed by a RET (returns to the point of interrupt) for non-disk systems, or a jump to an interrupt processor in SYSO if it is a DOS system. For DOS systems the interrupt handler consists of a task scheduler, where the exact cause of the interrupt is determined (usually a clock interrupt) and the next task from the task control block is executed. After task completion, control returns to the point of interrupt
003BH-003FH – PRINTER ROUTINE – “$PRT”
Character LPRINT routine. Same as 33H but outputs to line printer. (Contents of A Register will be printed).
A call to 003BH causes the character contained in the A-register to be sent to the printer. A line count is maintained by the driver in the DCB. When a full page has been printed (66 lines), the line count is reset and the status Register returned to the caller is set to zero. Control codes recognized by the printer driver are:
00=Returns the printer status in the upper two bits of the A-register and sets the status as zero if not busy, and non-zero if busy.
OB=Unconditionally skips to the top of the next page.
OC=Resets the line count (DCB 4) and compares its previous value to the lines per page (DCB 3) value. If the line count was zero, no action is taken. If the line count was non-zero then a skip to the top form is performed.
OD=Line terminator. Causes line count to be incremented and tested for full page. Usually causes the printer to begin printing.
Character LPRINT routine. Same as 33H but outputs to line printer. (Contents of A Register will be printed.)
Jump to the “WAIT FOR NEXT LINE” keyboard input routine at 05D9 (which takes keyboard entry until a carriage return, a break, or buffer overrun occurs)
*0043H-0045H – Model 4 Gen 1 Routine – Unused Code
*0043
RET
*0044
NOP
*0045
NOP
*0043H-0045H – Model 4 Gen 2 Routine – Called from new routine at 3790H
JUMP to 0674H which is the is the Level II BASIC keyboard driver entry routine.
0049H-004FH – KEYBOARD ROUTINE – “$KEY”
A call to 0049H returns as soon as any key on keyboard is pressed, exactly how the INKEY$ function works in BASIC. ASCII value for character entered is returned in A register. If you don’t want the program to hold while waiting for a key, you would use CALL 002BH instead.
Character input routine. This routine is the same as 2BH except that it will not return until a key is pressed, which often makes it often more useful than 2BH. Character is returned in the A Register (AF and DE used)
Wait For Keyboard Input: Returns as soon as any key on keyboard is pressed. ASCII value for character entered is returned in A- register. Uses A, status and DE registers
Go scan the keyboard and return with the key pressed, if any, in Register A. A CALL to this memory location returns as soon as any key on keyboard is pressed. ASCII value for character entered is returned in A register. Uses A, status, and DE registers.
Character input routine. This routine is the same as 2BH (=Scan the Keyboard routine) except that it will not return until a key is pressed
004C
OR AB7
Check the value in Register A to see if a key was pressed
This is a delay loop. The BC Register Pair is used as the loop counter. The duration of the delay, in microseconds, is the value of BC times 14.66. Register A is used.
Loop until the counter in Register Pair BC is equal to zero
0065
RETC9
RETurn to CALLer
*0060H – Model 4 Gen 2 – DELAY ROUTINE – “$PAUSE”
This is a delay loop. The BC Register Pair is used as the loop counter. The duration of the delay, in microseconds, is the value of BC times 14.66. Register A is used.
*This part of the initialization routine checks to see if a disk drive is connected. If so, it will jump to 00H. (This is why the reset button will reinitialize DOS.)
Continue with the communication region initialization by loading register pair HL with 42E5H.
0083-0084
LD (HL),3AHLD (HL),”:”36 3A
Save a 3AH (which a “:”) at the location of the memory pointer in Register Pair HL (which is 41E5H)
0085
INC HL23
Increment the memory pointer in Register Pair HL from 41E5H to 41E6H
0086
LD (HL),B70
Zero out 41E6H (the location of the memory pointer in Register Pair HL)
0087
INC HL23
Increment the memory pointer in Register Pair HL from 41E6H to 41E7H
0088-0089
LD (HL),2CHLD (HL),”,”36 2C
Save a 2CH (which is a “,”) at 41E7H (the location of the memory pointer in Register Pair HL)
008A
INC HL23
Increment the memory pointer in Register Pair HL from 41E7H to 41E8H, which is the input/output buffer BUFINI
This loads 40A7H with the I/O buffer location address 41E8H. (40A7H is the I/O buffer pointer and can be changed to relocate the buffer.)
008B-008D
LD (40A7H),HLLD (BUFPNT),HL22 A7 40
This loads the input buffer pointer (held at 40A7H) with the keyboard buffer location address of 41E8H. (40A7H is the I/O buffer pointer and can be changed to relocate the buffer.). Save the value in Register Pair HL as the starting address of the keyboard input buffer area. Note: 40A7H-40A8H holds the input Buffer pointer
In prepartaion for a jump, load Register Pair DE with the starting address of the ?L3 ERROR routine
0091H-0104H – The rest of the initialization routine. First, it fills the RAM locations pointing to all 28 DOS BASIC commands, set them to pointo ?L3 ERROR, ask MEMORY SIZE ?, sets the memory pointers accordingly and prints RADIO SHACK LEVEL II BASIC , then it jumps to 1A19H which is the entry point for the BASIC command mode
The rest of the initialization routine. Asks MEMORY SIZE ?, sets the memory pointers accordingly and prints RADIO SHACK LEVEL II BASIC , then it jumps to lAl9H which is the entry point for the BASIC command mode
0091-0092
LD B,1CHLD B,ERCNT06 1C
Since there are 28 pre-defined DOS BASIC commands in ROM, load Register B with the number of times (=28) to save the jump to the ?L3 ERROR routine
Load Register Pair HL with the starting address of the Disk Basic links (which is 4152H) in preparation for generating an error if disk basic commands are attempted. Note: 4152H-41A3H holds Disk Basic links
0096
LD (HL),C3H
Save a C3H to the location of the memory pointer in register pair HL. NOTE: C3H is the first byte of a 3 byte JUMP xxxxH command.
0098
INC HL23
Increment the memory pointer in Register Pair HL to point to the 2nd of each 3 byte instruction in the Disk Basic command list
0099
LD (HL),E73
Save the LSB of the ?L3 ERROR routine’s starting address in Register E (i.e., a 2DH) to the 2nd of each 3 byte instruction in the Disk Basic command list
009A
INC HL23
Increment the memory pointer in Register Pair HL to the 3rd of each 3 byte instruction in the Disk Basic command list
009B
LD (HL),D72
Save the MSB of the ?L3 ERROR routine’s starting address in Register D (i.e., a 01H) to the 3rd of each 3 byte instruction in the Disk Basic command list
009C
INC HL23
Increment the memory pointer in Register Pair HL to the 1st byte of the next Disk Basic command in the list
If the BREAK key was pressed, ask again. Note: 1BB3H jumps around A LOT but it is 0661H which processes a BREAK key, and starts by setting the carry flag
00C0
RST 10HCHRGETD7
Since we now need to increment the input buffer pointer until it points to the first character of the input, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
00C1
OR AB7
Set the status flag based on if the character at the location of the input buffer pointer in Register A is an end of the input character (00H)
If we are here then just an ENTER was hit in response to the MEMORY SIZE? question, so we need to figure it out dynamically, so load register pair HL with the starting address for the memory size check.Difference between M1 and M3: The instruction starting at 00C4H loads HL with 434CH in the Model I, 444CH in the Model III. If only “ENTER” was pressed in response to the “MEMORY SIZE?” prompt, a memory test is initiated starting at the location pointed to by HL, and continuing upward until the end of memory (or a bad memory location) is reached.
00C7 ↳ LOOPMM
INC HL23
We are going to start testing RAM at 17229 (i.e., 434DH) toward 65535, so increment the memory pointer in Register Pair HL
00C8-00C9
LD A,H OR L7C
There is no way to COMPARE HL against anything, so the common “trick” is to load Register A with Register H and then OR it against Register L. If you do this, Register A can only be zero if both Registers H and L are zero
Since we need to scan all the way up to 65535, jump to 00E7H (which drops the memory size pointer by 1) if the current memory pointer in Register Pair HL is equal to zero
00CC
LD A,(HL)7E
Load Register A with the value at the location of the current memory pointer in Register Pair HL
00CD
LD B,A47
Load Register B with the value in Register A to preserve it, as A is about to get used
00CE
CPL2F
Complement the value in Register A (which is basically a test pattern)
00CF
LD (HL),A77
Save the test pattern in Register A to the location of the current memory pointer in Register Pair HL
00D0
CP (HL)BE
Check to see if the value at the location of the memory pointer in Register Pair HL is the same as the value in Register A
00D1
LD (HL),B70
Put back the original memory value (which was saved in B) to the location of the memory pointed in Register Pair HL
Here the MEMORY SIZE? answer is in HL so call the ASCII TO INTEGER routine at 1E5AH (which will put the answer into DE in integer format). NOTE:
The routine at 1E5A converts the ASCII string pointed to by HL to an integer deposited into DE. If the routine finds a non-numerica character, the conversion is stopped
Display a ?SN ERROR if Register A is not equal to zero
00DD
EX DE,HLEB
Swap DE (where the integer version of the MEMORY SIZE? answer is located) and HL, so that Register Pair HL now has with the MEMORY SIZE answer again, but in integer format
00DE
DEC HL2B
Decrement the MEMORY SIZE? in Register Pair HL
00DF-00E0
LD A,8FH3E 8F
Load Register A with a memory test value of 8F or 10001111
00E1
LD B,(HL)46
Load Register B with the value at the location of the MEMORY SIZE? pointer in Register Pair HL (to save the data thats there)
00E2
LD (HL),A77
Put the test pattern (in A which is 8FH) into that the location of the MEMORY SIZE? pointer in Register Pair HL
00E3
CP (HL)BE
Check to see if the value in the memory location set in HL matches the test pattern in A
Load register pair DE with the minimum MEMORY SIZE? response (held at 4514H).
00EB
RST 18HCOMPARDF
Now we need to check to see if the MEMORY SIZE? pointer (in HL) is less than the minimum MEMORY SIZE? response (in DE), so we call the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status Register as: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal)
If C is set, then the amount of actual memory (in HL) is less than the minimum memory required (in DE), so we have to go to the Level II BASIC error routine and display an OM ERROR
Load Register Pair HL with the starting address of the RADIO SHACK LEVEL II BASIC message. 00FFH-0101H Go display the RADIO SHACK LEVEL II BASIC message
Go to the Level II BASIC error routine with 2CH loaded into Register E
0132H-0134H – LEVEL II BASIC POINT COMMAND ENTRY POINT – “GRPHCS” or “POINT”
0132 ↳ GRPHCS
RST 10HCHRGETD7
Since we need to bump the current BASIC program pointer until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
0133
XOR AAF
A will wind up being 0 if the POINT command was entered … otherwise
Z-80 Trick! The byte at this memory location, 01H, is there to turn the real instruction that follows (the operative action of the SET command) into a harmless LD BC,xxxx. This way, they didn’t have to jump over SET or RESET to get to the common graphics code. If parsing straight down, this loads BC with 0380H and then moves to 0136H. But if jump straight to 0136H, you skip that 01H opcode, and get a real instruciton of 3EH 80H
0135H-0137H – LEVEL II BASIC SET COMMAND – “SET”
0135
LD A,80H
Load register A with 80H (Decimal:128) which is SET.
Z-80 Trick! The byte at this memory location, 01H, is there to turn the real instruction that follows (the operative action of the SET command) into a harmless LD BC,xxxx. This way, they didn’t have to jump over SET or RESET to get to the common graphics code. If parsing straight down, this loads BC with 0380H and then moves to 0136H. But if jump straight to 0136H, you skip that 01H opcode, and get a real instruciton of 3EH 80H
0138H-0139H – LEVEL II BASIC RESET COMMAND ENTRY POINT – “RESET”
0138
LD A,01H
Load register A with 01H which is RESET.
013AH-019CH GRAPHICS ROUTINE
Common code for SET/RESET/POINT – A will be 0 if POINT, 80H if SET and 1 for RESET.
013A
PUSH AFF5
Save the flag which indicates which command was requested (held in Register A) to the STACK
013B
RST 08H + 28H
Since SET/RESET/POINT all need a “(” to start with, call the COMPARE SYMBOL routine which comparess the symbol in the input string pointed to by HL register to the value in the location following the RST 08 call. If there is a match, control is returned to address of the RST 08 instruction 2 with the next symbol in the Register A and HL incremented by one. If the two characters do not match, a syntax error message is given and control returns to the Input Phase).
Go evaluate the expression at the location of the current BASIC program pointer in register pair HL (which is the X variable) and return with the 8-bit value in register A.
0140-0141
CP 80HFE 80
Check to see if the X value in Register A is greater than 128
If A is greater than 128, go to 1E4AH to display a ?FC ERROR
0145
PUSH AFF5
Save the requested coordinate’s X value in Register A on the STACK
0146
RST 08H + “,”
At this point we have SET/RESET/POINT, an open parenthesis, and an X variable, so now we must find a ,. To do this call the COMPARE SYMBOL routine which comparess the symbol in the input string pointed to by HL register to the value in the location following the RST 08 call. If there is a match, control is returned to address of the RST 08 instruction 2 with the next symbol in the Aregister and HL incremented by one. If the two characters do not match, a syntax error message is given and control returns to the Input Phase).
Go evaluate the expression at the location of the current BASIC program pointer in Register Pair HL (which is the Y variable) and return with the 8-bit value in Register A
014B-014C
CP 30HFE 30
Check to see if the Y value in Register A is greater than 48
Loop until Register D equals the Y value divided by 3
0157-0158
ADD 03HC6 03
Make the remainder positive by adjust the remainder in Register A by adding back 3
0159
LD C,A4F
Save the remainder in Register C
015A
POP AFF1
Get the requested coordindate’s X value from the STACK and put it in Register A
015B
ADD A,A87
Multiply the X value in Register A by two, storing the result in Register A
015C
LD E,A5F
Load Register E with the newly doubled Register A
015D-015E
LD B,02H06 02
Load Register B with the number of times to shift Register Pair DE right (which is 2)
015F ↳ SHFTW
LD A,D7A
Load Register A with the adjusted Y value in Register D
0160
RRA1F
Divide the adjusted Y value in Register A by two. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0161
LD D,A57
Save the new Y value in Register A in Register D
0162
LD A,E7B
Load Register A with the adjusted X value in Register E
0163
RRA1F
Divide the adjusted X value in Register A by two. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0164
LD E,A5F
Load Register E with the new X value in Register A
Loop back to the prior instruction until the graphic mask has been completed in Register A
0170
LD C,A4F
Save the graphic mask in Register A in Register C
0171
LD A,D7A
Load Register A with the MSB of the video memory offset in Register D
0172-0173
OR 3CHOR 0011 1100F6 3C
Mask the MSB of the video memory offset in Register A with 0011 1100 so that it will point to the correct location in video RAM (i.e., the applicable screen RAM address)
0174
LD D,A57
Save the MSB of the video memory pointer in Register A in Register D
0175
LD A,(DE)1A
Load Register A with the character at the location of the video memory pointer in Register Pair DE
0176
OR AB7
Check to see if the character in Register A is a graphic character
Skip over the next instruction if the character in Register A is a graphic character
017A-017B
LD A,80H3E 80
Since the character at the screen location turned out not to be a graphics character, we need to set it to a blank graphics character, so load Register A with a blank graphic character which is CHR$(128)
017C ↳ FND4
LD B,A47
Save the character which is being modified by the SET/RESET (held in Register A) into Register B
017D
POP AFF1
Get the graphic character and the flags from the STACK and put it in Register A
017E
OR AB7
Set the flags according to the graphic mode in Register A
017F
LD A,B78
Get the existing graphic character on the screen (held in Register B) and put it in Register A
Load Register A with the graphic mask in Register C
0187
CPL2F
Reverse the graphic mask in Register A
0188
LD C,A4F
Load Register C with the adjusted graphic mask in Register A
0189
LD A,(DE)1A
Load Register A with the character at the location of the video memory pointer in Register Pair DE
018A
AND CA1
RESET the graphic bit by combining the graphic mask in Register C with the graphic character in Register A
018B ↳ FINSTB
LD (DE),A12
Save the adjusted graphic character in Register A at the location of the video memory pointer in Register Pair DE
018C
RST 08H + “)”
We need to check HL against 29H (ASCII: )) so we call the COMPARE SYMBOL routine which comparess the symbol in the input string pointed to by HL register to the value in the location following the RST 08 call. If there is a match, control is returned to address of the RST 08 instruction 2 with the next symbol in the Aregister and HL incremented by one. If the two characters do not match, a syntax error message is given and control returns to the Input Phase).
018E
RETC9
RETurn to CALLer
018F ↳ SBIT
OR CB1
SET the graphic bit by combining the graphic mask in Register C with the graphic character in Register A
019DH-01C8H – LEVEL II BASIC INKEY$ ROUTINE – “INKEY”
019D ↳ INKEY
RST 10HCHRGETD7
Since we need to bump the current BASIC program pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
019E
PUSH HLE5
Save the current BASIC program pointer in Register Pair HL on the STACK
019F-01A1
LD A,(4099H)LD A,(CHARC)3A 99 40
Put the last key pressed (stored at 4099H) and put it in Register A
Go send the character in Register A (i.e., the ASCII character to clear to the end of the screen) to the video display
01D3H-01D8H – LEVEL II BASIC RANDOM ROUTINE – “RANDOM”
This is part of the RANDOM routine which takes a value out of the REFRESH register, stores it in location 40ABH and then returns.
A call to 01D3H reseeds the random number seed (location 40AB) with the current contents of the refresh register.
NOTE: To run a RANDOM (seed the random number generator) via a ROM call just call CALL 01D3H. This causes the contents of R (memory refresh) to be stored in 40ABH. The entire 24 bit seed is stored in 40AAH-40ACH.
01D3 ↳ RANDOM
LD A,RED 5F
Load Register A with the current value of the refresh register
01D5-01D7
LD (40ABH),ALD (RNDX+1),A32 AB 40
Save the pseudi-random value in Register A to 40ABH (the random number seed)
01D8
RETC9
RETurn to CALLer
*01D9-01F7H – Model 4 Gen 1 Routine – Print Screen Routine – “$PRSCN” This routine copies all 1024 characters from the screen to the printer. If the printer is unavailable, this routine waits until the printer becomes available. If BREAK is pressed, this routine returns to the caller.
Load HL with the memory location for the beginning of the video RAM. Difference between M1 and M3: The routine to print the contents of the screen on the line printer is located from 01D9H to 01F4H on the Model III. On the Model I, 01D9H – 01F7H contains the routine to output one bit to the cassette.
*01DC
LD A,(HL)
Put the character at the screen location stored in HL into A.
*01DD
CP 80H
Check A against 80H, which represets the lowest graphic character. If A < 80H then the character is NOT graphic and the the CARRY will be set. Otherwise NC is set to show that the character is a graphic character.
GOSUB to 0214H for a new line. Difference between M1 and M3: 01F0H contains CALL 0221H instruction on Model I, and CALL 0214H instruction on Model III.
JUMP to 022EH to do the final cleanup before entering BASIC … Enable Interrupts and JUMP to READY prompt
*01F6
NOP00
No Operation
*01F7
NOP00
No Operation
01F8 – Turn Off The Cassette Motor – “$CSOFF” After writing data to the cassette, this routine should be called to turn off the cassette drive. There are no entry conditions and no registers are modified.
JUMP to 300CH to turn the cassette off. Difference between M1 and M3: In the Model I, the routine to turn off the cassette recorder is located from 01F8H to 0211H. In the Model III, 01F8H contains a jump to 300CH ( the location of a vector to the “turn off cassette” routine in the Model III). 01FBH through 0201H contain the time delay routine (see notes on 0060H), and 0202H through 020FH contains the text “(c) ’80 Tandy” and a carriage return.
01FB-0201 – DELAY ROUTINE – “$DELAY”
This is a delay loop. The BC register pair is used as the loop counter. The duration of the delay, in microseconds, is the value of BC times 14.65. Register A is used.
01FB
LD A,A
Top of the loop – Load A with A … just a waste of some cycles.
01FC
DEC BC
Decrement the counter in register pair BC
01FD-01FE
LD A,B OR C
The easiest way to test a 2 byte register for zero is to load the MSB into A and then OR it with the LSB. If the MSB was 0 and the LSB was 0, then A will be 0.
Loop until the counter in register pair BC is equal to zero.
0201
RET
Return.
202 – Message Storage Location
0202
(C) “80 Tandy” + 0DH
Copyright message storage area.
*0210 – Model 4 Gen 1 – These instructions are never called or used.
*0210-0211
LD E,3DH
I do not see that this command is ever executed as it is never called. However, it loads E with 3DH, most likely to toss off an error.
*0210 – Model 4 Gen 2 – These instructions are never called or used.
*0211
NOP
No Operation
*0211
NOP
No Operation
0212H – This continues a subroutine and was JUMPed to from 022CH. It zeroes A and all flags everything and RETURNs. Difference between M1 and M3: In the Model I, routines to define cassette drive (0212H – 021DH), reset the cassette input port FFH (021EH – 022BH), and to blink the asterisk while reading a cassette (022CH – 0234H). In the Model III, a routine to insure compatibility with programs that define the cassette drive (XOR A followed by RET, located at 0212H & 0213H), a subroutine used by the routine that begins at 01D9H (0214H 0227H), a couple of cassette-related segments (0228H – 022DH), and an EI instruction followed by JP 1Al9H (enable interrupts and return to BASIC “READY”, located at 022EH – 0231H).
Call the PRINT CHARACTER routine at 003B (which sends the character in the A register to the printer).
0219
XOR A
Set A to ZERO and clear all status flags.
021A
RET
Return.
021B-0227 – “$VDLINE” – Display a Line Until 03H or 0DH Reached. This subroutine displays a line. The line must be terminated with an ASCII ETX (X’03’) or carriage return (X’0D’). If the terminator is a carriage return, it will be printed; if it is an ETX, it will not be printed. This allows VDLINE to position the cursor to the beginning of the next line or leave it at the position after the last text character. On entry (HL) shuold contain the output text, terminated by a 03H or a 0DH.
021B “VIDLINE”
LD A,(HL)
Put the memory contents of (HL) into Register A.
021C
INC HL
Bump HL.
021D
CP 03H
Check those memory contents against 03H to see if it is the end of message.
If it was NOT a carriage return, loop back to load A with the next character.
0227
RET
If it WAS a carriage return, RETURN.
0228H – This continues a subroutine and was JUMPed to from 023DH. It puts 3000H into the memory location pointed to by the stack pointer, and JUMPs to 302AH.
0228
EX (SP),HL
Put HL (which presumably has a return address in it) into the memory location of the STACK pointer.
NOTE: 302AH is an entry in a jump vector table that JUMPs to 31F7H. 31F7H checks to see if we have a PRINT #.
022CH – BLINK ASTERISK routine – This routine is CALLED from 02E7 and alternatively displays and clears an asterisk in the upper right hand corner of the video display.
*0232 – Model 1 Gen 1 – These instructions are never called or used.
*0232
CCF
*0233
INC A
*0234
RET
*0232 – Model 1 Gen 2 – These instructions are never called or used.
*0232
NOP
*0233
NOP
*0234
NOP
0235-0240 – CASSETTE ROUTINE – Read a Byte from Cassette – “CSIN” After the completion of a $CSHIN call, this $CSIN routine begins inputting data, one byte at a time. This routine MUST be called often enough to keep up with the baud rate. There are no entry conditions. A is modified to hold the data byte. Difference between M1 and M3: In the Model I, 0235H – 0240H contains the routine to read one byte from the cassette, and 0241H – 0260H contains the routine to get one bit from the cassette. In the Model III, 0235H – 023CH contain the start of the Model III routine to read one byte from cassette, 023DH – 0242H is part of the routine that begins at 0287H (writes cassette leader and sync byte), 0243H – 024CH is the actual start of the routine to search for the cassette leader and aync byte, 024DH – 0252H is the actual start of the routine to write a byte to tape, and 0253H – 025EH is a subroutine used by the system to select 500 or 1500 baud tape speed.
0235 “CSIN”
PUSH DE
Save the value in register pair DE on the STACK.
0236
PUSH BC
Save the value in register pair BC on the STACK.
0237
PUSH HL
Save the value in register pair HL on the STACK.
0238
LD HL,(420EH)
Put the TAPE READ VECTOR (stored at 420EH) into HL.
023B
EX (SP),HL
Replace the value at the top of the stack with the TAPE READ VECTOR (stored at 420EH), and what used to HL at the top of this routine into Register Pair HL.
023C
RET
Go to the TAPE READ VECTOR. NOTE: When a routine is CALLed, the RETurn address is put at the top of the stack; so RET jumps to the value at the top of the STACK.
023D – This continues a subroutine and was JUMPed to from 028BH to to set the cassette write vector. It just PUSHes HL, puts 3000H into HL, and JUMPs out to 0228H.
024D-0252 – CASSETTE ROUTINE – Write a Byte to Cassette
024D
PUSH HL
Save the value in register pair HL on the STACK.
024E
LD HL,(420CH)
Load HL with the memory contents of the TAPE WRITE VECTOR.
0251
EX (SP),HL
Put the TAPE WRITE VECTOR (stored at 420CH) into the memory location pointed to by the STACK, and the memory location pointed to the STACK into HL.
0252
RET
RETURN to the memory address held in the TAPE WRITE VECTOR. NOTE: When a routine is CALLed, the RETurn address is put at the top of the stack; so RET jumps to the value at the top of the STACK.
0253
EX (SP),HL
Take the RETURN ADDRESS of whoever called this routine and put it into HL.
0254
LD A,(4211H)
Load A with the contents of memory location 4211H. Memory location 4211H is the Cassette Baud Rate Select. It will be Z for 500 baud, and NZ for 1500 baud.
Write the clock pulse by calling the WRITE ONE BYTE TO CASSETTE routine at 0264H (which writes the byte in the A Register to the cassette drive selected in the A register)
0264 – “$CSOUT” – Output a byte to cassette. After writing the header with $CSHWR, use this $CSOUT to write the data, one byte at a time. You MUST call $CSOUT often enough to keep up with the baud rate. Register A needs to hold the data byte on entry. Difference between M1 and M3: In the Model I, 0264H – 0283H contains the routine to output one byte to the cassette. In the Model III, 0264H – 0266H contains a jump to 024DH (the start of the Model III routine to output one byte to cassette), followed by time data (60 seconds, 60 minutes, 24 hours) at 0266H – 0268H, followed by twelve bytes which contain the length of each of the twelve months (0264H – 0274H). This is followed by two NOPs, then starting at 0277H is a 1DH byte, a 1EH byte, the message “Diskette?”, and finally a 03H byte (at 0282H).
0266 – Storage location for the maximum number of seconds in a minute, minutes in an hour, hours in a day, and days in a month.
0266
3C 3C 18
Time Data (60, 60, 24).
0269
1F 1C 1F 1E
Month Lengths.
026D
1F 1E 1F 1F
For DATE$.
0271
1E 1F 1E 1F
0275
00 00
Unused.
0277
1D
Group Separator.
0278
1E
Record Separator.
0279
“DISKETTE?” + 03H
Message Space.
0283
F2
UNUSED.
0284 – This subroutine is called by 2076H to turn the tape on, no header – it jumps out to 023DH. Difference between M1 and M3: In the Model I, this area contains several cassette I/O routines, including turn on cassette, write leader and sync byte (0284H); write leader and sync byte (0287H); turn on cassette, search for leader and sync byte (0293H); search for leader and sync byte (0296H), put 2 asterisks in upper right corner of video ( part of previous routines, begins at 029FH). In the Model III, 0284H contains a JP 0287H instruction (faster than three NOPs), while 0287H is the start of the routine to turn on the cassette, write leader and sync byte. 028DH – 0292H contains the fast routine to check if BREAK is depressed. 0293H contains a JP 0243H instruction, while 0296H contains a JR 0243H (0243H is the actual start of the routine to turn on the cassette, search for leader and sync byte). 0298H – 02A0H is the machine language routine to turn on the built-in clock display (in the upper right hand corner of the video display), while 02A1H – 02A8H is the location of the corresponding routine to turn the clock display back off.
JUMP to 0287H (the very next instruction anyway!), which was the the “$CSHWR” routine in the Model I ROM. That routine writes tape leader and the A5H sync byte. DE and HL are unchanged.
Load register B with the number of bytes to be written.
0287 – Write Leader and Sync Byte – “$CSHWR” Each cassette record begins with a header consisting of a leader sequence and a synchronization byte. This $CSHWR routine turns on the cassette and writes out this header. There are no entry conditions. A is altered by this routine.
028DH – “$KBBRK” -Check for a BREAK key only. This is a fast key scan routine which looks solely for the BREAK key. Use this routine if you want to minimize the keyboard scan time without totally locking out the keyboard. On exit NZ will be set if BREAK was set. This subroutine is called by 0444H (in the middle of the PRINTER ROUTINE) to check for a BREAK key.
028D “KBBRK”
LD A,(3840H)
Check for BREAK Key. First, load A with the memory contents of 3840H (which is the keyboard scan of 14400, the 7th keyboard line), to check for a BREAK. 14400 is ENTER (01) CLEAR (02) BREAK (04) RIGHT ARROW (08) LINE FEED (16) LEFT ARROW (32) SPACE (64)
0290
AND 4
AND the memory contents of 3840H with 04H (Binary: 0000 0100) to isolate only Bit 3. This a precursor to a future test to see if it was a BREAK key.
0292
RET
RETURN.
0293 – CASSETTE ROUTINE – Read the Header and Sync Bytes
0296 – CASSETTE ROUTINE – “CSHIN” – Search for Cassette Header and Sync Byte Each cassette record begins with a header consisting of a leader sequence and synchronization byte. $CSHIN turns on the cassette drive and begins searching for this header information. The subroutine returns to the calling program after the sync-byte has been read. There are no entry conditions. Register A is altered by the routine.
If a BREAK key was hit (because the Carry flag is now on), go to new routine at 006DH
02C6
RST 10HCHRGETD7
Since we need to bump the input buffer pointer in Register Pair HL until it points to the first character input, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
Top of a small loop. Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (whichh reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
02D4-02D5
CP 55HFE 55
Check to see if the byte read from the cassette in Register A is a header byte (=55H)
Jump out of this ‘read the filename from the cassette’ routine if the character at the location of the current input buffer pointer in Register A is an end of input character
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (which reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
02E1
CP (HL)BE
Check to see if the character at the location of the current input buffer pointer in Register Pair HL is the same as the character read from the cassette in Register A
02E2
INC HL23
Increment the input buffer pointer in Register Pair HL
Jump to 02D1H (skip to the next program on cassette) if the character at the location of the current input buffer pointer in Register Pair HL isn’t the same as the character read from the cassette in Register A
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (whichh reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
02ED-02EE
CP 78HFE 78
Check to see if the byte read from the cassette in Register A is an execution address header byte (which is 78H)
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (which reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
02F8
LD B,A47
Load Register B with the count of bytes to be loaded in Register A
Read the file block’s starting address from the cassette and return with it in Register Pair HL
02FC
ADD A,L85
For purposes of calculating a checksum, add the LSB of the file block’s starting address in Register L to the MSB of the file block’s starting address in Register A
02FD
LD C,A4F
Load Register C with the file block’s starting checksum in Register A
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (which reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
0301
LD (HL),A77
Save the byte read from the cassette in Register A at the location of the memory pointer in Register Pair HL
0302
INC HL23
Increment the memory pointer in Register Pair HL
0303
ADD A,C81
Add the value of the current checksum in Register C to the value in Register A
0304
LD C,A4F
Load Register C with the updated checksum in Register A
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (which reads one byte from the cassette drive specified in Register A, and returns the byte in Register A). This reads in the checksum from cassette
030A
CP CB9
Check to see if the computed checksum in Register C is the same as the checksum read from the cassette in Register A
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (whichh reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
0317
LD L,A6F
Load Register L with the byte read from the cassette in Register A (which is the LSB of the 16 bit value)
Calls the READ ONE BYTE FROM CASSETTE routine at 0235H (whichh reads one byte from the cassette drive specified in Register A, and returns the byte in Register A)
031B
LD H,A67
Load Register H with the byte read from the cassette in Register A (which is the MSB of the 16 bit value)
031C
RETC9
RETurn to CALLer
031DH – Execute the Cassette Program which was Loaded – “GODO”
031D ↳ GODO
EX DE,HLEB
Load Register Pair DE with the pointer to the BASIC command line being processed (held in Register Pair HL)
031E-0320
LD HL,(40DFH)LD HL,(TEMP)2A DF 40
Load Register Pair HL with the execution address (which is stored at 40DFH). Note: 40DFH-40E0H is also used by DOS
0321
EX DE,HLEB
So that we can run a RST 10H in the next instruction, we need to exchange the execution address in Register Pair HL with the input buffer pointer in Register Pair DE
0322
RST 10HCHRGETD7
Since we need to bump the current input buffer pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
Call the ASCII TO INTEGER routine at 1E5AH. NOTE: The routine at 1E5A converts the ASCII string pointed to by HL to an integer deposited into DE. If the routine finds a non-numeric character, the conversion is stopped
Jump if it turns out there weren’t any digits (i.e., bad input) in the input
0328
EX DE,HLEB
Since there were digits (or else we would have jumped in the prior instruction), exchange the input buffer pointer in Register Pair HL with the execution address in Register Pair DE
0329
JP (HL)E9
Jump to the execution address (i.e. “/xxxx”) which is in Register Pair HL
032AH-0347H – OUTPUT ROUTINE – “OUTCH1” and “OUTDO”
This is a general purpose output routine which outputs a byte from the A Register to video, tape or printer. In order to use it, the location 409CH must be loaded with -1 for tape, 0 for video or 1 for the line printer. Note: 409CH holds the current output device flag: -1=cassette, 0=video and 1=printer.
This routine outputs a byte to device determined by byte stored at (409CH) – FFH=Tape, 0=Video, l=Printer. When calling, A = output byte. Uses AF. Warning: This routine CALLs a Disk BASIC link at address 41ClH which may have to be “plugged” with a RETurn (C9H) instruction.
032A ↳ OUTDO
PUSH BCC5
We are going to need to use Register C, so push Register Pair BC into the STACK
032B
LD C,A4F
Load Register C with the character to be output in Register A
Go call the DOS link at 41ClH. In NEWDOS 2.1, this writes to the system output device
032F-0331
LD A,(409CH)LD A,(PRTFLG)3A 9C 40
Load Register A with the current output device number stored in 409CH. Note: 409CH holds the current output device flag: -1=cassette, 0=video and 1=printer
0332
OR AB7
Since LD doesn’t set flags, in order to be able to test Register A using flags we need to execute an OR A first. This will enable us to set the flags according to the current output device number in Register A
0333
LD A,C79
Load Register A with the character to be output in Register C
0334
POP BCC1
Get the value from the STACK and put it in Register Pair BC
If the value of the current output device number is positive it means CASSETTE, so jump to the the WRITE ONE BYTE TO CASSETTE routine at 0264H (which writes the byte in the A Register to the cassette drive selected in the A register)
Jump to 039CH if the character in Register A is to be sent to the printer
033AH-0347H – OUTPUT ROUTINE – “OUT2D”
A Print routine which performs the same function as 33H except that it doesn’t destroy the contents of the DE Register Pair. This means that all the general purpose registers are saved, which is often desirable
To use a ROM call to print a single character at the current cursor position, and to update the cursor position, load the ASCII value of the character into the A Register And then CALL 033AH.
To display special functions using a ROM call, load the A Register with the value given below for the special function and then CALL 033AH.
Backspace and erase previous character – 08H
Carriage return and linefeed – 0DH
Turn on cursor – 0EH
Turn off cursor – 0FH
Convert to 32 characters per line mode – 17H
Backspace cursor – 18H
Advance cursor one position – 19H
Downward line feed – 1AH
Upward line feed – 1BH
Home (cursor to upper left corner) – 1CH
Move cursor to beginning of current line – 1DH
Erase from cursor position to end of line – 1EH
Erase from cursor position to end of screen – 1FH
033A ↳ OUT2D
PUSH DED5
If we’re here, then that value in A wasn’t going to the cassette or the printer, so it must be going to the video. This routine performs the same function as 33H except that it doesn’t destroy the contents of the DE Register Pair. This means that all the general purpose registers are saved, which is often desirable.
Go update the current cursor position and test to see if the display memory is full
0342-0344
LD (40A6H),ALD (TTYPOS),A32 A6 40
Save the current cursor line position stored in 40A6H to Register A. Note: 40A6H holds the current cursor line position
0345
POP AFF1
Get the character from the STACK and put it in Register A
0346
POP DED1
Get the value from the STACK and put it in Register Pair DE
0347
RETC9
RETurn to CALLer
0348H-0357H – VIDEO ROUTINE – “DSPPOS”
0348-034A ↳ DSPPOS
LD A,(403DH)LD A,(CAST$)3A 3D 40
Load Register A with the contents of 403DH, which contains, among other things, the screen resolution (32 or 64 wide; Bit 3) the tape relay on/off instruction (Bit 2) and the positive/negative audio pulses (Bits 0-1). Note: 403DH-4040H is used by DOS
034B-034C
AND 08HAND 0000 1000E6 08
Mask Register A against 00001000 to isolate Bit 3 (the 32/64 character per line flag) in Register A
034D-034F
LD A,(4020H)LD A,(CURSOR)3A 20 40
Load Register A with the LSB of the current cursor position. Note: 4020H-4021H holds Video DCB – Cursor location
If Bit 3 of 403DH was a zero, then we have 64 characters per line mode so JUMP down a few instructions to skip over the division needed to drop everything by half to 32 character mode
0352
RRCA0F
Divide the LSB of the current cursor position in Register A by two
0353-0354
AND 1FHAND 0001 1111E6 1F
Mask the cursor line position in Register A for 32 character per line (AND against 0001 1111) to force its position to be no less than 3C00H
0355-0356
AND 3FHAND 0011 1111E6 3F
Mask the cursor line position in Register A for 64 characters per line (AND against 0011 1111) to force its position to be no more than 3FFFH
0357
RETC9
RETurn to CALLer
0358H-0360H – KEYBOARD ROUTINE – “ISCHAR” Here is the routine to simulate the INKEY$ function. It performs exactly the same function as 2BH but it restores all registers, whereas 2BH destroys the contents of the DE Register Pair. This makes 35BH more useful than 2BH
Get the value from the STACK and put it in Register Pair DE
0360
RETC9
RETurn to CALLer
0361H-0383H – INPUT ROUTINE – “INLIN”
This is one of the general purpose input routines (see 5D9 and 1BB3 also). This routine inputs a string from the keyboard, up to a maximum of 240 characters (F0H), and echoes them to the screen. It puts this data into a buffer located at the address pointed to by the buffer pointer at 40A7H. (e.g. If 40A7H contains 5000H the data will be stored from 5000H onwards). The string is terminated with a zero byte. The program returns from this routine as soon as the ENTER key has been pressed. When it does so, HL contains the start address of the input string and B contains the length of the string. (RST 10H can be used to make HL point to the first character of the string, if required.). Note: 40A7H-40A8H holds the input Buffer pointer.
0361 ↳ INLIN
XOR AAF
Zero Register A to clear the buffered character
0362-0364
LD (4099H),ALD (CHARC),A32 99 40
Save the value in Register A as the last key pressed (which is stored in 4099H). Note: 4099H holds the Last key pressed
0365-0367
LD (40A6H),ALD (TTYPOS),A32 A6 40
Save the value in Register A as the current cursor line position (which is stored in 40A6H). Note: 40A6H holds the current cursor line position
Zero Register A, which then means it contains the device code for VIDEO
038C-038E
LD (409CH),ALD (PRTFLG),A32 9C 40
Save the value in Register A (the current output device code of video) to 409CH. Note: 409CH holds the current output device flag: -1=cassette, 0=video and 1=printer
038F-0391
LD A,(409BH)LD A,(LPTPOS)3A 9B 40
Load Register A with the current printer carriage position (which is stored at 409BH). Note: 409BH holds the printer carriage position
0392
OR AB7
Set the flags for the carriage position in Register A
0393
RET ZC8
Return if the carriage position in Register A is equal to zero
Call the PRINT CHARACTER routine at 003B (which sends the character in the C Register to the printer)
03BE
POP BCC1
Get the value from the STACK and put it in Register Pair BC
03BF
POP DED1
Get the value from the STACK and put it in Register Pair DE
03C0
POP AFF1
Get the value from the STACK and put it in Register Pair AF
03C1
RETC9
RETurn to CALLer
03C2H-0451H – Model 4 Gen 1 PRINTER ROUTINE
In the Model III, 03C2H – 0451H is the line printer driver routine, 0452H – 0468H is the actual location of the routine to initialize all I/O drivers, 046BH – 0472H is a routine used by the RUN/EDIT/NEW commands to unprotect the video display and to load HL with the start of BASIC program pointer at 40A4H-40A5H, and 0473H-05D0H is the video driver routine and the keyboard driver begins at 3024H in the Model III).
*03C2
LD A,C
A = C (the current character).
*03C3
CP 20H
Check to see if the character is a control character by testing A – 20H. Results:
If we are here then the character must be a printable one, so we need to get the number of lines left in the page and put them into B for a DJNZ countdown.
GOSUB to 0440H to wait until the printer is ready (honoring BREAK, if hit).
*03D9
LD A,0AH
Put a LINE FEED character into A.
*03DB
OUT (0F8H),A
Output the LINE FEED character to port 0F8H. NOTE: 0F8H is the printer port. If you put data to it, it prints it. Otherwise, Bits 4-7 hold printer status.
JUMP to 0439H to set the number of lines printed to 01 and exit
03E5H – Model 4 Gen 1 Inside the PRINTER ROUTINE – If we are here, the characters to be sent to the printer are NOT control characters, so test for graphics (and jump away), and if not, put the character from the PRINTER LOOKUP TABLE into C.
*03E5
CP 80H
Test for a graphics character by comparing A to 80H. If it is a graphics character than NC will be set.
If there were ANY characters printed (so A is not zero), jump to 033FH.
*03FC
LD A,0AH
If there weren’t sny characters printed, the load A with 0AH.
*03FE
LD C,A
Load C with 0AH.
*03FF
CP 20H
Check to see if the character is a control character by testing A – 20H. If A=20H it sets the ZERO FLAG. If A<20H then the CARRY FLAG will be set and if A>=20H then the NO CARRY FLAG will be set. If A is a CONTROL CHARACTER then C will be set.
0403 – Model 4 Gen 1 Inside the PRINTER ROUTINE – If we are here, then C holds the printable character to be printed as determined by the PRINTER CHARACTER TABLE.
If we are here then the character must be a printable one, so we need to get the number of lines left in the page and put them into B for a DJNZ countdown.
GOSUB to 01DCH to wait for either PRINTER READY or BREAK key
*03D9
LD A,0AH3E 0A
Put a LINE FEED character into A.
*03DB
OUT (F8H),AD3 F8
Output the LINE FEED character to port 0F8H. NOTE: 0F8H is the printer port. If you put data to it, it prints it. Otherwise, Bits 4-7 hold printer status.
If the character in 41FBH was an 01H, JUMP 3045H which simply JUMPs to 378DH to a new routine for Rom GEN 2 which processes printing when a 01H or Line Feed or Carriage Return is the current character being printed
*03F4
JR 041FH18 29
If the character in 41FBH was not a 00H or 01H then JUMP to 041FH to check for exceeding a printer line, advancing if needed, and sending the character to the printer
03F6 – Model 4 Gen 2 PRINTER ROUTINE – Jumped here from 03EDH if the character stored at 41FBH is a ZERO
*03F6
LD A,(41FCH)3A FC 41
Put the character stored at (41FCH) into Register A
If the character is LESS than A0H then the CARRY FLAG will be sent, so JUMP to 041FH to check for exceeding a printer line, advancing if needed, and sending the character to the printer
*0401
CP C0HFE C0
Check to see if the character is a control character by comparing A to C0H
If the character held in Register C was < C0H then the CARRY FLAG will be set and we have a TAB or SPECIAL CHARACTER so JUMP 041FH to check for exceeding a printer line, advancing if needed, and sending the character to the printer
If the MAXIMUM PRINT WIDTH had been reached (meaning that it rolled to 0 when A was bumped), JUMP to 0434H to print a character and check to see if the line needs to be advanced
*0425
CP (IX+05H)DD BE 05
Check to see if the line is full by comparing A with (IX+05H) which holds the number of characters printed.
If A was a carriage return, skip the next few instructions and jump to 0445H to GOSUB to 3048H to JUMP to 377AH to check to see if we are on a new printable page and set the pointers accordingly.
*0441
CP 0AHFE 0A
Check to see if the character in register A is 0AH (ASCII: LINE FEED character).
GOSUB to 3048H which just JUMPs to 377AH to check to see if we are on a new printable page and set the pointers accordingly.
*0448
XOR AAF
Clear Register A and RESET all FLAGS
*0449
LD A,C79
Put the character held in Register C into Register A
*044A
RETC9
RETurn to CALLER
044B-0451 – Inside the PRINTER ROUTINE – Subroutine to check to see if PRINTER READY by polling port F8H
044B
IN A,(F8H)
Set A with the Printer Status Byte.
NOTE: F8H is the printer port. If Bit 7 is set, the printer is not busy. If Bit 6 is set the printer is not out of paper. If bit 5 is set, the device is selected. If Bit 4 is set, no printer fault.
044D
AND 0F0H
AND A against F0H (Binary: 11110000) to strip off BITS 3-0, leaving BITS 7-4 intact.
044F
CP 30H
Check the already masked A against 30H (Binary: 00110000) to see if the printer is ready.
NOTE: This translates to PRINTER NOT BUSY (Bit 7=0), PRINTER NOT OUT OF PAPER (Bit 6=0), PRINTER SELECTED (Bit 5=1), and NO PRINTER FAULT (Bit 4=1).
If the cursor is NOT on (A is Zero), then jump to 04A8H.
049B
LD (IX+05H),D
The cursor is on so put the character which is supposed to be there, there.
NOTE: IX+05H holds the character at the cursor position.
049E
LD A,(IX+06H)
Load A with the cursor character.
NOTE: IX+6 holds the cursor character.
04A1
CP 20H
Check to see if the character is a control character by testing A – 20H. If A=20H it sets the ZERO FLAG. If A<20H then the CARRY FLAG will be set and if A>=20H then the NO CARRY FLAG will be set. If A is a CONTROL CHARACTER then C will be set.
Getting ready to HOME the cursor, so load HL with 3C00H.
NOTE: 3C00H is the start of the video display RAM.
04D7
LD A,(4210H)
Load A with the memory contents of 4210H.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls. In this case, we are looking for the bit which holds whether we are in LARGE characters or SMALL characters.
04DA
AND FBH
Mask A with FBH (Binary: 1111 1011) to turn off Bit 2.
Loop back to 04E4H to either RETURN if we are at zero, or move down another line and try again.
04EB – Cursor Management – BACKSPACE
04EB
DEC HL
Decrement HL to back up the cursor.
04EC
LD A,(4210H)
Load A with the memory contents of 4210H.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls. In this case, we are looking for the bit which holds whether we are in LARGE characters or SMALL characters.
04EF
AND 04H
Mask A with 04H (0000 0100) to leave only bit 3 live, allowing Z to be set if Bit 3 is high, and NZ to be set if Bit 3 is low.
If it is Z is set, then we have small characters, so jump to skip the next instruction.
04F3
DEC HL
Decrement HL to back up the cursor another space.
04F4
LD (HL),20H
Put a space in the current cursor position.
04F6
RET
RETURN.
04F7 – Cursor Management – CURSOR BACK
04F7
LD A,(4210H)
Load A with the memory contents of 4210H.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls. In this case, we are looking for the bit which holds whether we are in LARGE characters or SMALL characters.
04FA
AND 04H
Mask A with 04H (0000 0100) to leave only bit 3 live, allowing Z to be set if Bit 3 is high, and NZ to be set if Bit 3 is low.
If we are here, then we are at the end of the line, so we need to move up one line. Start by putting FFC0H into DE.
NOTE: FFC0H is -64, or 1 line length.
0511
ADD HL,DE
Subtract 64 (the length a line on screen) from HL to move it to the previous line.
0512
RET
RETURN.
0513-0520 – Cursor Management – Turn on DOUBLE SIZE and put the cursor on EVEN columns only.
0513
LD A,(4210H)
Load A with the memory contents of 4210H.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls. In this case, we are looking for the bit which holds whether we are in LARGE characters or SMALL characters.
0516
OR 04H
OR A against 04 (0000 0100) to turn on the 3rd bit. This will turn on DOUBLE SIZE characters.
If A is CLEAR TO END OF SCREEN, jump to 05C5H to deal with it.
055F
RET
RETURN (to 048EH to makes sure the cursor is still on the screen).
0560 – Cursor Management – Control Characters.
0560
LD A,(IX+07H)
Load A with the contents of IX+07H, which toggles TAB and ALTERNATIVE.
0563
AND 01H
MASK A with 0000 0001, to keep only the character flag bit.
0565
XOR 01H
XOR A with 0000 0001 to toggle the character flag bit.
0567
LD (IX+07H),A
Put the MASKED and XORed value back into IX+07H, which toggles TAB and ALTERNATIVE.
056A
RET
RETURN.
056B – Cursor Management – Special and Alternative Characters
056B
LD A,(4210H)
Put the contents of memory location 4210H into A.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls.
056E
XOR 08H
XOR A with 08H (0000 1000). This will toggle bit 3 to deal with special/alternative characters.
NOTE: Bit 3 of ECH is the SPECIAL CHARACTER SELECT. It will be 0 for KANA and 1 for MISC.
0570
LD (4210H),A
Put the toggled A back into memory location 4210H.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls.
0573
OUT (ECH),A
Output A to Port ECH.
0575
RET
RETURN.
0576 – This routine displays a character, moves forward either 1 or 2 spaces depending on if we are double size or not, and advances the screen if that character pushed the cursor beyond the end of the screen.
0576
LD (HL),A
Display the character on screen.
NOTE: HL should be the current screen location and A should be the character.
0577
INC HL
Bump HL to advance the cursor.
0578
LD A,(4210H)
Put the contents of memory location 4210H into A.
NOTE: 4210H holds the bit mask for port ECH. Port ECH stores miscellaneous controls.
057B
AND 04H
Mask A with 04H (0000 0100), so the only possibilties are 4 (0000 0100) or 0 (0000 0000).
Set DE to 4000H which is 1 character off the screen.
05C9
LD (HL),20H
Put a BLANK into the current cursor position.
05CB
INC HL
Bump the current cursor position by one.
05CC
RST 18H
We need to check to see if the integer value in HL is greater than or equal to DE (which is 1 character off the screen) so we call the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status register as: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal).
If that RST 18H call is not zero, then we are not off the screen, so loop back to 05C9H until we are done.
05CF
POP HL
We have cleared the screen so now restore the cursor position back to HL from the STACK.
05D0
RETC9
RETurn to CALLer
*05D1-05D8 – Model 4 Gen 1 Message Storage Area
*05D1-05D3
“RON”52 4F 4E
*05D4
AND F0HE6 F0
I don’t think this is used
*05D6
CP 30HFE 30
I don’t think this is used
*05D1-05D8 – Model 4 Gen 2 Message Storage Area
*05D1-05D8
0EH + “Cass ? + 03H”
05D9H-0673H – Part of the Keyboard Routine – “KEYIN” Keyboard Line Handler Routine
This is the most basic of the string input routines and is used by the two others (1BB3H and 0361H) as a subroutine. To use it, load HL with the required buffer address and the B Register with the maximum buffer length required. Keyboard input over the specified maximum buffer length is ignored, and after pressing the (ENTER) key it will return with HL containing the original buffer address and B with the string length.
A call to this memory location Accepts keyboard input and stores each character in a buffer supplied by caller. Input continues until either a carriage return or a BREAK is typed, or until the buffer is full. All edit control codes are recognized, e.g. TAB, BACKSPACE, etc.
On exit the registers contain: HL=Buffer address, B=Number of characters transmitted excluding last, C=Orginal buffer size, A=Last character received if a carriage return or BREAK is typed. Carry Set if break key was terminator, reset otherwise. If the buffer is full, the A Register will contain the buffer size.
Accepts keyboard input and stores each character in a buffer supplied by caller. Input continues until either a carriage return or a BREAK is typed, or until the buffer is full. All edit control codes are recognized, e.g. TAB, BACKSPACE, etc
To use a ROM call to accept a restricted number of keyboard characters for input (n), use: LD HL,(40A7H) LD B,n CALL 05D9H.
Up to n characters will be accepted, after which the keyboard will simply be ignored until the ENTER (or LEFT ARROW, or BREAK, or CLEAR) key is pressed. These characters will be stored in consecutive memory cells starting at the address contained in 40A7H-40A8H (the keyboard buffer area), with a 0DH (carriage return) byte at the end. Upon completion, the HL Register Pair will contain the address of the first character of the stored input, and the B Register will contain the number of characters entered. NOTE: No “?” is displayed as a result of the execution of the above program. If the “?” display is desired to prompt the typing of the input, precede the above program segment with: LD A,3FH CALL 033AH LD A,20H CALL 033AH
According to the original ROM comments, on entry, HL to point to the input line address in RAM and Register B to hold the maximum number of input characters to fetch. On exit, Register A should hold the number of characters entered
05D9 ↳ KEYIN
PUSH HLE5
Save the start of the input buffer area pointer in Register Pair HL on the STACK
05DA-05DB
LD A,0EH3E 0E
Load Register A with a turn on the cursor character (which is 14)
Jump if the key that was pressed in Register A is a turn on the 32 character per line mode character
0608-0609
CP 0AHFE 0A
Check to see if the key that was pressed in Register A is a line feed character of CHR$(10)
060A
RET NZC0
Return (to 05E0H) if the key that was pressed in Register A isn’t a line feed character
060B
POP DED1
Get the return address from the STACK and put it in Register Pair DE (so that it isn’t 05E0H anymore)
060C ↳ KLNCHR
LD (HL),A77
We now know that the key pressed is a printable character so save the key that was pressed in Register A at the location of the input buffer pointer in Register Pair HL
060D
LD A,B78
Load Register A with the length of the buffer remaining in Register B
060E
OR AB7
Check to see if there is any more of the input buffer remaining (and set status)
Jump to 05E0H if the end of the input buffer has been reached
0611
LD A,(HL)7E
Now we know the end of the input buffer has not been reached, so load Register A with the value at the location of the input buffer pointer in Register Pair HL
0612
INC HL23
Increment the input buffer pointer in Register Pair HL
Gosub to wait for the next key and back up the input buffer pointer in Register Pair HL if necessary
0625
DEC HL2B
Backup to the previous character (the one before the CARRIAGE RETURN) by decrementing the input buffer pointer in Register Pair HL
0626
LD A,(HL)7E
Load Register A with the character at the location of the input buffer pointer in Register Pair HL
0627
INC HL23
Increment the input buffer pointer in Register Pair HL to the net availabile position
0628-0629
CP 0AHFE 0A
Check to see if the character in Register A is the line feed character of CHR$(10)
062A
RET ZC8
Return if the character in Register A is a line feed character
062B ↳ KLNCAN
LD A,B78
Now we know that character wasn’t a line feed, so we need to test for a buffer full. This loads Register A with the number of bytes remaining in the input buffer area in Register B
062C
CP CB9
Check to see if the number of characters remaining in the input buffer area in Register A is the same as the length of the input buffer area in Register C
Jump to 0622H if there is room for more characters
062F
RETC9
The buffer is full! Return
0630H – Part of the Display routine – “KLNBSP”
Backspace one character. On entry Register B to hold the number of characters received, and Register C to hold the size of the buffer
0630 ↳ KLNBSP
LD A,B78
Load Register A with the number of bytes remaining in the input buffer area in Register B
0631
CP CB9
Compare the number of bytes remaining in the input buffer (held in Register A) against the size of the buffer (held in Register C) to see if the buffer is full
0632
RET ZC8
Return if the input buffer area is full
0633
DEC HL2B
Decrement the input buffer area pointer in Register Pair HL to backspace the previous character .
0634
LD A,(HL)7E
… and then get that character into Register A
0635-0636
CP 0AHFE 0A
Check to see if the character in Register A is the line feed character of CHR$(10)
0637
INC HL23
Increment the input buffer area pointer in Register Pair HL
0638
RET ZC8
Return if the character in Register A is a line feed character
0639
DEC HL2B
Decrement the input buffer area pointer in Register Pair HL to backspace the previous character in the buffer .
063A-063B
LD A,08H3E 08
Load Register A with a backspace of CHR$(08) and then .
Call the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen). Since that is the 32 character per line mode, that’s what happens
Display the carriage return by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen). Since that is a CARRIAGE RETURN, that’s what happens
0669-066A
LD A,0FH3E 0F
Load Register A with a turn off the cursor character
JUMP to 2B8DH which is in the middle of the TOKENize routine. This address loads A with the current character at the BASIC line pointer, tests for end of line, puts it into the memory location pointed to by BC, and exits.
06A3
AND FDH
Mask A with FDH (1111 1101) to turn off Bit 1.
06A5
LD (409FH),A
Put A into the DATA FLAG held in 409FH. Note: Bit 0 HIGH means inside a quote. Bit 1 HIGH means inside a DATA. Bit 2 HIGH means inside a REM.
06A8
LD A,3AH
Load A with 3AH (which is a :).
06AA
OR A
Set the flags, to start a check for a reserved word.
If CARRY is set then we are in quoted string so JUMP to 06E2H which then JUMPs to 2B89H which is in the middle of the TOKENize routing. This address bumps BC (the input buffer pointer), reduces D (the buffer counter), moves the BASIC line pointer forward, and continues.
06B4
RRA
Rotate A right one bit, with the bit that falls off (BIT 0) being moved to the CARRY FLAG, and the CARRY FLAG is moved to BIT 7.
06B5
RRA
Rotate A right one bit, with the bit that falls off (BIT 0) being moved to the CARRY FLAG, and the CARRY FLAG is moved to BIT 7. This tests for a REM.
JUMP to 2B89H which is in the middle of the TOKENize routing. This address bumps BC (the input buffer pointer), reduces D (the buffer counter), moves the BASIC line pointer forward, and continues.
06E5H – This subroutine sets the DATA FLAG to “BIT 1 HIGH” to indicate that we are in a DATA command.
06E5
LD A,(409FH)
Load A with the DATA FLAG in 409FH.
06E8
OR 02H
OR A against 02H (0000 0010) to set BIT 1, the DATA bit.
06EA
LD (409FH),A
Put the revised A into the DATA FLAG.
06ED
XOR A
Clear A and all flags.
06EE
RET
RETURN.
06EFH – This subroutine sets the DATA FLAG to “BIT 2 HIGH” to indicate that we are in a REM command.
06EF
LD A,(409FH)
Load A with the DATA FLAG in 409FH.
06F2
OR 04H
OR A against 04H (Binary: 0000 0100) to turn on Bit 2, the REM bit.
Rotate A left one bit, with the bit that falls off (BIT 7) being moved to the CARRY FLAG, and the CARRY FLAG is moved to BIT 0. If this results in the CARRY FLAG being set, then we are in a DATA statement.
If A had Bit 7 high (which was rotated into CARRY for thsi test), then we are in a DATA statement, JUMP to 06E2H which then JUMPs to 2B89H which is in the middle of the TOKENize routing. This address bumps BC (the input buffer pointer), reduces D (the buffer counter), moves the BASIC line pointer forward, and continues.
Single-precision addition (ACCumulator=(HL)+ACC) involving a buffer pointed to by the HL Register Pair and ACCumulator (i.e., 4121H-4122H). This part of the program loads the BCDE registers with the value from the buffer, then passes control to 716H.
Load Register Pair HL address of the single precision value 1/2, which is stored in ROM at 1380H. This would be applicable if the entry jump was to this address (FADDH). If the entry is to FADDS, then this wouldn’t occur.
Move the argument from (HL) into the registers via a call to 09C2H (which loads a SINGLE PRECISION value pointed to by Register Pair HL into Register Pairs BC and DE)
Actually do the addition via a JUMP to the SINGLE PRECISION ADD routine at 0716H (which adds the single precision value in (BC/DE) to the single precision value in the ACCumulator (i.e., 4121H-4122H). The sum is left in the ACCumulator
Load a SINGLE PRECISION value pointed to by Register Pair HL into Register Pairs BC and DE via a GOSUB to MOVRM
– “FSUB”
Single-precision subtraction (ACCumulator=BCDE-ACCumulator). The routine actually inverts ACCumulator (i.e., 4121H-4122H) and adds it to the contents of the BCDE registers which, in effect, is a subtraction. The result will be stored in the ACCumulator (i.e., 4121H-4122H).
Single Precision Subtract: Subtracts the single precision value in (BC/DE) from the single precision value in the ACCumulator. The difference is left in the ACCumulator
Single-precision subtraction (ACC=BCDE-ACC). The routine actually inverts the ACC and adds it to the contents of the BCDE registers which, in effect, is a subtraction. The result will be stored in the arithmetic work area (ACC)
Note: If you wanted to subtract two single precision numbers, store the minuend in the BCDE registers and store the subtrahend in 4121H-4124H and then CALL 0713H. The result (in single precision format) is in 4121H-4124H in approximately 670 microseconds.
Single-precision addition (ACCumulator=BCDE+ACC). This routine adds two single-precision values and stores the result in the ACCumulator area.
Note: If you wanted to add 2 single precision numbers via a ROM call, store one input into BCDE (with the exponent in B and the LSB in E) and the other into 4121H-4124H, and then call 0716H. The single precision result will be in 4121H-4124H approximately 1.3 milliseconds later.
Single Precision Add: Add the single precision value in (BC/DE) to the single precision value in the ACCumulator. The sum is left in the ACCumulator
Single-precision addition (ACC=BCDE+ACC). This routine adds two singleprecision values and stores the result in the ACC area
Formula: FAC:=ARG+FAC
Routine ALTERS A,B,C,D,E,H,L
If INTFSF=1 the format of floating point numbers will be:
Reg B – SIGN AND BITS 1-7 OF EXPONENT
Reg C – Bit 8 of exponent ;and bits 2-8 of mantissa
Reg D – Bits 9-16 of mantissa
Reg E – Bits 17-24 of mantissa, and likewise for the ACCumulator format
Note: The exponent for intel will be 7FH
0716 ↳ FADD
LD A,B78
First we need to check to see if the first argument is zero, so we load Register A with the exponent of the single precision value in Register B
0717
OR AB7
Set the flags based on Register B to check to see if the single precision value in Register Pairs BC and DE is equal to zero
0718
RET ZC8
Return if the single precision value in Register Pairs BC and DE is equal to zero because the result is already in the ACCumulator.
0719-071B
LD A,(4124H)LD A,(FAC)3A 24 41
Next, we want to test to see if the exponent is zero, because if it is, then the answer is already in the registers. First, load Register A with the exponent of the single precision value in the ACCumulator (i.e., 4121H-4122H)
071C
OR AB7
Set the flags based on the exponent (now in A) is equal to zero
If the exponent is zero, then the result is already in BCDE, so CALL MOVFR to move the SINGLE PRECISION value in DC/DE into ACCumulator.
At this point we know that we are going to actually do the math, so the next step is to get the smaller number into the registers (BCDE) so we can just shift it rith and align the binary points of both numbers. If we do this, then we just add or subtract them bytewise.
0720
SUB B90
Subtract the value of the exponent for the single precision value in Register B from the value of the exponent for the single precision value in the ACCumulator (i.e., 4121H-4122H) in Register A so we can see which is smaller. NC will be set if BCDE < ACCumulator.
If the single precision value in Register Pairs BC and DE is smaller than the single precision value in the ACCumulator (i.e., 4121H-4122H), JUMP to FADD1 since they are in the right order.
0723
CPL2F
If we are here, then we want to swap the two numbers. First, we negate the shift count (adjust the difference in the exponents in Register A so that it is positive)
0724
INC A3C
Increment the difference in the exponents in Register A so that it will be the correct positive number
Call 09B4H which moves the SINGLE PRECISION value in DC/DE into ACCumulator
072D
POP BCC1
Next we finish the swap buyt putting the old ACCumulator into the registers with two POPs. First, get the 16-bit value from the STACK and put it in Register Pair BC
072E
POP DED1
Get the 16-bit value from the STACK and put it in Register Pair DE
At this point, the smaller number is in ABCD, so we proceed with the math.
072F-0730 ↳ FADD1
CP 19HFE 19
The highest math we can do is 24 bits, so first check to make sure we are not going to exceed that. To do this we check to see if the difference in the exponents in Register A is greater than 24
0731
RET NCD0
If the math is going to exceed 24 bits, then we need to fail and RETurn
0732
PUSH AFF5
Save the shift count (the difference in the exponents in Register A) on the STACK
Set the sign bits for the single precision values and return with the equality of the sign bits in Register A
0736
LD H,A67
Save the sign bits (the subtraction flag) into Register A
0737
POP AFF1
Get shift count (the difference of the exponents) back from the STACK and put it in Register A
If the numbers have the same sign, then we add them. if the signs are different, then we have to subtract them. we have to do this because the mantissas are positive. judging by the exponents, the larger number is in the ACCumulator, so if we subtract, the sign of the result should be the sign of the ACCumulator; however, if the exponents are the same, the number in the registers could be bigger, so after we subtract them, we have to check if the result was negative. if it was, we negate the number in the registers and complement the sign of the ACCumulator. (here the ACCumulator is unpacked) if we have to add the numbers, the sign of the result is the sign of the ACCumulator. so, in either case, when we are all done, the sign of the result will be the sign of the ACCumulator.
If there was NO overflow then we will JUMP away to round
0748
INC HL23
If we’re still here, then there was an overflow, but the most it can overflow is 1 bit, so we increment the memory pointer in Register Pair HL, so that it points to the exponent in the ACCumulator
0749
INC (HL)34
Increment the exponent in the ACCumulator at the location of the memory pointer in Register Pair HL
Check for another overflow (i.e., the exponent in the ACCumulator is too large) in which case go to 07B2H to output an OV ERROR message
074D-074E
LD L,01H2E 01
Prepare to shift the result one one bit and shift the CARRY FLAG in by load ingRegister L with the number of bits to shift the single precision result in Register Pairs BC and DE
If we did not shift 4 bytes of ZERO’es, shift 8 more via a loop until shift is completed
This routine will ZERO out the ACCumulator, changing only Register A in the process. A will exit as 0.
0778 ↳ ZERO
XOR AAF
Zero Register A
0779-077B ↳ ZERO0
LD (4124H),ALD (FAC),A32 24 41
Make the ACCUmulator’s exponent = whatever is in A. If entered from above, then it will be 0. This is done because Level II treats a number as zero if its exponent is zero.
077C
RETC9
Return with a single precision value of zero in the ACCumulator
077DH-07A7H – SINGLE PRECISION MATH SUPPORT ROUTINE – “NORM2”
077D ↳ NORM2
DEC B05
Decrement the shift count (exponent counter) in Register B
077E
ADD HL,HL29
Rotate (HL) left by 1 and shift in a 0
077F
LD A,D7A
Rotate the next higher order (NMSB) left 1 as well.
0780
RLA17
Shift the NMSB in Register A left one bit and shift a bit from Register Pair HL if necessary
0781
LD D,A57
Save the adjusted NMSB in Register A into Register D
0782
LD A,C79
Load Register A with the MSB in Register C
0783
ADC A,A8F
Shift the MSB in Register A left one bit and shift a bit from Register D if necessary. The flags will get set as well.
0784
LD C,A4F
Load Register C with the adjusted value in Register A
Jump if exponent is too small (i.e., an underflow). This jump is to code which just zeroes out A, puts it into (4124H), and RETurns
0795
RET ZC8
Return if exponent is equal to zero. Otherwise, we will pass down to the “ROUND” routine
The “ROUND” routine rounds the result in CDEB and puts the result into the ACCumulator. All registers are affected. CDE is rounded up or down based on the MSB of Register B.
Vernon Hester has flagged an error in the rounding of math routines. In base 10, rounding to k-digits examines digit k+1. If digit k+1 is 5 through 9, then digit k is adjusted up by one and carries to the most significant digit, if necessary. If digit k+1 is less than 5, then digit k is not adjusted. This should not get muddled with the conversion of base 2 to base 10. Nevertheless, four divided by nine should be: .444444 and not .444445
0796 ↳ ROUND
LD A,B78
Load Register A with the LSB of the single precision value in Register B
Load Register Pair HL with the address of the exponent in the ACCumulator. Note: The FDIV ROM Routine enters the routine here at ROUNDB with Register A set differently.
079A
OR AB7
Set the status flags to enable us to see if we need if we need to round up. If the M FLAG is set (the most significant bit of the value in Register A), we will need to round up.
Save the number into the ACCumulator via a JUMP to 09B4H which moves the SINGLE PRECISION value in BC/DE into ACCumulator
07A8H-07B6H – SINGLE PRECISION MATH SUPPORT ROUTINE – “ROUNDA”
This is a subroutine within the ROUND round. This will add one to C/D/E.
07A8 ↳ ROUNDA
INC E1C
Increment the LSB of the single precision value (which is stored in Register E). Note: This is the entry point from QUINT.
07A9
RET NZC0
If the NZ FLAG is set, then we have no overflow, so we are done!
07AAH
INC D14
Increment the NMSB of the single precision value (which is stored in Register D)
07AB
RET NZC0
If the NZ FLAG is set, then we have no overflow, so we are done!
07AC
INC C0C
Increment the MSB of the single precision value (which is stored in Register C).
07AD
RET NZC0
If the NZ FLAG is set, then we have no overflow, so we are done!
07AE-07AF
LD C,800E 80
If we are still here then the number overflowed all 3 registers. With this, we need to adjust the MSB of the single precision value in Register C and then …
07B0
INC (HL)34
… update the exponent (which is stored in the RAM location pointed to by HL)
07B1
RET NZC0
If the NZ FLAG is set, then we have no overflow, so we are done! If that overflowed as well, then we are out of luck and we pass through to an error.
Load Register Pair HL with the address of the sign flag storage location.
07C6
LD A,(HL)7E
Load Register A with the value of the sign flag at the location of the memory pointer in Register Pair HL
07C7
CPL2F
Complement the sign flag in Register A
07C8
LD (HL),A77
Save the adjusted sign flag in Register A back to (FAC+1)
07C9
XOR AAF
Zero Register A. This will allow us to zero Register L and to do negative math
07CA
LD L,A6F
Load Register L with a 0 (held in Register A) so that we can keep getting a zero back into Register A for the below math using less code.
07CB
SUB B90
NEGate the low order/LSB number by subtracting Register B from zero (held in Register A)
07CC
LD B,A47
Save that negated Register B back to Register B
07CD
LD A,L7D
Load Register A with zero
07CE
SBC A,E9B
NEGate the next highest order number by subtracting Register E from zero (held in Register A)
07CF
LD E,A5F
Save that negated Register E back to Register E
07D0
LD A,L7D
Load Register A with zero
07D1
SBC A,D9A
NEGate the next highest order number by subtracting Register D from zero (held in Register A)
07D2
LD D,A57
Save that negated Register D back to Register D
07D3
LD A,L7D
Load Register A with zero
07D4
SBC A,C99
NEGate the highest order number/MSB by subtracting Register C from zero (held in Register A)
07D5
LD C,A4F
Save that negated Register C back to Register C
07D6
RETC9
RETurn to CALLer
07D7H-07F7H – SINGLE PRECISION MATH ROUTINE – “SHIFTR”
This routine will shift the number in C/D/E right the number of times held in Register A. The general idea is to shift right 8 places as many times as is possible within the number of times in A, and then jump out to shift single bits once you can’t shift 8 at a time anymore. Alters everything except Register H.
07D7-07D8 ↳ SHIFTR
LD B,00H06 00
Load Register B, which will hold the overflow byte, with zero to reset the overflow byte
07D9-07DA ↳ SHFTR1
SUB 08HD6 08
Top of a loop. For speed, first check to see if the shift counter in Register A still indicates at least 8 bits have to be shifted right
If the CARRY FLAG is set, then there isn’t room to shift 8 bytes, so we are going to need to shift only 1 byte, by JUMPing away to SHFTR2. This is the routine’s exit.
If we are here, then we are good to shift 8 bytes at once. So B to E, E to D, D to C, and then Zero out C …
07DD
LD B,E43
Load Register B with the LSB of the single precision value in Register E
07DE
LD E,D5A
Load Register E with the NMSB of the single precision value in Register D
07DF
LD D,C51
Load Register D with the MSB of the single precision value in Register C
This routine will shift the number in C/D/E right the number of times held in Register A, but one byte at a time.
07E4-07E5 ↳ SHFTR2
ADD 09HC6 09
Adjust the shift counter in Register A to its correct value for working with individual bits instead of bytes
07E6
LD L,A6F
Load Register L with the shift counter in Register A so that L will hold the counter for shifts at the single bit level
07E7 ↳ SHFTR3
XOR AAF
Top of a loop. Clear the CARRY FLAG.
07E8
DEC L2D
Decrement the bit shift counter (held in Register L)
07E9
RET ZC8
Return if there are no more bits to be shifted. This is the routine’s exit.
07EA
LD A,C79
If we are here, then there are L bits to shift. First, load Register A with the High Order/MSB of the single precision value in Register C
07EB ↳ SHRADD
RRA1F
Shift the MSB of the single precision value in Register A one place to the right. Note: FADD enters at this point withRegister A set differently. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
07EC
LD C,A4F
Save the bit shifted MSB (held in Register A) back into Register C
07ED
LD A,D7A
Load Register A with the NMSB of the single precision value in Register D
07EE
RRA1F
Shift the NMSB of the single precision value in Register A one place to the right and pick up the value of the Carry flag. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
07EF
LD D,A57
Save the bit shifted NMSB (held in Register A) back into Register D
07F0
LD A,E7B
Load Register A with the LSB of the single precision value in Register E
07F1
RRA1F
Shift the LSB of the single precision value in Register A one place to the right and pick up the value of the Carry flag. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
07F2
LD E,A5F
Save the bit shifted LSB (held in Register A) back into Register D
07F3
LD A,B78
Load Register A with the overflow byte (held in Register B)
07F4
RRA1F
Shift the overflow byte one place to the right and pick up the value of the Carry flag. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
07F5
LD B,A47
Save the bit shifted overflow byte (held in Register A) back into Register B
07F8-07FB – SINGLE PRECISION CONSTANT STORAGE LOCATION
07F8-07FB
00 00 00 81
A single precision constant equal to 1.0 is stored here.
07FC-0808 – SINGLE PRECISION CONSTANTS STORAGE LOCATION2
07FC
03
The number of single precision constants which follows is stored here.
07FD-0800
AA 56 19 80
A single precision constant equal to .598978 is stored here.
0801-0804
F1 22 76 80
A single precision constant equal to .961471 is stored here.
0805-0808
45 AA 38 82
A single precision constant equal to 2.88539 is stored here.
0809H-0846H – LEVEL II BASIC LOG ROUTINE – “LOG”
The LOG(n) routine, (ACCumulator=LOG (ACCumulator)). This routine finds the natural log (base E) of the single precision value in the ACCumulator area.
The result is returned as a single precision value in the ACCumulator
To use a ROM call to find LOG(n), where X is a positive single precision variable, store the value of n in 4121H-4124H and then CALL 0809H. The result (in single precision format) is in 4121H-4124Hin approximately 19 milliseconds. NOTE: A fatal error occurs if the value of the input variable is zero or negative.
Vernon Hester has identified a bug in the LOG() routine. Regardless of the base, if the argument is 1 then the logarithm is zero, if the argument is > 1 then the logarithm is positive, and if the argument is > 0 and < 1 then the logarithm is negative. However, if the argument is just under 1, the ROM’s LOG function produces a positive value. e.g., 10 PRINT LOG(.99999994)
If the ACCumulator value is <= ZERO then we cannot proceed so go the Level II BASIC error routine and display a ?FC ERROR message. The SIGN routine will only return 00H, 01H, or FFH, so PE will be set if its 00H or FFH, but not 01H
Load Register DE with the NMSB and the LSB of a single precision constant (which is 1267). Register Pairs BC and DE are now equal to the single precision constant of .707107
081A
SUB B90
Remove the excess 80H (held in Register B) from the exponent of the n-value (of LOG (n)) held in Register A
081B
PUSH AFF5
Save the modified exponent to the the STACK for later
081C
LD (HL),B70
Set the exponent to 80H
The next two instructions save SQR(.5) to the STACK
081D
PUSH DED5
Save the NMSB and the LSB of the single precision value in Register Pair DE on the STACK
081E
PUSH BCC5
Save the exponent and the MSB of the single value in Register Pair BC on the STACK
Calculate (F-SQR(.5))/(F+SQR(.5)) where F = the number in the ACCumulator by GOSUBing to FADD which will add the x-value to the single precision constant in Register Pairs BC and DE and return with the result in the ACCumulator, by calling the SINGLE PRECISION ADD routine at 0716H (which adds the single precision value in (BC/DE) to the single precision value in the ACCumulator. The sum is left in the ACCumulator)
The next two instructions restore SQR(.5) from the STACK
0822
POP BCC1
Get the exponent and the MSB of the single precision value from the STACK and put it in Register Pair BC
0823
POP DED1
Get the NMSB and the LSB of the single precision value from the STACK and put it in Register Pair DE
The next two instructions get SQR(2)
0824
INC B04
Multiply the single precision value in Register Pairs BC and DE by two by bumping the exponent in Register B
Go subtract the x-value in the ACCumulator from the single precision constant of 1. 0 at the location of the memory pointer in Register Pair HL and return with the result in the ACCumulator
Add in the last constant via a GOSUB to FADD which will add the x-value in the ACCumulator to the single precision constant in Register Pairs BC and DE and return with the result in the ACCumulator, by calling the SINGLE PRECISION ADD routine at 0716H (which adds the single precision value in (BC/DE) to the single precision value in the ACCumulator. The sum is left in the ACCumulator)
083D
POP AFF1
Retrieve the original exponent from the STACK and put it in Register A
Go convert the value in Register A to a single precision number and add it to the x-value in the ACCumulator. Return with the result in the ACCumulator
The instructions are commented in the original ROM source code as: Get LN(2)
Load Register Pair DE with the NMSB and the LSB of a single precision constant. Register Pairs BC and DE are now equal to a single precision value of 0.693147
The original ROM source code had a jump to the muptlication routine; but to save bytes, the ROM was restructured to just fall into the MULTiplocation routine instead.
0847H-0891H – SINGLE PRECISION MULTIPLICATION, – “FMULT”
Single-precision multiplication (ACCumulator=BCDE*ACC or ACC = ARG * FAC)). Multiplies the current value in the ACCumulator by the value in (BC/DE). the product is left in the ACCumulator.
Note: If you wanted to multiply two single precision numbers store one operand in the BCDE registers, the other in 4121H-4124H CALL 0847H. The result (in single precision format) is in 4121H-4124H in approximately 2.2 milliseconds.
Single Precision Multiply Multiplies the current value in the ACCumulator by the value in (BC/DE). the product is left in the ACCumulator
Jump if the LSB of the single precision value in the ACCumulator is equal to zero
086E
PUSH HLE5
Save the memory pointer to the number in the ACCumulator (tracked by Register HL) on the STACK
086F-0870
LD L,08H2E 08
Load Register L with the bit shift counter
The original source code explains what is being done next. The product will be formed in C/D/E/B. This will be in C/H/L/B part of the time in order to use the “DAD” instruction. At FMULT2, we get the next byte of the mantissa in the ACCumulator to multiply by, which is tracked by HL and unchanged by FMULT2. If the byte is zero, we just shift the product 8 bits to the right. This byte is then shifted right and saved in Register D. The CARRY FLAG determines if we should add in the second factor, and, if we do, we add it to C/H/L. Register B is only used to determine which way we round. We then shift C/H/L/B right one to get ready for the next time through the loop. Note: The CARRY is shifted into the MSB of Register C. Register E has the count to determine when we have looked at all the bits of Register D.
0871 ↳ FMULT4
RRA1F
Shift the LSB of the single precision value in the ACCumulator in Register A one place to the right. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0872
LD H,A67
Load Register H with the adjusted LSB in Register A
0873
LD A,C79
Load Register A with the MSB of the single precision value in Register C
If the bit was zero, don’t add in any numbers and, instead, jump forward to 0881H
0876
PUSH HLE5
Save the counters (tracked in Register Pair HL) to the STACK
0877-0879
LD HL,(4150H)LD HL,(FMLTT2)2A 50 41
Load Register Pair HL with the NMSB and the LSB of the original value in Register Pairs BC and DE
087A
ADD HL,DE19
Add the NMSB and the LSB of the total figured so far in Register Pair DE to the NMSB and the LSB of the original value in Register Pair HL
087B
EX DE,HLEB
Load Register Pair DE with the adjusted total in Register Pair HL
087C
POP HLE1
Get the counters back from the STACK and put it in Register Pair HL
087D-087F
LD A,(414FH)LD A,(FMLTT1)3A 4F 41
Load Register A with the MSB of the original value in Register Pairs BC and DE
0880
ADC A,C89
Add the MSB of the original value in Register A to the MSB of the total figured so far in Register C
0881 ↳ FMULT5
RRA1F
Shift the adjusted MSB of the total in Register A one place to the right. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0882
LD C,A4F
Load Register C with the adjusted MSB of the total in Register A
0883
LD A,D7A
Load Register A with the NMSB of the total in Register D
0884
RRA1F
Shift the NMSB of the total in Register A one place to the right. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0885
LD D,A57
Load Register D with the adjusted NMSB of the total in Register A
0886
LD A,E7B
Load Register A with the LSB of the total in Register E
0887
RRA1F
Shift the LSB of the total in Register A one place to the right. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0888
LD E,A5F
Load Register E with the adjusted LSB of the total in Register A
0889
LD A,B78
Load Register A with the value in Register B
088A
RRA1F
Shift the value in Register A one place to the right. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
088B
LD B,A47
Load Register B with the adjusted value in Register A
088C
DEC L2D
Decrement the bit counter in Register L and set the flags accordingly
088D
LD A,H7C
Load Register A with the LSB of the number we are multiplying
Move the “10” into the ACCUulator via a call to 09B1H (which moves a SINGLE PRECISION number pointed to by HL to ACCumulator)
08A0 ↳ FDIVT
POP BCC1
Get the exponent and the MSB of the single precision value on the STACK and put it in Register Pair BC
With the numbers in their places, we now just fall into the floating division routine.
08A2H-0903H – SINGLE PRECISION DIVISION – “FDIV”
Single-precision division (ACCumulator=BCDE/ACCumulator or ACC = ARG / ACC). If ACCumulator=0 a ” /0 ERROR ” will result.
This routine will divide the SINGLE PRECISION value in Register Pairs BC and DE by the single precision value in the ACCumulator. The result is returned in the ACCumulator. Every register is used.
To use a ROM call to divide two single precision numbers, store the dividend in registers BCDE, and the divisor in 4121H-4124H and then CALL 08A2H. The result (in single precision format) is in 4121H-4124H and then pproximately 4.8 milliseconds. Overflow or /0 will error out and return to Level II.
08A1
POP DED1
Get the NMSB and the LSB of the single precision value from the STACK and put it in Register Pair DE
If the SIGN routine retuns Z FLAG set, then we have a division by zero problem so JUMP to the Level II BASIC error routine and display an /0 ERROR message
08A8-08A9
LD L,FFH2E FF
Load Register L with a flag for use when subtracting the two exponents.
Go adjust the exponent in the ACCumulator for division
08AD 08AE
INC (HL) INC (HL)34
Add two to the exponent pointed to by (HL) to correct scaling
08AF
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to now point to the High Order/MSB of the single precision number in the ACCumulator
08B0
LD A,(HL)7E
Load Register A with the MSB of the single precision value in the ACCumulator
08B1-08B3
LD (4089H),ALD (FDIVA+1),A32 89 40
Save the MSB of the single precision value in the ACCumulator in Register A at memory location 4089H
08B4
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to point to the Middle Order/NMSB of the single precision number in the ACCumulator
08B5
LD A,(HL)7E
Load Register A with the NMSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
08B6-08B8
LD (4085H),ALD (FDIVB+1),A32 85 40
Save the NMSB of the single precision value in the ACCumulator in Register A at memory location 4085H
08B9
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to point to the Low Order/LSB of the single precision number in the ACCumulator
08BA
LD A,(HL)7E
Load Register A with the LSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
08BB-08BD
LD (4081H),ALD (FDIVC+1),A32 81 40
Save the LSB of the single precision value in the ACCumulator in Register A at memory location 4081H
At this point, the memory locations are set up, and it’s time to get to work. According to the original ROM source:
The numerator will be kept in Registers B/H/L. The quotient will be formed in Registers C/D/E. To get a bit of the quotient, we first save Registers B/H/L on the stack, and then subtract the denominator that we saved in memory. The CARRY FLAG will indicate whether or not Registers B/H/L was bigger than the denominator. If Registers B/H/L are bigger, the next bit of the quotient is a one. To get the old Registers B/H/L off the stack, they are POPped into the PSW. If the denominator was bigger, the next bit of the quotient is zero, and we get the old Registers B/H/L back by POPping them off the stack. We have to keep an extra bit of the quotient in FDIVG+1 in case the denominator was bigger, in which case Registers B/H/L will get shifted left. If the MSB of Register B is one, it has to be stored somewhere, so we store it in FDIVG+1. Then the next time through the loop Registers B/H/L will look bigger because it has an extra High Order bit in FDIVG+1. We are done dividing when the MSB of Register C is a one, which occurs when we have calculated 24 bits of the quotient. When we jump to ROUND, the 25th bit of the quotient (whcih is in the MSB of Register A) determines whether we round or not. If initially the denominator is bigger than the numerator, the first bit of the quotient will be zero. This means we will go through the divide loop 26 times, since it stops on the 25th bit after the first non-zero bit of the exponent. So, this quotient will look shifted left one from the quotient of two numbers in which the numerator is bigger. This can only occur on the first time through the loop, so Registers C/D/E are all zero. So, if we finish the loop and Registers C/D/E are all zero, then we must decrement the exponent to correct for this.
08BE
LD B,C41
First, we need to get the number into B/H/L. First, load Register B with the MSB of the single precision dividend (held in in Register C)
08BF
EX DE,HLEB
Then, get the NMSB and LSB of the dividend from DE into Register Pair HL
08C0
XOR AAF
Next, we need to zero out C, D, E, and the Highest Order
08C1
LD C,A4F
Zero the MSB of the total by loading Register C with the value in Register A
08C2
LD D,A57
Zero the NMSB of the total by loading Register D with the value in Register A
08C3
LD E,A5F
Zero the LSB of the total by loading Register E with the value in Register A
08C4-08C6
LD (408CH),ALD (FDIVG+1),A32 8C 40
Zero memory location 408CH (which is holding the highest order)
08C7 ↳ FDIV1
PUSH HLE5
Save the NMSB and LSB of the single precision dividend (held in Register Pair HL) on the STACK
08C8
PUSH BCC5
Save the MSB of the dividend in Register B on the STACK
08C9
LD A,L7D
Next we will need to subtract the number that was in the ACCumulator, so load Register A with the LSB of the dividend in Register L
Go to the Level II BASIC division routine. Note: Per the original ROM source code, this division routine was moved to RAM for speed; it didn’t HAVE to be moved!
08CD-08CE
SBC 00HDE 00
Subtract the CARRY FLAG from it
08CF
CCF3F
Set the CARRY FLAG to correspond to the next quotient bit
If we subtracted too much then the NC flag will be set, in which case we need to get the old number back! To do this, JUMP down to 08D9H (which is a mid-instruction Z-80 trick)
08D2-08D4
LD (408CH),ALD (FDIVG+1),A32 8C 40
Update the highest order number held at FDIVG+1
08D5
POP AFF1
We want to clear the previous number off the stack since the subtraction didn’t cause an error
08D6
POP AFF1
And again
08D7
SCF37
Set the CARRY FLAG so that the next bit in the quotient is a 1 to indicate that the subtraction was good
08D8
D2
Z-80 Trick – See the note at 0134H for an explanation.
08D9
POP BC
Get the value from the STACK and put it in register pair BC.
08DA
POP HL
Get the value from the STACK and put it in register pair HL.
08DB
LD A,C79
We want to see if we are done by testing Register C, so load Register A with the MSB of the total in Register C
08DC 08DD
INC A DEC A3C
Increment and then Decrement the MSB of the total in Register A. This will set the SIGN FLAG without affecting the CARRY FLAG
08DE
RRA1F
Shift the CARRY into the MSB (held in Register A). RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0907H-0913H – DOUBLE PRECISION MATH ROUTINE – “MULDVS”
This routine is to check for special cases and to add exponents for the FMULT and FDIV routines. Registers A, B, H and L are modified.
0907-0908 ↳ MULDVS
LD A,FFH3E FF
This is the entry point from the DDIV routine. With this, we need to set up to subtract exponents. To do this we load Register A with an appropriate bit mask
0909
2E
Z-80 Trick – See the note at 0134H for an explanation.
Load Register Pair HL with the address of the exponent in the ACCumulator
091C
XOR (HL)AE
Combine the value of the exponent at the location of the memory pointer in Register Pair HL with the bit mask in Register A (formerly of Register L)
091D
ADD A,B80
Add the value of the exponent in Register B to the value of the exponent in Register A
091E
LD B,A47
Load Register B with the combined exponents (currently held in Register A)
091F
RRA1F
Shift the value of the combined exponents in Register A one place to the right so that we can check for an overflow. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0920
XOR BA8
Check to see if the Carry flag was set by combining the two exponents. This will cause an overflow if the sign is the same as the carry.
0921
LD A,B78
Load Register A with the combined/summed exponents value in Register B
Multiply the number by 5 by adding the original value in the ACCumulator to the adjusted value in Register Pairs BC and DE and return with the original result in the ACCumulator by calling the SINGLE PRECISION ADD routine at 0716H (which adds the single precision value in (BC/DE) to the single precision value in the ACCumulator. The sum is left in the ACCumulator)
Prepare to add 1 to the expenent (to thus multiply it by 2, which is then 10 times the original number). First, load Register Pair HL with the address of the exponent in the ACCumulator
0950
INC (HL)34
Increment the value of the exponent in the ACCumulator at the location of the memory pointer in Register Pair HL. ACCumulator now holds the original value times ten
0951
RET NZC0
Return if the new value in the ACCumulator is in an acceptable range
Display an ?OV ERROR if the value of the exponent at the location of the memory pointer in Register Pair HL is too large
0955H-0963H – SINGLE PRECISION MATH ROUTINE – “SIGN”
Puts the SIGN of the ACCumulator into Register A. Only Register A is modified by this routine; the ACCumulator is left untouched.
To take advantage of the RST instructions to save bytes, FSIGN is defined to be an RST. “FSIGN” is equivalent to “call sign” the first few instructions of SIGN (the ones before SIGNC) are done in the 8 bytes at the RST location.
0955-0957 ↳ SIGN
LD A,(4124H)LD A,(FAC)3A 24 41
Prepare to check to see if the number in the ACCumulator is ZERO by loading Register A with the value of the exponent in the ACCumulator
0958
OR AB7
Check to see if the exponent in Register A is equal to zero
0959
RET ZC8
Return if the single precision value in the ACCumulator is equal to zero
095A-095C ↳ SIGNC
LD A,(4123H)LD A,(FAC-1)3A 23 41
Load Register A with the SIGN of the ACCumulator
095D-095E
CP 2FHFE 2F
Z-80 Trick. If passing through, this will check the value of Register A and skip the next CPL instruction.
095F ↳ ICOMPS
RLA17
Put the value of the sign bit in Register A into the CARRY FLAG
0960 “SIGNS”
SBC A,A9F
If the CARRY FLAG is 0 (i.e., POSITIVE), then make Register A = 0. If the CARRY FLAG is 1 (i.e., NEGATIVE), make Register A = FFH
0961
RET NZC0
If the CARRY FLAG was 1, then the number is negative, and we want to RETurn
0962 ↳ INRART
INC A3C
Increment the value in Register A so that Register A will be equal to 1 if the single precision value in the ACCumulator is positive
0963
RETC9
RETurn to CALLer
0964H-0976H – SINGLE PRECISION MATH ROUTINE – “FLOAT”
This routine will take a signed integer held in Register A and turn it into a floating point number. All registers are modified.
0964-0965 ↳ FLOAT
LD B,88H06 88
Load Register B with an exponent for an integer value
0977H-0989H – LEVEL II BASIC ABS() ROUTINE – “ABS”
ABS routine (ACCumulator=ABS(ACCumulator)) input and output can be integer, single-precision or double-precision, depending on what is placed in the NTF (NTF=2, 4 or 8). A call to 0977H converts the value in Working Register Area 1 (the ACCumulator) to its positive equivalent. The result is left in the ACCumulator. If a negative integer greater than 2** 15 is encountered, it is converted to a single precision value. The data type or mode flag (40AFH) will be updated to reflect any change in mode. All registers are modified.
NOTE: To use a ROM call to find ABS(X),store the value of X in 4121H-4122H (integer), in 4121H-4124H (single precision), or in 411DH and then H (double precision), and store the variable type (2, 4, or 8, respectively) in 40AFH. Then CALL 0977H. The result (in the same format as the input variable) is in the same locations in which the input variable was stored. If the input was an integer, the result is also in the HL Register Pair.
ABS routine (ACC=ABS(ACC)) input and output can be integer, single-precision or double-precision, depending on what is placed in the NTF (NTF=2, 4 or 8). (For a definition of NTF, see Part 2.)
Absolute Value: Converts the value in Working Register Area 1 (ACCumulator) to its positive equivalent. The result is left in the ACCumulator. If a negative integer greater than 2**15 is encountered, it is converted to a single precision value. The data type or mode flag (40AF) will be updated to reflect any change in mode
GOSUB to VSIGN to get the SGN of the ACCumulator into Register A
097A
RET PF0
If that sign is POSITIVE, then I guess we are done, so just RETurn
This routine will negate any value in the ACCumulator. Every Register is affected.
097B ↳ VNEG
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Load Register Pair HL with the address of the MSB (which holds the SIGN bit) in the ACCumulator.
0985
LD A,(HL)7E
Load Register A with the MSB (which holds the SIGN bit) in the ACCumulator at the location of the memory pointer in Register Pair HL
0986-0987
XOR 80HXOR 1000 0000EE 80
Complement the sign bit in the MSB in Register A. Since we know the number is negative, this is really just switching it to positive.
0988
LD (HL),A77
Save the adjusted MSB (which holds the SIGN bit) in Register A in the ACCumulator at the location of the memory pointer in Register Pair HL
0989
RETC9
RETurn to CALLer
098AH-0993H – LEVEL II BASIC SGN() ROUTINE – “SGN”
SGN function (ACCumulator=SGN(ACCumulator)). After execution, NTF=2 and ACCumulator=-l, 0 or 1 depending on sign and value of ACC before execution. Registers A, H, and L are affected.
NOTE: To use a ROM call to find SGN(X), store the value of X in 4121H-4122H (integer), in 4121H-4124H (single precision), or in, s-4124H (double precision) and then store the variable type (2, 4, or 8, respectively) in 40AFH and then CALL 098AH. The result (in integer format) is in 4121H-4122H and in the HL Register Pair.
SGN function (ACC=SGN(ACC)). After execution, NTF=2 and ACC=-l, 0 or 1 depending on sign and value of ACC be fore execution. 0994 This routine checks the sign of the ACC. NTF must be set. After execution A register=00 if ACC=0, A=01 if ACC > 0 or A=FFH if A < 1. The Flags are also valid
This routine will convert a signed number (held in Register A) into an integer.
098D ↳ CONIA
LD L,A6F
Load Register L with the result of the sign test in Register A
098E
RLA17
Shift the sign bit in Register A into the Carry flag
098F
SBC A,A9F
Adjust the value in Register A so that it will be equal to zero if the current value in the ACCumulator is positive and equal to -1 if the current value in the ACCumulator is negative
0990
LD H,A67
Save the adjusted value in Register A in Register H
Jump to 0A9AH to return the result and set the VALTYP
0994H-09A3H – LEVEL II BASIC MATH ROUTINE – “VSIGN”
This routine checks the sign of the ACCumulator. NTF must be set. After execution A register=00 if ACCumulator=0, A=01 if ACC > 0 or A=FFH if A < 1. The Flags are also valid.
0994 ↳ VSIGN
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Since P means string, single precision, or double precision; and if it was a string it would have jumped already, this line says jump to 0955H if the current value in the ACCumulator is single precision or double precision, as those are processed the same way
099B-099D
LD HL,(4121H)LD HL,(FACLO)2A 21 41
At this point, we know we have an integer. Load Register Pair HL with the integer value in the ACCumulator
This routine finds the sign of the value held at (HL). Only Register A is altered.
099E ↳ ISIGN
LD A,H7C
Load Register A with the MSB (which holds the SIGN bit) of the integer value in Register H
099F
OR LB5
Check to see if the integer value in the ACCumulator is equal to zero
09A0
RET ZC8
Return if the integer value in the ACCumulator is equal to zero
09A1
LD A,H7C
If its not zero, then the sign of the number is the same as the sign of Register H so load Register A with the MSB (which holds the SIGN bit) of the integer value in Register H
09A4H-09B0H – SINGLE PRECISION MATH ROUTINE – “PUSHF”
Move ACCumulator To STACK: Moves the single precision value in the ACCumulator to the STACK. It is stored in LSB/MSB/Exponent order. Registers D and E are affected. Note, the mode flag is not tested by the move routine, it is simply assumed that ACCumulator contains a single precision value
Loads Single-precision value from ACC to STACK ((SP)=ACC). To retrieve this value, POP BC followed by POP DE. A, BC and HL are unchanged by this function.
09A4 ↳ PUSHF
EX DE,HLEB
Preserve (HL) by swapping HL and DE
09A5-09A7
LD HL,(4121H)LD HL,(FACLO)2A 21 41
Load Register Pair HL with the LSB and the NMSB of the single precision value in the ACCumulator
09A8
EX (SP),HLE3
Swap (SP) and HL so that the return address is now in HL and the NMSB and the LSB of the single precision value are now at the top of the STACK
09A9
PUSH HLE5
Save the return address in Register Pair HL on the STACK
09AA-09AC
LD HL,(4123H)LD HL,(FAC-1)2A 23 41
Load Register Pair HL with the exponent and the High Order/MSB of the single precision value in the ACCumulator
09AD
EX (SP),HLE3
Swap (SP) and HL so that the return address is now in HL and the MSB of the single precision value is now at the top of the STACK
09AE
PUSH HLE5
Save the return address in Register Pair HL on the STACK
09AF
EX DE,HLEB
Restore the original Register Pair HL from DE
09B0
RETC9
RETurn to CALLer
09B1H-09BEH – SINGLE PRECISION MATH ROUTINE – “MOVFM”
This routine moves a number from memory (pointed to by HL) into the ACCumulator — (ACCumulator=(HL)). All registers except Register A are affected, with HL = HL + 4 on exit.
Load the SINGLE PRECISION value pointed to by Register Pair HL into Register Pairs BC/DE via a CALL to 09C2H Then fall into the MOVFR routine.
This routine loads the ACC with the contents of the BC and DE Register Pairs. (ACC=BCDE). Only Registers D and E are modified.
Move SP Value In BC/DC Into ACCumulator: Moves the single precision value in BC/DE into ACCumulator. HL is destroyed BC/DE is left intact. Note – the mode flag is not updated!
09B4 ↳ MOVFR
EX DE,HLEB
Load Register Pair HL with the NMSB and the LSB of the single precision value in Register Pair DE.
09B5-09B7
LD (4121H),HLLD (FACLO),HL22 21 41
Save the NMSB and the LSB of the single precision value into the ACCumulator (at the locations pointed to by Register Pair HL)
09B8
LD H,B60
Let HL = BC (so the High Orders/MSB + Exponent) … part 1 …
09B9
LD L,C59
… part 2
09BA-09BC
LD (4123H),HLLD (FAC-1),HL22 23 41
Save the exponent and the MSB of the single precision value into the ACCumulator pointed to by Register Pair HL
09BD
EX DE,HLEB
Restore the original HL from DE
09BE
RETC9
RETurn to CALLer
09BF-09CA – SINGLE PRECISION MATH ROUTINE “LDRASA”
This routine is the opposite of the 09B4H routine. It loads four bytes from REG 1 (single-precision) into the BC and DE register pairs. (BCDE=ACC). A is unchanged.
Move FAC to registers (B,C,D,E). Alters B,C,D,E,H,L
Load register pair HL with the starting address for a single precision value in REG 1.
09C2 ↳ MOVRM
LD E,(HL)5E
Load Register E with the LSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL.
This routine will load the BCDE Register Pairs with four bytes from the location pointed to by HL. (BCDE=(HL)). With these types of data movements, the E Register is loaded with the LSB and the B register. with the MSB
09C3
INC HL23
Increment the value of the memory pointer in Register Pair HL to point to the middle order/NMSB number
09C4 ↳ GETBCD
LD D,(HL)56
Load Register D with the NMSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
09C5
INC HL23
Increment the value of the memory pointer in Register Pair HL to point to the high order/MSB number
09C6
LD C,(HL)4E
Load Register C with the MSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
09C7
INC HL23
Increment the value of the memory pointer in Register Pair HL to point to the exponent
09C8
LD B,(HL)46
Load Register B with the exponent of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
09C9 ↳ INXHRT
INC HL23
Increment the value of the memory pointer in Register Pair HL so that it points to the beginning of the next number
09CA
RETC9
RETurn to CALLer
09CBH-09D1H – SINGLE PRECISION MATH ROUTINE – “MOVMF”
This routine is the opposite of the 09B1H routine. It loads the number from the ACCumulator to the memory location pointed to by HL. ((HL)=ACC). Modifies all Registers except for Register C
Load Register Pair DE with the starting address for a single precision value in the ACCumulator. Then pass throgh to the following routine.
Data move routine. This moves four bytes from the location pointed to by DE into the location pointed to by HL. ((HL)=(DE)). Modifies all Registers except for Register C
09CE-09CF ↳ MOVE
LD B,04H06 04
Load Register B with the number of bytes to be moved for a single precision value so that B will act as a counter.
Jump to 09D7H (which is the GENERAL PURPOSE MOVE routine and moves the contents of the B Register Bytes from the address in DE to the address in HL)
09D2H-09DEH – MOVE VALUE POINTED TO BY HL TO THE LOCATION POINTED TO BY DE – “MOVVFM”
This is the VARIABLE MOVE routine which moves the number of bytes specified in the variable type flag (40AFH) from the address in DE to the address in HL. Uses A, B, DE and HL.
Data move routine. The location pointed to by DE is loaded with bytes from the location pointed to by HL. The number of bytes moved is determined by the value in the NTF. ((DE)=(HL))
09D2 ↳ MOVVFM
EX DE,HLEB
Exchange the value in Register Pair HL with the value in Register Pair DE, and then fall through to the VMOVE routine.
This routine is similar to 9D2H above. The only difference is that it moves data in the opposite direction. ((HL) = (DE))
09D3-09D5 ↳ VMOVE
LD A,(40AFH)LD A,(VALTYP)3A AF 40
Load Register A with the current value of the number type flag (which is in 40AFH). This, not coincidentally, is also the length of the number being worked on!
09D6
LD B,A47
Load Register B with the number of bytes to be moved in Register A.
This routine is the same as 9D6H except that the number of bytes shifted is determined by the value in the B Register ((HL)=(DE))
Moves contents of B-register bytes from the address in DE to the address given in HL. Uses all registers except C
09D7 ↳ MOVE1
LD A,(DE)1A
Top of a loop to move (DE)’s content into (HL). First, load Register A with the value at the location of the memory pointer in Register Pair DE.
This routine is the same as 9D6H except that the number of bytes shifted is determined by the value in the B Register ((HL)=(DE)). This is the GENERAL PURPOSE MOVE routine and moves the contents of the B Register Bytes from the address in DE to the address in HL)
09D8
LD (HL),A77
and then Save the value in Register A at the location of the memory pointer in Register Pair HL
09D9
INC DE13
Increment the value of the memory pointer in Register Pair DE
09DA
INC HL23
Increment the value of the memory pointer in Register Pair HL
09DB
DEC B05
Decrement the value of the byte counter in Register B
09DFH-09F3H – SINGLE PRECISION MATH ROUTINE – “UNPACK”
This routine “UNPACKS” the ACCumulator and the Registers. Registers A, C, H, and L are altered.
When the number in the ACCumulator is unpacked, the assumed one in the mantissa is restored, and the complement of the sign is placed in ACCumulator+1.
Load Register Pair HL with the address of the MSB (including the SIGN) of the value in the ACCumulator
09E2
LD A,(HL)7E
Load Register A with the MSB (and SIGN) of the value in the ACCumulator at the location of the memory pointer in Register Pair HL
09E3
RLCA07
Duplicate the sign into the CARRY and the LSB
09E4
SCF37
Set the Carry flag to restore the hidden “1” for the mantissa
09E5
RRA1F
Turn off the sign bit in Register A by moving the value of the Carry flag into Register A and moving the previous value of the sign bit from bit 0 of Register A into the Carry flag. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
09E6
LD (HL),A77
Save the adjusted High Order/MSB+Sign in Register A in the ACCumulator at the location of the memory pointer in Register Pair HL
09E7
CCF3F
Invert the value of the sign bit in the Carry flag
09E8
RRA1F
Move the inverted sign bit from the Carry flag into Register A. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
09E9
INC HL INC HL23
Increment the value of the memory pointer in Register Pair HL twice to now point to the temporary sign byte
09EB
LD (HL),A77
Save the complemented sign (in Register A) to the location of the memory pointer in Register Pair HL
09EC
LD A,C79
Load Register A with the MSB+SIGN of the single precision value in Register C
09ED
RLCA07
Duplicate the sign in both the CARRY FLAG and the LSB
09EE
SCF37
Set the Carry flag to restore the hidden “1” for the mantissa
09EF
RRA1F
Restore the High Order (MSB+Sign) in A. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
09F0
LD C,A4F
Load Register C with the adjusted High Order (MSB+Sign) in Register A
09F1
RRA1F
Move the value of the sign bit from the Carry flag into Register A. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
09F2
XOR (HL)AE
Combine the value of the sign bit of the ACCUMULATOR and the SIGN BIT of the Registers
09F3
RETC9
RETurn to CALLer
09F4H-09FBH – LEVEL II BASIC MATH ROUTINE – “VMOVFA”
This routine moves a number of bytes (the number depending on the value stored in the VALTYPE) from (HL) to the ACCumulator. All Registers except C are affected.
09FCH-0A0BH – LEVEL II BASIC MATH ROUTINE – “VMOVAF”
This is the opposite of 9F4H. This routine moves a number of bytes (the number depending on the value stored in the VALTYPE) from the ACCumulator to (HL). All Registers except C are affected.
Entered here from INT, STR, and SNG. In that case, we must load Register Pair DE with the starting address for a single precision value in the ACCumulator
0A06
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0A07
RET CD8
If that test is anything other than double precision, return out of this subroutine to the address which was fed in
If we are here, then we have a double precision number, so set Register Pair DE to point to the the LSB of a double precision number.
0A0B
RETC9
RETurn to the place we set up to return to
0A0CH-0A25H – SINGLE PRECISION COMPARE – “FCOMP”
According to the original ROM source code, this routine will compare two single precision numbers. On Exit, A=1 if ARG < ACCumulator, A=0 if ARG=Accmulator, and A=-1 if ARG > ACCumulator. This routine exits with the CARRY FLAG on. Alters Registers A, H, and L.
Single-precision compare. Compares ACCumulator with the contents of BCDE registers. After execution of this routine, the A Register will contain: A=0 if ACCumulator=BCDE, A=1 if ACC>BCDE or A=FFH if ACC<BCDE.
Single Precision Comparison: Algebraically compares the single precision value in (BC/DE) to the single precision value ACCumulator. The result of the comparison is returned in the A and status as: IF (BC/DE) > ACCumulator A = -1, IF (BC/DE) < ACCumulator A = +1, IF (BC/DE) = ACCumulator A = 0
NOTE: To use a ROM call to compare two single precision numbers, store the first input in registers BCDE, the second input in 4121H-4124H and then CALL 0A0CH. If the numbers are equal, the Z (zero) flag will be set. If they are not equal, the Z flag will be turned off. If the first input number is the smaller, the S (sign) and C (carry) flags will also be turned off. If the second input number is the smaller, the S and C flags will both be set.
0A0C ↳ FCOMP
LD A,B78
First we need to check to see if ARG is zero, so load Register A with the value of the exponent in Register B
Check to see if the ACCumulator is zero via a GOSUB to SIGN
0A18
LD A,C79
If the ACCumulator is ZERO, then the result is simply the NEGative of ARG, so, to prepare for that, load Register A with the MSB of the single precision value in Register C
0A19
RET ZC8
If the ACCumulator was zero, RETurn with Register A holding Register C
Now that we have resolved the signs, JUMP to FCOMP2 to check the rest of the numbers
0A23 ↳ FCOMPD
RRA1F
If are are here, then the numbers are different, so the next step is to change the signs if both numbers are negative. To do this, first move the value of the Carry flag from the comparison into Register A. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0A24
XOR CA9
Combine the value of the MSB/SIGN of the single precision value in Register C with the value in Register A
0A25
RETC9
With Register A now set, RETurn to CALLer
0A26H-0A38H – Part of the SINGLE PRECISION COMPARISON ROUTINE – “FCOMP2”
0A26
INC HL23
Increment the value of the memory pointer in Register Pair HL so that it points to the exponent of the single precision number in the ACCumulator
0A27
LD A,B78
Load Register A with the value of the exponent for the single precision value held in ARG (stored in Register B)
0A28
CP (HL)BE
Check to see if the exponent for the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL is the same as the value of the exponent for the single precision value in Register A
0A29
RET NZC0
If the value of the exponent for the single precision number in the ACCumulator isn’t the same as the value of the exponent for the single precision number in ARG (held in Register A), RETurn
0A2A
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL so it now will point to the HIGH ORDER/MSB of the single precision number in the ACCumulator
0A2B
LD A,C79
Load Register A with the HIGH ORDER/MSB of the single precision number in ARG (stored in Register C)
0A2C
CP (HL)BE
Check to see if the HIGH ORDER/MSB for the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL is the same as the value of the MSB for the single precision value in Register A
0A2D
RET NZC0
If the value of the HIGH ORDER/MSB for the single precision number in the ACCumulator isn’t the same as the value of the HIGH ORDER/MSB for the single precision number in ARG (held in Register A), RETurn
0A2E
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL so it now will point to the MIDDLE ORDER/NMSB of the single precision number in the ACCumulator
0A2F
LD A,D7A
Load Register A with the MIDDLE ORDER/NMSB of the single precision number in ARG (stored in Register D)
0A30
CP (HL)BE
Check to see if the MIDDLE ORDER/NMSB for the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL is the same as the value of the MIDDLE ORDER/NMSB for the single precision value in Register A
0A31
RET NZC0
If the value of the MIDDLE ORDER/NMSB for the single precision number in the ACCumulator isn’t the same as the value of the MIDDLE ORDER/NMSB for the single precision number in ARG (held in Register A), RETurn
0A32
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL so it now will point to the LOW ORDER/LSB of the single precision number in the ACCumulator
0A33
LD A,E7B
Load Register A with the LOW ORDER/LSB of the single precision number in ARG (stored in Register E)
0A34
SUB (HL)96
Use subtraction to check to see if the LOW ORDER/LSB for the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL is the same as the value of the LOW ORDER/LSB for the single precision value in Register A. We use subtraction so that if the numbers are the same then Register A will be filled with the 0 response.
0A35
RET NZC0
Return if the value of the LSB in the ACCumulator isn’t the same as the value of the LSB in Register A
0A36
POP HLE1
If we are here then the numbers are the same so we need to get the extra data off of the STACK
According to the original ROM source code, this routine will compare two integers. On Exit, A=1 if (DE) < (HL), A=0 if (DE)=(HL), and A=-1 if (DE) > (HL). Alters only Register A.
Integer compare. Compares HL with DE. After execution, A=0 if HL=DE, A=1 if HL>DE or A=FFH if HL<DE. The S and Z flags are valid.
NOTE: To use a ROM call to compare two integers, store the first input in DE, the second in HL and then CALL 0A39H. If the numbers are equal, the Z (zero) flag will be set. If they are not equal, the Z flag will be turned off. If the first input number is the smaller, the S (sign) and C (carry) flags will also be turned off. If the second input number is the smaller, the S and C flags will both be set.
Compares HL with DE. After execution, A=0 if HL=DE, A=1 if HL>DE or A=FFH if HL<DE. The S and Z flags are valid
Algebraically compares two integer values in DE and HL. The contents of DE and HL are left intact. The result of the comparison is left in the A Register and status register: If DE > HL A = -1, IF DE < HL A = +1, IF DE = HL A = 0
0A39 ↳ ICOMP
LD A,D7A
First we test the signs, so load Register A with the SIGN of the integer value in Register D
0A3A
XOR HAC
Check to see if the sign bit for the MSB of the integer value in Register H is the same as the sign bit for the SIGN for the integer value in Register A
0A3B
LD A,H7C
If the signs are NOT the same, then the result is the sign of (HL), so put the SIGN of the number in (HL) into the response register of Register A.
If the sign bits are NOT the same, JUMP to ICOMPS to check the numbers
0A3F
CP DBA
If we are here, then the signs are the same, so now check to see if the HIGH ORDER/MSB for the integer value in Register D is the same as the HIGH ORDER/MSB for the integer value in Register A
if the HIGH ORDER/MSB for the integer value in Register D isn’t the same as the HIGH ORDER/MSB for the integer value in Register A, JUMP to SIGNS to set up the appropriate response in Register A
0A43
LD A,L
Load register A with the LSB of the integer value in register L.
0A44
SUB E93
Use subtraction to check to see if the LOW ORDER/LSB for the integer value in Register E is the same as the LOW ORDER/LSB for the integer value in Register A
If the LSB for the integer value in Register E isn’t the same as the LSB for the integer value in Register A, JUMP to SIGNS to set up the appropriate response in Register A
0A48
RETC9
If we are here, then two things. First, they are the same. Second, A is zero. So RETurn to CALLer
Load Register Pair HL with the starting address of ARG (a/k/a REG 2). If entering here, then (DE) already needs to be set with the pointer to ARG. Note: 4127H-412EH holds ARG (a/k/a REG 2)
If the double precision value in ARG (a/k/a REG 2) is equal to zero, then we are done, so JUMP to SIGN to set up Register A with the appropriate response.
Load Register Pair HL with the address of the SIGN of the double precision value in the ACCumulator
0A65
XOR (HL)AE
Check to see if the sign bit the double precision value in the ACCumulator at the location of the memory pointer in Register Pair HL is the same as the sign bit of the double precision value in ARG (a/k/a REG 2) in Register A
0A66
LD A,C79
In case they are the same, get the sign from C into Register A.
0A67
RET MF8
If they are NOT the same, RETurn to FCOMPS to set set Register A
0A68
INC DE13
Increment the value of the memory pointer in Register Pair DE so that DE now points to the exponent of ARG
0A69
INC HL23
Increment the value of the memory pointer in Register Pair HL so that HL now points to the exponent of the ACCumulator
0A6A-OA6B
LD B,08H06 08
Load Register B with the number of bytes to be compared, as B will act as a counter
0A6C ↳ DCOMP1
LD A,(DE)1A
Load Register A with a byte from the double precision number in ARG (pointed to by Register Pair DE)
0A6D
SUB (HL)96
Use subtraction to compare that byte from ARG with the correspondible byte from the ACCumulator (pointed to by Register Pair HL)
The DEC of B will set the flags. If B is NOT ZERO, then Loop back to DCOMP1 until all of the bytes have been compared
0A76
POP BCC1
If we are here, then the numbers are the same, so we need to clean the RETurn to FCOMPS off the stack, as that is not where we want to RETurn to
0A77
RETC9
RETurn to the actual CALLer
0A78H-0A7EH – DOUBLE PRECISION COMPARE – “DCOMP”
According to the original ROM source code, this routine will compare two double precision numbers, but is the opposite of the ICOMP, FCOMP, and XDCOMP routines. This one swaps ARC and ACC, so on Exit, A=1 if ARG > ACCumulator, A=0 if ARG=Accmulator, and A=-1 if ARG < ACCumulator. Every register is affected.
Double-precision compare. This compare is the opposite of the A4FH compare. It compares the ARG (a/k/a REG 2) with the ACC. (Remember that a compare is actually a subtraction that is never executed therefore a compare can be done in two ways with the same values. (A-B and B-A)). The results are the same as the A4FH routine.
Double Precision Compare: Compares the double precision value in the ACCumulator to the value in ARG (a/k/a REG 2). Both Register areas are left intact. The result of the comparison is left in the A and status registers as: IF ACCumulator > ARG (a/k/a REG 2) A = -1, IF ACCumulator < ARG (a/k/a REG 2) A = +1, IF ACCumulator = ARG (a/k/a REG 2) A = 0
NOTE: To use a ROM call to compare two double precision number, store the first input in 411DH-4124H, and store the second input in 4127H-412EH and then CALL 0A78H. If the numbers are equal, the Z (zero) flag will be set. If they are not equal, the Z flag will be turned off. If the first input number is the smaller, the S (sign) and C (carry) flags will also be turned off. If the second input number is the smaller, the S and C flags will both be set.
If the double precision value in the ACCumulator and the double precision value in ARG (a/k/a REG 2) aren’t the same then JUMP to FCOMPS to negate the answer and set up the CARRY FLAG for the DOCMP routine
0A7E
RETC9
RETurn to CALLer
0A7FH-0AB0H – LEVEL II BASIC CINT ROUTINE – “FRCINT”
CINT routine. Takes a value from ACC, converts it to an integer value and puts it back into the ACC. On completion, the HL Register Pair contains the LSB of the integer value, and the NTF contains 2 (Integer=2). If NTF=3 (string) a TM ERROR will be generated and control will be passed to BASIC. Every register is affected. No rounding is performed
NOTE: To use a ROM call to call the CINT routine, store the single precision input variable in 4121H-4124H and then call to 0A8AH and bypass all the foregoing. After the call, the integer result would be in 4121H-4122H and in the HL Register Pair. Too big a number will generate a ?OV Error.
0A7F ↳ FRCINT
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0A80-0A82
LD HL,(4121H)LD HL,(FACLO)2A 21 41
Just in acse we already have an integer, Load Register Pair HL with the integer value in the ACCumulator (which is stored at FACLO and FACLO+1)
0A83
RET MF8
If that test showed we have an INTEGER, then return out of this subroutine
If the exponent for the single precision value in the ACCumulator in Register A indicates more than 16 bits of precision, JUMP to CONIS2 to make sure that the reason it is “too big” isn’t because it is -32768
If we are here then the number isn’t too big, so GOSUB to QINT to convert the single precision value in the ACCumulator to an integer and return with the integer value in Register Pair DE
0A98
EX DE,HLEB
Load Register Pair HL with the integer value that was put into Register Pair DE by QINT
0A99 ↳ CONIS1
POP DED1
Get the error address from the STACK and put it in Register Pair DE
0A9AH – LEVEL II BASIC CONVERSION ROUTINE – “MAKINT”
This is the routine that returns the value in the HL Register Pair to the BASIC program that called it. In effect it moves the content of HL into the ACCumulator so it is ACCumulator = (HL) with VALTYPE set accordingly
0A9A-0A9C ↳ MAKINT
LD (4121H),HLLD (FACLO),HL22 21 41
Save the integer value in Register Pair HL as the current value in the ACCumulator.
0A9D-0A9E ↳ VALINT
LD A,02H3E 02
Load Register A with an integer number type flag.
0A9F-0AA1 ↳ CONISD
LD (40AFH),ALD (VALTYP),A32 AF 40
Save the integer number type flag in Register A as the current value of the number type flag. Note: 40AFH holds Current number type flag. This is the entry point from the CONDS routine
0AA2
RETC9
RETurn to CALLer
0AA3H – LEVEL II BASIC CONVERSION ROUTINE – “CONIS2”
This routine’s purpose is to check to see if a number from the FIN routine is -32768. First, load up the register paird BCDE with 9080H/0000H for purposes of using FCOMP to test
Load Register Pair DE with the NMSB and the LSB of a single precision value. Register Pairs BC and DE now hold a single precision value equal to -32768
Call the SINGLE PRECISION COMPARISON routine at 0A0CH.
NOTE: The routine at 0A0CH algebraically compares the single precision value in BC/DE to the single precision value ACCumulator. The results are stored in A as follows:
A=0 if ACCumulator = BCDE
A=1 if ACCumulator>BCDE; and
A=FFH if ACCumulator<BCDE.
0AAC
RET NZC0
If FCOMP returns a NZ, then there was an error and the number could NOT be converted into an integer. In this case, display an ?OV ERROR
0AAD
LD H,C61
If we are here, then the value is -32768, so we need to put that into (HL). First, load Register H with the MSB of the single precision value in Register C
0AAE
LD L,D6A
Load Register L with the NMSB of the single precision value in Register D
Jump to 0A99H to store (HL) into the ACCumulator and set the VALTYPE accordingly.
0AB1H-0ACBH – LEVEL II BASIC CSNG ROUTINE – “FRCSNG”
Force the number in the ACCumulator to be a single-precision number. Every register is affected.
CSNG routine. Takes value from ACC and converts it to single-precision. The result is put in ACC and NTF contains 4.
CSNG routine. Takes value from ACC and converts it to single-precision. The result is put in ACC and NTF contains 4
Integer To Single: The contents of ACCumulator are converted from integer or double precision to single precision. All registers are used
0AB1 ↳ FRCSNG
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0AB2
RET POE0
IF PO is set, then we have SINGLE PRECISION number already, so nothing to do! RETurn out of this subroutine
Move the HIGH ORDER/MSB’s into the registers via a call to MOVRF which loads the SINGLE PRECISION value in the ACCumulator (which is currently the most significant four bytes of the double precision value in the ACCumulator) into Register Pair BC/DE
We now know the number isn’t zero, so we need to unpack the number via a CALL to UNPACK which will turn on the most significant bit of the single precision value in the ACCumulator
Jump to 0796H to round the chopped number up and RETurn
0ACCH-0ADAH -LEVEL II BASIC NUMBER CONVERSION ROUTINE – “CONSI”
Convert Integer to Single Precision. Every register is affected.
Note: If you wanted to convert integer to single precision via a ROM call, you would store the integer input variable in 4121H-4122H and then call to 0ACCH. The result (as a single precision number) will be in 4121H-4124H.
0ACC-0ACE ↳ CONSI
LD HL,(4121H)LD HL,(FACLO)2A 21 41
Load Register Pair HL with the integer value from the ACCumulator
Jump to 0969H to float the integer into single precision
0ADBH-0AEDH – LEVEL II BASIC CDBL ROUTINE – “FRCDBL”
CDBL routine. Takes a value from ACCumulator (regardless of integer or single precision) and convert it to double-precision. The result will be in ACC and NTF will be 8.
0ADB ↳ FRCDBL
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0ADC
RET NCD0
If that test shows we have already a DOUBLE PRECISION number, then we are done, so RETurn out of the subroutine
If that test shows we have an INTEGER, then go to 0ACCH to convert that integer to SINGLE PRECISION and then fall into the CONDS routine to convert a single precision number into double precision.
0AE3H – LEVEL II BASIC CDBL ROUTINE – “CONDS”
Convert a single precision number to double precisions. Modifies Registers A, H, and L.
However we got here, Register A now holds the desired VALTYPE, so jump away to 0A9FH to save the value in Register A as the VALTYPE and RETurn
0AF4H-0AFAH – LEVEL II BASIC MATH ROUTINE – “CHKSTR”
This routine will force the ACCUmlator to be a STRING. Only Register A is modified.
0AF4 ↳ CHKSTR
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0AF5
RET ZC8
If that test shows we already have a STRING then we are done, so RETturn out of the subroutine. Otherwise, fall into the ?TM ERROR routine, placed here to save bytes and avoid a JUMP.
Display a TM ERROR message if the current value in the ACCumulator isn’t a string
0AFBH-0B1EH – LEVEL II BASIC MATH ROUTINE – “QINT”
This routine is a quick “Greatest Integer” function. Registers A-E are affected.
The result of INT(ACCumulator) is left in C/D/E as a signed number
This routine assumes that the number in the ACCumulator is less than 8,388,608 (i.e., 2^23) and that the exponent of the ACCumulator is held in Register A on entry.
This routine can also be used to reset the BC and DE Register Pairs if the A Register contains 0. (XOR A before calling this routine).
0AFB ↳ QINT
LD B,A47
Load Register B with the exponent of the single precision number in Register A. If a XOR A was executed before calling this routine, then the following will zero all of the registers.
0AFC
LD C,A4F
Load Register C with the exponent of the single precision number in Register A
0AFD
LD D,A57
Load Register D with the exponent of the single precision number in Register A
0AFE
LD E,A5F
Load Register E with the exponent of the single precision number in Register A
0AFF
OR AB7
Check to see if the single precision number in the ACCumulator is equal to zero
0B00
RET ZC8
If Register A was 0 on entry (meaning that the exponent of the number is 0), then RETurn the same way but with C/D/E = 0, as any number whose exponent is 0 is 0.
The original ROM source code has this to say about the next set of instructions:
The hard case in QINT is negative non-integers. To handle this, if the number is negative, we regard the 3-byte mantissa as a 3-byte integer and subtract one. Then all the fractional bits are shifted out by shifting the mantissa right. Then, if the number was negative, we add one.
So, if we had a negative integer, all the bits to the right of the binary point were zero and the net effect is we have the original number in C/D/E.
If the number was a negative non-integer, there is at least one non-zero bit to the right of the binary point and the net effect is that we get the absolute value of int(fac) in C/D/E. C/D/E is then negated if the original number was negative so the result will be signed.
If the number was negative, we need to substract 1 from the LOW ORDER/LSB and to do that we GOSUB to QINTA
0B0D-0B0E
LD A,98H3E 98
Next we need to see how many number of bits we need to shift to change the number to an integer, so start that calculation by loading Register A with the maximum exponent
0B0F
SUB B90
and then subtract the exponent in Register B from the exponent in Register A
If the original number was negative, we need to negate the number because we need a signed mantissa
0B1D
POP HLE1
Restore HL from the STACK where it was saved at the top of this routine
0B1E
RETC9
RETurn to CALLer
0B1FH-0B25H – LEVEL II BASIC MATH ROUTINE – “QINTA”
0B1F ↳ QINTA
DEC DE1B
Decrement C/D/E by 1
0B20
LD A,D7A
Now we need to see if we need to carry that further and subtract one from C, so load Register A with the value of the NMSB for the single precision value which is held in Register D
0B21
AND EA3
Combine the LSB of the single precision value in Register E with the NMSB of the single precision value in Register A
0B22
INC A3C
Increment the combined value in Register A
0B23
RET NZC0
If both D and E were -1 (i.e., DE was FFFFH) then RETurn
0B24 ↳ DCXBRT
DEC BC0B
Decrement the value of the exponent and the MSB of the single precision value in Register Pair BC. A note in the original ROM source said that this was put in specifically at the request of Bill Gates and that Register C would never be ZERO, so DEC BC and DEC C would be functionally equivalent.
0B25
RETC9
RETurn to CALLer
0B26H-0B58H – LEVEL II BASIC FIX ROUTINE – “FIX”
This is the FIX(n) routine. It returns SGN(n)*INT(ABS(n))
Takes a value from ACC and converts it to an integer value. The result will be in ACC. NTF will be 2 if value is smaller than 32767 else it will be 4. An error will be generated if NTF=3 (string). A call to 0B26H unconditionally truncates the fractional part of a floating point number in the ACCumulator. The result is stored in the ACCumulator and the type flag is set to integer.
Note: If you wanted to call the FIX routine via a ROM call, you would store the single-precision input variable in 4121H-4124H, then put a 4 into 40AFH to flag as single precision, and then call to 0B26H. If the result can be an integer, it will be in 4121H-4122H and in the HL Register Pair. If single precision, the result will be in 4121H-4124H. If double precision, in 411DH-4124H. In all cases 40AFH will have the data mode flag as 2, 4, or 8, accordingly.
FIX routine. Takes a value from ACC and converts it to an integer value. The result will be in ACC. NTF will be 2 if value is smaller than 32767 else it will be 4. An error will be generated if NTF=3 (string)
Floating To Integer: Unconditionally truncates the fractional part of a floating point number in the ACCumulator. The result is stored in the ACCumulator and the type flag is set to integer
0B26 ↳ FIX
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0B27
RET MF8
If that test shows we have an INTEGER then we are all done, so RETurn to the caller
If the current value in the ACCumulator is positive, then we only need to do a regular INT(n), so JUMP to 0B37H (which returns the integer portion of a floating point number. If the value is positive, the integer portion is returned. If the value is negative with a fractional part, it is rounded up before truncation. The integer portion is left in the ACCumulator. The mode flag is updated.)
If we are here then the number was negative, and we need it to be positive, so GOSUB the NEG routine to convert the current value in the ACCumulator to positive
Now that ACCumulator is positive, GOSUB to do a regular INT(n), so JUMP to 0B37H (which returns the integer portion of a floating point number. If the value is positive, the integer portion is returned. If the value is negative with a fractional part, it is rounded up before truncation. The integer portion is left in the ACCumulator. The mode flag is updated.)
Since it was negative, we now need to make it negative again so JUMP to 097BH to re-NEGate the number and RETurn to the caller of this routine
0B37H – LEVEL II BASIC INT( ROUTINE – “VINT”
Return Integer: Returns the integer portion of a floating point number. Every flag is affected. If the value is positive, the integer portion is returned. If the value is negative with a fractional part, it is rounded up before truncation. The integer portion is left in the ACCumulator
Note: If you wanted to call the INT routine via a ROM call, you would store the single precision input variable in 4121H-4124H, put a 4 into 40AFH (to flag as single precision), and then call 0B3DH and bypass all the foregoing. After the call, the integer result would be in 4121H-4122H and in the HL Register Pair IF the absolute value of the input did not exceed 32767. Otherwise it will be in 4121H-4124H in single precision format, and 40AF will be a 2 for integer or 4 for single precision
According to Vernon Hester, there is are a number of bugs in this routine.
First, INT(value) should produce a result equal to or less than value. However, if the value is double-precision (by definition), the ROM rounds value to single-precision first, then performs the INT function. e.g., PRINT INT(2.9999999) produces 3 instead of 2.
Next, INT(value) should never overflow. However, if the value is double-precision 32767.9999#, the ROM overflows.
Next, INT(value) should produce a result equal to or less than value. However, if the value is double-precision equal to ?2″n+2″(n-7) where n is an integer >14, the ROM produces an incorrect value. e.g., PRINT INT(?44800#) produces ?45056
0B37 ↳ VINT
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0B38
RET MF8
If that test shows we have an INTEGER then we are done, so RETurn to CALLer.
Now we try to use the CONIS routine to convert the single precision value in the ACCumulator to an integer. If we can’t we will return here to give a single precision result instead.
Load Register Pair HL with the address of the exponent in the ACCumulator
0B5C
LD A,(HL)7E
Load Register A with the value of the exponent for the double precision number in the ACCumulator (stored at the location of the memory pointer in Register Pair HL)
0B5D-0B5E
CP 90HFE 90
Check to see if the double precision number in the ACCumulator uses more or less than 16 bits of precision
If the double precision value in the ACCumulator uses less than 16 bits of precision, then we can use the FRCINT routine to do it, so JUMP to the CONVERT TO INTEGER routine at 0A7F (where the contents of ACCumulator are converted from single or double precision to integer and stored in HL)
If the NZ flag was set, we still need to make sure we didn’t have the special case number of -32768, so JUMP to DINT2
0B64
LD C,A4F
If we’re here then we have to do it the hard way. First, load Register C with the exponent for the double precision number in Register A
0B65
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to point to the HIGH ORDER (MSB+SIGN) portion of the double precision number
0B66
LD A,(HL)7E
Load Register A with the HIGH ORDER (MSB+SIGN) of the double precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
0B67-0B68
XOR 80HXOR 1000 0000EE 80
Complement the value of the sign bit in Register A (which is 1000 0000)
0B69-0B6A
LD B,06H06 06
Next we need to check to see if the rest of the number is ZERO, so load Register B with the number of bytes to be checked
0B6B ↳ DINT1
DEC HL2B
Top of a loop. Decrement the value of the memory pointer in Register Pair HL to point to the next byte of the number
0B6C
OR (HL)B6
Combine the value at the location of the memory pointer in Register HL with the value in Register A. If any of the bits are non-zero, then A will then be non-zero
If the sign is negative, GOSUB to DROUNA to add 1 to the value in the ACCumulator
0B97
XOR AAF
Zero Register A so we can put a zero into the extra LOW ORDER byte, so that when we normalize it, we shift in zeroes
0B98-0B9A
LD (411CH),ALD (DFACLO-1),A32 1C 41
Put a ZERO into the starting address of ACCumulator minus one
0B9B
POP AFF1
Get the value of the original exponent test from the STACK and put it in Register Pair AF. This also will restore the CARRY FLAG if we entered here FOUT to indicate that we should NOT re-float the number
0B9C
RET NCD0
IF called from FOUT, then RETurn to skip re-floating the number.
Loop until the value at the location of the memory pointer in Register Pair HL is equal to a nonzero value
0BA9
RETC9
RETurn to CALLer
0BAAH-0BC6H – LEVEL II BASIC MATH ROUTINE – “UMULT”
This is the integer multiply routine for multiplying dimensioned array. It will calculate DE = BC * DE. If there is an overflow, a ?BS ERROR will get thrown. Every register except HL is affected.
Swap so that the return result is in DE. We don’t care about HL because …
0BC5
POP HLE1
… restore the original HL from the STACK
0BC6
RETC9
RETurn to CALLer
The next bunch of routines are the integer arithmetic routines. According to the original ROM source code, the conventions are.
Integer variables are 2 byte signed numbers, with the LSB coming first
For one argument functions, the argument is in (HL) and the results are put into (HL)
For two argument operations, the first argument is in (DE), the second in (HL), and the restuls are left in the ACCumulator and, if there was no overflow, (HL). If there was an overflow, then the arguments are converted to single precision.
When integers are stored in the ACCumulator, they are stored at FACLO+0 and FACLO+1, with VALTYPE=2
0BC7H-0BD1H – INTEGER SUBTRACTION – “ISUB”
Integer subtract. (ACCumulator=DE-HL) The result is returned in both ACCumulator and, if there was no overflow, the HL Register Pair. Subtracts the value in DE from the value in HL. The difference is left in the HL Register Pair. DE is preserved. In the event of underflow, both values are converted to single precision and the subtraction is repeated. The result is left in the ACCumulator and the mode flag is updated accordingly.
Note: If you wanted to subtract 2 integers via a ROM call, store one into DE and the subtrahend in HL (i.e., to do 26-17, DE gets 26), and then call 0BC7H. The integer result will be stored in 4121H-4122H approximately 210 microseconds later, and 40AFH will be set to 2 (to flag it as an integer). If there is an overflow, it will be converted to single precision (with 40AFH being a 4 in that case) and will be stored in 4121H-4124H.
Every register is affected.
Integer Subtraction: Subtracts the value in DE from the value in HL. The difference is left in the HL Register Pair. DE is preserved. In the event of underflow, both values are converted to single precision and the subtraction is repeated. The result is left in the ACCumulator and the mode flag is updated accordingly
0BC7 ↳ ISUB
LD A,H7C
The first thing we need to do is to extend the sign of (HL) into Register B. That’s the next 4 instructions. First, load Register A with the MSB+SIGN of the integer value in Register H
0BC8
RLA17
Rotate the value of the sign bit into the CARRY FLAG
0BC9
SBC A,A9F
Adjust Register A according to the value of the sign bit
Integer addition (ACCumulator=DE+HL), where ACCumulator = 4121H-4122H. After execution NTF=2, or 4 if overflow has occurred, in which case the result in the ACCumulator will be single-precision. The result is returned in both ACCumulator and the HL Register Pair.
Adds the integer value in DE to the integer in HL. The sum is left in HL and the orginal contents of DE are preserved. If overflow occurs (sum exceeds 2**15), both values are converted to single precision and then added. The result would be left in the ACCumulator and the mode flag would be updated.
Every register is affected.
Note: If you wanted to add 2 integers via a ROM call, store one input into DE and the other into HL, and then call 0BD2H. The result will be in 4121H-4122H and in HL, with a 2 in 40AFH, and will take about 130 microseconds. If there is an overflow, the result will be converted to Single Precision and put into 4121H-4124H (with a 4 in 40AFH).
0BD2 ↳ IADD
LD A,H7C
The first thing we need to do is to extend the sign of (HL) into Register B. That’s the next 4 instructions. First, load Register A with the MSB+SIGN of the integer value in Register H
0BD3
RLA17
Rotate the value of the sign bit into the CARRY FLAG
0BD4
SBC A,A9F
Adjust Register A according to the value of the sign bit
0BD5 ↳ IADDS
LD B,A47
Load Register B with the result of the sign test
0BD6
PUSH HLE5
Save the second argument (held in Register Pair HL) to the the STACK in case we have an overflow
0BD7
LD A,D7A
The next 4 instructions extend the sign of (DE) into Register A. First, load Register A with the MSB+SIGN of the integer value in Register Pair DE
0BD8
RLA17
Rotate the value of the sign bit into the CARRY FLAG
0BD9
SBC A,A9F
Adjust Register A according to the value of the sign bit
0BDA
ADD HL,DE19
Add the two LSBs, result in Register Pair HL
0BDB
ADC A,B88
Add the extra HIGH ORDER (held in Register B) to the value of the sign test for the integer value in Register Pair DE in Register A
0BDC
RRCA0F
The next 2 instructions are to see if the LSB of A is different from the MSB of H, in which ase an overflow occurred. So, put the value of the Carry flag in Register A
0BDD
XOR HAC
Combine the value of the sign bit for the result in Register H with the value in Register A
If the P FLAG is set, then we had no overflow. In this case, we need to restore the original (HL) from the stack and we are done. So JUMP to CONIS1 to do all that AND put (HL) into the ACCumulator as well.
0BE1
PUSH BCC5
If we are here then we have an overflow. First, save the extended sign of (HL) (held in Register B) to the STACK
0BE2
EX DE,HLEB
Load Register Pair HL with the integer value in Register Pair DE
At this point the basic values are good enough to be added via single precision, so JUMP to FADDT to do that
0BF2H-0C1EH – INTEGER MULTIPLICATION – “IMULT”
Integer multiply. (ACCumulator (and HL) =DE*HL). Multiplies HL by DE. The product is left in HL and DE is preserved. If overflow occurs, both values are converted to single precision and the operation is restarted. The product would be left in the ACCumulator.
Note: If you wanted to multiply two integers, store one input in DE, the other in HL CALL 0BF2H. The result is in 4121H-4122H and in HL, with a 2 in 40AFH (but in an overflow the result is converted to single precision format and stored in 4121H-4124H, with a 4 in 40AFH. Process takes approximately 900 microseconds.
0BF2 ↳ IMULT
LD A,H7C
Load Register A with the MSB of the integer value in Register H
0BF3
OR LB5
Combine the LSB of the integer value in Register L with the MSB of the integer value in Register A
The next 6 instruction are to roate the first argument left one to see if we need to add BC to it or not. If the NC FLAG is set, then we don’t add in BC. Otherwise we do.
0C07
EX DE,HLEB
Exchange the integer value in Register Pair DE with the integer result in Register Pair HL
0C08
ADD HL,HL29
Multiply the integer value in Register Pair HL by two
0C09
EX DE,HLEB
Exchange the integer result in Register Pair DE with the integer value in Register Pair HL
Save the floated FIRST agument via a GOSUB to 09A4 which moves the SINGLE PRECISION value in the ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
Multiply the arguments via regular old FMULT – the SINGLE PRECISION MULTIPLY routine at 0847H (which multiplies the current value in the ACCumulator by the value in (BC/DE). The product is left in the ACCumulator
0C37H-0C44H – LEVEL II BASIC MATH ROUTINE – “IMULT4”
0C37 ↳ IMULT4
LD A,B78
We need to see if the result is +/- 32768. First, load Register A with the result of the sign test in Register B
0C38
OR AB7
Check the result
0C39
POP BCC1
Discard the original SECOND argument from the STACK
If the P FLAG is set, then the integer value in Register Pair HL is positive and we don’t need to NEGate it. So we JUMP to MAKINT to save the result into the ACCumulator for when the operators come back tt this routine.
Negate HL routine. This routine changes the sign of the HL Register Pair and stores it in the ACC. (HL=ACCumulator=-HL) The result is returned in both the HL Register Pair and the ACC.
0C51 ↳ INEGHL
XOR AAF
Zero Register A.
0C52
LD C,A4F
Load Register C with the ZERO held in Register A
0C53
SUB L95
Subtract the LSB of the integer value in Register L from the ZERO in Register A
0C54
LD L,A6F
Save the adjusted value in Register A in Register L
0C55
LD A,C79
Load Register A with a ZERO
0C56
SBC A,H9C
Subtract the HIGH ORDER (MSB+SIGN) of the integer value in Register H from the value in Register A
0C57
LD H,A67
Save the adjusted value in Register A into Register H
The next bunch of routines are the double precision arithmetic routines. According to the original ROM source code, the conventions are.
Double prevision numbers are 8 bytes long: The first 4 bytes are 32 low order bits of precision and the last 4 bytes are are in the same format as single precision numbers. The lowest order byte comes first in RAM.
For one argument gunctions: The argument is in the ACCumulator, and the results is put there too.
For two argument operations, the first argument is in the ACCumulator and the second argument is in ARG-7,-6,-5,-4,-3,-2,-1,-0. ARGLO=ARG-7. The result is left in the ACCumulator.
Note that the order of the numbers is reversed from integers and single precisions values
Double-precision subtraction (ACCumulator = ACCumulator – ARG). Subtracts the double precision value in ARG (a/k/a REG 2) from the value in the ACCumulator. The difference is left in the ACCumulator.
Note: If you wanted to subtract two double precision numbers, store the minuend in 411DH-4124H and the subtrahend in 4127H-412EH, and CALL 0C70H. The result (in double precision format) is in 411DH-4124H in approximately 1.3 milliseconds.
Vernon Hester has flagged a bug. Double-precision subtraction should produce an difference accurate to 16 digits. However, the difference resulting from doubleprecision subtraction is erroneous when the smaller operand’s value is significantly less than the larger operand’s value
Example: In the code Y# = .20# : X# = 1D16 : J# = X# – Y# : PRINT J# – X# J# is incorrect and J#-X# shows a positive result when it is negative.
Since addition is easier than subtraction, first we need to negate the SECOND argument by first loading Register Pair HL with the address of the MSB in ARG (a/k/a REG 2)
0C73
LD A,(HL)7E
Load Register A with the HIGH ORDER (i.e., MSB+SIGN) of the double precision value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair HL
0C74-0C75
XOR 80HXOR 1000 0000EE 80
Invert the value of the sign bit for the MSB of the double precision value in Register A which is 1000 0000
0C76
LD (HL),A77
Save the adjusted HIGH ORDER (i.e., MSB+SIGN) of the double precision value in Register A in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair HL. To save RAM we now fall into the DADD addition routine.
0C77H-0CCEH -DOUBLE PRECISION ADDITION – “DADD”
Double-precision addition (ACCumulator=ACCumulator+ARG (a/k/a REG 2)). Adds the double precision value in ARG (a/k/a REG 2) to the value in the ACCumulator. Sum is left in the ACCumulator. All registers are affected.
Note: If you wanted to add 2 double precision numbers via a ROM call, store one input into 411DH-4124H and the other in 4127H-412EH. Then call 0C77H. The double precision result will be stored in 411DH-4124H approximately 1.3 milliseconds later.
Load Register Pair HL with the address of the exponent in the FIRST argument held at ARG (a/k/a REG 2)
0C7A
LD A,(HL)7E
Prepare to test that for ZERO by first loading Register A with the exponent of the double precision value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair HL
0C7B
OR AB7
Check to see if the double precision value in ARG (a/k/a REG 2) is equal to zero
0C7C
RET ZC8
Return if the double precision value in ARG (a/k/a REG 2) is equal to zero, since that means that the ACCumulator (i.e., the FIRST argument) is actually the sum
0C7D
LD B,A47
Preserve the exponent for the double precision value in Register A into Register C
0C7E
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to now point to the HIGH ORDER (i.e., MSB + SIGN) for unpacking
0C7F
LD C,(HL)4E
Load Register C with the value of the HIGH ORDER (i.e., MSB + SIGN) of the double precision value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair HL
If the exponent is zero, then the number is zero, so we are once again adding 0 to a number. In this case, the non-zero number is in the wrong reghister, so JUMP to VMOVFA to move ARG to the ACCumulator and exit.
0C88
SUB B90
Now we know we do not have any zero’s, so we next need to get the shift count by subtracting the exponents. First, subtract the value of the exponent for the double precision value in ARG (a/k/a REG 2) in Register B from the value of the exponent for the double precision value in the ACCumulator in Register A
If the NC FLAG is set, then the we need to put the smaller number into the ACCumulator, so JUMP to DADD2 to do that
0C8B
CPL2F
Negate the shift count held in Register A
0C8C
INC A3C
Increment the value of the difference for the exponents in Register A so that Register A will hold the positive difference
0C8D
PUSH AFF5
Save the shift count (i.e., the difference for the exponents, held in Register A) to the STACK
Next we are going to switch ARG and the ACCumulator.
0C8E-0C8F
LD C,08H0E 08
Load Register C with a counter value which is 8
0C90
INC HL23
Increment the value of the memory pointer in Register Pair HL so that it will be pointing to the exponent of the double precision value in ARG (a/k/a REG 2)
0C91
PUSH HLE5
Save the value of the memory pointer in Register Pair HL (which is pointing to ARG) on the STACK
0C92 ↳ DADD1
LD A,(DE)1A
Top of a loop. Load Register A with the value of the ACCumulator pointed to by Register Pair DE
0C93
LD B,(HL)46
Load Register B with the value of ARG (a/k/a REG 2) pointed to by Register Pair DE
0C94
LD (HL),A77
Save the ACCumulator value into the corresponding ARG (a/k/a REG 2) byte.
0C95
LD A,B78
Load Register A with the ARG byte (held in Register B)
0C96
LD (DE),A12
Save the ARG byte (held in Register A) into the corresopnding ACCumulator byte (pointed to by Register Pair DE)
0C97
DEC DE1B
Decrement the value of the memory pointer in Register Pair DE to the next lower byte of the ACCumulator
0C98
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to the next lower byte in ARG
Loop until the double precision values in the ACCumulator and ARG (a/k/a REG 2) have been exchanged
0C9C
POP HLE1
Get the HIGH ORDER back from the stack into Register Pair HL
0C9D
LD B,(HL)46
Fetch the exponent for the double precision value in ARG (a/k/a REG 2) pointed to by Register Pair HL
0C9E
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to now point to the HIGH ORDER (MSB + SIGN)
0C9F
LD C,(HL)4E
Load Register C with the value of the MSB+SIGN for the double precision value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair HL
0CA0
POP AFF1
Get the shift count (i.e., difference for the exponents) back into Register A
0CA1-0CA2 ↳ DADD2
CP 39HFE 39
Check to see if the difference between the two exponents is greater than 56 bits
0CA3
RET NCD0
Return if the difference between the two exponents is greater than 56 bits
0CA4
PUSH AFF5
Save the shift count (i.e., difference for the exponents) from Register A onto the STACK
Check for OVERFLOW because of that too! If the Z FLAG is set, then display an ?OV ERROR if the exponent for the double precision result in the ACCumulator is too large
Right now HL isn’t pointing where we need it to point for a call to DNEGR, so load Register Pair HL with the address of the sign, and then, to save RAM, fall through into DNORML.
Load Register Pair HL with the address of the exponent in the ACCumulator
0D08
ADD A,(HL)86
Add the value of the exponent for the double precision value in the ACCumulator at the location of the memory pointer in Register Pair HL to the value of the shift counter in Register A
0D09
LD (HL),A77
Save the adjusted exponent for the double precision value in Register A at the location of the memory pointer in Register Pair HL
Check for overflow. If the Z FLAG is set, then JUMP to the Level II BASIC error routine and display an OV ERROR message if the exponent for the double precision value in the ACCumulator is too large
0D2F
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to point to the HIGH ORDER
0D30-0D31
LD (HL),80H36 80
Save a new MSB+SIGN at the location of the memory pointer in Register Pair HL
0D32
RETC9
RETurn to CALLer
0D33H-0D44H – DOUBLE PRECISION MATH ROUTINE – “DADDAA” and “DADDA”
DADD enters here, so we need to set both HL and DE. In that case, set Register Pair HL with the starting address of ARG (a/k/a REG 2). Note: 4127H-412EH holds ARG (a/k/a REG 2)
If we can shift 8 bits at once (which is then shifting a byte at a time) the NC FLAG will be set. If not, the CARRY FLAG will be sent and we need to JUMP to DSHFR3 to do it one byte at a time.
0D6F
POP HLE1
Get the value of the memory pointer from the STACK and put it in Register Pair HL
0D70 ↳ DSHFRM
PUSH HLE5
Save the value of the memory pointer in Register Pair HL on the STACK. This is the entry point from DMULT.
LOOP back to the top to see we can shift another 8 bits.
0D7D-0D7EH – DOUBLE PRECISION MATH ROUTINE – “DSHFR3”
0D7D-0D7E ↳ DSHFR3
ADD 09HC6 09
At this point, we cannot shift 8 bytes at once and need to do them individually. First, set a corrected shift counter
0D7F
LD D,A57
Preserve the adjusted shift counter into Register D
0D80 ↳ DSHFR4
XOR AAF
Clear the CARRY FLAG
0D81
POP HLE1
Restore the pointer to the HIGH ORDER byte into Register Pair HL
0D82
DEC D15
Decrement the number of bits to be shifted in Register D
0D83
RET ZC8
Return if all of the bits have been shifted
0D84 ↳ DSHFRA
PUSH HLE5
If all the bits have not been shifted, first save the pointer to the LOW ORDER byte. This is the entry from DADD and DMULT.
0D85-0D86
LD E,08H1E 08
Load Register E with the counter of the number of bytes to be shifted
0D87 ↳ DSHFR5
LD A,(HL)7E
Top of a loop. Load Register A with a byte from the ACCumulator pointed to by HL
0D88
RRA1F
Shift the value in Register A. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0D89
LD (HL),A77
Put the rotated byte back
0D8A
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL so we deal with the next lower order byte
0D8B
DEC E1D
Decrement the number of bytes to be shifted in Register E
Double-precision multiplication (ACCumulator=ACC*ARG (a/k/a REG 2)). Multiplies the double precision value in the ACCumulator by the value in ARG (a/k/a REG 2). The product is left in the ACCumulator.
Note: If you wanted to multiply two double precision numbers store one operand in 411DH-4124H, and store the other in 4127H-412EH and then CALL 0DA1H. The result (in double precision format) is in 411DH-4124H in approximately 22 milliseconds.
As always, we first start by checking to see if we are operating with any ZEROes. First, go check to see if the value in the ACCumulator is equal to zero
0DA4
RET ZC8
If the double precision value in the ACCumulator is equal to zero then we already have our answer (i.e., 0) in the ACCumulator, so RETurn
If Register A is zero, then we are multiplying by ZERO, so JUMP to DMULT5
0DB5-0DB6
LD C,08H0E 08
Otherwise, we need to set up for another loop for bit rotation. First, load Register C with the numberof bits to be shifted
0DB7 ↳ DMULT3
PUSH BCC5
Top of a loop. Save the counters (held in Register Pair BC) to the STACK
0DB8
RRA1F
Shift the multiplier value (held in Register A) one place to the right. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
0DB9
LD B,A47
Preserve the shifted multiplier byte into Register B
If the bit of the multiplier that got shifted into the CARRY FLAG was a 1, we need to add in the old ACCumulator value via a GOSUB to DADDAA which adds the value in ARG (a/k/a REG 2) to the total in the ACCumulator
If we have not hit 0 on the counter, LOOP back to 0DB7H to multiply by the next bit of the multiplier until all of the bits have been shifted
0DC5 ↳ DMULT4
POP DED1
If we are here, then we finished rotating that one byte. Top of a loop. First, get the pointer to ARG back from the STACK and put it in Register Pair DE
0DC6
DEC B05
Decrement the number of bytes to be figured (tracked in Register B)
If we are here then we are done (first, all the bits in each number were rotated, then that was done by all the bytes). Jump to 0CD8H to normalize and round the result.
0DCCH – DOUBLE PRECISION MULTIPLICATION Support Routine – “DMULT5”
Divides the double precision value in the ACCumulator by the value in ARG (a/k/a REG 2). The quotient is left in the ACCumulator. All registers are affected
To use a ROM call to divide two double precision numbers, store the dividend in 411DH-4124H, and the divisor in 4127H-412EH and then CALL 0DE5H. The result (in double precision format) is in 411DH-4124H and then pproximately 42 milliseconds. Overflow or /0 will error out and return to Level II.
According to Vernon Hester, there is a bug this routine. Double-precision division should return a zero quotient when the dividend is zero. However, when the dividend is zero and the divisor is less than .25#, the ROM’s double-precision division produces an non-zero quotient. e.g., PRINT 0 / .24# produces a quotient of 1.171859195766034D-38. If the divisor is 2.938735877055719D-39 then the quotient is .5
Another bug is that double-precision division should perform correctly for absolute values that are from 2.938735877055719D-39 to 1.701411834604692D+38. If the divisor is the minimum magnitude or the minimum magnitude times 2, then double-precision division errors. 10 Z# = 1 / (2^125 + 2^125) * .25 ‘This values Z# with 2.938735877055719D-39 20 PRINT 1 / Z# ‘displays 2.938735877055719D-39 instead of overflow
0DE5-0DE7 ↳ DDIV
LD A,(412EH)LD A,(ARG)3A 2E 41
As always, start by checking to see if we are dealing with a ZERO. First, load Register A with the value of the exponent for the double precision value in ARG (a/k/a REG 2)
0DE8
OR AB7
Check to see if the double precision value in ARG (a/k/a REG 2) is equal to zero
Prepare to subtract from the extra HIGH ORDER byte by first loading Register A with the value at the location of the memory pointer in Register Pair DE
0E03
SBC A,C99
Subtract the value in Register C from the value in Register A
0E04
CCF3F
If the subtraction was good then the CARRY FLAG will be set, so complement the value of the CARRY FLAG so that NC FLAG will mean good
If the subtraction was bad, meaning that the double precision value in ARG (a/k/a REG 2) is greater than the double precision value in FBUFFER, then JUMP to DDIV2
Z-80 TRICK. Since the CARRY was just cleared, this cannot ever execute and it won’t even see the next instruction. It is designed to allow for passing through but not running the next 2 instructions.
0E14-0E16
LD A,(4123H)LD A,(FAC-1)3A 23 41
Prepare the check to see if we are finished dividing. First, getch the byte at FAC-1
0E17 0E18
INC A DEC A3C
INCrement and DECrement Register A so that the SIGN FLAG will be set without chaning the status of the CARRY FLAG
0E19
RRA1F
In preparation for DROUNB, put the CARRY FLAG into the MSB via a RRA rotation. RRA rotates the contents of Register A right one bit position, with Bit 0 going to the CARRY FLAG, and the CARRY FLAG going to Bit 7. RRA also can be used to divide a number in 2.
Load Register Pair HL with the starting address of the LOW ORDER/LSB byte of the double precision result in the ACCumulator. Note: 411DH-4124H holds ACCumulator
0E21-0E22
LD C,07H0E 07
Load Register C with the number of bytes to be shifted
Go shift the double precision value dividend (in FBUFFR) one to the left
0E2C
LD A,B78
Test to see if this was the first time by first loading Register A with the value of the counter in Register B. Note that B will get changed on the first or second subtraction
If we are here, then this was the first iteration, so we need to subtract one from the exponent to correct scaling. To do that, first load Register Pair HL with the address of the exponent for the double precision result in the ACCumulator
0E33
DEC (HL)35
Decrement the value of the exponent for the double precision result in the ACCumulator at the location of the memory pointer in Register Pair HL. If (HL) is reduced to zero then we have a problem!
Display an ?OV ERROR if the exponent for the result in the ACCumulator is too small
0E39H-0E4CH – DOUBLE PRECISION MATH ROUTINE – “DMULDV”
This routine will transfer the double prevision number held in the ACCumulator to FBUFFR for the DMULT and DDIV routines. All registers are affected.
0E39 ↳ DMULDV
LD A,C79
We need to put the unpacked HIGH ORDER back into ARG, so first load Register A with the HIGH ORDER of the double precision value in ARG (a/k/a REG 2) in Register C
0E3A-0E3C
LD (412DH),ALD (ARG-1),A32 2D 41
Save the MSB of the double precision value in ARG (a/k/a REG 2) in Register A
0E3D
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to now point to the HIGH ORDER of the ACCumulator
Loop until the double precision value has been moved from the ACCumulator to FBUFFR
0E4C
RETC9
RETurn to CALLer
0E4DH-0E64H – LEVEL II BASIC MATH ROUTINE – “DMUL10”
This routine multiplies the current double-precision value by 10 by adding it to itself. First the current value is moved to a saved location, and then DP add routine adds the current value to that saved value. All registers are affected
Go move the value in the ACCumulator to ARG (a/k/a REG 2)
0E50
EX DE,HLEB
Since VMOVAF exits with DE pointing to ACCumulator + 1 we need to swap those so that HL points to the ACCumulator
0E51
DEC HL2B
As always, the first thing we need to do is see if we are deadling with a 0. First, decrement the value of the memory pointer in Register Pair HL to point to the exponent of the number in the ACCumulator
0E52
LD A,(HL)7E
Fetch the exponent from the ACCumulator
0E53
OR AB7
Check to see if the value in the ACCumulator is equal to zero
0E54
RET ZC8
Return if the value in the ACCumulator is equal to zero
0E55-0E56
ADD 02HC6 02
Add two to the exponent which is the same as multiplying the ACCumulator by 4
Add in that number one more time (so now it is time 5) by GOSUBing to the DOUBLE PRECISION ADD function (whcih adds the double precision value in ARG (a/k/a REG 2) to the value in the ACCumulator. Result is left in the ACCumulator)
0E5F
POP HLE1
Get the memory pointer to the ACCumulator from the STACK and put it in Register Pair HL
Display an ?OV ERROR if the exponent in the ACCumulator at the location of the memory pointer in Register Pair HL is too large
0E65H-0F88H – ASCII to Double Precision Converter – “FINDBL”
This routine converts an ASCII string (pointed to by HL) to a double-precision value and stores it in the ACCumulator. The NTF is fixed accordingly. The string must be terminated with a , or zero byte. Note that the ARG (a/k/a REG 2) is destroyed in the process and that HL will point to the delimiter at the end of the string. The string formats must follow the same rules as in BASIC. All registers are affected
On entry (HL) must point to the first character in the string buffer, with the first character being in A. On exit, the the double precision number is left in the ACCumulator.
In processing, the digits are packed into the ACCumulator as an integer, with tracking for the decimal point. C=80H if we have not seen a decimal point, and 00H if we have. Register B holds the number of digits after the decimal point.
At the end, Register B and the exponent (held in Register E) are used to determine how many times we multiply or divide the number by 10 to get the correct number.
GOSUB to VALDBL to force the VALTYP to to double precision
0E6B
OR 0AFH F6 AFF6 AF
Part of a Z-80 Trick. If passing through, the next instruction of XOR A will not execute. This is done so that if passing through, the XOR A doesn’t cause us to CALL MAKINT. If the next instruction is JUMPed to, and executes, MAKINT will be CALLed
0E6CH – ASCII to Binary Converter – “FIN”
A call to 0E6CH converts the ASCII string pointed to by HL to binary. If the value is less than 2** 16 and does not contain a decimal point or an E or D descriptor (exponent), the string will be converted to its integer equivalent. If the string contains a decimal point or an E, or D descriptor or if it exceeds 2** 16 it will be converted to single or double precision. The binary value will be left in the ACCumulator and the mode flag will be to the proper value.
Evaluate a numeric string that begins at the address pointed to by the HL Register Pair, store it in ACCUM and set the NTF. This routine stops as soon as it encounters a character that is not part of the number (it will return a value of zero if no valid numeric characters are found). It will accept signed values in Integer, Real or Scientific Notation. Number returned will be in integer format if possible, else single precision unless the string has over seven digits (not including exponent), in which case number will be returned as double precision.
This routine will convert the ASCII string pointed to by register pair HL to binary. The result will be returned in the ACCumulator, and the number type flag will be updated accordingly. The routine will convert the ASCII string to the least amount of precision required.
Note: If you wanted to do this conversion via a ROM call, first have the characters assembled in consecutive memory locations, with either a comma or a 00H at the end. Load HL with the address of the first character. Call 0E6CH. If the output can be an integer, it will be in 4121H-4122H (with 40AFH being a 2). If the output has to be single precision, it will be in 4121H-4124H (with 40AFH being a 4). If the output has to be double precision, it will be in 411DH-4124H (with 40AFH being an 8).
0E6B “ASTOR”
OR AFH
Zero register A (by issuing an OR against 1010 1111). This routine is the same as E65H above, except that it fixes REG 1 and NTF to the smallest possible number type.
0E6D ↳ FINCHR
EX DE,HLEB
Load Register Pair DE with the pointer to the current BASIC line being interpreted
If the character at the current position in the string being interpreted is a then JUMP to FINC to process it
0E82
DEC HL2B
Decrement the value of the current input buffer pointer in Register Pair HL to point to the first character in the string being interpreted
0E83H – Process a + or – at the location of the current input buffer.
0E83 ↳ FINC
RST 10HCHRGETD7
Since we need to bump the current input buffer pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H (which Loads the next character from the string pointed to by the HL Register set into the A-Register And clears the CARRY flag if it is alphabetic, or sets it if is alphanumeric. Blanks and control codes 09 and OB are ignored causing the following character to be loaded and tested. The HL Register will be incremented before loading any character therfore on the first call the HL Register should contain the string address minus one. The string must be terminated by a byte of zeros)
Jump to FINDBF (since this needs to be forced into double precision) if the character at the location of the current input buffer pointer in Register A is a #
0E9A-0E9B
CP 21HCP “!”FE 21
Check to see if the character at the location of the current input buffer pointer in Register A is a !
Jump to FINSNF (since this needs to be forced into single precision) if the character at the location of the current input buffer pointer in Register A is a !
0E9F-0EA0
CP 44HCP “D”FE 44
Check to see if the character at the location of the current input buffer pointer in Register A is a D
Load Register Pair HL with the return address to the FINEC routine
0EAB
EX (SP),HLE3
Swap (SP) and HL, so that the return address goes into Register Pair HL and the current input buffer pointer to the text string goes to the top of the STACK
0EAC
RST 10HCHRGETD7
Next we need the first character of the exponent. Since we need to bump the current input buffer pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
0EAD
DEC D15
Decrement the value in Register D to turn the sign of the exponent to NEGATIVE
0EAE-0EAF
CP 0CEHCP “-“FE CE
Check to see if the character at the location of the current input buffer pointer in Register A is a – token
0EB0
RET ZC8
If the character at the location of the current input buffer pointer in Register A is a minus sign token then RET
0EB1-0EB2
CP 2DHCP “-“FE 2D
Check to see if the character at the location of the current input buffer pointer in Register A is a – sign (not token)
0EB3
RET ZC8
If the character at the location of the current input buffer pointer in Register A is a minus sign then RET
0EB4
INC D14
If we are here then the exponent is still positive, so increment the value in Register D to re-set that flag, as we are now going to process the notations for positive
0EB5-0EB6
CP 0CDHCP “+”FE CD
Check to see if the character at the location of the current input buffer pointer in Register A is a + token (0CDH)
0EB7
RET ZC8
Return if the character at the location of the current input buffer pointer in Register A is a + token (CDH)
0EB8-0EB9
CP 2BHCP “+”FE 2B
Check to see if the character at the location of the current input buffer pointer in Register A is a +
0EBB
RET Z2B
Return if the character at the location of the current input buffer pointer in Register A is a +
0EBA
DEC HLC8
If we are still here then the first character wasn’t a sign, so we are going to need to check it for a digit. Since CHARGET INC’s HL, we need to DEC HL
0EBC
POP AFF1
Discard the FINCE return address as we no longer need it … we are now passing right to it!
0EBD
RST 10H
Since we need to bump the current input buffer pointer in register pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H. NOTE:
The RST 10H routine loads the next character from the string pointed to by the HL register into the A-register and clears the CARRY FLAG if it is alphabetic, or sets it if is alphanumeric.
Blanks and control codes 09H and 0BH are ignored causing the following character to be loaded and tested.
The HL register will be incremented before loading any character therfore on the first call the HL register should contain the string address minus one.
The string must be terminated by a byte of zeros.
0EBC ↳ FINEC
RST 10HCHRGETF1
Since we need to bump the current input buffer pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
If the character at the location of the input buffer pointer in Register A is numeric, then JUMP to FINEDG to pack the digit into the exponent
0EC1
INC D14
If we didn’t JUMP away to FINEDG, then we didn’t get a digit, so we need to adjust the sign of the exponent again … to positive by INCrementing the value in Register D
If the Z FLAG is set, then convert the current value to negative
0ED8
POP HLE1
Get the value of the current input buffer pointer of the string being parsed from the STACK and put it in Register Pair HL
Next we want -32768 to be an integer (it would be single precision at this point)
0ED9
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Integer
NZ/C/M/E and A is -1
String
Z/C/P/E and A is 0
Single Precision
NZ/C/P/O and A is 1
Double Precision
NZ/NC/P/E and A is 5.
0EDA
RET PEE8
If that test shows we have anything other than a SINGLE PRECISION number, then we do no thave -32678, so RETurn
0EDB
PUSH HLE5
If we are here, then we have a single preciosin number. Save the value of the current input buffer pointer of the string being parsed in Register Pair HL to the STACK
Check to see if we have -32768 via a GOSUB to CONIS2 which will convert the current value in the ACCumulator to an integer if possible
0EE3
RETC9
RETurn to CALLer. If we didn’t have -32768 then this will RETurn to POPHRT
0EE4 – Math Routine – “FINDP”
This routine checks to see if we have seen TWO decimal points and to set the decimal point flag. We jumped here when we found a single decimal point.
0EE4 ↳ FINDP
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0EE5
INC C0C
Increment the value in Register C to adjust the flag
If we are still here, then we have 1 decimal point, so convert the ACCumulator to single prevision via a GOSUB to 0EFBH to convert the current value in the ACCumulator to single precision
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
If that test shows anything but an INTEGER, jump to the Level II BASIC error routine and display a ?SN ERROR message
0EF2 ↳ INFINE
INC HL23
Top of a loop. If we are here, then we have something other than a single precision number. Next we move past the % character at the input buffer pointer to the sting being processed (tracked in Register Pair HL). We know this is the last character (a trailing %).
Force a type conversion via a GOSUB to FINFRC to convert the current value in the ACCumulator to either single precision or double precision, based on the concents of Register A (Z=Force to Single or NZ=Force to Double)
Bump the pointer in HL and go to FINE via a JUMP to INFINE
0EFB – Math Routine – “FINFRC”
This routine will force the ACCumulator to be either single precision or double precision based on the Z FLAG. Z FLAG = Force to single precision; NZ FLAG = Force to double precision.
0EFB ↳ FINFRC
PUSH HLE5
Save the value of the current input buffer pointer of the string being parsed in Register Pair HL on the STACK
0EFC
PUSH DED5
Save the exponent (held in Register Pair DE) to the STACK
0EFD
PUSH BCC5
Save the decimal point information (held in Register Pair BC) to the STACK
0EFE
PUSH AFF5
Save the sp/dp value flag for the conversion (held in Register A) to the STACK
If the Z FLAG is set, call the CONVERT TO SINGLE PRECISION routine at 0AB1H (which converts the contents of ACCumulator from integer or double precision into single precision)
0F02
POP AFF1
Restore the sp/dp value flag for the conversion from the STACK and put it in Register Pair AF
If the NZ FLAG is set, Call the CONVERT TO DOUBLE PRECISION routine at 0ADBH (where the contents of ACCumulator are converted from integer or single precision to double precision)
0F06
POP BCC1
Restore the decimal point information from the STACK and put it in Register Pair BC
0F07
POP DED1
Restore the exponent from the STACK and put it in Register Pair DE
0F08
POP HLE1
Restore the value of the current input buffer pointer of the string being parsed from the STACK and put it in Register Pair HL
0F09
RETC9
RETurn to CALLer
0F0A – Math Routine – “FINMUL” and “FINMLT”
This subroutine multiplies a number by 10 once. The original ROM source notes that the reason this is a subroutine is that it can also double as a check to see if A is ZERO, thus saving bytes. All registers are affected.
0F0A ↳ FINMUL
RET ZC8
If the exponent is ZERO then exit right back out
0F0B ↳ FINMLT
PUSH AFF5
Save the exponent (held in Register Pair AF) to the STACK. FOUT enters the routine here.
0F0C
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0F0D
PUSH AFF5
Save exponent and the value type from AF to the STACK
If that test shows DOUBLE PRECISION, go to 0E4DH to multiply the current value in the ACCumulator by “10D0”
0F15
POP AFF1
Get the exponent and the value type from the STACK and put it in Register Pair AF
0F16 ↳ DCRART
DEC A3D
Decrement the exponent (held in Register A) since we have now multiplied by 1 since (x^10 = 10x^9).
0F17
RETC9
RETurn to CALLer
0F18 – Math Routine – “FINDIV”
This subroutine divides a number by 10 once. FIN and FOUT use this routine. Registers A, B, and C are affected.
0F18 ↳ FINDIV
PUSH DED5
Preserve DE to the STACK for POPing at the end
0F19
PUSH HLE5
Preserve HL to the STACK for POPing at the end
0F1A
PUSH AFF5
Since we need to divide we need to preserve the exponent, so save the value in Register A on the STACK
0F1B
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Variable Type
Flags
Register A
Integer
NZ/C/M/E
-1
String
Z/C/P/E
0
Single Precision
NZ/C/P/O
1
Double Precision
NZ/NC/P/E
5
0F1C
PUSH AFF5
Save the value of the FLAGS from the RST 20H call to the STACK
If that test shows DOUBLE PRECISION, go to 0DDCH to divide the current value in the ACCumulator by “10D0”
0F24
POP AFF1
Restore the flags from the STACK and put it in Register Pair F
0F25
POP HLE1
Get the value from the STACK and put it in Register Pair HL
0F26
POP DED1
Get the value from the STACK and put it in Register Pair DE
0F27
INC A3C
Increment the exponent (stored in Register A) since 10x^9 = x^10
0F28
RETC9
RETurn to CALLer
0F29 – Math Routine – “FINDIG”
This routine will pack the next digit of the number into the ACCumulator. To do this, the ACCumulator is multipled by ten to shift everything over and make room for the digit, and then the digit is added in.
0F29 ↳ FINDIG
PUSH DED5
Save the exponent (held in Register Pair DE) on the STACK
0F2A
LD A,B78
We need to check where the decimal point is, so load Register A with the value in Register B
0F2B
ADC A,C89
Increement the decimal place count if we are past the decimal point by adding the value in Register C to the value in Register A
0F2C
LD B,A47
Save the revised decimal point location (tracked in Register B)
0F2D
PUSH BCC5
Save the decimal point information (tracked in Register Pair BC) on the STACK
0F2E
PUSH HLE5
Save the value of the current input buffer pointer of the string being parsed in Register Pair HL on the STACK
0F2F
LD A,(HL)7E
Fetch the digit we want to pack at the location of the current input buffer pointer in Register Pair HL
0F30-0F31
SUB 30HSUB “0”D6 30
Subtract 30H from the ASCII value in Register A so that it will be binary
0F32
PUSH AFF5
Save the adjusted value in the digit (held in Register A) to the STACK
0F33
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H which determines the type of the current value in the ACCumulator and returns a combination of STATUS flags and unique numeric values in the register A according to the data mode flag (40AFH). The results are returned as follows:
Load Register Pair DE with 3277 to see if we will overflow
0F3D
RST 18HCOMPARDF
Now we need to check to see if the integer value in HL is greater than or equal to 0CCDH (in DE), so we call the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status Register As: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal)
If the NC FLAG is set then HL (the number we are working on) > DE (an overflow value), so the number is too big. JUMP to FING2
0F40 0F41
LD D,H LD E,L54
Let DE = HL
0F42
ADD HL,HL29
Multiply the integer value in Register Pair HL by two
0F43
ADD HL,HL29
Multiply the integer value in Register Pair HL by two. Register Pair HL now holds the original integer value times four
0F44
ADD HL,DE19
Add the original integer value in Register Pair DE to the integer value in Register Pair HL. Register Pair HL now holds the original integer value times five
0F45
ADD HL,HL29
Multiply the integer value in Register Pair HL by two. Register Pair HL now holds the origmal integer value times ten
At this point, the number has shifted over to make room for the new digit in the ones place.
0F46
POP AFF1
Get the binary value for the number we want to pack in from the STACK and put it in Register A
0F47
LD C,A4F
Load Register C with the value of the character in Register A. Why C? The DAD routine needs it there and B is already zero.
0F48
ADD HL,BC09
Add the value of the character in Register Pair BC to the newly shifted integer value in Register Pair HL
0F49
LD A,H7C
We next need to test for an overflow, so load Register A with the MSB of the integer value in Register H
Load Register Pair DE with the NMSB and the LSB of a single precision constant. Register Pairs BC and DE now hold a single precision constant equal to 1E6
Call the SINGLE PRECISION COMPARISON routine at 0A0CH to algebraically compare the single precision value in BC/DE (which is 1000000) to the single precision value ACCumulator. The results are stored in A as follows:
If the single precision value in the ACCumulator is greater than or equal to 1000000 then we need to change from single precision to double precision, so JUMP to FINDG3 to covert the number to double precision.
Call the DOUBLE PRECISION ADD function (whcih adds the double precision value in ARG (a/k/a REG 2) to the value in the ACCumulator. Result is left in the ACCumulator)
Jump to the SINGLE PRECISION ADD routine at 0716H (which adds the single precision value in (BC/DE) to the single precision value in the ACCumulator. The sum is left in the ACCumulator)
0F94H-0FA6H – LEVEL II BASIC MATH ROUTINE – “FINEDG”
Pack in a digit of the exponent. This is done by multiplying the old exponent by 10 and then adding in the desired digit. Note: This routine does NOT check for overflow.
0F94 ↳ FINEDG
LD A,E7B
Load Register A with the value of the exponent in Register E
0F95-0F96
CP 0AHFE 0A
Test for overfly by checking to see if the value of the exponent in Register A is greater than or equal to 10. This is necessary because if it overflows the Register E will be corrupted
The routine at 28A7 displays the message pointed to by HL on current system output device (usually video).
The string to be displayed must be terminated by a byte of machine zeros or a carriage return code 0D.
If terminated with a carriage return, control is returned to the caller after taking the DOS exit at 41D0H (JP 5B99H).
0FAE
POP HLE1
Get the value from the STACK and put it in Register Pair HL and then pass through to the LINPRT routine.
0FAFH-0FBCH – CONVERT BINARY TO ASCII AND DISPLAY RESULT – “LINPRT”
This routine converts the two byte number in the HL Register Pair (which is assumed to be an integer) to ASCII and displays it at the current cursor position on the video screen. The space for the sign at the beginning of a line is removed. All registers are affected.
Go display the message pointed to by Register Pair HL
0FBDH-1363H – BINARY TO ASCII CONVERSION ROUTINE – “FOUT”
According to the original ROM source code:
This routine will output the value held in the ACCumulator according to the format specifications held in Registers A, B, and C. The ACCumulator contents are lost and all registers are affected.
The format codes are as follows:
Register A:
Bit 7:
0 means free format output, i.e. the other bits of a must be zero, trailing zeros are suppressed, a number is printed in fixed or floating point notation according to its magnitude, the number is left justified in its field, and Registers B and C are ignored.
1 means fixed format output, i.e. the other bits of a are checked for formatting information, the number is right justified in its field, trailing zeros are not suppressed. this is used for print using.
Bit 6:
0 means means don’t print the number with commas.
1 means group the digits in the integer part of the number into groups of three and separate the groups by commas.
Bit 5: 1 means fill the leading spaces in the field with asterisks (“*”)
Bit 4: 1 means output the number with a floating dollar sign (“$”)
Bit 3: 1 means print the sign of a positive number as a plus sign (“+”) instead of a space
Bit 2: 1 means print the sign of the number after the number
Bit 1: Unused
Bit 0:
1 means print the number in floating point notation i.e. “e notation”. If this bit is on, the comma specification (bit 6) is ignored.
0 means print the number in fixed point notation. Numbers > 1e16 cannot be printed in fixed point notation.
Register B: The number of places in the field to the left of the decimal point (B does not include the decimal point)
Register C: The number of places in the field to the right of the decimal point (C includes the decimal point)
Note 1: B and C do not include the 4 positions for the exponent. If bit 0 is on FOUT assumes b+c <= 24 (decimal)
Note 2: If the number is too big to fit in the field, a percent sign (“%”) is printed and the field is extended to hold the number.
According to other sources:
Conversion routine. Converts the value from ACCumulator to an ASCII string delimited with a zero byte. The number type can be any of Integer, single or double-precision. After execution HL will be pointing to the start of the string. ACCumulator and ARG (a/k/a REG 2) are destroyed by the process.
To use a ROM call to convert a number to a string of digits, and to display the latter on the video screen starting at the current cursor position, store the number in 4121H-4122H (if it’s an integer), or in 4121H-4124H (if it’s single precision), or in 411DH-4124H (if it’s double precision). Then store the variable type (2, 4, or 8, respectively) in 40AFH. Call 0FBDH and then call the WRITE MESSAGE routine at 28A7H.
NOTE 1: The subroutine at 28A7H is a general program for displaying a string of characters and updating the cursor position. The string to be displayed must be terminated by a zero byte, and the HL Register Pair must contain the address of the first character of the string before 28A7H is called. (The routine at 0FBDH effects this setup automatically.)
NOTE 2: DISK SYSTEM CAUTION: The subroutine at 28A7H has two exits to DISK BASIC, with RAM transfer points at 41C1H and 41D0H. To use this routine safely, either be certain that DISK BASIC is in place or have your assembly language program fill locations 41C1H and 41D0H with RET’s (C9H), before calling the routine.
0FBD ↳ FOUT
XOR AAF
Zero Register A so that the format is set for free output
0FBEH-0FC0H – FLOATING to ASCII Conversion Routine– “PUFOUT”
This routine converts a single or double precision number in the ACCumulator to its ASCII equivalent. The ASCII value is stored at the buffer pointed to by the HL Register Pair. As the value is converted from binary to ASCII, it is formatted as it would be if a PRINT USING statement had been invoked. The format modes that can be specified are selected by loading the following values into the A, B, and C registers as follows:
A=0 means do not edit; this is a binary to ASCII conversion
A=X means edit as follows: Bit 7=1 means edit the value, Bit 6=Print commas every third digit, Bit 5=Include leading asterisks, Bit 4=Print a leading $, Bit 3=Sign Follows Value, and Bit 1=Exponential Notation
B = The number of digits to the left of the decimal point.
C = The number of digits after the decimal point.
Note: If you wanted to convert any integer/single/double into its character string, store the variable in 4121H-4122H for integer, 4121H-4124H for single, or in 411DH-4124H for double. Then load 40AFH with a 2, 4, or 8 depending on whether that variable was integer, single, or double. Then call 0FBDH. Upon return, the character string is stored in 4130H and on, ending with a 00H.
Save the formt specification in Register A and put a space for positive numbers into the buffer and loads HL with the starting address of the input buffer
0FC1-0FC2
AND 08HE6 08
Turn off some bits so we can check the value of Register A to see if a plus sign is required to be included for positive numbers
GOSUB to 097BH to convert the negative value in the ACCumulator to its positive equivalent
0FD6
POP HLE1
Restore the buffer pointer from the STACK into HL
0FD7
POP BCC1
Restore the field length specifications from the STACK into B and C
0FD8
OR HB4
Turn off the Z FLAG. This relies on the fact that FBUFR is never on page 0
0FD9 ↳ FOUT2
INC HL23
Increment the buffer pointer in Register Pair HL to where the next character will be placed
0FDA-0FDB
LD (HL),30H36 30
Save an ASCII zero (0) at the location of the input buffer pointer in Register Pair HL EITHER because a “0” will ultimately go there (if we are processing in free format) OR to to reserve a space fro a floating dollar sign (if we are processing in fixed format)
0FDC-0FDE
LD A,(40D8H)LD A,(TEMP3)3A D8 40
Load Register A with the format specification (held in a temporary storage location)
0FDF
LD D,A57
Preserve the format specification into Register D
0FE0
RLA17
Move the “free format” or “fixed format” bit into the Carry flag
0FE1-0FE3
LD A,(40AFH)LD A,(VALTYP)3A AF 40
Since VNEG may have changed VALTYP, re-fetch it (as -32768 is and integer but 32768 is single-precision).
Call the INTEGER TO ASCII routine at 1232F to convert the integer in the ACCumulator to ASCII and stores the ASCII string in the buffer pointed to in HL. We then fall through to FOUTZS
This routine will zero suppress the digits in FBUFFR and asterisk fill and zero suppress if necessary.
If we do NOT need to do an asterisk fill in the ASCII string, JUMP to FOTZS1.
1003
LD A,B78
If we’re here, then we do need to do the asterisk fill, so first lets see what the sign was. Load Register A with the character at the location of the input buffer pointer in Register B
1004
CP CB9
Check to see if the character at the location of the input buffer pointer in Register A is a SPACE. The Z FLAG will be set if it was a SPACE
1005-1006
LD C,2AHLD C,”*”0E 2A
Load Register C with a the fill character, which, in this case, will be a
If the character at the location of the input buffer pointer in Register A isn’t SPACEthen JUMP to FOTS1 to change the SPACEwhere the sign would be into a .
If we do NOT need to do an asterisk fill in the ASCII string, JUMP to FOTZS1.
1003
LD A,B78
If we’re here, then we do need to do the asterisk fill, so first lets see what the sign was. Load Register A with the character at the location of the input buffer pointer in Register B
1004
CP CB9
Check to see if the character at the location of the input buffer pointer in Register A is a SPACE. The Z FLAG will be set if it was a SPACE
1005-1006
LD C,2AHLD C,”*”0E 2A
Load Register C with a the fill character, which, in this case, will be a
If the character at the location of the input buffer pointer in Register A isn’t SPACEthen JUMP to FOTS1 to change the SPACEwhere the sign would be into a .
1009
LD B,C41
Load Register B with the character in Register C
100A ↳ FOTZS1
LD (HL),C71
Fill the zero or the sign with the filler character held in Register C at the location of the input buffer pointer in Register Pair HL
100B
RST 10HCHRGETD7
We need the next character from the buffer. Using CHRGET is, however, a RAM saving method since there is no SPACEto skip, so the RST 10H really just does INC HL and LD A,(HL)
If the character at the location of the input buffer pointer in Register Pair HL is the end of the input buffer character (00H) then we are done with the number. In this casse, we need to back up and put in a ZERO, so JUMP to FOTZS4. CHRGET would have set the ZERO FLAG on any 00H or :, but there are no :going to be found.
100E-100F
CP 45HCP “E”FE 45
Check to see if the character at the location of the input buffer pointer in Register A is an E
If the Z FLAG is set, then we don’t have a dollar sign, so skip the next 2 instructions (which puts in a $) if a $isn’t to be included in the ASCII string
102A
DEC HL2B
Need to add a $, so first we decrement the value of the input buffer pointer in Register Pair HL …
102B-102C
LD (HL),24HLD (HL),”$”36 24
… and then put a $there
102D ↳ FOTZS3
LD A,E7B
Next we need to check to see if we need a trailing sign. First, load the format specs into Register A
102E-102F
AND 04HAND 0000 0100E6 04
Turn off every bit except Bit 2 (by ANDing against 00000100). If Bit 2 was on, then NZ will be set. If Bit 2 was off, then Z will be set. So this checks to see if the sign is to follow the ASCII string
1030
RET NZC0
If the sign isn’t to follow the ASCII string, then we are done so RETurn
1031
DEC HL2B
Decrement the value of the input buffer pointer in Register Pair HL
1032
LD (HL),B70
Save the sign (in Register B) to the location of the input buffer pointer in Register Pair HL
1033
RETC9
RETurn to CALLer
1034 – LEVEL II BASIC MATH ROUTINE– “FOUINI”
Initially set up the format specs and put in a SPACEfor the sign of a positive number. This routine gets called by the FLOATING to ASCII Conversion Routine (at 0FBEH) and by the BINARY to ASCII Conversion Routine (at 0FAFH)
1034-1036 ↳ FOUINI
LD (40D8H),ALD (TEMP3),A32 D8 40
Save the format specification (in Register A) to 40D8H. Note: 40D8H-40D9H holds the temporary storage location
Set up a pointer into FBUFFR, starting at FBUFFR+1 just in case the number will overflow its field, in which case there is still room in FBUFFR for the %character.
103A-103B
LD (HL),” “36 20
Save a SPACEat the location of the input buffer pointer in Register Pair HL
103C
RETC9
RETurn to CALLer
103D – LEVEL II BASIC MATH ROUTINE– “FOUFRV”
This routine gets called by the FLOATING to ASCII Conversion Routine (0FBEH-0FC0H) if the value being converted is either Single Precision or Double Precision. This will print a single or double precision number in free format
103D-103E ↳ FOUFRV
CP 05HFE 05
Company Register A against 05H and if A < 05H, set the C FLAG. With this, the CARRY FLAG will be set if we are dealing with a double precision number.
103F
PUSH HLE5
Save the pointer to the buffer (held in Register Pair HL) to the STACK
OK, this is fun. The next instructions are supposed to set Register D to be the counter for the number of digits to display. There is no agreement on what the next two instructions do:
“Microsoft BASIC Decoded & Other Mysteries” says it turns 04 (SP) and 08 (SP) into 08 (SP) and 10 (DP) into 09 (SP) and 0B (DP)
“Model III ROM Commented” says it turns D into 07 (SP) and 17 (DP)
The original ROM Source Code comment says it turns D into 04 to 06 (SP) and 10 to 20 (DP)
1040-1041
SBC A,00HDE 00
Adjust the value of the number type in Register A. It will be 04H if SINGLE PRECISION and it will be 08H if DOUBLE precision
1042
RLA17
Multiply the value of the number type in Register A by two, so now A will be 08H if SINGLE precision and 0AH if DOUBLE precision
1043
LD D,A57
Load Register D with the adjusted value of the number type in Register A
1044
INC D14
Bump the value of the number type in Register D (so D will be 09H if SINGLE precision and 0BH if DOUBLE precision)
Go scale (normalize) the current value in ACCumulator so that all the significant digits will be in the integer portion (i.e., 99,999 <= X <= 999,999). Returns wihth A being the number of times the DOUBLE precision value was scaled up or down
If the number is too big (i.e., greater than 10^D-1), JUMP to FOFRS1
1053
INC A3C
If we are here, then we are able to display the number in fixed point notation, so we must bump the number of decimal point count
1054
LD B,A47
Load Register B with the decimal point count (stored in Register A)
1055-1056
LD A,02H3E 02
Set up for fixed point output. Fixed point notation has no exponent, so loading Register A with a two so that the next instruction will turn A (which is tracking the exponent) to 0.
1057-1058 ↳ FOFRS1
SUB A,02HD6 02
Compute the exponent value (which will be a zero if we were passing through), so now D-2 will be added to it
1059
POP HLE1
Get the pointer to the string buffer from the STACK and put it in Register Pair HL
105A
PUSH AFF5
Save the exponent value (currently in Register A) to the STACK
Next we need to convert the number to decimal digits by a GOSUB to FOUTCV which will convert the binary value in ACCumulator to ASCII, the result being stored in the input buffer pointer
The FOFRS2 routine will suppress trailing zeroes.
1066 ↳ FOFRS2
DEC HL2B
Backspace to the last character by decrementing the value of the input buffer pointer in Register Pair HL
1067
LD A,(HL)7E
Fetch the last character (at the location of the input buffer pointer in Register Pair HL)
If its NOT a decimal point, GOSUB to bump the value of the input buffer pointer in Register Pair HL. Otherwise, HL is now sitting at the decimal point to suppress that character too.
1071
POP AFF1
Restore the exponent from the STACK into Register A
If the exponent is zero then we are done, so jump to 1093H. Otherwise, pass down to FOFLDN.
1074 – LEVEL II BASIC MATH ROUTINE– “FOFLDN”
This routine will put the exponent and a Dor Einto the buffer. On entry, Register A holds the exponent and it is assumed that all FLAGs are set correctly.
1074 ↳ FOFLDN
PUSH AFF5
Save the exponent (stored in A) to the STACK
1075
RST 20HGETYPEE7
Determine the precision by checking the value of the current number type flag. In this case, this is a really cool trick. The purpose is to load a bit into the CARRY flag if we are going to display an Einstead of a D. We start off with 1/2 of the ascii value for the “D”, then, in 1 instruction, multiply it by 2 and add in the carry bit. So if the CARRY FLAG is off, then it is a “D” and if the CARRY FLAG is on, then it is an “E”
1076-1077
LD A,22H3E 22
Load Register A with the starting value for a D or E character. In this case, A is set for 1/2 of the ASCII code for D
1078
ADC A,A8F
Multiply the value of the character in Register A by two and add in the value of the Carry flag from the number type flag test. This will result with A it being a Dif the value is SINGLE precision and an Eif the value is DOUBLE precision
1079
LD (HL),A77
Save the exponent designation (the Dor Ein Register A) at the location of the input buffer pointer in Register Pair HL
107A
INC HL23
Bump the value of the buffer pointer in Register Pair HL, which is the first position of the exponent in the buffer
107B
POP AFF1
Get the value of the exponent from the STACK and put it in Register A
107C-107D
LD (HL),2BHLD (HL),”+”36 2B
Save a +at the location of the input buffer pointer in Register Pair HL. This is done to save bytes. Instead of testing for + or – and then putting in the appropriate character, a +is put in, and then it is overwritten if a –should be there.
If the exponent is positive, then the +we just put into the buffer (and HL is still pointing to that location) is good, so skip the next 3 instructions (by jumping to 1085H) if the exponent is positive
1081-1082
LD (HL),2DHLD (HL),”-“36 2D
Save a –(which is 2DH) at the location of the input buffer pointer in Register Pair HL, thus overwriting the initially placed +in that same location
1083
CPL2F
If we are here then we have a negative exponent (or we would have jumped to 1085H back in 107EH), so convert the negative exponent to positive by reversing the value of the exponent in Register A
1084
INC A3C
We also need to bump the value of the exponent in Register A by 1 when switching from negative to positive. We then pass through and rejoin where we would have jumped if the number had been positive.
1085 – LEVEL II BASIC MATH ROUTINE– “FOUCE1” and “FOUCE2”
This routine will calculate the two digit exponent.
1085-1086 ↳ FOUCE1
LD B,2FHLD B,”0″-106 2F
At this point, the exponent is positive. Next step is to load Register B with a 0minus one. This is because the next instruction, which is the top of a loop, bumps it by one.
1087 ↳ FOUCE2
INC B04
Top of a loop. Bump the value of the ASCII character in Register B. This is the start of a 3 Opcode routine to divide by 10 using compound subtraction
1088-1089
SUB A,0AHD6 0A
Subtract ten from the value of the exponent in Register A
Loop until the value of the exponent in Register A is less than ten. B holds the quotient (e.g., the number of times the subtraction had to occur to get to a remainder less than 10)
108C-108D
ADD A,3AHC6 3A
Since A is holding the remainder of the ‘divide-by-10’ routine above, add 3AH to it so that it will be an ASCII digit + 10
108E
INC HL23
Bump the value of the buffer pointer in Register Pair HL
108F
LD (HL),B70
Save the ASCII character in Register B (which is the first digit of the exponent in ASCII – the 10’s digit) at the location of the input buffer pointer in Register Pair HL
1090
INC HL23
Bump the value of the buffer pointer in Register Pair HL
1091
LD (HL),A77
Save the value of the ASCII character in Register A (which is the second digit of the exponent in ASCII – the 1’s digit) at the location of the input buffer pointer in Register Pair HL
1092 ↳ FOUTZR
INC HL23
Bump the value of the buffer pointer in Register Pair HL
1093 – LEVEL II BASIC MATH ROUTINE– “FOUTDN”
This routine will print a free format zero.
1093-1094 ↳ FOUTDN
LD (HL),00H36 00
Save an end of the ASCII string character (designated as 00H) at the location of the input buffer pointer in Register Pair HL
1095
EX DE,HLEB
Since the FFXFLV routine will need the buffer pointer in DE instead of HL, swap those registers
Load Register Pair HL with the starting address of the buffer pointer. Note: 4130H-4149H holds an internal print buffer
1099
RETC9
DONE! RETurn to CALLer
109A- LEVEL II BASIC MATH ROUTINE– “FOUTFX”
This routine will print a number in fixed format.
109A ↳ FOUTFX
INC HL23
Bump the value of the buffer pointer in Register Pair HL
109B
PUSH BCC5
Save the field length specifiers (B has the number of #‘s before the current vale of the input buffer pointer and C has the number of #’s after) to the STACK
109C-109D
CP 04HFE 04
Check to see if the current number type in ACCumulator is single or double precision
109E
LD A,D7A
Load Register A with the format specifiers (held in Register D)
If the current value in ACCumulator is either single precision or double precision then JUMP away to FOUFXV. If its an integer we will pass through.
10A2
RRA1F
Rotate Register A so that we can check to see if this has to be printed in floating format or not. RRA rotates Register A right one bit, with Bit 0 going to CARRY and CARRY going to Bit 7.
Go check to see if commas are needed. If no comma is needed, set C to zero
10AC
POP DED1
Restore the field lengths (the number of #’s to the left and right of the decimal point) from the STACK into Register DE
10AD
LD A,D7A
Load Register A with the number of digits requested to the left of the decimal point in Register D
10AE-10AF
SUB A,05HD6 05
Since the maximim number of digits allowed for an integer to the left of the decimal point is 5, subtract 5 from the number of digits to the left of the decimal point requested. This will test to see if we have to print extra spaces because the field is too big.
If the field is too big, and we have to print extra spaces, we GOSUB to FOTZER to put in zeroes which will later be converted to either SPACEor by FOUTZS
Convert the number to decimal digits by GOSUBing to the INTEGER TO ASCII routine at 1232F (which converts the integer in ACCumulator to ASCII and stores the ASCII string in the buffer pointed to in HL)
10B6 ↳ FOUTTD
LD A,E7B
Next we need to test to see if we need a decimal point. First, load Register A with the number of digits to the right of the decimal point requested (which is stored in Register E)
10B7
OR AB7
Check to see if there are any digits to the right of the decimal point requested and set the status flags accordingly
If the Z FLAG is set, then we do NOT need a decimal point, and need to backspace over it, so GOSUB to DCXHRT to decrement the value of the buffer pointer in Register Pair HL
10BB
DEC A3D
Next we need to test to see how many trailing zeroes we need to print. Decrement the number of digits to the right of the decimal point in Register A.
If the Z FLAG is set, then we do NOT have a trailing sign, so we JUMP away to 10C8H
10C6
LD (HL),B70
So now we know a sign does follow the value so we save sign (held in Register B) at the location of the buffer pointer in Register Pair HL
10C7
INC HL23
Bump the value of the input buffer pointer in Register Pair HL
10C8-10C9 ↳ FFXIX1
LD (HL),00H36 00
Terminate the buffer by saving an end of the ASCII string character (=00H) at the location of the input buffer pointer in Register Pair HL
Now we need to check to see if the fixed format/fixed point number overflowed its field length. The location if the decimal point needs to be in TEMP2.
Load Register Pair HL with the starting address of the input buffer pointer (which is 412FH) minus 1 (because the first instruction of the following common code is add 1 to HL)
10CD ↳ FOUBE1
INC HL23
Bump the value of the buffer pointer in Register Pair HL
10CE-10D0 ↳ FOUBE5
LD A,(40F3H)LD A,(TEMP2)3A F3 40
Load Register A with the LSB of the address of the decimal point for the ASCII string. Why just the LSB? FBUFFR is only 35 bytes long, so we only need to check the LSB to see if the field is big enough.
10D1
SUB A,L95
First, subtract the LSB of the input buffer pointer address in Register L from the value in Register A to see how much space we have taken up
10D2
SUB A,D92
Next, set the flags by subtract the number of digits to the left of the decimal point in Register D from the adjusted value in Register A to determine if we have taken the right amount of space. Z FLAG will mean we did!
10D3
RET ZC8
If we have taken the right amount of space, then we are done, so we RETurn
10D4
LD A,(HL)7E
If we are here, then we took too much space. How do we know it is too much instead of just “different”? Well, we started checking from the beginning of the buffer, and the field must be small enough to fit into the buffer. With this, we need to fetch the next character from the buffer into Register A
10D5-10D6
CP 20HCP ” “FE 20
Check to see if the character at the location of the buffer pointer in Register A is a space, meaning we can just ignore the character to make the field shorter
If it is a space, then LOOP back to FOUBE1 to ignore it
10D9-10DA
CP 2AHCP “*”FE 2A
Check to see if the character at the location of the input buffer pointer in Register A is a , meaning we can just ignore the character to make the field shorter
10DD
DEC HL2B
Since we want to ignore ‘s we decrement the value of the input buffer pointer in Register Pair HL so it will get re-tested
10DE
PUSH HLE5
Save the value of the buffer pointer in Register Pair HL to the STACK
10DF – LEVEL II BASIC MATH ROUTINE– “FOUBE2”
In this routine, we check to see if we can ignore the leading zero before a decimal point. We can do this if if we see the following: (in order)
+,-
a sign (either “-” or “+”)
[optional]
$
a dollar sign
[optional]
0
a zero
[mandatory]
.
a decimal point
[mandatory]
0-9
another digit
[mandatory]
If we see a leading zero, it must be the one before a decimal point or else FOUTZS would have akready suppressed it. In that case, we just INC HLover the character following the zero, and not have to check for the decimal point explicitly.
10DF ↳ FOUBE2
PUSH AFF5
Save the the current character (which is the value in Register Pair AF) to the STACK. This also saves the ZERO FLAG.
Load Register Pair BC with the return address for use in case we have a –, a +, or a $
10E3
PUSH BCC5
Save the return address in Register Pair BC to the STACK
10E4
RST 10HCHRGETD7
Since we need to bump the current input buffer pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
10E5-10E6
CP 2DHCP “-“FE 2D
Check to see if the character at the location of the input buffer pointer in Register A is a –
10E7
RET ZC8
Return (to 10DFH) if the character at the location of the input buffer pointer in Register A is a –
10E8-10E9
CP 2BHCP “+”FE 2B
Check to see if the character at the location of the input buffer pointer in Register A is a +
10EA
RET ZC8
Return (to 10DFH) if the character at the location of the input buffer pointer in Register A is a +
10EB-10EC
CP 24HCP “$”FE 24
Check to see if the character at the location of the input buffer pointer in Register A is a $
10ED
RET ZC8
Return (to 10DFH) if the character at the location of the input buffer pointer in Register A is a $
10EE
POP BCC1
We don’t need a shortcut to jump to 10DFH anymore, so let’s get rid of the now unneeded return address from the STACK
10EF-10F0
CP 30HCP “0”FE 30
Check to see if the character at the location of the input buffer pointer in Register A is a 0
If the character at the location of the input buffer pointer in Register A isn’t a 0then we can no longer just get rid of the characters, so JUMP to FOUBE4 to continue
10F3
INC HL23
Bump the value of the input buffer pointer in Register Pair HL so that we skip over the decimal point to the next character
10F4
RST 10HCHRGETD7
Since we need to bump the current input buffer pointer in Register Pair HL until it points to the next character, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
Z-80 Trick! The byte at this memory location, 01H, is there to turn the real instruction that follows in 10F9H into a harmless LD BC,xxxx. This way, if you are processing straight down in order, it skips the next command at 10F9H (in this case a DEC HL) because it wasn’t a command, it was a hex number to be loaded into BC! Instead, if you jump to 10F9H, you skip this byte and it is an DEC HL
10F9 – LEVEL II BASIC MATH ROUTINE– “FOUBE3”
If we can get rid of the zero, we put the characters on the STACK back into the buffer one position in front of where they originally were.
Note that the maximum number of STACK levels this uses is three — one for the last entry flag, one for a possible sign, and one for a possible dollar sign.
We don’t have to worry about the first character being in the buffer twice because the pointer when FOUT exits will be pointing to the second occurance.
10F9 ↳ FOUBE3
DEC HL2B
If passing through, this instruction won’t get executed. If JUMPed to, decrement the value of the buffer pointer in Register Pair HL (we needed that Z-80 trick to avoid a double backspace if passing through)
10FA
LD (HL),A77
If passing through, this instruction won’t get executed. If JUMPed to, save the character in Register A at the location of the input buffer pointer in Register Pair HL
10FB
POP AFF1
Get the character from the STACK and put it in Register Pair AF
If the Z FLAG is set, then LOOP back 1 instruction to leave the number in the buffer alone
1105
POP HLE1
Get the starting address of the field from the STACK and put it in Register Pair HL. This will be the pointer to the beginning of the number – 1
1106-1107
LD (HL),25HLD (HL),”%”36 25
Show that we have overflowed the field by putting a %character at the front.
1108
RETC9
All done! RETurn to CALLer
1109 – LEVEL II BASIC MATH ROUTINE– “FOUFXV”
This is where the PRINT USING routine will print a single or double precision number in a fixed format
1109 ↳ FOUFXV
PUSH HLE5
Save the buffer pointer in Register Pair HL to the STACK
110A
RRA1F
Rotate Register A so that the “fixed notation” or “floating notation” flag bit moves into the CARRY FLAG for testing. RRA rotates Register A right one bit, with Bit 0 going to CARRY and CARRY going to Bit 7.
Load Register Pair DE with the address of the DOUBLE PRECISION value to be compared to the current value in ACCumulator. Register Pair DE points to a double precision constant equal to 1D16
Since we can’t print a number which is greater than 10^16 in fixed format, GOSUB to compare the double precision constant pointed to by Register Pair DE (which is 1Dl6) to the double precision value in ACCumulator
1116-1117
LD D,10H16 10
Load Register D with the maximum length of a double precision value (which is 16 in decimal)
If the M FLAG is set, then the number in the ACCumulator is small enough to print (i.e., less than or equal to 1Dl6), so JUMP to 1132H
111B – LEVEL II BASIC MATH ROUTINE– “FFXSDO”
This routine will print a number which is greaster than 10^16 in free format with a percent sign
111B ↳ FFXSDO
POP HLE1
Get the current buffer pointer from the STACK and put it in Register Pair HL
111C
POP BCC1
Get the field specifier from the STACK and put it in Register Pair BC, resulting in B containing the number of #‘s before and C containing the number of #‘s after
Call the SINGLE PRECISION COMPARISON routine at routine at 0A0CH which algebraically compares the single precision value in BC/DE to the single precision value ACCumulator. The results are stored in A as follows:
If the P FLAG is set then the number is too big, so we need to JUMP to FFXSDO to print it in free format with a %overflow symbol
1130-1131
LD D,06H16 06
Now we know that the SINGLE precision value in ACCumulator is less than 1×10^16. Load Register D with the maximum length of a single precision value (which is 6) and then fall through to the FFXSDC routine
1124 – LEVEL II BASIC MATH ROUTINE– “FFXSDC”
This routine will print a SINGLE PRECISION or DOUBLE PRECISION number in fixed format/fixed point notation
If we do NOT have a ZERO, then GOSUB to FOUTNV to normalize the number so that all digits to be printed are located in the initeger part
1138
POP HLE1
Get the buffer pointer from the STACK and put it in Register Pair HL
1139
POP BCC1
Get the value from the STACK and put it in Register Pair BC, resulting in B containing the number of #‘s before and C containing the number of #‘s after
If there are number to be put there (i.e., merging in E leaves a number greater than Zero), then GOSUB to FOTZEC to put trailing zeros into the input buffer if necessary
114F
OR EB3
Check to see if commas or the decimal point is needed
Jump to 10B6H to check the size, run zero suppression, and convert the fractional portion of the number to ASCII to finish up
1157 – LEVEL II BASIC MATH ROUTINE– “FFXXVS”
This routine will print a SINGLE PRECISION or DOUBLE PREVISION number that has fractional digits
1157 ↳ FFXXVS
LD E,A5F
Preserve the exponent into Register E
1158
LD A,C79
Prepare to divide by 10 the right number of times so that the result will be rounded correctly and have the correct number of significant digits. First, load Register A with the number of digits requested to the right of the decimal point
1159
OR AB7
Check to see if any digits to the right of the decimal point was requested
Go decrement the number of digits requested to the right of the decimal point if necessary
115D
ADD A,E83
Add the number of times the current value was multiplied in Register E to the number of digits to the right of the decimal point requested in Register A
Loop until the value in ACCumulator is properly adjusted. When this is done, A will hold the number of times it was divided by 10
116A
POP BCC1
Get the original scale count from the STACK and put it in Register Pair BC
116B
LD A,E7B
We now need to test as to whether the number has integer digits or not. First, load Register A with the number of times the value in ACCumulator was multiplied in Register E
116C
SUB A,B90
Subtract the value in Register B from the value in Register A
116D
POP BCC1
Get the value from the STACK and put it in Register Pair BC, resulting in B containing the number of #‘s before and C containing the number of #‘s after
116E
LD E,A5F
Calculate the number of decimal places before the number ends by first loading Register E with the adjusted scale factor value in Register A …
116F
ADD A,D82
… and then adding the length of the maximum size for the current value in Register D to the adjusted scale factor value in Register A. This will set the sign flag
1170
LD A,B78
Load Register A with the number of #‘s before (stored in B)
Jump to 117FH if there are no digits to the left of the decimal point
This routine will print numbers with integer digits, and will print some leading zeroes if the field is bigger than the number of digits we need to print.
1174
SUB A,D92
We now know there are leading digits so, subtract the maximum length for the current value in Register D (6 for SINGLE precision and 10 for DOUBLE precision) from the adjusted value in Register A
1175
SUB A,E93
Then, subtract the adjusted scale value in Register E from the adjusted value in Register A
GOSUB to 12A4H to convert the integer portion of the SINGLE precision value in ACCumulator to an ASCII string. These will be the decimal digits.
1193
POP BCC1
Get the number of #‘s before and number of #‘s after and put it back in Register Pair BC
1194
OR CB1
Check to see if we need to print any zeroes after the last digit (i.e., if there are any digits to the right of the decimal point requested) and set the status accordingly
If the NZ FLAG is set, then there are digits to the right of the decimal point to fill, so JUMP to 119AH to do that
1197-1199
LD HL,(40F3H)LD HL,(TEMP2)2A F3 40
Now we know that there are no digits to the right of the decimal point. Load Register Pair HL with the position of the decimal point (which is stored in 40F3H). Note: 40F3H-40F4H is a temporary storage location
119A – LEVEL II BASIC MATH ROUTINE– “FFXXV7”
This routine will print trailing zeroes.
119A ↳ FFXXV7
ADD A,E83
Add the value in Register E to the value in Register A to get the number of digits before the decimal point
GOSUB 0ACCH to convert the integer value in ACCumulator to a SINGLE precision value
11A8
POP DED1
Restore DE from the STACK
11A9
XOR AAF
Zero Register A, clear the status flags. This will denote to the next routine that we are printing a number as a SINGLE PRECISION number, and then fall into the FFXFLV routine
11AA – LEVEL II BASIC MATH ROUTINE– “FFXFLV”
This routine will print a SINGLE or DOUBLE PRECISION number in fixed format/floating point notation.
If we do not have a zero, then we need to normalize the number so that all digits to be printed are in the integer portion, so GOSUB to 1201H to scale the current value in ACCumulator
11B9
POP HLE1
Get the buffer position from the STACK and put it in Register Pair HL
11BA
POP BCC1
Get the number of #‘s before and the number of #‘s after from the STACK and put it in Register Pair BC
11BB
PUSH AFF5
Save the exponent in Register Pair AF to the STACK
11BC
LD A,C79
We need to calculate how many significant digits we must print, so load Register A with the number of digits to the right of the decimal point requested (stored in Register C)
11BD
OR AB7
Set the status so we can see if there are any digits to the right of the decimal point requested through a zero register
11BE
PUSH AFF5
Save the original trailing digit count (in Register Pair AF) to the STACK
If the trail count is not zero, then GOSUB to 0F16H to decrement the number of digits requested to the right of the decimal point in Register A
11C2
ADD A,B80
Add the number of digits requested for the left of the decimal point in Register B to the number of digits requested to the right of the decimal point in Register A
11C3
LD C,A4F
Load Register C with the total digit count (held in Register A)
11C4
LD A,D7A
Load Register A with the value of the edit flag in Register D
11C5-11C6
AND 04HAND 0000 0100E6 04
Check to see if the sign follows the ASCII string (i.e., is a “trailing” sign)
11C7-11C8
CP 01HFE 01
Set the Carry flag according to the sign following the ASCII string test (it will be No Carry if a sign follows, and will be CARRY if A=0)
11C9
SBC A,A9F
If we have a trailing sign, this will set Register D to 0. Otherwise, D will be FFH if we don’t have a trailing sign.
11CA
LD D,A57
Load Register D with those results
11CB
ADD A,C81
Add the value in Register C to the value in Register A so as to set the number of significant digits to print
11CC
LD C,A4F
Load Register C with the adjusted value in Register A
11CD
SUB A,E93
If the number of significant digits to print is less than E, then we have to get rid of some numbers! Subtract the value in Register E from the adjusted value in Register A so that A will now contain the number of times to divide by 10
11CE
PUSH AFF5
Save the divisor count (from Register Pair AF) to the STACK. This is the result of the comparison of the number of significant digits and the number of digits we will actually print.
11CF
PUSH BCC5
Save the “B” field spec and the number of significant digits (from Register Pair BC) to the STACK
Skip the next instruction (i.e., jump to 11DEH) if there are any trailing zeroes
11DD
XOR AAF
Zero Register A and all status flags
11DE ↳ FFXLV3
CPL2F
Make the trailing zero count posivite by inverting the value in Register A
11DF
INC A3C
Bump the value in Register A so that it will be positive
11E0
ADD A,B80
Set the decimal place count by adding the number of digits requested to the left of the decimal point in Register B to the adjusted value in Register A
11E1
INC A3C
Bump the adjusted value in Register A
11E2
ADD A,D82
Take into account if the sign is trailing by adding the value of the maximum length for the current number type in Register D (6 for single precision, 16 fo double precision) to the adjusted value in Register A
11E3
LD B,A47
Copy Register A into Register B so that B holds the number of digits before the decimal point
11E4-11E5
LD C,00H0E 00
Set the comma count to zero by loading Register C with zero (so that there are no commas)
If C = 0 then the last character was a decimal point, so ignore it via a GOSUB to 092FH to decrement the input buffer pointer in Register Pair HL if there are none
11F2
POP AFF1
Get the exponent back from from the STACK and put it in Register Pair AF
Jump to 10BFH to put on the trailing sign and finish up
1201 – Test the magnitude of SP and DP numbers, and clear the times the value was scaled– “FOUTNV”
This routine will scale (normalize) the number in the accumulator so that all the digits are in the integer part (i.e., between 99,999 and 999,999). The signed base 10 exponent is returned in Register A. Registers D and E are unchanged.
1201 ↳ FOUTNV
PUSH DED5
Save the value in Register Pair DE to the STACK. We are going to pop this back at the end of the routine.
1202
XOR AAF
Zero Register A, which will be the exponent
1203
PUSH AFF5
Save the exponent in Register Pair A to the STACK
1204
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H. NOTE:The RST 20H routine determines the type of the current value in ACCumulator and returns a combination of STATUS flags and unique numeric values in the A Register according to the data mode flag (40AFH). The results are returned as follows:
GOSUB to 0DA1H to call the DOUBLE PRECISION MULTIPLY routine at 0DA1H (which multiplies the double precision value in ACCumulator by the value in REG 2. The product is left in ACCumulator)
121C
POP AFF1
Retrieve the original exponent from the STACK into Register A
121D-121E
SUB A,0AHD6 0A
Subtract ten from the value in Register A to do a proper offset for an exponent
121F
PUSH AFF5
Save the adjusted exponent (held in Register A) to the STACK
Force it to be bigger via a JUMP to 1208H so as to loop until the integer portion exceeds 2e16
1222 – LEVEL II BASIC MATH ROUTINE– “FOUNDB”
There is a big bug in this routine which was fixed in v1.2 of the ROM. The fixing of that bug caused a renumbering from 1228H-124CH. The numbering here will show both.
Check to see if the number in the ACCumulator is too big or too small via a GOSUB to 124FH to compare the current value in ACCumulator to 999999.5
1225 ↳ FOUNV1
RST 20HGETYPEE7
In order to determine if the ACCumulator is big enough, we need to know what kind of value we have in the ACCumulator so call the TEST DATA MODE routine at RST 20H. NOTE:The RST 20H routine determines the type of the current value in ACCumulator and returns a combination of STATUS flags and unique numeric values in the A Register according to the data mode flag (40AFH). The results are returned as follows:
Load Register Pair DE with the NMSB and the LSB of a single precision constant. Register Pairs BC and DE are now equal to a single precision constant of 99,999.945
GOSUB to routine at 0A0CH which algebraically compares the single precision value in BC/DE to the single precision value ACCumulator. The results are stored in A as follows:
Go compare the double precision constant pointed to by Register Pair DE to the double precision value in ACCumulator to see if the number is still too small
If the number isn’t too small anymore then we are done so JUMP to 124BH
123D 123C
POP AFF1
If we are here then the number is still too small so we will need to multiply it by 10. Get the value of the scaled counter from the STACK and put it in Register Pair AF
We need to see if the ACCumulator is small enough so GOSUB to 124FH to loop until the value in ACCumulator is < 999,999
124C ↳ FOUNV3 124B
POP AFF1
At this point, we are done scaling, so restore the exponent into Register A. A = + times divided or – times multiplied
124D 124C
POP DED1
Restore DE from where it was preserved at the top of this routine
N/A 124D
OR A
In ROM v1.2 sets the status flags. This also realigns the memory addresses from changes to v1.2 ROM
124E
RETC9
RETurn to CALLer
124F – LEVEL II BASIC MATH ROUTINE– “FOUNVC”
This routine will see if the number in the ACCumulator is small enough yet
124F ↳ FOUNVC
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H. NOTE:The RST 20H routine determines the type of the current value in ACCumulator and returns a combination of STATUS flags and unique numeric values in the A Register according to the data mode flag (40AFH). The results are returned as follows:
Load Register Pair DE with the NMSB and the LSB of a single precision constant. Register Pairs BC and DE are now equal to a single precision constant of 999,999.5
Call the SINGLE PRECISION COMPARISON routine at routine at 0A0CH which algebraically compares the single precision value in BC/DE to the single precision value ACCumulator. The results are stored in A as follows:
If we are here, then we have a DOUBLE PRECISION number to deal with, so start by loading Register Pair DE with the starting address of a double precision constant equal to 9,999,999,999,999,999.5
Check to see if the number is too big via a GOSUB to 0A49H to compare the double precision constant pointed to by Register Pair DE to the double precision value in ACCumulator
1264 ↳ FONVC2
POP HLE1
Get the return address from the STACK and put it in Register Pair HL so we can go to 1244H
If the P FLAG is set, then the number is still too big (i.e., the number in the ACCumulator has more than 6 digits in the integer portion), so JUMP to 1244H
If the number isn’t too big, then just RETurn by JUMPing to (HL)
1269H – LEVEL II BASIC MATH ROUTINE– “FOTZER”
This routine puts leading zeroes into the input buffer. The count is held in Register A and it can be zero, but the Z FLAG needs to be set in that case. Only (HL) and Register A are affected.
1269 ↳ FOTZER
OR AB7
This is the entry point from FFXXV3 where the flags have not yet been set, so set the flags, particularly the Z FLAG
126A ↳ FOTZR1
RET ZC8
Top of a loop. If the number of 0’s we need to display is zero, then just RETurn
126B
DEC A3D
Decrement the value in Register A to show that an ASCII zero was moved to the print buffer
126C-126D
LD (HL),30HLD (HL),”0″36 30
Save a 0at the location of the input buffer pointer in Register Pair HL
Jump back to 126AH until the number in Register A of ASCII zeroes were moved
1271 – LEVEL II BASIC MATH ROUTINE– “FOTZNC”
This routine will put zeroes in the buffer along with commans or a decimal point in the middle. The count is held in Register A and it can be zero, but the Z FLAG needs to be set in that case. Registers B (decimal point count) and C (comma count) are updated accordingly. Everything but DE is affected.
Loop back and keep looping until the number of zeroes to add is 0.
127D – LEVEL II BASIC MATH ROUTINE– “FOUTCD”
This routine will put a possible comma count into Register C and will zero Register C if we are not using commas in the specification.
127D ↳ FOUTCD
LD A,E7B
The next bunch of math is to set up the decimal point count. First, load Register A with the value in Register E so that A holds the decimal point countcount of the times the value was scaled up or down
127E
ADD A,D82
Add the number of digits to print (from Register D) to the value in Register A
127F
INC A3C
Bump the adjusted value in Register A so now A holds the number of digits before the decimal point
1280
LD B,A47
Load Register B with the leading digit count (from Register A)
1281
INC A3C
Next, we are going to set up the comma count. First bump the value in Register A so not A holds the leading digits + 2
1282-1283 ↳ FOTCD1
SUB A,03HD6 03
Subtract three from the adjusted value in Register A which, when combined with the next instruction as a loop, divides modulo 3
Loop back 1 instruction until the value in Register A is -1, -2, or -3
1286-1287
ADD A,05HC6 05
Add 5 (which is 3 back plus 2 more for scaling) to A to get a positive remainder. This will give 4, 3, or 2 as the comma count
1288
LD C,A4F
Save the possible comma count into Register A
1289-128B ↳ FOUICC
LD A,(40D8H)LD A,(TEMP3)3A D8 40
Load Register A with the format specs from the temporary storage location
128C-128D
AND 40HAND 0100 0000E6 40
Mask against 0100 0000 to isolate the comma bit to see if commas are requested
128E
RET NZC0
If the NZ FLAG is set then we are using commas, so just RETurn
128F
LD C,A4F
If we are here, then we aren’t using commas, so Zero the comma counter in Register C
1290
RETC9
RETurn to CALLer
1291 – LEVEL II BASIC MATH ROUTINE– “FOUTED”
This routine will put decimal points and commas in their correct places. This subroutine should be called before the next digit is put in the buffer. Register B = the decimal point count and Register C = the comma count.
The counts tell how many more digits have to go in before the comma ;or decimal point go in.
The comma or decimal point then goes before the last digit in the count. For example, if the decimal point should come after the first digit, the decimal point count should be 2.
1291 ↳ FOUTED
DEC B05
First we need to test to see if it is time to put in a decimal point. To do this, we DECrement the decimal point counter in Register B to see if the zero flag sets or not
If the decimal point position hasn’t been reached then JUMP to FOUED1 to see if a comma needs to go there.
1294-1295 ↳ FOUTDP
LD (HL),2EHLD (HL),”.”36 2E
If we are here, then the decimal point time has come. Save a decimal point at the location of the input buffer pointer in Register Pair HL
1296-1298
LD (40F3H),HLLD (TEMP2),HL22 F3 40
Save the address of the decimal point position (held in Register Pair HL). Note: 40F3H-40F4H is a temporary storage location
1299
INC HL23
Bump the buffer pointer in Register Pair HL
129A
LD C,B48
We just put in a decimal point, so we KNOW we don’t need to put a comma here, so ZERO out the comma counter
129B
RETC9
RETurn to CALLer
129C – LEVEL II BASIC MATH ROUTINE– “FOUED1”
Part of the above routine, jumped here to test to see if a comma needs to be placed at (HL).
129C ↳ FOUED1
DEC C0D
First, we need to test to see if it is time to put in a comma by DECrementing the comma counter in Register C
129D
RET NZC0
If the NZ FLAG is set, then we are not putting in a comma, so RETurn
129E-129F
LD (HL),2CHLD (HL),”,”36 2C
If didn’t jump out, then we need a comma here so put a comma (which is ASCII code 2CH) at the location of the input buffer pointer in Register Pair HL
12A0
INC HL23
Bump the input buffer pointer (to account for the new comma) in Register Pair HL
12A1-12A2
LD C,03H0E 03
Reset the comma counter by setting it to 3 (since commas come after units of 3 numbers)
12A3
RETC9
RETurn to CALLer
12A4 – LEVEL II BASIC MATH ROUTINE– “FOUTCV”
This routine will convert a SINGLE PRECISION or a DOUBLE PRECISION number that has been normalized to decimal digits. The decimal point count is in Register B and the comma count is in Register C. (HL) points to where the first digit will go. Routine will exit with A=0.
12A4 ↳ FOUTCV
PUSH DED5
Generally preserve Register Pair DE. This will get POPped when the subroutine is done.
12A5
RST 20HGETYPEE7
We need to check the value of the current number type flag, so we call the TEST DATA MODE routine at RST 20H. NOTE:The RST 20H routine determines the type of the current value in ACCumulator and returns a combination of STATUS flags and unique numeric values in the A Register according to the data mode flag (40AFH). The results are returned as follows:
If we have a single precision number (by the Parity Odd flag being set) JUMP to 12EAH to convert a SINGLE precision number into its INTEGER equivalent)
12A9
PUSH BCC5
Now that we know we have a DOUBLE PRECISION number, save decimal/comma count (in Register Pair BC) to the STACK
12AA
PUSH HLE5
Save the buffer address (in Register Pair HL) to the STACK
Call the DOUBLE PRECISION ADD function (which adds the double precision value in REG 2 to the value in ACCumulator (which is the constant 0.5D0). Result is left in ACCumulator)
12B7
XOR AAF
Zero Register A and clear the status flags; particularly the CARRY FLAG
Load Register Pair DE with the starting address of a series of double precision constants (i.e., a table of powers of 10 from 1.0x10E15 – 1.0x10E6) for the binary to ASCII conversion
12C0-12C1
LD A,0AH3E 0A
We are going to want to convert ten digits, so load Register A with the number of times to divide the double precision value in ACCumulator by a power of 10
Top of a loop to convert the next digit. It is executed “A” times.
Check to see if we need to put in a decimal point or a comma at the location pointed to by HL via a GOSUB to 1291H
12C5
PUSH BCC5
Save the count of digits before the decimal point and the count of digts after the decimal point (stored in Register Pair BC) to the STACK
12C6
PUSH AFF5
Save the number of digits to process / division count (stored in Register Pair A) to the STACK
12C7
PUSH HLE5
Save the current buffer address (stored in Register Pair HL) to the STACK
12C8
PUSH DED5
Save the address of the power of 10 table (stored in Register Pair DE) to the STACK
12C9-12CA
LD B,2FHLD B,”0″-106 2F
Load Register B (which will be the quotient in ASCII for each division) with the ASCII value for a zero character minus one since the loop which follows starts by INCrementing the value
12CB ↳ FOUCD2
INC B04
Top of a loop. Bump the ASCII value for the digit in Register B so as to start with ASCII “0”
12CC
POP HLE1
Get the address of the power of 10 table (i.e., the divisor) from the STACK and put it in Register Pair HL and
12CD
PUSH HLE5
…. put it right back into the STACK so that it can be restored during the loop
GOSUB to 0D48H to subtract the double precision value pointed to by Register Pair HL from the double precision value in REG l. This is to divide the current integer value by of a power of 10 starting at 10e15 working its way down to 10e6 in a loop until the remainder is less than the current power)
Jump back to do another subtraction and keep looping until the Carry flag gets set by the subtraction (meaning that the remainder is now less than the current power)
12D3
POP HLE1
If we are here because the C FLAG fired, then we have subtracted once too many times. So we need to un-subtract once. To do that we first need to get the address of the power table from the STACK and put it in Register Pair HL
GOSUB to 0D36H to add the double precision value pointed to by Register Pair HL (which is the table of powers of 10) to the double precision remainder in ACCumulator to make it a positive value. Return with the correct remainder in ACCumulator
12D7
EX DE,HLEB
Swap DE and HL so that th eopoert of ten pointer is now in DE.
12D8
POP HLE1
Get the current buffer address from the STACK and put it in Register Pair HL
12D9
LD (HL),B70
Save the ASCII value for the digit in Register B at the location of the input buffer pointer (stored in Register Pair HL)
12DA
INC HL23
Bump the buffer pointer in Register Pair HL since we have just put a digit there
12DB
POP AFF1
Get the loop counter back into Register A
12DC
POP BCC1
Get the decimal point and comma counter from the STACK and put it in Register Pair BC
12DD
DEC A3D
Decrement the loop counter value in Register A (we are going to loop 10 times)
Loop 10 times until the ASCII string has been figured
12E0
PUSH BCC5
At this point, we have finished printing the last digit, so now we want to convert the remaining digits using single precision routines (which are faster). First, save the decimal and comma counters (stored in Register Pair BC) to the STACK
12E1
PUSH HLE5
Save the input buffer pointer (stored in Register Pair HL) to the STACK
This routine is to convert a SINGLE precision value to an INTEGER which will be the decimal digits. Divide the integer equivalent by 100,000 and 10,000. Use the code at 1335H to convert the last 1000 to ASCII.
12EA ↳ FOUTCS
PUSH BCC5
Save the decimal/comma count (in Register Pair BC) to the STACK
12EB
PUSH HLE5
Save the buffer pointer (stored in Register Pair HL) to the STACK
Round the number to the nearest integer via a GOSUB to 0708H which will add a single precision value of 0.5 to the single precision value in ACCumulator. The result is stored in BC/DE
12EF
INC A3C
When a number is positive and non-zero, a FADDH call to round will always exit with the HIGH ORDER of 0 in Register A. So we add 1 to force A to be non-zero.
Loads the SINGLE PRECISION value in ACCumulator into Register Pair BC/DE via A GOSUB to MOVRF.
1307
POP HLE1
Get the power of 10 table address (the integer value for 100,000) from the STACK and put it in Register Pair HL
1308-1309
LD B,2FHLD B,”0″-106 2F
Set B to be the next digit to print. Since the next step INCremenets B, we need to start off with B one too low.
130A – LEVEL II BASIC MATH ROUTINE– “FOUCS2”
This routine divides the integer portion of the current value by 100,000 using compound subtraction. The quotient is kept in Register B as an ASCII value.
130A ↳ FOUCS2
INC B04
Bump the ASCII value from the digit in Register B to increase the ASCII value from 0 and upward
130B
LD A,E7B
Load Register A with the Low Order/LSB of the single precision value in Register E
130C
SUB (HL)96
Subtract the value at the location of the memory pointer in Register Pair HL (the LSB of 100,000) from the value of the LSB of the single precision value in Register A
130D
LD E,A5F
Load Register E with the adjusted LSB of the single precision value in Register A
130E
INC HL23
Bump the value of the memory pointer in Register Pair HL to the next digit of 100,000
130F
LD A,D7A
Load Register A with the Middle Order/NMSB of the single precision value in Register D
1310
SBC A,(HL)9E
Subtract the value at the location of the memory pointer in Register Pair HL (the middle byte of 100,000) from the value of the NMSB of the single precision value in Register A
1311
LD D,A57
Load Register D with the adjusted NMSB of the single precision value in Register A
1312
INC HL23
Bump the value of the memory pointer in Register Pair HL to the MSB of 100,000
1313
LD A,C79
Load Register A with the High Order/MSB of the single precision value in Register C
1314
SBC A,(HL)9E
Subtract the value at the location of the memory pointer in Register Pair HL from the value of the MSB of 100,000 (a single precision value in Register A)
1315
LD C,A4F
Load Register C with the adjusted MSB of the single precision value in Register A
1316
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to the NMSB of 100,000
1317
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL again, now to the LSB of 100,000
We need to add 100,000 to C/D/E and make it positive so we GOSUB to 07B7H to add the value at the location of the memory pointer in Register Pair HL to the value in Register Pairs BC and DE
131D
INC HL23
Bump the value of the memory pointer in Register Pair HL to now point to the 10,000 constant
If the carry flag is set, then reset it and loop the dividing by 10,000 until the integer portion is found
1329
INC DE13
If we fall through to here, we have divided the integer part of the single precision variable by 100,000 and then by 10,000 with the remainder being positive and saved as the current value. With this we bump the value of the memory pointer in Register Pair DE
132A
INC DE13
and again bump the value of the memory pointer in Register Pair DE, so now DE points to the constant 1,000
132B-132C
LD A,04H3E 04
Load Register A with the number of digits for the ASCII string to be figured
Jump to 1335H to convert the remainder to 4 ASCII digits. Note that the CARRY FLAG will be off.
132F – This routine will convert an INTEGER to ASCII– “FOUTCI”
This routine converts an integer into decimal digits by dividing the integer portion of the current value by 100,000 using compound subtraction. The quotient is kept in Register B as an ASCII value and A=0 on exit.
132F ↳ FOUTCI
PUSH DED5
Generally preserve DE. This will be POPped just before the RETurn
Top of the big loop. Check to see if a decimal point or comma needs to be placed before the digit being processed via a GOSUB to FOUTED
1338
PUSH BCC5
Save the decimal and comma counter (stored in Register Pair BC) to the STACK
1339
PUSH AFF5
Save the number of digits-to-process counter (stored in Register A) to the STACK
133A
PUSH HLE5
Save the address of the power table (stored in Register Pair HL) to the STACK
133B
EX DE,HLEB
Load Register Pair HL with the starting address of the descending powers of 10 starting at 10,000 (stored in Register Pair DE)
133C
LD C,(HL)4E
Load Register C with the LSB for the power of 10 stored in Register Pair HL
133D
INC HL23
Bump the value of the memory pointer in Register Pair HL to be the MDB of the power of 10
133E
LD B,(HL)46
Load Register B with the MSB for the integer value at the location of the memory pointer in Register Pair HL
133F
PUSH BCC5
Save the integer value of the power of 10 in Register Pair BC to the STACK
1340
INC HL23
Bump the value of the memory pointer in Register Pair HL to the next value in the power of 10 table
1341
EX (SP),HLE3
Swap (SP) and HL so that the pointer to the power of 10 table is in the STACK and the power of ten is in HL
1342
EX DE,HLEB
Put the power of ten into DE
1343-1345
LD HL,(4121H)LD HL,(FACLO)2A 21 41
Load Register Pair HL with the integer value in ACCumulator
1346-1347
LD B,2FHLD B,”0″ – 106 2F
Since we are about to start a loop which starts with an INC, compensate by loading Register B with the ASCII value for a zero character minus one
This loop divides the current value by a power of 10 starting at 10,000 and working down to 10. The remainder frome ach division is added to the division and the sum becomes the dividend for the next division until done. The quotient is +2FH (which is the ASCII equivalent of a quotient).
1348 ↳ FOUCI2
INC B04
Bump the ASCII value for the digit in Register B (so it starts at 0 and moves up each loop)
1349
LD A,L7D
Load Register A with the LSB of the integer value in Register L
134A
SUB E93
Subtract the value of the LSB of the integer value in Register E from the value of the LSB of the integer value in Register A
134B
LD L,A6F
Load Register L with the adjusted value of the LSB of the integer value in Register A
134C
LD A,H7C
Load Register A with the value of the MSB of the integer value in Register H
134D
SBC A,D9A
Subtract the MSB of the integer value in Register D from the value of the MSB of the integer value in Register A
134E
LD H,A67
Load Register H with the adjusted value of the MSB of the integer value in Register A
If the quotient (stored in HL) >= the current power of 10 (stored in DE) then we need to loop back to 1348H
1351
ADD HL,DE19
The problem with using the CARRY FLAG as a trigger is that it triggers once you have already gone too far. So we need to go back 1. To do this, add the remainder (stored as an integer in Register Pair DE) to the quotient (stored in Register Pair HL as an integer)
1352-1354
LD (4121H),HLLD (FACLO),HL22 21 41
Save the integer remainder (stored in Register Pair HL) in ACCumulator
1355
POP DED1
Get the address of the next power of 10 from the STACK and put it in Register Pair DE
1356
POP HLE1
Get the memory pointer for the buffer from the STACK and put it in Register Pair HL
1357
LD (HL),B70
Save the ASCII value for the digit (from Register B that tracked the number of divisions) to the location of the output buffer pointer (stored in Register Pair HL)
1358
INC HL23
Bump the value of the buffer pointer in Register Pair HL since we just filled that spot with an ASCII value
1359
POP AFF1
Get the number of digits to convert (i.e., the digit counter) from the STACK and put it in A
135A
POP BCC1
Get the decimal/comma counts from the STACK and put it into Register Pair BC
135B
DEC A3D
Decrement the value of the counter in Register A (which is a countdown from 5)
So now all the digits have been calculated in ASCII, so GOSUB 1291H to put a decimal point or comma into the input buffer if necessary
1361
LD (HL),A77
Save a zero (the value in Register A which hit zero when the loop from 5 finished) to the input buffer, pointed to by Register Pair HL. Note that we do not advance HL, so we can overwrite this trailing zero if necessary.
1362
POP DED1
Get the value from the STACK (which was whatever value was in DE when this routine started) and put it in Register Pair DE
A double precision constant equal to 0.5D0 is stored here. BYTE SAVING NOTE: Referencing 1380H, which is half-way through this double precision value of .5, results in a single precision value of 0.5
1380-1383 ↳ FHALF
00 00 00 80
A double precision constant equal to 0.5E0 is stored here. BYTE SAVING NOTE: Referencing 1380H, which is half-way through this double precision value of .5, results in a single precision value of 0.5
So now all the digits have been calculated in ASCII, so GOSUB 1291H to put a decimal point or comma into the input buffer if necessary
13E7-13F1 – LEVEL II BASIC SQR(n)– “SQR”
This routine computes the square root of any value in ACCumulator. It processes it by raising n to the power of 0.5. The root is left in ACCumulator as a single precision value. Single-precision values only should be used
Jump to the EXP(n)routine at 13F5H (which will be using a .5 exponent to do the square root) skipping 13F2H since the exponent is already single precision
13F2-1478H LEVEL II BASIC X to the Y Power (X^Y) ROUTINE– “FPWRQ”
A call to 13F2H raises the single precision value which has been saved to the STACK to the power specified in ACCumulator. The result will be returned in ACCumulator. The method of computation is e ** (y ln x).
Make sure that the exponent is single precision by GOSUB to 0AB1H which is the CONVERT TO SINGLE PRECISION routine at 0AB1H (which converts the contents of ACCumulator from integer or double precision into single precision)
13F5 ↳ FPWRT
POP BCC1
Get the MSB of the single precision value from the STACK and put it in Register Pair BC
13F6
POP DED1
Get the NMSB and the LSB of the single precision value from the STACK and put it in Register Pair DE
13F7 – LEVEL II BASIC Exponentiation routine– “FPWR”
This routine handles the exponentiation routine of X^Y. To do so, first Y is checked for 0 and, if so, then the answer is simply 1. Then we check X for 0 and, if so, then the answer is simply 0.
If neither of those scenarios is the case, then must check to see if X is positive and, if not, check to see if Y is negative and if it is even or odd.
If Y is negative, the we negate it to avoid the LOG routine giving a ?FC ERROR when we call it.
If X is negative and Y is odd, the NEG routine is pushed to the STACK as the exit rouine so that the result will be negative.
After knowing that X isn’t 0, we must check the sign of Y. If it is positive, then JUMP to POSEXP to skip the next 2 opcodes (which check to see if zero is involved) if the exponent (the single precision value in ACCumulator) is positive
1400
OR AB7
Check to see if this is a ZERO raised to the minus power.
If it is 0 raised to a minus power, display a ?/0 ERRORmessage since the single precision value in ACCumulator is negative and the single precision value in Register Pairs BC and DE is equal to zero. /0 ERROR entry point
1404 ↳ POSEXP
OR AB7
ANOTHER check to see if the value to be raised (i.e., the single precision value in Register Pairs BC and DE) is equal to zero
If the value to be raised (i.e., the single precision value in Register Pairs BC and DE) is equal to zero, then we already know the result will be zero, so JUMP to ZERO0.
1408
PUSH DED5
At this point we know that none of the values are zero, and we are raising the number to a positive power. Save the value to be raised (the NMSB and the LSB of the single precision value in Register Pair DE) to the STACK
1409
PUSH BCC5
Save the exponent and the MSB of the single precision value in Register Pair BC to the STACK
140A
LD A,C79
Now we want to check the sign of X. First, load Register A with the value of the MSB of the single precision value to be raised (which is stored in Register C)
140B-140C
OR 7FHF6 7F
Turn the Z FLAG off by ORing against 7FH (0111 1111) in Register A
If X is positive, then jump down to FPWR1 as we have nothing advanced to process
1413
PUSH DED5
Otherwise, we need to do some more math. Save the Y value to the STACK first by saving the NMSB and the LSB of the exponent (the single precision value in Register Pair DE) to the STACK
1414
PUSH BCC5
and then Save the exponent and the LSB of the single precision value in Register Pair BC to the STACK
Check to see if the Y is an integer via a GOSUB to 0B40H to figure the integer portion of the exponent (i.e., the single precision value in ACCumulator) into A with the truncated floating point portion into ACCumulator
1418
POP BCC1
Restore the exponent and the MSB of the Y value from the STACK and put it in Register Pair BC
1419
POP DED1
Restore the NMSB and the LSB of the Y value from the STACK and put it in Register Pair DE
141A
PUSH AFF5
Save the LSB of the integer to the STACK for even and odd information
Make sure we have an integer by GOSUBing to FCOMP which will compare the original exponent to the truncated one by GOSUB to routine at 0A0CH which algebraically compares the single precision value in BC/DE to the single precision value ACCumulator. The results are stored in A as follows:
If ACCumulator = BCDE
A=00
If ACCumulator > BCDE
A=01
If ACCumulator < BCDE
A=FF
141E
POP HLE1
Get the exponent as an integer from the STACK and put it in Register H. This will help us determine if it is even or odd.
141F
LD A,H7C
Load Register A with the exponent as an integer (as stored in Register H)
1420
RRA1F
Rotate that exponent right by one, so we can tell if it is even or odd. If the exponent (as an integer) is odd, set the CARRY FLAG. RRA rotates Register A right one bit, with Bit 0 going to CARRY and CARRY going to Bit 7.
1421 ↳ FPWR1
POP HLE1
Prepare to get the X back into the ACCumulator by fetching the number from the top of the STACK into Register Pair HL
1422-1424
LD (4123H),HLLD (FAC-1),HL22 23 41
Save the HIGH ORDERs of X to the ACCumulator
1425
POP HLE1
Get the LOW ORDERS of X from the STACK and put it in Register Pair HL
1426-1428
LD (4121H),HLLD (FACLO),HL22 21 41
Save the rest of the exponent (i.e., as stored in Register Pair HL as the NMSB and the LSB of the single precision value) in ACCumulator
Now we want to compute EXP(Y*LOG(X)) so we CALL the LOG(N) routine at 0809H (which computes the natural log (base E) of the single precision value in ACCumulator. The result is returned as a single precision value in ACCumulator. Can give an ILLEGAL FUNCTION CALL erro if a negative base is raised to a power with a fraction)
1434
POP BCC1
Get the exponent and the MSB of the single precision value from the STACK and put it in Register Pair BC
1435
POP DED1
Get the NMSB and the LSB of the single precision from the STACK and put it in Register Pair DE
We need to multiply the ln(value) * the exponent so we have to GOSUB to 0847H to SINGLE PRECISION MULTIPLY routine (which multiplies the current value in ACCumulator by the value in (BC/DE). The product is left in ACCumulator
1439 – LEVEL II ROM EXPROUTINE.
Single-precision only. (ACCumulator = EXP(REG1)).
To process this function we first save the original argument and multiply the ACCumulator by log2(e). The result of that is then used to determine if we will get overflow, since exp(x)=2^(x*log2(e)) where log2(e)=log(e) base 2.
We then save the integer part of this to scale the answer at the end, since 2^y=2^int(y)*2^(y-int(y)) and 2^int(y) is easy to compute.
So in the end we compute 2^(x*log2(e)-int(x*log2(e))) by p(ln(2)*(int(x*log2(e))+1)-x) where p is an approximation polynomial.
The result is then scaled by the power of 2 we previously saved.
A call to 1439H raises E (natural base) to the value in ACCumulator which must be a single precision value. The result will be returned in ACCumulator as a single precision number.
Load Register Pair DE with the NMSB and the LSB of a single precision constant. Register Pairs BC and DE are now equal to a single precision constant of 1.442695 (which is approximately 2 + ln 2)
We next want to calculate INT(ARG/LN(2)) = INT(ARG*LOG2(E)). So we will need to multiply the exponent value by 2 ln 2 so we call the SINGLE PRECISION MULTIPLY routine at 0847H (which multiplies the current value in ACCumulator by the value in (BC/DE). The product is left in ACCumulator
1445-1447
LD A,(4124H)LD A,(FAC)3A 24 41
Load Register A with the result of the math just done (i.e., which was multiplying the exponent value by 2 ln 2) which was stored in ACCumulator.
1448-1449
CP 88HFE 88
Next we want to see if ABS(ACCumulator) is >= 128 (i.e., if the integer portion of the single precision value in ACCumulator uses more than 7 bits of precision) by comparing it against a mask of 1000 1000
If the single precision value in ACCumulator uses more than 7 bits of precision for its integer portion then it is too big and we need to JUMP to MLDVEX to deal with that.
So now that we know the integer portion is not too big, but we need to see if the argument is too big as well so we GOSUB to 0B40H to get the integer portion of the value in ACCumulator and return with it in Register A
1450-1451
ADD A,80HC6 80
Adjust the value in Register A by masking it against 1000 0000
1452-1453
ADD A,02HC6 02
Adjust the value in Register A by adding 2 more. We will either get an overflow (C FLAG) or we wont (NC FLAG)
Need to multiply that by ln 2, so GOSUB to 0841H to multiply (1 + [EXP * 2 ln 2]) (as stored in ACCumulator) by 0.693147
1461
POP AFF1
Get the scale factor (as stored in the STACK) and put it in Register Pair AF
1462
POP BCC1
Get the original exponent into BC/DE in 2 steps fist get the exponent and the MSB of the single precision value from the STACK and put it in Register Pair BC
1463
POP DED1
and then get the NMSB and the LSB of the single precision value from the STACK and put it in Register Pair DE
1464
PUSH AFF5
Put the scale factor (the integerized EXP * 2 ln 2) as stored in Register Pair AF onto the STACK
Now we need to subtract the original exponent from the integerized exponent so we GOSUB to 0713H which is the SINGLE PRECISION SUBTRACT routine (which subtracts the single precision value in BC/DE from the single precision value in ACCumulator. The difference is left in ACCumulator)
Load Register Pair HL with the starting address for a series of 8 coefficients so as to enable us to evaluate the approximation polynomial in the next instruction
We want to make sure that FMULT will check for an exponent overflow at the end of this routine, so we can’t just add it to the exponent. Rather, we will multiply it by 2^(B-1) so that FMULT will check. So first, load the integerized equivalent of EXP * 2 lnt 2 into BC/DE so first we load Register Pair DE with zero …
1474
POP BCC1
… and then get the value from the STACK and put it in Register Pair BC
1475
LD C,D4A
Load Register C with zero (since Register D was filled with a zero in 1471H)
We need to multiply by the sum from the series and return so we jump to 0847H which is the the SINGLE PRECISION MULTIPLY routine at 0847H (which multiplies the current value in ACCumulator by the value in (BC/DE). The product is left in ACCumulator
1479-1499 – SINGLE PRECISION CONSTANT STORAGE LOCATION This represents 1/6, -1/5, 1/4, -1/3, 1/2, -1, and 1– “EXPCON”
1479 ↳ EXPCON
09
The number of single precision constants (9) which follow are stored here
147A-147D
40 2E 94 74
A single precision constant equal to -0.00014171607 (-1.413165 * 10e-4) is stored here
147E-1481
70 4F 2E 77
A single precision constant equal to 0.00132988204 (1.32988 * 10e-3, roughly -1/6) is stored here
1482-1485
6E 02 88 7A
A single precision constant equal to -0.00830136052 (-8.30136 * 10e-3, roughly -1/5) is stored here
1486-1489
E7 A0 2A 7C
A single precision constant equal to 0.04165735095 (roughly 1/4) is stored here
148A-148D
50 AA AA 7E
A single precision constant equal to -0.16666531543 (roughly -1/3) is stored here
148E-1491
FF FF 7F 7F
A single precision constant equal to 0.49999996981 (roughly 1/2) is stored here
1492-1495
00 00 80 81
A single precision constant equal to -1.0 is stored here
1496-1499
00 00 00 81
A single precision constant equal to 1.0 is stored here
149A-14C8 – LEVEL II BASIC MATH ROUTINE– “POLYX”
This is a general purpose summation routine which computes the series C0*X+C1*X^3+C2*X^5+C3*X^7+…+C(N)*X^(2*N+1) for I=0 to N when entered at 149AH If entered at 14A9H the series changes to SUM ((((x*c0+c1)x*c2)x+c3)x+.cN. On entry, the x is held in BC/DE and HL points to a list containing the number of terms followed by the coefficients.
The pointer to degree+1 is in (HL) and the constants should follow the egree, stored in reverse order. X is in the ACCumulator.
We need to square X, so we do that in the next two steps. First, GOSUB to 09BFH which loads the SINGLE PRECISION value in ACCumulator into Register Pair BC/DE
Since ACCumulator and BC/DE now hold the same number, you can square that by a GOSUB to 0847H which is the SINGLE PRECISION MULTIPLY routine (which multiplies the current value in ACCumulator by the value in (BC/DE). The product is left in ACCumulator)
14A8
POP HLE1
Restore the consatnt pointer from the STACK and put it in Register Pair HL, and then fall through into the POLY routine
14A9 – LEVEL II BASIC MATH ROUTINE– “POLY”
General polynomial evaluator routine. Pointer to degree+1 is in (HL), and that gets updated through the computation. The Constants follow the degree and should be stored in reverse order. The ACCumulator has the X. The formula is c0+c1*x+c2*x^2+c3*x^3+…+c(n-1)*x^(n-1)+c(n)*x^n
Save the “X” to the STACK. We need to move either x or x**2 (depending on the routine entry point) to the STACK so we GOSUB to 09A4 which moves the SINGLE PRECISION value in ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
14AC
LD A,(HL)7E
Fetch the degree (i.e., the number of values to be figured at the location of the memory pointer in Register Pair HL) into Register A
14AD
INC HL23
Bump the value of the memory pointer in Register Pair HL so that it points to the first constant/coefficient
Now load that constant/coefficient (stored in HL) and move it to ACCumulator by GOSUB to 09B1H (which moves a SINGLE PRECISION number pointed to by HL to ACCumulator)
14B1
LD B,0F106 F1
Z-80 Trick! If passing through, this will simply alter Register B and the next instruction of POP AF will not be processed.
14B2 ↳ POLY1
POP AFF1
Get the degree (count of coefficients left) from the STACK and put it in Register A
14B3
POP BCC1
Get the value of “X” from the STACK and put it in Register Pair BC/DE – Step 1 and …
14B4
POP DED1
… Step 2
14B5
DEC A3D
Count 1 of the terms as computed by decrementing the counter in Register A
14B6
RET ZC8
If that decrement results in a zero (meaning the series of computations has been completed) return out of the subroutine
14B7-14B8
PUSH DE PUSH BCD5
Save the NMSB and the LSB of “X” from DE to the STACK and save the MSB of “X” from BC to the STACK
14B9
PUSH AFF5
Save counter of the remaining degrees (terms to compute) as tracked by Register A into the STACK
14BA
PUSH HLE5
Save the value of the memory pointer to the next constant/coefficient (stored in Register Pair HL) to the STACK
Compute C(I)*x by GOSUB to 0847H which is the SINGLE PRECISION MULTIPLY routine (which multiplies the current value in ACCumulator by the value in (BC/DE). The product is left in ACCumulator
14BE
POP HLE1
Restore the coefficient table address (from the STACK) to Register Pair HL
Get the next coefficient from HL into BC/DE by GOSUB to 09C2H (which loads a SINGLE PRECISION value pointed to by Register Pair HL into Register Pairs BC and DE)
14C2
PUSH HLE5
Save the next coefficient (stored in Register Pair HL) to the STACK
Compute C(I)*x+C(I+1) by GOSUB to 0716H which is the SINGLE PRECISION ADD routine (which adds the single precision value in (BC/DE) to the single precision value in ACCumulator. The sum is left in ACCumulator)
14C6
POP HLE1
Restore the coefficient table address (from the STACK) to Register Pair HL
Jump back to 14B2H to continue the series. ACCumulator contains the current term
14C9-1540 – LEVEL II BASIC RND(n)ROUTINE– “RND”.
If the passed argument is 0, the last random number generated is returned. If the argument is < 0, a new sequence of random numbers is started using the argument.
To form the next random number in the sequence, we multiply the previous random number by a random constant, and add in another random constant. Then the HIGH ORDER and LOW ORDER bytes are switched, the exponent is put where it will be shifted in by normal, and the exponent in the ACCUMULATOR is set to 80H so the result will be less than 1. This is then normalized and saved for the next time.
The reason we switch the HIGH ORDER and LOW ORDER bytes is so we have a random chance of getting a number less than or greater than .5
Integer, single or double-precision. Output will be single-precision. (ACC=RND (ACC))
A call to 14C9H Generates a random number between 0 and 1, or 1 and n depending on the parameter passed in ACCumulator, The random value is returned in ACCumulator as an integer with the mode flag set. The parameter passed will determine the range of the random number returned. A parameter of 0 will return an interger between 0 and 1. A parameter greater than 0 will have any fraction portion truncated and will cause a value between 1 and the integer portion of the parameter to be returned.
There is a bug in the operation of this command. According to Vernon Hester RND(n) where n is an integer from 1 to 32767 is supposed to return an integer from 1 to n. However, when n is a power of two raised to a positive integer exponent from 0 to 14 sometimes returns n+1
First convert the argument to an integer via a GOSUB to the CONVERT TO INTEGER routine at 0A7FH (where the contents of ACCumulator are converted from single or double precision to integer and deposited into HL)
14CC
LD A,H7C
Load Register A with the value of the MSB for the integer value in Register H
14CD
OR AB7
Check to see if the integer value in Register Pair HL is negative
If it is zero, we don’t need the rest of the below which functions to generate RND(n)so we just jump to 14F0H (which generates RND(0), which is a number between 0 and 1)
14D5
PUSH HLE5
Since it wasn’t zero, we need to save the argument (as stored in Register Pair HL) to the STACK
Generate a random number between 0 and 1 via a call to GOSUB to 14F0H (which generates RND(0)) and return with the single precision result in ACCumulator
Move the random number we just generated into BC/DE via a GOSUB to 09BFH which loads the SINGLE PRECISION value in ACCumulator into Register Pair BC/DE
14DC
EX DE,HLEB
Swap some registers so that the random number is now in B/C/H/L
14DD
EX (SP),HLE3
Swap (SP) and HL so that the LOW ORDER bytes of the random number are at the top of the STACK, and HL now holds the integer argument
14DE
PUSH BCC5
Save the HIGH ORDER bytes of the random number value to the STACK
Convert the original x of RND(x) to single precision by GOSUB to 0ACFH which converts the integer value in Register Pair HL to single precision and return with the result in ACCumulator
14E2-14E3
POP BC POP DEC1
Restore the RND(0) value from the STACK and put it into Register Pair BC/DE
Multiply the RND(0) value (currently in BC/DE) by the n of RND(n)(currently in ACCumulator) by GOSUB to 0847H which is the SINGLE PRECISION MULTIPLY routine (which multiplies the current value in ACCumulator by the value in (BC/DE). The product is left in ACCumulator
Increase the random number by one by GOSUB to 070BH which adds the single precision constant pointed to by Register Pair HL (which is 1.0) to the single precision value in ACCumulator (which is the random number). Return with the single precision result in ACCumulator
With the random number now in ACCumulator, jump to 0B40H (which will convert it to an integer and RETurn to the subroutine caller, thus exiting out of this routine)
Jump to 0765H which will normalize the value and then jump to 14D9H unless RND(0)was called in which case return to caller
1541-1546 – LEVEL II BASIC COS ROUTINE– “COS”.
Single-precision only.(ACCumulator = COS(ACCumulator)). A call to 1541H computes the cosine for an angle given in radians. The angle must be a floating point value in ACCumulator; the cosine will be returned in ACCumulator as a floating point value.
GOSUB to 070BH to add 1.57079637029 (stored in HL) to the single precision value in ACCumulator and then pass through to the SIN() routine which is next
First we want to divide the ACCumulator by 2*PI. First, GOSUB to 09A4 which moves the SINGLE PRECISION value in ACCumulator (the x in a SIN(x) call) to the STACK (stored in LSB/MSB/Exponent order)
Load Register Pair DE with the NMSB and the LSB of a single precision constant. Register Pairs BC and DE now hold a single precision constant equal to 6.2831855 (which is pi * 2)
To divide the x from a SIN(x) call (held in BC/DE) by pi*2 (held in ACCumulator) we must GOSUB 80A2H to divide the single precision value in Register Pairs BC and DE by the single precision value in ACCumulator. Return with the single precision result in ACCumulator
Move that value (x / 2*pi) from ACCumulator to the STACK by GOSUB to 09A4H which moves the SINGLE PRECISION value in ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
To get the remainder we need to subtract the integer portion from the full portion so we GOSUB 0713H (the SINGLE PRECISION SUBTRACT routine) to subtract the single precision value in BC/DE (the entire result) from the single precision value in ACCumulator (the integer part of the result). The difference is left in ACCumulator)
Next in calculating a SIN we would need to subtract .25 (held in HL) from the fractional part (held in ACCumulator) so as to see if it is <= to 90 degrees. To do this we GOSUB 0710H to subtract the single precision value in ACCumulator from the single precision constant pointed to by Register Pair HL. Return with the result in ACCumulator
158B-158E – SINGLE PRECISION CONSTANT STORAGE LOCATION– “PI2”
158B-158EH ↳ PI2
DB 0F 49 81DB 0F
A single precision constant equal to 1.57079637029 is stored here
158F-1592 – SINGLE PRECISION CONSTANT STORAGE LOCATION– “FR4”
158F-1592H ↳ FR4
00 00 00 7F
A single precision constant equal to 0.25 is stored here
1593-15A7 – SINGLE PRECISION CONSTANTS STORAGE LOCATION– “SINCON”
1593 ↳ SINCON
05H
The number of single precision constants (05) which follows is stored here. These are the coefficients used in the power series to compute SIN(x)
1594-1597H
BAH D7H 1EH 86H
A single precision constant equal to 39.7106704708 is stored here
1598-159BH
64H 26H 99H 87H
A single precision constant equal to -76.5749816893 is stored here
159C-159FH
58H 34H 23H 87H
A single precision constant equal to 81.6022338865 is stored here
15A0-15A3H
E0H 6DH A5H 86H
A single precision constant equal to -41.3416748045 is stored here
15A4-15A7H
DAH 0FH 49H 83H
A single precision constant equal to 6.28318500497 is stored here
15A8-15BC – LEVEL II BASIC TAN(n)ROUTINE– “TAN”
Single-precision only.(ACCumulator = TAN(ACCumulator)). A call to 15A8H computes the tangent of an angle in radians. The angle must be specified as a single precision value in ACCumulator. The tangent will be left in ACCumulator. Uses the fact that TAN(x) = SIN(x) / COS(x)
Call the COSINE routine at 1541H (which computes the cosine for an angle given in radians. The angle must be a floating point value; the cosine will be returned in ACCumulator as a floating point value
Jump to 08A0H to compute SIN(n)/ COS(n)and return the value as TAN(n)
15BD-15E2 – LEVEL II BASIC ATN(n)ROUTINE– “ATN”.
Single-precision only.(ACCumulator = ATN(ACCumulator)). A call to 15BD returns the angle in radians, for the floating point tangent value in ACCumulator. The angle will be left as a single precision value in ACCumulator.
The method of computation used in this routine is:
Test the sign of the tangent to see if a negative angle is in the 2nd or 4th quadrant. Set the flag to force the result to positive on exit. If the value is negative, invert the sign.
Test magnitude of tangent. If it is < 1 go to step 3. Otherwise, compute its reciprocal and put the return address on the STACK that will calculate pi/2 – series value.
Evaluate the series: (((x^2*c0+c1) x^2+c2) . c8)x
If the flag from step 1 is not set, then invert the sign of the series result.
If the original value is < 1 then return to the caller. Otherwise, compute pi/2-value from step 4 and then return.
GOSUB 082AH to get the reciprocal of the tangent. This routine divides the single precision value in ACCumulator into the single precision constant in Register Pairs BC and DE
Load Register Pair HL with the starting address of a single precision constant equal to 1.57079637029 (which is pi/2)
15E2
RETC9
Return. The return address was set to 0710H above, which will then subtract the last term from pi/2 and then return
15E3-1607 – SINGLE PRECISION CONSTANTS STORAGE LOCATION– “ATNCON”
15E3 ↳ ATNCON
09
The number of single precision constants (9) which follows is stored here
15E4-15E7
4A D7 3B 78
A single precision constant equal to 0.00286622549 is stored here
15E8-15EB
02 6E 84 7B
A single precision constant equal to -0.01616573699 is stored here
15EC-15EF
FE C1 2F 7C
A single precision constant equal to 0.04290961441 is stored here
15F0-15F3
74 31 9A 3D
A single precision constant equal to 0.07528963666 is stored here
15F4-15F7
84 3D 5A 7D
A single precision constant equal to 0.10656264407 is stored here
15F8-15FB
C8 7F 91 7E
A single precision constant equal to -0.14208900905 is stored here
15FC-15FF
E4 BB 4C 7E
A single precision constant equal to 0.19993549561 is stored here
1600-1603
6C AA AA 7F
A single precision constant equal to -0.33333146561 is stored here
1604-1607
00 00 00 01
A single precision constant equal to 1.0 is stored here
1608-18C8 – LIST OF BASIC RESERVED WORDS, TOKENS, AND ENTRY LOCATIONS AS FOLLOWS:
The original ROM source code makes an interesting note about the order of these reserved words. Some reserved words are contained in other reserved words, which will cause a problem. They given examples of:
IF J=F OR T=5will process a FOR
INPis part of INPUT
IF T OR Q THENwill process a TO
SO, the smaller word always has to appear later in the reserved word table.
ABS
D9
0977
|
AND
D2
25FD
ASC
F6
2A0F
|
ATN
E4
15BD
AUTO
B7
2008
|
CDBL
F1
0ADB
CHR$(
F7
2A1F
|
CINT
EF
0A7F
CLEAR
B8
1E7A
|
CLOAD
B9
2C1F
CLOSE
A6
4185
|
CLS
84
01C9
CMD
85
4173
|
CONT
B3
1DE4
COS
El
1541
|
CSAVE
BA
2BF5
CSNG
F0
0ABl
|
CVD
E8
415E
CVI
E6
4152
|
CVS
E7
4158
DATA
88
1F05
|
DEF
DD
415B
DEFDBL
9B
1E09
|
DEFINT
99
1E03
DEFSNG
9A
1E06
|
DEFSTR
98
1E00
DELETE
B6
2BC6
|
DIM
8A
2608
EDIT
9D
2E60
|
ELSE
95
1F07
END
80
1DAE
|
EOF
E9
4161
ERL
C2
24DD
|
ERR
C3
24CF
ERROR
9E
1FF4
|
EXP
E0
1439
FIELD
A3
417C
|
FIX
F2
0B26
FN
BE
4155
|
FOR
81
1CA1
FRE
DA
27D4
|
GET
A4
4174
GOSUB
91
1EB1
|
GOTO
5D
1EC2
IF
8F
2039
|
INKEY$
C9
019D
INP
DB
2AEF
|
INPUT
89
219A
INSTR
C5
419D
|
INT
D8
0B37
KILL
AA
4191
|
LEFT$
F8
2A61
LEN
F3
2A03
|
LET
8C
1F21
LINE
9C
41A3
|
LIST
B4
2B2E
LLIST
B5
2B29
|
LOAD
A7
4188
LOC
EA
4164
|
LOF
EB
4167
LOG
DF
0809
|
LPRINT
AF
2067
LSET
AB
4197
|
MEM
C8
27C9
MERGE
A8
418B
|
MID$
FA
2A9A
MKD$
EE
4170
|
NAME
A9
418E
NEW
BB
1B49H
|
NEXT
87
22B6
NOT
CB
25C4
|
ON
A1
1FC6
OPEN
A2
4179
|
OR
D3
25F7
OUT
AO
2AFB
|
PEEK
E5
2CAA
POINT
C6
0132
|
POKE
B1
2CB1
POS
DC
27F5
|
PRINT
B2
206F
PUT
A5
4182
|
RANDOM
86
01D3
READ
8B
21EF
|
REM
93
1F07
RESET
82
0138
|
RESTORE
90
1D91
RESUME
9F
1FAFH
|
RETURN
92
1EDEH
RIGHT$
F9
2A91
|
RND
DE
14C9
RSET
AC
419A
|
RUN
8E
1EA3
SAVE
AD
41A0
|
SET
83
0135
SGN
D7
098A
|
SIN
E2
1547
SQR
CD
13E7
|
STEP
cc
2B01
STOP
94
1DA9
|
STR$
F4
2836
STRING$
C4
2A2F
|
SYSTEM
AE
02B2
TAB(
BC
2137
|
TAN
E3
15A8
THEN
CA
|
TIME$
C7
4176
TO
BD
|
TROFF
97
1DF8
TRON
96
1DF8
|
USING
BF
2CBD
USR
C1
27FE
|
VAL
FF
2AC5
VARPTR
C0
24EB
|
+
CD
249F
–
CE
2532
|
CF
/
D0
|
?
D1
>
D4
|
=
D5
<
D6
|
&
26
‘
FB
3A93
18C9-18F6 – STORAGE LOCATION FOR LEVEL II BASIC ERROR MESSAGES– “ERRTAB”
18C9
“NF”
NEXT without FOR Error Message (Error 00H)
18CB
“SN”
Syntax Error Error Message (Error 02H)
18CD
“RG”
RETURN without GOSUB Error Message (Error 04H)
18CF
“OD”
Out of DATA) Error Message (Error 06H)
18D1
“FC”
Illegal Function Call Error Message (Error 08H)
18D3
“OV”
Overflow Error Message (Error 0AH)
18D5
“OM”
Out of Memory Error Message (Error 0CH)
18D7
“UL”
Underfined Line Number Error Message (Error 0EH)
18D9
“BS”
Subscript out of Range Error Message (Error 10H)
18DB
“DD”
Redimensioned Array Error Message (Error 12H)
18DD
“/0”
Division by Zero Error Message (Error 14H)
18DF
“ID”>
Illegal Direct Operation Error Message (Error 16H)
18E1
“TM”
Type Mismatch Error Message (Error 18H)
18E3
“OS”
Out of String Message (Error 1AH)
18E5
“LS”
Out of Memory Error Message (Error 1CH)
18E7
“ST”
String Too Long Error Message (Error 1EH)
18E9
“CN”
Can’t Continue Error Message (Error 20H)
18EB
“NR”
No RESUME Error Message (Error 22H)
18ED
“RW”
RESUME Without Error Error Message (Error 24H)
18EF
“UE”
Unprintable Error Error Message (Error 26H)
18F1
“HO”
Missing Operand Error Message (Error 28H)
18F3
“FD”
Bad file Data Error Message (Error 2AH)
18F5
“L3”
Disk BASIC Command Error Message (Error 2CH)
18F7-1904 – STORAGE LOCATION FOR THE SINGLE PRECISION DIVISION ROUTINE This code is moved from 18F7-191DH to 4080H-40A5H during non-disk initial setup.
18F7
SUB 00HD6 00
Subtract the LSB
18F9
LD L,A6F
Restore the value to L
18FA
LD A,H7C
Get the middle byte
18FB
SBC A,00HDE 00
Subtract the middle byte
18FD
LD H,A67
Move the difference to H
18FE
LD A,B78
Get the MSB
18FF
SBC A,00HDE 00
Subtract the MSB
1901
LD B,A47
Move it back to A
1902
LD A,00H3E 00
Clear A
1904
RETC9
RETurn to CALLer
1905-191C – STORAGE LOCATION FOR VALUES PLACED IN RAM UPON INITIALIZATION.
This code is moved to 408E during non-disk initial setup.
191D-1923 – MESSAGE STORAGE LOCATION– “ERR”
191D-1923 ↳ ERR
“Error” + 00H20 45
The word “ERROR”
1924-1928 ↳ INTX
” in ” + 00H
The word ” IN “
1929-192F – MESSAGE STORAGE LOCATION– “REDDY”
1929-192F ↳ REDDY
“READY” + 0DH + 00H
The Level II BASIC READY message is stored here
1930-1935 – MESSAGE STORAGE LOCATION– “BRKTXT”
1930-1935 ↳ BNKTXT
“Break” + 00H
The Level II BASIC BREAK message is stored here
1936-1954 – SCAN STACK ROUTINE– “FNDFOR”
This routine is called with DE as the address of the NEXT index. It scans the STACK backwards looking for a FORpush. If one is found, it gets the address of the index and compares with the DE that was in place when this routine was called. If it is equal, then it exits with A=0 and HL=Address of the variable. If it is not equal it will keep scanning until no FOR push is found and then exit with A<>0.
According to the original ROM source, this routine is part of the general storage management routines, and if designed to find a FORentry on the STACK with the variable pointer passed in Register Pair DE.
Load Register Pair HL with 4 so that we can backspace
1939
ADD HL,SP39
Add the 4 (held in HL) to the current value of the STACK pointer. HL will hold the current STACK pointer, the STACK point will bump forward 4
193A ↳ LOOPER
LD A,(HL)7E
Load Register A with the value held at the current STACK point MINUS 4. This is to enable seeing what type of data is on the STACK
193B
INC HL23
Bump the value of the memory pointer in Register Pair HL so as to backspace one more byte in case a FORtoken is located
193C-193D
CP 81HFE 81
Check to see if the value in Register A (which is the current STACK pointer – 4) is a FORtoken to make sure that the item in the STACK was associated with a FOR loop
193E
RET NZC0
If the item isn’t associated with a FOR statement (as the value in Register A isn’t a FORtoken), exit out of this routine. This returns with A being non-zero because there was no FORpush
193F
LD C,(HL)4E
If we are here, then the entry on the STACK is associated with a FOR statement. Load Register C with the LSB of the FOR‘s variable address
1940
INC HL23
Bump the value of the memory pointer in Register Pair HL so as to backspace the current STACK pointer by yet another byte
1941
LD B,(HL)46
Load Register B with the MSB of the FOR‘s variable address
1942
INC HL23
Bump the value of the memory pointer in Register Pair HL so now HL will be the address of the FORvariable on the STACK
1943
PUSH HLE5
Save the value in Register Pair HL (which is the address of the FORvariable) to the STACK
1944
LD L,C69
Load Register L with the LSB of the FORvariable’s address in Register C
1945
LD H,B60
Load Register H with the MSB of the FORvariable’s address in Register B
1946-1947
LD A,D OR E7A
Z-80 Trick to check a Register Pair (in this case DE) for zero. Load D into A and then OR that against E. If both D and E are zero, then A will be zero. This sets up to handle a NEXT statement that doesn’t have a variable argument
1948
EX DE,HLEB
Exchange the variable address in Register Pair HL with the variable address in Register Pair DE so that DE will now hold the address of the FORvariable from the STACK. This is to ensure that we return with DE pointing to the variable
Skip the next 2 opcodes if the variable address in Register Pair DE was equal to zero, meaning that DE was not, in fact, pointing to the variable as we needed it to be
194B
EX DE,HLEB
Exchange the variable address in Register Pair HL with the variable address in Register Pair DE so that HL will now have the address of the FORvariable from the STACK
194C
RST 18HCOMPARDF
This routine was entered with DE being the address of the NEXTindex, so we need to compare that against the index from the STACK. To do this, we RST 18 to see if the variable address in HL is the same as in DE, so we call the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status Register as: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal)
At this point, we should be pointing to the start of the NEXTentry, so JUMP to keep looking until the appropriate FORblock has been located
1955-1962 – DATA MOVEMENT ROUTINE– “BLTU”
This routine moves a variable into another area specified by the caller. On entry BC is set as the end address of the list to move (which is the upper limit); DE is set as the start address of the list to move; and HL is the end of the area to move it to.
According to the original ROM source, this routine is part of the general storage management routines, and if designed to make space by shoving everything forward and to check to make sure a reasonable amount of space remains between the top of the STACK and the highest location transferred to. On Entry, HL should be the destination of the high address, DE should be the low address to be transferred there, and BC should be the high address to be transferred there. On exit, HL=DE=Low BC=The location LOW was moved to.
GOSUB to 196CH to make sure there’s enough room in memory for the string area and make sure the STACK won’t be overrun
1958 ↳ BLTUC
PUSH BCC5
The next 3 instructions are really just to exchange HL and BC. First, save the end address of the list to move (stored in BC) to the STACK
1959
EX (SP),HLE3
Save the end address of the list to move (stored in the STACK now) to Register Pair HL
195A
POP BCC1
Get the end address of the move from the STACK and put it in Register Pair BC
195B ↳ BLTLOP
RST 18HCOMPARDF
Check is we are done by checking to see if the memory pointer in HL is the same as the memory pointer in DE (to see if the move is finished), so we call the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status Register as: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal)
195C
LD A,(HL)7E
Get a byte from the source list to transfer and put it in Register A
195D
LD (BC),A02
Transfer it by saving the byte into wherever BC is pointing
195E
RET ZC8
Return if finished with the move (i.e., the memory pointer in Register Pair HL is the same as the value of the memory pointer in Register Pair DE)
195F
DEC BC0B
Decrement the source address (in Register Pair BC)
1960
DEC HL2B
Decrement the destination address (in Register Pair HL)
This routine computes the amount of space between HL and the end of memory at FFC6. On entry, Register C should hold the number of desired bytes.
According to the original ROM source, this routine is part of the general storage management routines, and if designed to make sure that a certain number of locations remain available for the STACK. To use this routine, Register C needs to hold the number of two byte entries needed, and then do a CALL GETSTK. This routine must be called by any reoutine which puts an arbitrary amount of stuff into the STACK (such as a recursive routine like FRMEVL). It is also called by routines such as GOSUBand FORwhich make permanent entries in the STACK.
1963 ↳ GETSTK
PUSH HLE5
Save the value in Register Pair HL to the STACK
1964-1966
LD HL,(40FDH)LD HL,(STREND)2A FD 40
Load Register Pair HL with the starting address of free memory (which is stored at 40FDH). Note: 40FDH-40FEH holds Free memory pointer
1967-1968
LD B,00H06 00
Load Register B with zero
1969
ADD HL,BC09
Add 2 times the number of bytes required to start of free area (held in Register Pair BC) to the value in Register Pair HL
196A
ADD HL,BC09
Add the value in Register Pair BC to the value in Register Pair HL leaving HL to now contain the end of the free area
196B-196C
LD A,0E5H3E E5
Z-80 Trick! See the general explanation at 10F8H
196C ↳ REASON
PUSH HLE5
Now we check t omake sure there is at least “NUMLEV” bytes between the address and the top of the STACK. First, save the new free area pointer (which is the start) to the STACK
196D-196E
LD A,C6H LD A,256-(2*NUMLEV)3E C6
Load Register A with C6H (which is the the LSB of FFC6H; the top of memory)
196F
SUB L95
Subtract the LSB of the value of the new memory pointer in Register L from the value in Register A
1970
LD L,A6F
Load Register L with the adjusted value in Register A (i.e., the free memory pointer resulting from subtracting the new starting address)
1971-1972
LD A,FFH3E FF
Load Register A with the MSB of the top of memory
1973
SBC A,H9C
Subtract the MSB of the new memory pointer in Register H from the value in Register A. If the free space list exceeds 7FFC6 then the Carry flag gets set, meaning memory overflowed
197E-1AF7 – LEVEL II BASIC COMMAND MODE ERROR HANDLING– “PRGEND”
197E-1980 ↳ PRGEND
LD HL,(40A2H)LD HL,(CURLIN)2A A2 40
Load Register Pair HL with the value of the current BASIC line number. Note: 40A2H-40A3H holds the current BASIC line number
1981
LD A,H7C
Test to see if this was a direct command instead a line number by first loading Register A with the MSB of the current BASIC line number in Register H
1982
AND LA5
Combine the LSB of the current BASIC line number in Register L with the MSB of the current line number in Register A
1983
INC A3C
Bump the value of the combined BASIC line number in Register A. If the current line is FFFFH then we have not started execution of a BASIC program yet (meaning we are still in the inputting phase)
Z-80 Trick. JUMPing here will load BC but skip the reload of Register E as follows
19A0-19A1
LD E,24HLD E,ERRRE1E 24
Load Register E with a ?RW ERRORcode. ?RW ERRORentry point
19A2-19A4 ↳ ERROR
LD HL,(40A2H)LD HL,(CURLIN)2A A2 40
Load Register Pair HL with the value of the current BASIC line number which has the error. Note: 40A2H-40A3H holds the current BASIC line number
19A5-19A7
LD (40EAH),HLLD (ERRLIN),HL22 EA 40
Save the value of the current BASIC line number with the error into the RAM Location which tracks the ERL variable. Note: 40EAH-40EBH holds Line number with error
19A8-19AA
LD (40ECH),HLLD (DOT),HL22 EC 40
Save the value of the current BASIC line number with the error into the RAM location used for EDIT or LIST
Jump to 1B9AH to reinitialize the system variables, including reinitializing the STACK to the location now held in SAVSTK
19B4 – LEVEL II BASIC COMMAND MODE ERROR HANDLING– “ERRMOR”
19B4 ↳ ERRMOR
POP BCC1
Discard the entry at the top of the STACK (which is the FNDFOR Stopper)
19B5
LD A,E7B
Load Register A with the value of the error code in Register E
19B6
LD C,E4B
Load Register C with the value of the error code in Register E, as we will need to restore it later as well
19B7-19B9
LD (409AH),ALD (ERRFLG),A32 9A 40
Save the value of the error code (from in Register A) into 409AH. Note: 409AH holds the RESUME flag
19BA-19BC
LD HL,(40E6H)LD (SAVTXT),A2A E6 40
Load Register Pair HL with the value of the current BASIC program pointer (which is stored in 40E6H) (i.e., the address of the last byte executed in the current line). Note: 40E6H-40E7H holds the temporary storage location
19BD-19BF
LD (40EEH),HLLD (ERRTXT),HL22 EE 40
Save the value of the current BASIC program pointer (which is stored in 40EEH) in Register Pair HL. Note: 40EEH-40EFH is used by RESUME
19C0
EX DE,HLEB
Load Register Pair DE with the value of the current BASIC program pointer in Register Pair HL so that the SAVTXT is preserved in Register Pair DE
19C1-19C3
LD HL,(40EAH)LD HL,(ERRLIN)2A EA 40
Load Register Pair HL with the line number where the error occurred. Note: 40EAH-40EBH holds Line number with error
19C4,19C4
LD A,H AND L7C
Z-80 Trick to test if HL is zero or not – If H AND L are 0, then they are each zero
19C6
INC A3C
Bump the value of the combined current BASIC line number. If the line with the error was a direct command, this would have been FFFF, so the INC A will then set the ZERO flag if it was direct
If this was a direct command (and ZERO FLAG is set), then we do not want to modify OLDTXT or OLDLIN, so jump to 19D0H
19C9-19CB
LD (40F5H),HLLD (OLDLIN),HL22 F5 40
Let OLDLIN = ERRLIN by saving the value of the current BASIC line number in Register Pair HL to (40F5H). Note: 40F5H-40F6H holds the last line number executed
19CC
EX DE,HLEB
Get bacK SAVTXT by swapping HL and DE
19CD-19CF
LD (40F7H),HLLD (OLDTXT),HL22 F7 40
Let OLDTXT = SAVTXT by saving the value of the current BASIC program pointer in Register Pair HL. Note: 40F7H-40F8H holds Last byte executed
19D0-19D2 ↳ NTMDCN
LD HL,(40F0H)LD HL,(ONELIN)2A F0 40
See if we are trapping errors by first loading Register Pair HL with the current ON ERRORaddress. Note: 40F0H-40F1H is used by ON ERROR
19D3,19D4
LD A,H OR L7C
Z-80 Trick to test if HL is zero or not – If H OR L are 0, then they are each zero
19D5
EX DE,HLEB
Load Register Pair DE with the ON ERRORaddress to go to if there’s an error which is currently in Register Pair HL
Jump to 19E3H (to error out) if we aren’t otherwise trapping errors (because there isn’t an ON ERRORaddress)
19DB
AND (HL)A6
Since Register A is currently non-zero (or we would have jumped away in the prior instruction), combining that number with with the ONEFLG will result in either a 0 or non-zero based on whether whether the flag was already set
19E3 – LEVEL II BASIC COMMAND MODE ERROR HANDLING– “NOTRAP”
19E3 ↳ NOTRAP
XOR AAF
Zero Register A
19E4
LD (HL),A77
Reset ONMEFLG. Clear the error override flag by save a zero (from Register A) as the current error flag at the location of the memory pointer in Register Pair HL
19E5
LD E,C59
Restore the error code (which was tucked away in Register C at 19B6H) into Register E
GOSUB to 032AH to display the first character of the error message in Register A
19FA
RST 10HCHRGETD7
Error codes are 2 characters so we need to set the second character of the error in Register A, call the EXAMINE NEXT SYMBOL routine at RST 10H.
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
This basically reserves the line number 65534 as a trigger for the next few steps
1A0D
RST 18HCOMPARDF
Now we need to compare the BASIC line number causing the error (held in HL) with FFFEH (held in DE) so as to see if we are in the initialization routine, so we call the COMPARE DE:HL routine, which numerically compares DE and HL. Will not work for signed integers (except positive ones). Uses the A-register only. The result of the comparison is returned in the status Register as: CARRY SET=HL<DE; NO CARRY=HL>DE; NZ=Unequal; Z=Equal)
Jump to 0674H if we are in the initialization routine because the error line number was FFFEH
1A11
LD A,H7C
Next, let’s see if we were in direct mode (i.e., entered from the command line). Load Register A with the MSB of the current BASIC line number in Register H
1A12
AND LA5
Combine the LSB of the current BASIC line number in Register L with the MSB of the current BASIC line number in Register A
1A13
INC A3C
Bump the combined value of the current BASIC line number in Register A to test to see if the line number is 00H (meaning command mode)
GOSUB to 0FA7H to display the current BASIC line number in Register Pair HL if Level II BASIC isn’t in the command mode
The original ROM has this note: The following code is for “LIST” command stopping and for returning from a failed “CVER” and to correct a direct GOSUB which does input.
1A17
LD A,0C1H3E C1
Z-80 Trick! If passing through from the above routine, then A will loaded and the instruction at 1A18 will be skipped.
1A18 ↳ STPRDY
POP BCC1
Get the value from the STACK and put it in Register Pair BC
Load Register A with the value of the current error code
1A2E-1A2F
SUB 02HD6 02
Check to see if the current error code is a ?SN ERRORcode by subtracting 2 from the error code, resulting in a zero if its a SN ERROR and any other number if it isn’t
If the current error code is a SN ERROR code, then automatically enter EDIT mode on that line via this CALL
1A33 – MAIN LEVEL II BASIC INTERPRETER ENTRY– “MAIN”
If the jump here was from an AUTOcall, (40E4H) will have the increment number, (40E1H) will be 0 if no AUTOand non-zero if AUTO, and (40E2H) will have the starting line number.
While not for this specific routine, this is the best place to mention it. Vernon Hester has pointed out that while BASIC is supposed to ignore spaces in commands, it fails to properly handle some commands because of spaces
If you have a statement with a type declaration tag after a number and a space before an add or subtract arithmetic operator, the ROM applies the operator as a unary operator for the following argument
Example: PRINT 2% + N will display two numbers. PRINT 2%+N will display one number.
Also, if you have a statement with a type declaration tag after a number and a space before a multiply or divide arithmetic operator, the ROM willl throw a ?SN ERROR
Example: PRINT 2%*N will display a number. PRINT 2% * N will display a ?SN ERROR.
Display the current AUTO line number on the screen via a GOSUB to 0FAFH to call the HL TO ASCII routine at 0FAFH (which converts the value in the HL Register Pair (assumed to be an integer) to ASCII
1A46
POP DED1
Get the current AUTOline number from the STACK and put it in Register Pair DE
1A47
PUSH DED5
Save the current AUTOline number in Register Pair DE to the STACK
See if the line number already exists by CALLing the SEARCH FOR LINE NUMBER routine at 1B2CH which looks for the line number specified in DE. Returns C/Z with the line found in BC, NC/Z with line number is too large and HL/BC having the next available location, or NC/NZ with line number not found, and BC has the first available one after that