These 3 pages are a breakdown of the Model I ROM, with comments to help you understand what is going on 0000H-0FFFFH / 1001H-2002H / 2003H-2FFFH. The two thing to keep in mind while reading this disassembly are:
The ROM had to be one big long program starting from 0000H and ending at 2FFFH and everything it does had to be wedged into that single long block of code. This means that jumps to other locations are a necessity because once a particular portion of the ROM executes, if it was allowed to keep going, unintended portions would also execute. With this, there is a LOT of jumping away.
There are only so many variables and only so many Z-80 instructions. There is no Z-80 instruction, for example, LD BC,SP. With this, there is a huge amount of variable swapping. I would dare say that whatever part of the ROM isn’t jumping, is just swapping variables around. The ROM would be a fraction of the size if there were more variables and more Z-80 instructions.
ROM Constants
The ROM was written by Bill Gates, Paul Allen, and M Davidoff on a PDP-10. Since the source code was cross-compiled, the authors put in some variables and constants
$CODE
0
Used by the authors when testing. When applied to the ROM itself, all references to $CODE are 0
CURCHR
95
Cursor character
STRSZD
50
Number of bytes reserved for the STRING area. This can be adjusted in BASIC by using the CLEAR nnnn function, which defaults to 50
DSEL$
37E1
Disk Drive Select Latch Address. Value of 01H is Drive :0
PRTAD$
37E8
Line Printer Address. If a parallel line printer is in use, it is mapped to this address
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)
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
000BH-000CH – DISK ROUTINE – “WHERE”
000B ↳ WHERE
POP HLE1
Get the address from the STACK and put it in Register Pair HL
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
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
0010H-0012H – RST 10 – GET A CHARACTER FROM THE BUFFER
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 lC90H 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
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
002B-002D ↳ $KBD
LD DE,4015HLD DE,KDCB$11 15 40
Load Register Pair DE with the starting address of the keyboard device control block. Note: 4015H holds Keyboard DCB – Device type
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.
0033-0035 ↳ $DSP
LD DE,401DHLD DE,DDCB$11 1D 40
Load Register Pair DE with the starting address of the video display device control block. Note: 401DH holds Video DCB – Device type
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 C-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.)
003B-003D ↳ $PRT
LD DE,4025HLD DE,PDCB$11 25 40
Load Register Pair DE with the starting address of the printer device control block. Note: 4025H holds Printer DCB – Device type
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)
0043
RET
004400
NOP
0045
NOP
0046H-0048H – DRIVER ENTRY ROUTINE – Part 2 – “CIOJ”
Jump to 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 table of control characters used by BASIC.
0050 ↳ KEYTAB
0DH
ENTER (0DH)
0051
0DH
Shift ENTER (0DH)
0052
1FH
CLEAR (1FH)
0053
1FH
Shift CLEAR (1FH)
0054
01H
BREAK (01H)
0055
01H
Shift BREAK (01H)
0056
5BH
Up Arrow (5BH)
0057
1BH
Shift Up Arrow (1BH) aka Escape
0058
0AH
Down Arrow (0AH) aka Line Feed
0059
1AH
ROM before v1.3 – Shift Down Arrow (1AH); ROM v1.3 – NOP to permit Shift-Down-Arrow to act as a control key
005A
08H
Left Arrow (08H)
005B
18H
Shift Left Arrow (18H)
005C
09H
Right Arrow (09H)
005D
19H
Shift Right Arrow (19H)
005E
20H
SPACE (20H)
005F
20H
Shift SPACE (20H)
0060H-0065H – 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.
0060 ↳ PAUSE
DEC BC0B
Decrement the counter in Register Pair BC
0061-0062
LD A,B OR C78 B1
There is no way to COMPARE BC against anything, so the common “trick” is to load Register A with Register B and then OR it against Register C. If you do this, Register A can only be zero if both Registers B and C are zero
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.)
0069-006B
LD A,(37ECH)LD A,FDCAD$3A EC 37
Load Register A with the disk controller status (stored in 37ECH)
006C
INC A3C
Increment the disk controller status in Register A
006D-006E
CP 02HFE 02
Check the value in Register A to see if a disk is present. It is usually FFH if there is no expansion interface operating
Since we are without disk drives at this, this would be for power on or reset … so jump to the Level II BASIC READY routine at 06CCH
0075H-0104H – INITIALIZATION ROUTINE – “INIT2”
This is part of the Level II initialization procedure. It moves a block of memory from 18F7H to 191EH up to 4080H to 40A7H. (reserved RAM. area). >Note: 4080H-408DH is a division support routine.
0075-0077
LD DE,4080HLD DE,RAMLOW11 80 40
Load Register Pair DE with the ROM storage location of the Level II BASIC division routine. Note: 4080H-408DH is a division support routine
0078-007A
LD HL,18F7HLD HL,CONSTR21 F7 18
Load Register Pair HL with the RAM storage location of the Level II BASIC division routine
007B-007D
LD BC,0027HLD BC,CNSLNR+101 27 00
Load Register Pair BC with the length of the Level II BASIC division routine (39 bytes)
007E-007F
LDIRED B0
Move the Level II BASIC division routine in ROM (18F7H-191DH) to RAM (4080H-40A6H)
0080-0082
LD HL,41E5HLD HL,BUFINI-321 E5 41
Continue with the communication region initialization by loading Register Pair HL with a RAM pointer to 41E5H
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
008E-0090
LD DE,012DHLD DE,NAVERR11 2D 01
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
0093-0095
LD HL,4152HLD HL,ERCALL21 52 41
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-0097 ↳ ERLOPS
LD (HL),0C3H36 C3
Save a C3H (the first OPCODE for JP nnnn) to the first of every 3 byte instruction in the Disk Basic command list
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
Loop from 4156H until all of the DOS links have been set to RETs
001BH-001FH – VIDEO AND PRINTER ROUTINE
00A8-00AA
LD HL,42E8HLD HL,ENBINI21 E8 42
Load Register Pair HL with the starting address of user RAM (which is 42E8H). In the original ROM source, this address was “ENBINI” although this is also defined as “TSTACK” elsewhere
00AB
LD (HL),B70
Zero the end of the buffer (i.e., 42E8H, the location of the memory pointer in Register Pair HL)
00AC-00AE
LD SP,41F8HLD SP,BUFINI+2031 F8 41
Set the current STACK pointer to 41F8H (which is 16888). In the original ROM source, this address was set as “BUFINI+20”
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
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)
Jump forward to 00D6H if there was a response to the MEMORY SIZE? question
00C4-00C6 ↳ MEMSIZ
LD HL,434CHLD HL,TSTACK+10021 4C 43
If just an ENTER was hit, need to figure it out dynamically, so load Register Pair HL with the starting address for the memory size check, which is 100 bytes (064H) after ENBINI
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
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
00EF-00F1 ↳ STRSZD
LD DE,FFCEHLD DE,65536-STRSZD11 CE FF
Load Register Pair DE with the default size of the string area (i.e., negative fifty)
00F2-00F4
LD (40B1H),HLLD (MEMSIZ),HL22 B1 40
Save the MEMORY SIZE? amount (which is in HL) to 40B1H (which holds the MEMORY SIZE? pointer)
00F5
ADD HL,DE19
Subtract the size of the string data (which was -50) from the highest memory address (stored in HE)
00F6-00F8
LD (40A0H),HLLD (STKTOP),HL22 A0 40
Save the start of string space pointer (which is now held Register Pair HL) to 40A0H. Note: 40A0H-40A1H holds the start of string space pointer
Go initialize/reset the Level II BASIC variables and pointers
00FC-00FE
LD HL,0111HLD HL,HDGMSG21 11 01
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
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
0134
LD BC,803EH01 3E 80
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-0136 ↳ SET
LD A,80H3E 80
Load Register A with 80H (which is 128) which is SET
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 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)
013D-013F
CALL 2B1CHCALL GETBYTCD 1C 2B
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-0147
RST 08H 2CHSYNCHK “,”CF
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 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)
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
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
01D9H-01F7H – CASSETTE ROUTINE
Output a pulse to the cassette recorder
01D9-01DB ↳ CTPULS
LD HL,0FC01HLD HL,<252*256>+121 01 FC
Load Register Pair HL with the command to send to the cassette … here, a top of pulse
Jump to the TURN ON MOTOR routine at 0212H if the character at the location of the current BASIC program pointer in Register Pair HL isn’t a # character
If the drive number in Register A is invalid, jump to 14E4H to display a FC ERROR
0211
DEC A3D
Decrement the drive number in Register A
0212H-021DH – CASSETTE ROUTINE (TURN ON CASSETTE) “DEFDRV”
CALL 212H will select the cassette unit specified in A-Register and start the motor. Units are numbered from one. Put 00H in A Register to turn on cassette 1, or O1H to turn on cassette 2. All registers are used.
To use a ROM call to turn on the cassette, execute the following instructions: LD A,0 and then CALL 0212H
0212-0214 ↳ DEFDRV
LD (37E4H),A32 E4 37
Set the current drive as specified by Register A
0215
PUSH HLE5
Save the current BASIC program pointer in Register Pair HL on the STACK so we can use HL for the next instruction
0216-0218
LD HL,0FF04HH,<255*256>+421 04 FF
Load Register Pair HL with the command to turn on the cassette motor
Go send the “turn on the cassette motor” command stored in HL to the cassette controller
021C
POP HLE1
Get the current BASIC program pointer from the STACK and put it in Register Pair HL
021D
RETC9
RETurn to CALLer
021EH-022BH – CASSETTE ROUTINE – “CTSTAT”
021E-0220 ↳ CTSTAT
LD HL,0FF00HLD HL,<255*256>21 00 FF
Load Register Pair HL with the mask to preserve video controller flags, but otherwise clear the cassette latch
0221-0223 ↳ CTCHG
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
0224
AND HA4
Combine the value in Register H with the contents of (403DH)
0225
OR LB5
Combine the value to send to the cassette in Register L with the adjusted value in Register A
0226-0227
OUT (0FFH),AOUT (CASIO$),AD3 FF
Send the value in Register A to port 255 (which is the cassette and video port)
0228-022A
LD (403DH),ALD (CAST$),A32 3D 40
Save the value in Register A into (403DH). Note: 403DH-4040H is used by DOS
Alternately displays and clears an asterisk in the upper right hand comer. Uses all registers.
022C-022E ↳ BCASIN
LD A,(3C3FH)3A 3F 3C
Get the character being displayed in the upper right hand corner of the video display from 3C3FH and put that character in Register A
022F-0230
XOR 0AHEE 0A
If the character in Register A is a * then make it a SPACE, else if the character in Register A is a SPACE make it a *
0231-0233
LD (3C3FH),A32 3F 3C
Display the character in Register A in the upper right hand comer of the video display
0234
RETC9
RETurn to CALLer
0235H-0240H – CASSETTE ROUTINE (READ A BYTE) – “CASIN”
Read One Byte: Reads one byte from the currently selected unit. The byte read is returned in the A-register. All other registers are preserved
This routine will read a byte from tape. A CALL 235H will return with the byte read from tape in the A Register BC, DE and HL are unchanged
To use a ROM call to read a character from cassette (after the cassette has been turned on and leader and sync have been found), CALL 0235H. The input character will be in the A register. Again, the routine at 0235H must be called frequently enough to sustain the 500 baud rate if more than one character is to be read.
0235 CASIN
PUSH BCC5
Save the value in Register Pair BC on the STACK
0236
PUSH HLE5
Save the value in Register Pair HL on the STACK
0237-0238
LD B,08H06 08
Load Register B (which is what DJNZ decrements to loop) with the number of bits to read (which is 8)
Loop that instruction until all eight bits have been read into A
023E
POP HLE1
Get the value from the STACK and put it in Register Pair HL
023F
POP BCC1
Get the value from the STACK and put it in Register Pair BC
0240
RETC9
RETurn to CALLer
0241H-0260H – CASSETTE ROUTINE (READ A BIT) – “CTBIT”
Routine waits for timing pulse, and then performs a timing loop. When the time is up it tests the tape for a bit, which will be “1” if present and “0” if not. A CALL 241H is used by 235H eight times to input one byte.
0264 Writes the byte in the A Register to tape. BC, DE and HL are unchanged by a CALL 264H
0241 ↳ CTBIT
PUSH BCC5
Save the value in Register Pair BC on the STACK
0242
PUSH AFF5
Save the value in Register A on the STACK
0243-0244 CB0
IN A,(0FFH)IN A,(CASIO$)DB FF
Read a bit from the cassette port (waiting for a clock pulse)
0245
RLA17
Rotate Register A left one bit position with the contents of bit 7 copied to the carry flag. By doing this, the start bit would rotate to the carry flag for easy testing
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)
0264H-027DH – CASSETTE ROUTINE (WRITE A BYTE) – “CASOUT”
Writes the byte in the A Register to tape. BC, DE and HL are unchanged by a CALL 264H.
To use a ROM call to write a character onto cassette tape (after the cassette has been turned on and leader and sync have been recorded), load the character into the A Register And CALL 0264H. If more than one character is to be written, the CALL 0264H must be executed with sufficient frequency to sustain the 500 baud recording rate. The routine provides automatic timing.
0264 ↳ CASOUT
PUSH HLE5
Save the value in Register Pair HL on the STACK
0265
PUSH BCC5
Save the value in Register Pair BC on the STACK
0266
PUSH DED5
Save the value in Register Pair DE on the STACK
0267
PUSH AFF5
Save the value in Register A on the STACK
0268-0269
LD C,08H0E 08
Load Register C with the number of bits to be written (i.e., 8)
026A
LD D,A57
Load Register D with the byte to be written from Register A because A is about to get used in the next line
0284H-0292H – CASSETTE ROUTINE (TURN ON CASSETTE AND WRITE LEADER) – “CWRTON”
A call to 0284H writes a Level II leader on currently selected unit. The leader consists of 256 (decimal) binary zeros followed by a A5H. Uses the B and A registers.
Calls 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). In this case, it writes the SYNC byte
0293H-029EH – CASSETTE ROUTINE (TURN ON CASSETTE AND READ LEADER) – “CSRDON”
Go evaluate the drive number and turn on that cassette drive’s motor
Read Leader: Reads the currently selected cassette unit until an end of leader (A5) is found. An asterisk is displayed in the upper right hand corner of the video display when the end is found. Uses the A-register
Reads from tape until the leader is found, then keeps going until it is bypassed and the sync byte (ASH) is found, when it returns. DE, BC and HL are unchanged by this
0296 ↳ CSRDON+3
PUSH HLE5
Reads from tape until the leader is found, then keeps going until it is bypassed and the sync byte (A5H) is found, when it returns. DE, BC and HL are unchanged by this.
Save the current BASIC program pointer in Register Pair HL on the STACK
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
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
*02E4
INC HL
Increment the input buffer pointer in Register Pair HL
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
0314H – Read 2 bytes from the tape into Register Pair HL – “CADRIN”
This routine is commonly used by the SYSTEM routine to read the last two bytes on tape which give the entry point. A JP (HL) can then be executed to jump to the location specified, when used for this purpose. Only HL is used by this routine
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
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
At this point, A is either +1, -1, or 0. The ROM handles this by testing for a positive number (1 = Cassette), and then a non-zero number (-1 = Printer), and then flows down (0 = Display) if neither of those apply
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
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
035B
PUSH DED5
Since the next routine uses DE, save the value in Register Pair DE on the STACK
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
This routine resets device type flag at 409CH to zero (output to video display), also outputs a carriage return to the line printer if printer is not at beginning of line (determined by checking the contents of the printer line position flag at 409BH – if flag contains zero, printer is at start of line). Note that if printer line position flag does not contain zero and the printer is not on line, the computer will “hang up” waiting for a “printer ready” signal.
038B ↳ FINLPT
XOR AAF
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-03E2H – DRIVER ENTRY ROUTINE – “CIO”
This routine is called from a RST 14 (with a device code of 01H in Register B), RST 1C (with a device code of 02H in Register B), and RST 24 (with a device code of 04H in Register B).
On entry, BC shoud contain the Device Control Block and A may contain (if needed) the output control/data
According to the original ROM notes, this is the Character I/O Linkage to Device Driver routine. On entry Register Pair DE to point at the Device Control Block and Register A will hold the output or control data, if any. On exit, the codes depend on whether a byte or a control code was passed. If this was an I/O operation, Register A will hold that input/output data byte. If this was a I/O control operation, Register A will hold the device status and the Z FLAG will be set if the device is ready.
03C2 ↳ CIO
PUSH HLE5
Save Register Pair HL on the STACK
03C3-03C4
PUSH IXDD E5
Save the value in Register Pair IX on the STACK
03C5
PUSH DED5
Save the starting address of the device control block in Register Pair DE on the STACK
03C6-03C7
POP IXDD E1
Get the starting address of the device control block from the STACK and put it in Register Pair IX, so now IX = DCB + 0
03C8
PUSH DED5
Save the value in Register Pair DE on the STACK
03C9-03CB
LD HL,03DDHLD HL,CIORTN21 DD 03
Load Register Pair HL with a return address of 03DDH
03CC
PUSH HLE5
Save the return address in Register Pair HL on the STACK
03CD
LD C,A4F
Save the character to process (current held in Register A) to Register C so we can use Register A
03CE
LD A,(DE)1A
Load Register A with the device type code (stored the memory location pointed to by DE)
03CF
AND BA0
Isolate the device code bits in A by AND’ing with the device codes in B
03D0
CP BB8
Check to see if the updated device type code in Register A is the same as the driver entry code in Register B
Jump to the DOS exit link at 4033H if the updated device type code in Register A isn’t the same as the driver entry code in Register B
03D4-03D5
CP 02HFE 02
At this point we know that the updated device type code in A is the same as the driver code entry, so let’s move on. First, reset the flags
03D6-03D8
LD L,(IX+01H)DD 6E 01
Load Register L with the LSB of the driver entry address at the location of the device control block pointer in Register Pair IX plus one
03D9-03DB
LD H,(IX+02H)DD 66 02
Load Register H with the MSB of the driver entry address at the location of the device control block pointer in Register Pair IX plus one
03DC
JP (HL)E9
Jump to the driver entry address in Register Pair HL
03DD ↳ CIORTN
POP DED1
Get the value from the STACK and put it in Register Pair DE
03DE-03DF
POP IXDD E1
Get the value from the STACK and put it in Register Pair IX
03E0
POP HLE1
Get the value from the STACK and put it in Register Pair HL
03E1
POP BCC1
Get the value from the STACK and put it in Register Pair BC
03E2
RETC9
RETurn to CALLer
03E3H-0457H – KEYBOARD DRIVER – “KEY”
This is the keyboard driver. It scans the keyboard and converts the bit pattern obtained to ASCII and stores it in the A register.
According to the original ROM notes, this is the Keyboard Driver. On exit, Register A to hold the data byte received (or 0 if none). On entry, [IX] should point to the DCB, which is laid out as follows:
DCB + 0 = DCB Type
DCB + 1 = Driver Address (LSB)
DCB + 2 = Driver Addres (MSB)
DCB + 3 = 0
DCB + 4 = 0
DCB + 5 = 0
DCB + 6 = “K”
DCB + 7 = “I”
03E3-03E5 ↳ KEY
LD HL,4036HLD HL,KYBT$21 36 40
Load Register Pair HL with the keyboard work area’s starting address (which is 4036H). Note: 4036H-403CH is the keyboard work area
03E6-03E8
LD BC,3801HLD BC,KEYAD$+101 01 38
Load Register Pair BC with the keyboard memory’s starting address (which is 3801H)
03E9-03EA
LD D,00H16 00
Zero Register D, which will be used to track the keyboard code
03EB ↳ KEYLP
LD A,(BC)0A
Load Register A with the value at the location of the keyboard memory pointer in Register Pair BC (which is row N)
03EC
LD E,A5F
Load Register E with the keyboard memory value in Register A (8 column bits)
03ED
XOR (HL)AE
Check for inequality by XORing the value at the location of the keyboard work area pointer in Register Pair HL with the keyboard memory value in Register A
03EE
LD (HL),E73
Save the keyboard memory value (the column bits) in Register E at the location of the keyboard work area pointer in Register Pair HL
03EF
AND EA3
Test for the active row by masking the adjusted value in Register A with the value at the location of the keyboard work area pointer in Register Pair HL
Jump to 03EBH if the whole of keyboard memory hasn’t been checked (by POSITIVE bit [Bit 7] being on). This has the effect of looping over the first 6 rows of the keyboard but NOT doing row 7, which has the shift key
03F9
RETC9
RETurn to CALLer
03FAH-040AH – Accept a Keyboard Downstroke and Convert it to ASCII – “KEYDWN”
03FA ↳ KEYDWN
LD E,A5F
Save the column number from the new keypress in Register A in Register E
03FB
LD A,D7A
Load Register A with the row counter in Register D (this is going to cycle from 0 through 6)
03FC
RLCA07
Multiply the row counter in Register A by two
03FD
RLCA07
Multiply the row counter in Register A by two
*03FB-03FD
JP 011CH
For ROM v1.2, jump to the new keyboard debounce routine. That routine includes the now deleted RCLA, RCLA instructions which had to be killed to make room for this 3 byte jump opcode
03FE
RLCA07
Multiply the row counter in Register A by two
03FF
LD D,A57
Load Register D with the row counter in Register A
0400-0401
LD C,01H0E 01
Load Register C with the starting column counter (as bit 0)
0402 ↳ KEYDLP
LD A,C79
Load Register A with the column counter in Register C (as a mask)
0403
AND EA3
Turn off some bits so we can check to see if the column counter in Register A is the same as the active column number in Register E
Call the delay routine at 0060H (which will delay BC times 14.65)
0452
LD A,D7A
Load Register A with the ASCII value for the key pressed (saved in Register D)
0453-0454
CP 01HFE 01
Check to see if the BREAK key was pressed
0455
RET NZC0
Return if the BREAK key wasn’t pressed
0456
RST 28HEF
If the BREAK key was pressed,call 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)
044B
RET57
RETurn to CALLer
0458H-058CH – DISPLAY DRIVER – “DSP”
This is the video driver. On entry, the character to be displayed should be in the C register. On exit, A would contain the character at the cursor (if called for an INPUT). This routine handles scrolling etc.
Register IX points to the DCB, so IX+0 = the DCB type, IX+1 = LSB of the Driver Address, IX+2 = MSB of the Driver Address, IX+3 = LSB of the Cursor Position, IX+4 = MSB of the Cursor Position, IX+5 = Cursor Character, IX+6 = “D”, and IX+7=”O”
According to the original ROM notes, this is the Display Driver. On exit, Register A to hold the character read from the new cursor position (if the routine was called to look for that). On entry, [IX] should point to the DCB, which is laid out as follows:
DCB + 0 = DCB Type
DCB + 1 = Driver Address (LSB)
DCB + 2 = Driver Addres (MSB)
DCB + 3 = Cursor Position Address (LSB)
DCB + 4 = Cursor Position Address (MSB)
DCB + 5 = Cursor Character
DCB + 6 = “D”
DCB + 7 = “O”
0458-045A ↳ DSP
LD L,(IX+03H)DD 6E 03
Load Register L with the LSB of the current cursor position at the location of the video device control block pointer in Register Pair IX plus three
045B-045D
LD H,(IX+04H)DD 66 04
Load Register H with the MSB of the current cursor position at the location of the video device control block pointer in Register Pair IX plus four
Since CP returns C set if Register A (the character to be displayed) is less than the test value (20H; meaning it is a control character below SPACE), jump to 0506H if the character to be displayed in Register A is a control code
046D-046E
CP 80HFE 80
Check to see if the character to be displayed in Register A is a graphic character or space compression code
Since CP returns NC set if Register A (the character to be displayed) is greater than or equal to the test value (80H; meaning it is a graphic character or a space compression code), jump to 04A6H if the character to be displayed in Register A is a graphic character or space compression code
0471-0472
CP 40HFE 40
Check to see if the character to be displayed in Register A is an alphabetic character
Since CP returns C set if Register A (the character to be displayed) is less than the test value (40H; meaning it is below the “@” – so a special character or a number), jump to 047DH if the character to be displayed in Register A is a nonalphabetic character
0475-0476
SUB 40HSUB “@”D6 40
If we are still here, then the character in Register A is between 40H (“@”) and 7FH (the last character before the graphics). Drop this down 40H
0477-0478
CP 20HFE 20
Check to see if the character to be displayed in Register A is a lower case character
Since CP returns C set if Register A (the newly subtracted character) is less than the test value (20H or 32), jump to 047DH since the character to be displayed in Register A isn’t lower case
047B-047C
SUB 20HD6 20
Convert the lower case character in Register A to upper case by subtracting 32
Since the cursor is on, save the character being displayed in Register D as the cursor on/ off flag at the location of the video device control block pointer in Register Pair IX plus five
0490-0491
LD (HL),5FHLD (HL),CURCHR36 5F
Display the cursor character (of 5FH) at the current location of the cursor in Register Pair HL
0492-0494 ↳ DSPRTN
LD (IX+03H),LDD 75
Save the LSB of the current cursor position in Register L at the location of the video device control block in Register Pair IX plus three
0495-0497
LD (IX+04H),HDD 74
Save the MSB of the current cursor position in Register H at the location of the video device control block in Register Pair IX plus four
0498
LD A,C79
Load Register A with the character that was displayed in Register C
0499
RETC9
RETurn to CALLer
049AH – Read the character at the current position of the display – “DSPRD”
049A-049C ↳ DSPRD
LD A,(IX+05H)DD 7E 05
Load Register A with the cursor on/off flag
049D
OR AB7
Check to see if the cursor is on or off
049E
RET NZC0
Return if the cursor is on and is therefore covering the character
049F
LD A,(HL)7E
If the cursor is off, show the character it was hiding instead by loading Register A with the character at the location of the current cursor position in Register Pair HL
04A0
RETC9
RETurn to CALLer
04A1H – Go to the beginning of the line – “DSPBOL”
04A1 ↳ DSPBOL
LD A,L7D
Load Register A with the LSB of the current position in Register L
04A2-04A3
AND 0C0HAND 1100 0000E6 C0
Turn off some bits so we can it will point to the beginning of the line by ANDing it against 1100 0000 to remove the lowest 6 bits (so it will be XX00H, XX40H, XX80H, or XXC0H). This is the same thing as a CARRIAGE RETURN but without the associated LINE FEED
04A4
LD L,A6F
Load Register L with the updated value in Register A
04A5
RETC9
Return with the new video buffer address stored in HL
04A6H – Handle graphic characters – “DSPHRC”
04A6-04A7 ↳ DSPGRC
CP C0HFE C0
Check to see if the character to be displayed in Register A is a space compression character
Since CP returns C set if Register A (the character to be displayed) is less than the test value (C0H; meaning it is below the space compression characters or, another way, is a graphic character), jump to 047DH if the character to be displayed in Register A isn’t a space compression character (which means it is a graphic character)
04AA-04AB
SUB C0HD6 C0
Now we know we have a space compresison code, so adjust the value in Register A so that it will hold the number of spaces to be displayed
Jump to 0480H if there aren’t any spaces to be displayed
04AE
LD B,A47
Now we know it is a space compression character and that at least one space is to be displayed, so load Register B with the number of spaces to be displayed in Register A (since the space compression codes are sequential from 0C0H up)
04AFH – Handle Space Compression characters – “DSPSPC”
Load Register Pair HL with the starting address of video memory (which is 3C00H)
04C3-04C5
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
04C6-04C7
AND 0F7HAND 1111 0111E6 F7
Mask Register A against 1111 0111, forcing Bit 3 to OFF to denote 64 characters per line
04C8-04CA
LD (403DH),ALD (CAST$),A32 3D 40
Put the masked Register A back into 403DH
04CB-04CC
OUT (0FFH),AD3 FF
Send the value in Register A out port 255 which is the video/cassette port
04CD
RETC9
RETurn to CALLer
04CEH – Backspace – “DSPBSP”
04CE ↳ DSPBSP
DEC HL2B
Decrement the current cursor position (i.e., backspace) in Register Pair HL
04CF-04D1
LD A,(CAST$)LD A,(403DH)3A 3D 40
Load Register A with the 32/64 character per line flag (which is at 403DH). Note: 403DH-4040H is used by DOS
04D2-04D3
AND 08HAND 0000 1000E6 08
Turn off some bits so we can check to see if it’s 32 or 64 characters per line by ANDing it against 0000 1000
Since the AND leaves us with either a 0 (64 characters per line) or a 1 (32 characters per line), jump to 04D7H if it’s 64 characters per line
04D6
DEC HL2B
Right now we know it is 32 characters per line (since we didn’t jump away), so we need to backspace AGAIN by decrementing the current cursor position in Register Pair HL
04D7-04D8 ↳ DSPBS2
LD (HL),20HLD (HL),” “36 20
Display a space character at the location of current cursor position in Register Pair HL
04D9
RETC9
RETurn to CALLer
04DAH – Cursor Left – “DSPLFT”
04DA-04DC ↳ DSPLFT
LD A,(403DH)LD A,(CAST$)3A 3D 40
Load Register A with the 32/64 character per line flag (which is at 403DH). Note: 403DH-4040H is used by DOS
04DD-04DE
AND 08HAND 0000 1000E6 08
Mask Register A against 0000 1000 to isolate Bit 3 and set the flags Z and NZ based on that bit
If Bit 3 was not zero then we have 32 characters per line, so GOSUB to 04E2H. Note: This is a little trick. By doing a GOSUB to the next line, you effectively block the RET at the end of the routine from jumping out, and instead it jumps back here; thus running the routine TWICE
04E2 ↳ DSPLF2
LD A,L7D
We are actually here regardless of what Bit 3 was. Load Register A with the LSB of the current cursor position in Register L
04E3-04E4
AND 3FHAND 0011 1111E6 3F
Mask the value in Register A by ANDing it against 0011 1111 to backspace LSB of curstor to the previous line and then ..
04E5
DEC HL2B
Backspace the cursor by 1 by decrementing the current cursor position in Register Pair HL
04E6
RET NZC0
Return if still on the same line
04E7H – Cursor Down – “DSPDWN”
This is a space saver because if the cursor isn’t on the same line it needs to move down, so jumping here is also jumping to a CURSOR DOWN routine that was simply a fall-through from a wrap around
04E7-04E9 ↳ DSPDWN
LD DE,0040H11 40 00
We know we are not on the same line anymore so load Register Pair DE with the length of a line on the video display (which is 64)
04EA
ADD HL,DE19
Skip down 1 line by adding the length of a line on the video display in Register Pair DE to the current cursor position in Register Pair HL
04EB
RETC9
RETurn to CALLer
04ECH – Cursor Right – “DSPRHT”
04EC ↳ DSPRHT
INC HL23
Increment the current cursor position in Register Pair HL
04ED
LD A,L7D
Load Register A with the LSB of the current cursor position in Register L
04EE-04EF
AND 3FHAND 0011 1111E6 3F
Turn off some bits so we can check to see if the cursor is still on the same line via an overflow through ANDing it against 0011 1111
04F0
RET NZC0
Return if the cursor is still on the same line
04F1H – Cursor Up – “DSPUP”
Same trick as dealing with CURSOR DOWN if there was an overflow, this does a CURSOR UP if you back up too far
04F1-04F3 ↳ DSPUP
LD DE,0FFC0H11 C0 FF
Now we know the cursor is no longer on the same line, so we need to load Register Pair DE with a negative length of a line on the video display (which is -64)
04F4
ADD HL,DE19
Add the negative length of a line on the video display in Register Pair DE (i.e., -64) to the current cursor position in Register Pair HL
04F5
RETC9
RETurn to CALLer
04F6H – Set up 32-Character mode – “DSPETB”
04F6-04F8 ↳ DSPETB
LD A,(403DH)LD A,(CAST$)3A 3D 40
This routine is going to change the display to 32 character mode so first we need to load Register A with the 32/64 character per line flag stored at 04F6H. Note: 403DH-4040H is used by DOS
04F9-04FA
OR 08HOR 0000 1000F6 08
Turn on some bits in Register A to adjust the value in Register A for 32 characters per line by ORing it against 0000 1000
04FB-04FD
LD (403DH),ALD (CAST$),A32 3D 40
Save the resulting value in Register A back into 403DH (the 32/64 character per line flag). Note: 403DH-4040H is used by DOS
04FE-04FF
OUT (0FFH),AD3 FF
Send the value in Register A out the port 255 (the video/cassette)
0500
INC HL23
Increment the current cursor position in Register Pair HL
0501
LD A,L7D
Load Register A with the LSB of the current cursor position in Register L
0502-0503
AND 0FEHE6 FE
Turn off some bits so we can make the LSB value (in Register A) to be an even value (since we are in 32 character per line mode) by ANDing it against 1111 1110
0504
LD L,A6F
Load Register L with the updated value in Register A
0505
RETC9
RETurn to CALLer
0506H – Process control characters – “DSPCTL”
0506-0508 ↳ DSPCTL
LD DE,0480HLD DE,DSPSKP11 80 04
Load Register Pair DE with the return address
0509
PUSH DED5
Save the return address in Register Pair DE on the STACK
050A-050B
CP 08HFE 08
Check to see if the character in Register A is a backspace and erase character (i.e., 08H)
Since CP returns C set if Register A (the character to be displayed) is less than the test value (08H), jump to 04CEH as the character to be displayed in Register A is a backspace cursor and erase character
050E-050F
CP 0AHFE 0A
Check to see if the character in Register A is less than a line feed character
0510
RET CD8
Return if the character to be displayed in Register A is less than a line feed character so we can ignore them
0511-0512
CP 0EHFE 0E
Check to see if the character to be displayed in Register A is less than or equal to a turn on the cursor character
Increment the current cursor position in Register Pair HL
054B ↳ DSPOT2
LD A,H7C
Load Register A with the MSB of the current cursor position in Register H
054C-054D
CP 40HFE 40
Check to see if the end of video memory plus one has been reached
054E
RET NZC0
Return if the end of video memory plus one hasn’t been reached
054F-0551
LD DE,FFC0H11 C0 FF
Load Register Pair DE with a negative length of a line on the video display (i.e., -64)
0552
ADD HL,DE19
Move the pointer back 1 line by adding the negative length of a line on the video display in Register Pair DE to the current cursor position in Register Pair HL
0553
PUSH HLE5
Save the current cursor position in Register Pair HL on the STACK
0554H – Part of the Display routine – “DSPROL”
Scroll the screen upward by one line
0554-0556 ↳ DSPROL
LD DE,3C00HLD DE,DSPAD$11 00 3C
Load Register Pair DE with the starting address of video memory
0557-0559
LD HL,3C40HLD HL,DSPAD$+6421 40 3C
Load Register Pair HL with the starting address of the second line of the video memory
055A
PUSH BCC5
Save the value in Register Pair BC on the STACK
055B-055D
LD BC,03C0HLD BC,1024 – 6401 C0 03
Load Register Pair BC with the length of video memory to be moved (which is 15 lines or 960 bytes)
055E-055F
LDIRED B0
Move the last fifteen lines of video memory to the first fifteen lines of video memory to scroll 1 line
0560
POP BCC1
Get the value from the STACK and put it in Register Pair BC
0561
EX DE,HLEB
Load Register Pair HL with the starting address of the sixteenth line of video memory
To use a ROM call to clear the video screen from (including) position N – where N is an integer between 0 and 1023 (decimal), inclusive, to the end of the display, Load the HL Register with the value 3C00H + N and then CALL 057CH
057C ↳ DSPEOF
PUSH HLE5
Save the cursor position in Register Pair HL on the STACK.
Clear to end of frame routine. To use this routine load the HL Register Pair with the screen address from which you want the erasing to start. The DE and A registers are used
057D-057F ↳ DSPERF
LD DE,4000HLD DE,DSPAD$+102411 00 40
Load Register Pair DE with the end of video memory plus one (which is 4000H)
0580-0581 ↳ DSPERA
LD (HL),20HLD (HL),” “36 20
Display a space (which is 20H) at the video memory pointer in Register Pair HL
0582
INC HL23
Increment the video memory pointer in Register Pair HL
0583
LD A,H7C
Load Register A with the MSB of the video memory pointer held in Register H
0584
CP DBA
Check to see if the MSB of the video memory pointer in Register A is the same as the MSB of the ending memory pointer in Register D
LOOP back to 0580H (display a space and move forward 1) if the MSB of the video memory pointer in Register A isn’t the same as the MSB of the ending memory pointer in Register D
0587
LD A,L7D
Load Register A with the LSB of the video memory pointer in Register L
0588
CP EBB
Check to see if the LSB of the video memory pointer in Register A is the same as the LSB of the ending memory pointer in Register E
LOOP back to 0580H (display a space and move forward 1) if the LSB of the video memory pointer in Register A isn’t the same as the LSB of the ending memory pointer in Register E
058B
POP HLE1
Get the current cursor position from the STACK and put it in Register Pair HL
058C
RETC9
RETurn to CALLer
058DH-0D8H – PRINTER DRIVER – “PRT”
According to the original ROM notes, this is the Printer Driver. On entry, Register C to hold the character to be sent to the printer, and [IX] should point to the DCB, which is laid out as follows:
DCB + 0 = DCB Type
DCB + 1 = Driver Address (LSB)
DCB + 2 = Driver Addres (MSB)
DCB + 3 = Lines Per Page (or 0 if top-of-page)
DCB + 4 = Line Counter
DCB + 5 = 0
DCB + 6 = “P”
DCB + 7 = “R”
058D ↳ PRT
LD A,C79
Load Register A with the character to be sent to the printer in Register C
058E
OR AB7
Set the status flags and test A to see if it is zero
Jump to 05B4H if the character to be sent to the printer in Register A isn’t a conditional skip to the top of the form character (a/k/a isn’t a form feed)
0599
XOR AAF
Zero Register A (which causes the null character to be printed)
059A-059C
OR (IX+03H)DD B6 03
Check to see if the number of lines per page at the location of the printer device control block pointer in Register Pair IX plus three is the same as the value in Register A
Load Register A with the number of lines per page at the location of the printer device control block in Register Pair IX plus three
05A2-05A4 ↳ PRTVT
SUB (IX+04H)DD 96 04
Subtract the lines printed so far at the location of the printer device control block pointer in Register Pair IX plus four from the number of lines per page in Register A
05A5
LD B,A47
A now has the number of lines to skip, so load B with with the number of lines to skip (since DJNZ loops based on B)
Call the GET PRINTER STATUS routine at 05D1H (which returns the status of the line printer in the status Register As 0 if the printer is ready, and otherwise with a reason code if not ready)
Call the GET PRINTER STATUS (which returns the status of the line printer in the status Register As 0 if the printer is ready, and otherwise with a reason code if not ready)
Get the character to be sent to the printer from the STACK and put it in Register A
05BB-05BD
LD (37E8H),ALD (PRTAD$),A32 E8 37
Send the character in Register A to the printer by putting the character into 37E8H
05BE-05BF
CP 0DHFE 0D
Check to see if the character which was just sent to the printer was a carriage return
05C0
RET NZC0
If the character sent to the printer in Register A isn’t a carriage return, return out of the print routine
05C1-05C3
INC (IX+04H)DD 34 04
Now we know the printer just got a carriage return, so increment the number of lines printed so far at the location of the printer device control block pointer in Register Pair IX plus four
05C4-05C6
LD A,(IX+04H)DD 7E 04
Load Register A with the number of lines printed so far at the location of the printer device control block pointer in Register Pair IX plus four
05C7-05C9
CP (IX+03H)DD BE 03
Check to see if the number of lines printed so far in Register A is the same as the number of lines per page at the location of the printer device control block pointer in Register Pair IX plus three
05CA
LD A,C79
Load Register A with the character sent to the printer in Register C
05CB
RET NZC0
Return if the number of lines printed so far isn’t the same as the number of lines per page
05CC-05CF ↳ PRTOP
LD (IX+04H),04HDD 36 04 00
Zero the number of lines printed so far at the location of the printer device control block pointer in Register Pair IX plus four
05D0
RETC9
RETurn to CALLer
05D1H – Part of the Printer Routine – “PRTSTA” Get printer status routine
A call to 05D1 will return the status of the line printer in the status Register As 0 if the printer is ready/selected, and non-zero if not ready, as follows:
Bit 7 = Printer Busy. 1=Busy
Bit 6 = Paper Status. 1= Out of paper.
Bit 5 = Printer Ready. 1 = Ready.
All other bits are not used.
05D1-05D3
LD A,(37E8H)LD A,(PRTAD$)3A E8 37
Get the status value from the printer (which is held in 37E8H) and put it in Register A
05D4-05D5
AND F0HAND 1111 0000E6 F0
Since only bits 7, 6, and 5 are used when checking status, mask out the rest of Register A by ANDing it against 1111 0000
05D6-05D7
CP 30HFE 30
Check to see if the printer is ready by comparing the ANDed value against 0011 0000 (which isolates bits 5 and 4, so the result is either ZERO if they are set or anything else if they are not)
05D8
RETC9
RETurn to CALLer
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
Turn off the cursor by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
066E
LD A,C79
Load Register A with the length of the input (=buffer size) in Register C
066F
SUB B90
Subtract the number of bytes remaining in the input buffer area in Register B from the length of the input buffer area in Register A
0670
LD B,A47
Load Register B with the number of characters in the input buffer area in Register A
0671
POP AFF1
Get the value from the STACK and put it in Register Pair AF. This also sets the CARRY flag if BREAK and unsets it if CARRIAGE RETURN
0672
POP HLE1
Get the starting address of the input buffer area from the STACK and put it in Register Pair HL
0673
RETC9
RETurn to CALLer
0674H-06D1H – INITIALIZATION ROUTINE – “INIT”
0674-0675 ↳ INIT
OUT (0FFH),AD3 FF
Send the zero in Register A out the video/cassette port. Earlier versions of the ROM looped back to this instruction instead of the next one, which pounded 0FFH!
0676-0678
LD HL,06D2HLD HL,INITR21 D2 06
Load Register Pair HL with the starting address of the RST’s and the video/keyboard/printer DCB’s
0679-067B
LD DE,4000HLD DE,RST1$11 00 40
Load Register Pair DE with the starting address of the communications region
067C-067E
LD BC,0036HLD BC,INITRL01 36 00
Load Register Pair BC with the length of the ROM area to be moved (which is 54 bytes)
067F-0680
LDIRED B0
Move the RST’s and DCB’s (06D2H-0707H) to the RAM vector area of 4000H-4035H
Jump to the Level II Initialization Routine at 0075H if the disk controller isn’t present
069FH – Bootstrap from Diskette – “BOOT”
069F-06A0 ↳ BOOT
LD A,01H3E 01
So now we know there is a disk controller, so lets load Register A with the unit select mask for Drive :0
06A1-06A3
LD (37E1H),ALD (DSEL$),A32 E1 37
Select Drive :0 and turn the motor on by loading 01H into 37E1H
06A4-06A6
LD HL,37ECHLD HL,FDCAD$21 EC 37
Load Register Pair HL with the address of the disk command/status Register of 37ECH
06A7-06A9
LD DE,37EFHLD DE,FDCAD$+311 EF 37
Load Register Pair DE with the address of the disk data Register of 37EFH
06AA-06AB
LD (HL),03H36 03
Load the disk command/status Register (37ECH) with command 03H (RESTORE and POSITION TO TRACK 0). More specifically, this sends 0000 0011 to the register, which is broken down as follows (left to right): Restore (0000), Do NOT load head (0), Verify Off (0), 20 ms step (11)
Call the delay routine at 0060H (which will delay BC times 14.65; about 3 seconds)
06B2-06B3 ↳ BOOTDL
BIT 0,(HL)CB 46
Top of a loop.Check to see if the diskette controller is busy by testing Bit 0 of 37ECH (Floppy Disk Controller Status). Bit 0 is the BUSY status bit. Per WD, commands should only be loaded into the command Register when the Busy Status but is off
Loop back to that test in 06B2H until the disk is no longer busy
06B6
XOR AAF
Zero Register A
This routine loads Drive 0, Track 0, Sector 0 into 4200H-4455H, and then jumps to 4200H
06B7-06B9
LD (37EEH),ALD (FDCAD$+2),A32 EE 37
Save the value in Register A in the disk sector Register (at 37EEH)
06BA-06BC
LD BC,4200HLD BC,MEM$01 00 42
Load Register Pair BC with the address in memory to place the sector read (which is 4200H)
06BD-06BE
LD A,8CH3E 8C
Load Register A with the command to read the sector. More specifically, send 10001100, which is broken down (from left to right) as Read Sector (100xxx00), Single Record (0), IBM Format (1), Enable HLD, HLT, and 10 msec delay
06BF
LD (HL),A77
Put the command in Register A (read the sector) in the disk command Register of 37ECH. This then reads Drive 0 Track 0 Sector 0 into 4200H-4455H
06C0-06C1 ↳ BOOTLP
BIT 1,(HL)CB 4E
Top of a loop to check the disk command/status Register of 37ECH to see if there is data available
Now that the entire first sector has been read into 4200H-4455H, jump there!
06CCH-06CEH – Alternative re-entry point into BASIC – “RESETR”
This is an alternative re-entry point into BASIC. A JP 6CCH is often better than a jump to lA19H as the latter sometimes does strange things to any resident BASIC program
06CC-06CE ↳ RESETR
LD BC,1A18HLD BC,STPRDY01 18 1A
Load Register Pair BC with the starting address of the Level II BASIC READY routine (which is kept at 1A18H)
This will be 4009H – it is a jump to RST 20H (tests for data type). DOS will overwrite this value
06DE
RETC9
This will be 400CH – is a RETurn from RST 28H (which is a jump to 4BA2H for DOS)
06DF
NOP
06E000
NOP
06E1
RETC9
This will be 400FH – it is a RETurn from RST 30H (which is a jump to 44B4H for DOS)
06E2
NOP
06E300
NOP
This will be 4012H – RST 38H vector DI/RET (JP 4518H for DOS)
06E4
EIFB
This is the interrupt entry point vector
06E5
RET
06E600
NOP
This is the keyboard DCB
06E7
01
Keyboard DCB + 0
06E8
03 E3
Keyboard DCB + 1 – Driver Address
06EA
00
Keyboard DCB + 3
06EB
00
Keyboard DCB + 4
06EC
00
Keyboard DCB + 5
06ED
“K”
Keyboard DCB + 6
06EE
“I”
Keyboard DCB + 7
This is the display DCB
06EF
07
Display DCB + 0
06F0-06F1
58 04
Display DCB + 1,2 – Driver Address
06F2-06F3
00 3C
Display DCB + 3,4 – Cursor Position Address
06F4
0
Display DCB + 5 – Cursor Character
06F5
“D”
Display DCB + 6
06F6
“O”
Display DCB + 7
This is the printer DCB
06F7
6
Printer DCB + 0
06F8-06F9
8D 05
Printer DCB + 1,2 – Driver Address
06FA
43
Printer DCB + 3 – Lines per page
06FB
0
Printer DCB + 4 – Line Counter
06FC
0
Printer DCB + 5
06FD
“P”
Printer DCB + 6
06FE
“R”
Printer DCB + 7
06FF
JP 5000HC3 00 50
This will be 402DH, and SYS 0 will change this to JP 4400H
0702
RST 00HC7
This will be 4030H, and SYS 0 will change this to LD A,A3H
0703
NOP
070400
NOP
This will be 4032H, and SYS 0 will change this to RST 28H
0705
LD A,00H3E 00
This will be 4033H, and SYS 0 will change this to 44BBH
0707
RET
070BH – MATH!
The math routines in the Level II ROM are fairly complex because they have to be. The following is a brief-ish description of the overall intentions of the authors:
RAM Locations / Purpose
DFACLO
4
Four lowest orders for double precision
FACLO
3
Low order of Mantissa, Middle Order of Mantissa, High Order of Mantissa
FAC
2
Exponent, Temporary Complement of the Sign in the MSB
ARGLO
7
Temporary location of second argument for double precision
ARG
1
FBUFFR
Buffer for FOUT
Floating Point Formula
The sign is the first bit of the mantissa
The mantissa is 24 bits long
THe binary point is to the left of the MSB
The manitssa is positive, with a one assumed to be where the sign bit is
The sign of the exponent is the first bit of the exponent
The exponent is stored in excess of 80H (i.e., it is a signed 8 bit number with 80H added to it)
An exponent of zero means the number is zero, and all other bytes are ignored
In memory a number looks like this:
Bits 17-24 of the mantissa
Bits 9-16 of the mantissa
The sign is in Bit 7
Bits 2-8 of the mantassa are in bits 6-0
The exponent is stored as a signed number + 80H
Bit 1 of the mantissa is always a 1
Calling Math Routines
To call a ONE argument routine, the argument should be in the FAC
To call a TWO argument routine, the first argument should be in BCDE and the second argument should be in the FAC
Regardless of which is desired, the result will be in the FAC
ROM routines with a “S” point to two argument operations which have (HL) pointing to the first argument instead of it being in BCDE. “MOVERM” is called to get the argument into the registers.
ROM routines with a “T” assume that the first argument is on the stack. “POPR” is used to get the arguments into the registers. Note: Never CALL a “T” routine, the return address will be confused with a number.
Stack Usage
The to LO’s are pushed first, and then the HO and finally the sign. The lower byte of each part is in the lower memory address, so when the number ios POPed into the registers, the higher order byte will be in the higher order register of the register pair (i.e., B, D, and H).
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.
0708-070A ↳ FADDH21 80 13
LD HL,1380HLD HL,FHALF
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
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
Shift the single precision value in Register Pairs BC and DE until it lines up with the single precision value in the ACCumulator
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.
073B
OR HB4
Get the subtraction flag to see if the sign bits are equal
073C-073E
LD HL,4121HLD HL,FACLO21 21 41
Load Register Pair HL with the starting address of 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.
0796 ↳ ROUND
LD A,B78
Load Register A with the LSB of the single precision value in Register B
0797-0799 ↳ ROUNDB
LD HL,4124HLD HL,FAC21 24 41
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.
Go to the Level II BASIC error routine and display an OV ERROR message if the value has overflowed
07B7H-07C2H SINGLE PRECISION MATH ROUTINE – “FADDA”
This routine adds (HL+2),)(HL+1),(HL+0) to C,D,E. This is called by FADD and FOUT.
07B7 ↳ FADDA
LD A,(HL)7E
Load Register A with the LSB of the single precision value in the ACCumulator (pointed to by Register Pair HL)
07B8
ADD A,E83
Add Register E (the LSB of the other number being added; stored in Register E) to the LSB of the single precision value in the ACCumulator
07B9
LD E,A5F
… and put that sum into Register E
07BA
INC HL23
Onto the middle number/NMSB. Increment the memory pointer in Register Pair HL to point to the NMSB (ACCumulator + 1).
07BB
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
07BC
ADC A,D8A
Add the NMSB of the single precision value in Register D to the NMSB of the single precision value in Register A
07BD
LD D,A57
Load Register D with the result in Register A
07BE
INC HL23
Onto the high order number/MSB. Increment the memory pointer in Register Pair HL to point to the MSB (ACCumulator + 2).
07BF
LD A,(HL)7E
Load Register A with the MSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
07C0
ADC A,C89
Add the MSB of the single precision value in Register C to the MSB of the single precision value in Register A
07C1
LD C,A4F
Load Register C with the result in Register A
07C2
RETC9
RETurn to CALLer
07C3H-07D6H – SINGLE PRECISION MATH ROUTINE – NEGR
This routine negates the number in C/D/E/B. CALLd by FADD and QUINT. Alters everything except Register H.
07C3-07C5 ↳ NEGR
LD HL,4125HLD HL,FAC+121 25 41
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
07F8H-07FBH – SINGLE PRECISION CONSTANT STORAGE LOCATION – “FONE”
07F8-07FB ↳ FONE
00 00 00 8100
A single precision constant equal to 1.0 is stored here
07FCH-0808H – SINGLE PRECISION CONSTANTS STORAGE LOCATION 2 – “LOGCN2”
07FC ↳ LOGCN2
0303
The number of single precision constants which follows is stored here
07FD-0800
AA 56 19 80AA
A single precision constant equal to 0.598978650 is stored here
0801-0804
F1 22 76 80F1
A single precision constant equal to 0.961470632 is stored here
0805-0808
45 AA 38 8245
A single precision constant equal to 2.88539129 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.
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
0810-0812
LD HL,4124HLD HL,FAC21 24 41
Load Register Pair HL with the address of the exponent in the ACCumulator
0813
LD A,(HL)7E
Load Register A with the exponent of the single precision value in the ACCumulator (held at the location of the memory pointer in Register Pair HL)
The next two instructions are commented in the original ROM source code as: Get SQR(.5)
0814-0816
LD BC,8035H01 35 80
Load Register BC with the exponent and the MSB of a single precision constant (which is 32821)
0817-0819
LD DE,04F3H11 F3 04
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
082E-0830
LD HL,07FCHLD HL,LOGCN221 FC 07
Load Register Pair HL with the starting address of a storage location for the single precision constants of a “approximation polynomial” to be used.
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)
0841-0843 ↳ MULLN2
LD BC,8031H01 31 80
Load Register Pair BC with the exponent and the MSB of a single precision constant
0844-0846
LD DE,7218H11 18 72
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
08A1
POP DED1
Get the NMSB and the LSB of the single precision value from the STACK and put it in Register Pair DE
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.
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
If we JUMP here, then the subtraction was too much and we need to get the old number from the STACK and put it in Register Pair BC
08DA
POP HLE1
… and get the old number back into 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
LD L,0AFH2E AF
Z-80 Trick. If we are passing through, the Register L will change, but the XOR in 090A will not trigger.
090A ↳ MULDVA
XOR AAF
This is the entry point from the DMULT routine. With this, we need to set up to ADD exponents. To do this we load Register A with an appropriate bit mask
090B-090D
LD HL,412DHLD HL,ARG-121 2D 41
Load Register Pair HL with the address of the SIGN and the High/Order MSB in ARG (a/k/a REG 2) (a/k/a ARG)
090E
LD C,(HL)4E
Load Register C with the High Order/MSB and the sign of the value in ARG (a/k/a REG 2) for unpacking
090F
INC HL23
Increment the value of the memory pointer in Register Pair HL to now point to the exponent
0910
XOR (HL)AE
Get the exponent by XORing the mask in Register A (which varied based on where this routine was entered from)
0911
LD B,A47
Save the adjusted exponent into Register B for processing below
0912-0913
LD L,00H2E 00
Load Register L with a 00H which will indicate that the below routine needs to ADD the exponents and then pass through to the MULDIV routine
0914H-0930H – SINGLE PRECISION MATH ROUTINE – “MULDIV”
0914 ↳ MULDIV
LD A,B78
First we should test to make sure that the number isn’t zero, so Load Register A with the exponent in Register B
0915
OR AB7
Check to see if the exponent in Register A is equal to zero
If the exponent in Register A is equal to zero then we just need to ZERO out the ACCumulator and we are done. Do that by JUMPing to 0937H
0918
LD A,L7D
Next, we need to determine if we are ADDing or SUBtracting, which is held in Register L. So load Register A with the bit mask in Register L
0919-091B
LD HL,4124HLD HL,FAC21 24 41
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)
094D-094F
LD HL,4124HLD HL,FAC21 24 41
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.
095E ↳ FCOMPS
CPL2F
Complement the sign. This is ignored if passing through and proceesed only if specifically jumped to.
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
0966-0968
LD DE,0000H11 00 00
Load Register Pair DE with zero
This routine will float the singed number in B/A/D/E. All registers are modified.
0969-096B ↳ FLOATR
LD HL,4124HLD HL,FAC21 24 41
Load Register Pair HL with the address of the exponent in the ACCumulator
096C
LD C,A4F
Load Register C with the High Order/MSB of the integer value
096D
LD (HL),B70
Save the exponent in Register B into the ACCumulator at the location of the memory pointer in Register Pair HL
096E-096F
LD B,00H06 00
Load Register B with zero to zero the overflow byte
0970
INC HL23
Increment the memory pointer in Register Pair HL to now point to the sign of the number in the ACCumulator
0971-0972
LD (HL),80H36 80
Assume a positive number by putting an 80H there
0973
RLA17
Shift the value of the sign bit into the CARRY FLAG
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
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 showed STRING, Display a ?TM ERROR message
This routine will negate the single or double precision number in the ACCumulator. Registers A, H, and L are affected.
To use this routine, the number must already be PACKed.
0982-0984 ↳ NEG
LD HL,4123HLD HL,FAC-121 23 41
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.
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
09BFH-09CAH – SINGLE PRECISION MATH ROUTINE – “MOVRF”
This routine is the opposite of the 09B4H routine. It loads four bytes from ACCumulator (single-precision) into the BC/DE Register Pairs. Only Register A is unchanged.
Loads A SP Value From ACCumulator Into BC/DE: Loads a single precision value from ACCumulator into BC/DE. Note, the mode flag is not tested by the move routine. It is up to the caller to insure that ACCumulator actually contains a single precision value
This routine is the opposite of the 9B4H routine. It loads four bytes from the ACC (single-precision) into the BC and DE Register Pairs. (BCDE=ACC). A is unchanged
Load A SP Value Into BC/DE: Loads a single precision value pointed to by HL into BC/DE. Uses all registers
On Exit, HL = HL + 4
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
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
09CB-09CD ↳ MOVMF
LD DE,4121HLD DE,FACLO11 21 41
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.
09DF-09E1 ↳ UNPACK
LD HL,4123HLD HL,FAC-121 23 41
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.
09F4-09F6 ↳ VMOVFA
LD HL,4127HLD HL,ARGLO21 27 41
This is the entry point from the DADD routine. To facilitate, we need to set HL to point to ARG (a/k/a REG 2)) instead of the ACCumulator
09F7-09F9 ↳ VMOVFM
LD DE,09D2HLD DE,MOVVFM11 D2 09
Load Register Pair DE with the return address of the routine that does an exchange and then falls into the MOVE1 routine.
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.
09FC-09FE ↳ VMOVAF
LD HL,4127HLD HL,ARGLO21 27 41
Entered here from FIN, DMUL10, and DDIV10. They require that Register Pair HL to point to ARG (a/k/a REG 2) instead of the ACCumulator
09FF-0A01 ↳ VMOVMF
LD DE,09D3HLD DE,VMOVE11 D3 09
When entered from here, we need to load Register Pair DE with the return address of the MOVE routine.
0A02 ↳ VMVVFM
PUSH DED5
When entered here, save Register Pair DE (which, if passed through, is a return address) on the STACK
0A03-0A05 ↳ VDFACS
LD DE,4121HLD DE,FACLO11 21 41
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
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
0A08-0A0A
LD DE,411DHLD DE,DFACLO11 1D 41
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
0A1A-0A1C
LD HL,4123HLD HL,FAC-121 23 41
Load Register Pair HL with the address of the MSB+SIGN in Register A
0A1D
XOR (HL)AE
Check to see if the signs of the ACCumulator and the ARG are the same via a XOR
0A1E
LD A,C79
If they are different, then the result of that XOR will be the sign of the number in ARG, so load Register A with the MSB+SIGN of 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
0A33
LD A,L7B
Next, check to see if the LOW ORDER/LSB’s are the same by first loading Register A with the LOW ORDER/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
According to the original ROM source code, this routine will compare two double precision numbers. 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. Compares ACCumulator with the ARG (a/k/a REG 2). After execution the A Register will contain: A=0 if ACCumulator=ARG (a/k/a REG 2), A=1 if ACC > ARG (a/k/a REG 2) or A=FFH if ACC < ARG (a/k/a REG 2). S and Z flags are valid.
0A49-0A4B ↳ DCOMPD
LD HL,4127HLD HL,ARGLO21 27 41
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.
0A57-0A59
LD HL,095EHLD HL,FCOMPS21 5E 09
Load Register Pair HL with a return address to the FCOMPS routine
0A5A
PUSH HLE5
Save the return address in Register Pair HL on the STACK
Go check to see if the double precision value in the ACCumulator is equal to zero
0A5E
DEC DE1B
Decrement the value of the memory pointer in Register Pair DE so that DE now points to the MSB+SIGN of the number in ARG (a/k/a REG 2)
0A5F
LD A,(DE)1A
Load Register A with the MSB+SIGN of the double precision value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair DE
0A60
LD C,A4F
Presetve the MSB+SIGN of the double precision value in ARG (a/k/a REG 2) into Register C
0A61
RET ZC8
If the number in the ACCumulator = 0, then the sign of the result is the sign of ARG, so RETurn wto FCOMPS
0A62-0A64
LD HL,4123HLD HL,FAC-121 23 41
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.
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”
0AA3-0AA5 ↳ CONIS2
LD BC,9080H01 80 90
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
0AA6-0AA8
LD DE,0000H11 00 00
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
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
0AC5-0AC7
LD HL,4120HLD HL,FACLO-121 20 41
Load Register Pair HL with the address of the first byte below a single-prevision value (i.e., chop off the MSB of a double double precision value)
0AC8
LD B,(HL)46
Loaded Register B with the chopped number, as that is where the ROUND routine expects the number to be
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.
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.
0AE3-0AE5 ↳ CONDS
LD HL,0000H21 00 00
Load Register Pair HL with zero so we can zero out the ACCumulator
0AE6-0AE8
LD (411DH),HLLD (DFACLO),HL22 1D 41
Zero out the first and second bytes of the double precision number in the ACCumulator. Note: 411DH-4124H holds ACCumulator
0AE9-0AEB
LD (411FH),HLLD (DFACLO+2),HL22 1F 41
Zero out the third and fourth bytes of the double precision number in the ACCumulator
0AEC-0AED ↳ VALDBL
LD A,08H3E 08
Load Register A with a double precision number type flag
0AEEH-0AF3H – LEVEL II BASIC MATH ROUTINE – “VALSNG”
0AEE
LD BC,043EH01 3E 04
Z-80 Trick. If passing through to this routine, BC will be modified but the next instruction will be skipped.
0AEF-0AF0 ↳ VALSNG
LD A,04H 3E 04
Load Register A with a single precision number type flag (of 4)
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
0BlFH-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
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
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.
0B40-0B42 ↳ INT
LD HL,4124HLD HL,FAC21 24 41
Load Register Pair HL with the address of the exponent in the ACCumulator
0B43
LD A,(HL)7E
Load Register A with the value of the exponent in the ACCumulator (held at the location of the memory pointer in Register Pair HL)
0B44-0B45
CP 98HFE 98
Check to see if there are fractional bits used by the current value in the ACCumulator. If are none, then the NC CARRY flag will be set.
0B46-0B48
LD A,(4121H)LD A,(FACLO)3A 21 41
Load Register A with the LSB of the single precision number in the ACCumulator
0B49
RET NCD0
If there are no fractional bits, then we are done, so RETurn with Register A holding the single precision value in the ACCumulator
0B4A
LD A,(HL)7E
Load Register A with the exponent of the single precision number in the ACCumulator
Get the LSB of the single precision value from the STACK and put it in Register A
0B58
RETC9
RETurn to CALLer
0B59H-0B9DH – LEVEL II BASIC MATH ROUTINE – “DINT”
Greated Integer function for double-precision numbers. All registers are affected.
0B59-0B5B ↳ DINT
LD HL,4124HLD HL,FAC21 24 41
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.
0BAA ↳ UMULT
PUSH HLE5
Save the value in Register Pair HL on the STACK
0BAB-0BAD
LD HL,0000H21 00 00
Load Register Pair HL with zero to zero the product registers
0BAE 0BAF
LD A,B OR C78 B1
First let’s see if (BC) is zero by loading Register A with the MSB of the integer value in Register B and then ORing the LSB held in Register C
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.
0C70-0C72 ↳ DSUB
LD HL,412DHLD HL,ARG-121 2D 41
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.
0C77-0C79 ↳ DADD
LD HL,412EHLD HL,ARG21 2E 41
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
0C80-0C82
LD DE,4124HLD DE,FAC11 24 41
Load Register Pair DE with the address of the exponent of the SECOND argument (held in the ACCumulator)
0C83
LD A,(DE)1A
Fetch the value of the exponent of the double precision value in the ACCumulator at the location of the memory pointer in Register Pair DE
0C84
OR AB7
Set the flags to see if the double precision value in the ACCumulator is equal to zero
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.
If the shift counter is zero, then proceed to round the number and finish up by JUMPing to DROUND
0D05-0D07
LD HL,4124HLD HL,FAC21 24 41
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”
0D33-0D35 ↳ DADDAA
LD HL,4127HLD HL,ARGLO21 27 41
DADD enters here, so we need to set both HL and DE. In that case, set HL to point to ARG (a/k/a REG 2). Note: 4127H-412EH holds ARG (a/k/a REG 2)
0D36-0D38 ↳ DADDFO
LD DE,411DHLD DE,DFACLO11 1D 41
FOUT enters here, and DADD passes through to here. Load Register Pair DE with the starting address of ACCumulator. Note: 411DH-4124H holds ACCumulator
0D39-0D3A ↳ DADDS
LD C,07H0E 07
Load Register C with the number of bytes to be added
0D3B
XOR AAF
Clear the Carry flag
0D3C ↳ DADDLS
LD A,(DE)1A
Top of a loop. Load Register A with the value in the ACCumulator at the location of the memory pointer in Register Pair DE
0D3D
ADC A,(HL)8E
Add the value in ARG (a/k/a REG 2) at the location of the memory value in Register A
0D3E
LD (DE),A12
Save the result of that addition into the ACCumulator at the location of the memory pointer in Register Pair DE
0D3F
INC DE13
Increment the value of the memory pointer in Register Pair DE
0D40
INC HL23
Increment the value of the memory pointer in Register Pair HL
0D41
DEC C0D
Decrement the number of bytes to be added in Register C
Loop until all of the bytes for the double precision values have been added
0D44
RETC9
RETurn to CALLer
0D45H-0D56H – DOUBLE PRECISION MATH ROUTINE – “DADDAS”
This routine subtracts numbers in the pure version. This needs to be done in two subroutines since the ROM cannot be modified.
0D45-0D47 ↳ DADDAS
LD HL,4127HLD HL,ARGLO21 27 41
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)
0D48-0D4A ↳ DADDFS
LD DE,411DHLD DE,DFACLO11 1D 41
FOUT enters here, and DADD passes through to here. Load Register Pair DE with the starting address of ACCumulator. Note: 411DH-4124H holds ACCumulator
0D4B-0D4C ↳ DADDSS
LD C,07H0E 07
Load Register C with the number of bytes to be subtracted
0D4D
XOR AAF
Clear the Carry flag
0D4E ↳ DADDLS
LD A,(DE)1A
Top of a loop. Load Register A with the value in the ACCumulator at the location of the memory pointer in Register Pair DE
0D4F
SBC A,(HL)9E
Subtract the value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair HL from the value in Register A
0D50
LD (DE),A12
Save the result in Register A in the ACCumulator at the location of the memory pointer in Register Pair DE
0D51
INC DE13
Increment the value of the memory pointer in Register Pair DE
0D52
INC HL23
Increment the value of the memory pointer in Register Pair HL
0D53
DEC C0D
Decrement the number of bytes to be subtracted for the double precision values in Register C
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.
0D71-0D73
LD DE,0800H11 00 08
This LD command shifts a zero into the HIGH ORDER byte and sets up a counter
0D74 ↳ DSHFR2
LD C,(HL)4E
Top of a loop. Preserve a byte of the ACCumulator into Register C
0D75
LD (HL),E73
Overwrite that location with the last byte (held in Register E)
0D76
LD E,C59
Load Register E with the value in Register C so that THIS is the byte to write next.
0D77
DEC HL2B
Decrement the value of the memory pointer in Register Pair HL to point to the next lower order byte
0D78
DEC D15
Decrement the number of bits shifted in Register D
LOOP back to the top to see we can shift another 8 bits.
0D69H-0D8FH – 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”
This routine handles multiplying by zero.
0DCC-0DCE ↳ DMULT5
LD HL,4123HLD HL,FAC-121 23 41
Load Register Pair HL with the address of the HIGH ORDER/MSB of the ACCumulator
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.
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
0E07-0E09
LD DE,414AHLD DE,FBUFFR+2711 4A 41
Put the pointer to the end of the BUFFR into Register Pair DE
0E0A-0E0C
LD HL,4127HLD HL,ARGLO21 27 41
Load Register Pair HL with the address of the END of the double precision value in ARG (a/k/a REG 2)
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.
0E12 ↳ DDIV2
LD (DE),A12
If this line is executed (i.e., JUMPed to, but not passed down to), put the new highest order byte into Register A
0E13
INC B04
If this line is executed (i.e., JUMPed to, but not passed down to), increment the flag in Register B to show that we can do the division
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.
If the M FLAG is set, then we are done and have 57 bits of accuracy, so JUMP to DROUNB to finish up.
0E1D
RLA17
Restore the CARRY BIT to where it belongs
0E1E-0E20
LD HL,411DHLD HL,DFACLO21 1D 41
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 Register B is not ZERO, then we have more to go so LOOP back up to DDIV1
0E30-0E32
LD HL,4124HLD HL,FAC21 24 41
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
0E3E-0E40
LD DE,4150HLD DE,FMLTT211 50 41
Load Register Pair DE with the end of FBUFFR
0E41-0E43
LD BC,0700H01 00 07
Load Register B with the number of bytes to be moved (which is 7) and put a zero into Register C
0E44 ↳ DMLDV1
LD A,(HL)7E
Top of a loop. Fetch a byte from the ACCumulator (tracked by Register Pair HL) into Register A
0E45
LD (DE),A12
Save that byte into FBUFFR (tracked by Register Pair DE)
0E46
LD (HL),C71
Zero out that location in the ACCumulator
0E47
DEC DE1B
Decrement the value of the memory pointer to FBUFFR (tracked by Register Pair DE)
0E48
DEC HL2B
Decrement the value of the memory pointer to the ACCumulator (tracked by Register Pair HL)
0E49
DEC B05
Decrement the value of the byte counter in Register B to see if we are done
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).
0E6C ↳ FIN
XOR AAF
Zero Register A for the purpose of triggering the GOSUB to MAKINT at 0E73H
0E6D ↳ FINCHR
EX DE,HLEB
Load Register Pair DE with the pointer to the current BASIC line being interpreted
0E6E-0E70
LD BC,00FFH01 FF 00
Load Register Pair BC with a zero and a negative one. Register B will track the decimal point location and C will be a flag.
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
Convert the current value in the ACCumulator to either single precision or double precision
0EA7
PUSH HLE5
Save the current input buffer pointer to the string being processed (tracked in Register Pair HL) to the STACK
0EA8-0EAA
LD HL,0EBDHLD HL,FINEC21 BD 0E
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
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!
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
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
0EDC-0EDE
LD HL,0890HLD HL,POPHRT21 90 08
Load Register Pair HL with the return address of the POPHRT routine because CONIS2 does funny things to the stack.
0EDF
PUSH HLE5
Save the value of the return address in Register Pair HL on the STACK
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
0EE4 – 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.
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
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
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:
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
If the current value in the ACCumulator is double precision, then JUMP to FINGD to use the double precision routine to pack in the next digit
These next 2 instruction set up BCDE to hold “1000000”
0F5F-0F61
LD BC,9474H01 74 94
Load Register Pair BC with the exponent and the MSB of a single precision constant
0F62-0F64
LD DE,2400H11 00 24
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
If the value of the exponent in Register A is greater than or equal to 10 then we already have two digits, so JUMP to FINEDO to keep processing
0F99
RLCA07
Multiply the value in Register A by two
0F9A
RLCA07
Multiply the value in Register A by two. Register A now holds the original value of the exponent times four
0F9B
ADD A,E83
Add the original value of the exponent in Register E to the adjusted value of the exponent in Register A
0F9C
RLCA07
Multiply the value in Register A by two. Register A now holds the original value of the exponent times ten
0F9D
ADD A,(HL)86
Add the value of the number at the location of the input buffer pointer in Register Pair HL to the adjusted value in Register A
0F9E-0F9F
SUB 30HSUB “0”D6 30
Convert the adjusted value in Register A to it’s binary equivalent (which is subtracting 0011 0000)
0FA0
LD E,A5F
Save the adjusted exponent into Register E
0FA1-0FA3
JP M,321EHFA 1E 32
Z-80 TRICK. If passing through, this sill never trigger, but neither will the next instruction!
0FA2-0FA3 ↳ FINEDO
LD E,32H1E 32
If JUMPed here, E will be reset (from Register A’s value) to 50 Decimal, which, as an exponent, will SAFELY cause an overflow or underflow. If passing through, this will not be seen
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.
0FF5-0FF7 ↳ FOUTZS
LD HL,4130HLD HL,FBUFFR+121 30 41
Load Register Pair HL with the starting address of the buffer, which will hold the SIGN
0FF8
LD B,(HL)46
Load Register B with the sign (i.e., the character at the location of the buffer pointer in Register Pair HL)
0FF9-0FFA
LD C,20HLD C,” “0E 20
Load Register C with a SPACE
0FFB-0FFD
LD A,(40D8H)LD A,(TEMP3)3A D8 40
Load Register A with format specifications
0FFE
LD E,A5F
Put the format specifications into Register E
0FFF-1000
AND 20HAND 0010 0000E6 20
MASK the format specifications (by AND against 0010 0000) to see an asterisk fill is required