Page Customization
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 things 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:
0000H-0004H – Power Up Routine– “START”
0000-0002 Disables the interrupts, clears the A register, then jumps to initialisation routine at 674H
0008H – RST 08H – Syntax Check– “SYNTAX”
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. 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 008H)
This is the COMPARE SYMBOL routine which compares 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 Register A and HL incremented by one. If the two characters do not match, a syntax error message is given and control returns to the Input Phase.
000BH-000CH – Disk Routine– “WHERE”
000DH-000FH – Disk Bootstrap– “$BOOT”
0010H-0012H – RST 10H – 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 characters 9 and 10 (shifted left and down arrows respectively). Upon return from this routine HL will point to the next non-blank character.
(RST 010H)
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.
0018H – RST 18H – Compare DE:HL– “RST18”
This routine 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.
0020H – RST 20H – Data Mode Check
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.
(RST 020H)
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”
0028H – RST 28H – DOS Function Call
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.
(RST 028H)
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. 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.
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 0049Hor you would write a routine that jumps back to the call if A is 0.
Note: 4015H holds Keyboard DCB – Device type
0030H – RST 30H – Load Debug
This location passes control to 400FH which contains a RET (C9H) under Level II. This location is only used by a Disk system.
(RST 030H)
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.
Note: 401DH holds Video DCB – Device type
0038H – RST 38H – Interrupt Entry Point
This 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.
(RST 038H)
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. A call to 003BH causes the character contained in the A-register to be sent to the printer. A line count is maintained by the driver in the DCB. When a full page has been printed (66 lines), the line count is reset and the status Register returned to the caller is set to zero.
Control codes recognized by the printer driver are:
- 00=Returns the printer status in the upper two bits of the A-register and sets the status as zero if not busy, and non-zero if busy.
- OB=Unconditionally skips to the top of the next page.
- OC=Resets the line count (DCB 4) and compares its previous value to the lines per page (DCB 3) value. If the line count was zero, no action is taken. If the line count was non-zero then a skip to the top form is performed.
- OD=Line terminator. Causes line count to be incremented and tested for full page. Usually causes the printer to begin printing.
Note: 4025H holds Printer DCB – Device type
0040H-0042H – Input Routine– “$KEYIN”
0046H-0048H – Driver Entry Routine – Part 2– “CIOJ”
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 002BHinstead.
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 more useful than 2BH. Character is returned in the A Register (AF and DE used).
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
0050H-005FH – Keyboard Lookup Table– “KEYTAB”
This is a table of control characters used by BASIC.
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.
OR C78 B1
0066H-0068H – NMI Vector
0066 is the location to which program control jumps when the RESET button is pressed (Non Maskable Interrupt address).
This is the location to which program control jumps when the RESET button is pressed (Non Maskable Interrupt address)
0069H-0074H – NMI Interrupt Routine (RESET)– “$INITIO”
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.)
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.
Note: 4080H-408DH is a division support routine
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.)
Note: 40A7H-40A8H holds the input Buffer pointer
0091H-0104H – The rest of the initialization routine. First, it fills the RAM locations pointing to all 28 DOS BASIC commands, set them to point to ?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.
Note: 4152H-41A3H holds Disk Basic links
00A8H – VIDEO AND PRINTER ROUTINE
NOTE:
- 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).
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.
↳ MEMSIZ
↳ LOOPMM
OR L7C
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
↳ USEDEF
↳ STRSZD
Note: 40A0H-40A1H holds the start of string space pointer
NOTE:
- 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).
0105H-0110H – MESSAGE STORAGE
The “MEMORY SIZE” message is located here
↳ MEMMSG
012DH-0131H – ?L3 ERROR ROUTINE– “NAVERR”
↳ NAVERR
0132H-0134H – LEVEL II BASIC POINTCOMMAND ENTRY POINT – “GRPHCS” or “POINT”
0132, 0135, 0138 These are the entry points for the POINT, SET and the RESET commands in that order, see Part 2 for more data on the graphics routines
↳ GRPHCS
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.
0135H-0137H – LEVEL II BASIC SETCOMMAND– “SET”
↳ SET
0138H-0139H – LEVEL II BASIC RESETCOMMAND ENTRY POINT– “RESET”
↳ RESET
013AH-019CH GRAPHICS ROUTINE
Common code for SET/RESET/POINT– A will be 0 if POINT, 80H if SETand 1 for RESET.
This is a suitable entry point for the graphics routines. (see Part 2)
↳ LOPMD3
↳ SHFTW
↳ PWR2
↳ FND4
↳ FINSTB
↳ FINPTB
↳ SBIT
↳ TBIT
019DH-01C8H – LEVEL II BASIC INKEY$ROUTINE– “INKEY”
↳ INKEY
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.
↳ BUFCIN
Note: 4099H holds the last key pressed
↳ NULRT
Note: 40AFH holds current variable’s type flag
01C9H-01D2H – LEVEL II BASIC CLSROUTINE– “CLS”
A CALL 1C9H will clear the screen, select 64 characters and home the cursor. All registers are used.
To use a ROM call to clear the screen, CALL 01C9H. The cursor is reset to the home position, which is 3C00H.
↳ CLS
01D3H-01D8H – LEVEL II BASIC RANDOMROUTINE– “RANDOM”
This is part of the RANDOMroutine 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.
↳ RANDOM
01D9H-01F7H – CASSETTE ROUTINE
Output a pulse to the cassette recorder
↳ CTPULS
Delay loop between pulses
01F8H-01FDH – CASSETTE ROUTINE (TURN OFF CASSETTE)– “CTOFF”
↳ CTOFF
01FEH-0211H- CASSETTE ROUTINE (EVALUATE DRIVE NUMBER)– “CTON”
↳ CTON
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,0and then CALL 0212H
↳ DEFDRV
↳ CTCHG2
021EH-022BH – CASSETTE ROUTINE– “CTSTAT”
021E-0220
↳ CTSTATLD HL,0FF00HLD HL,<255*256>21 00 FFLoad Register Pair HL with the mask to preserve video controller flags, but otherwise clear the cassette latch
0221-0223
↳ CTCHGLD A,(403DH)LD A,(CAST$)3A 3D 40Load 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
0224AND HA4Combine the value in Register H with the contents of (403DH)
0225OR LB5Combine the value to send to the cassette in Register L with the adjusted value in Register A
0226-0227OUT (0FFH),AOUT (CASIO$),AD3 FFSend the value in Register A to port 255 (which is the cassette and video port)
0228-022ALD (403DH),ALD (CAST$),A32 3D 40Save the value in Register A into (403DH).
Note: 403DH-4040H is used by DOS
022BRETC9RETurn to CALLer
022CH-0234H – CASSETTE ROUTINE (BLINK **)– “BCASIN”
Alternately displays and clears an asterisk in the upper right hand comer. Uses all registers.
022C-022E
↳ BCASINLD A,(3C3FH)3A 3F 3CGet the character being displayed in the upper right hand corner of the video display from 3C3FH and put that character in Register A
022F-0230XOR 0AHEE 0AIf the character in Register A is a *then make it a SPACE, else if the character in Register A is a SPACEmake it a *
0231-0233LD (3C3FH),A32 3F 3CDisplay the character in Register A in the upper right hand comer of the video display
0234RETC9RETurn 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
CASINPUSH BCC5Save the value in Register Pair BC on the STACK
0236PUSH HLE5Save the value in Register Pair HL on the STACK
0237-0238LD B,08H06 08Load Register B (which is what DJNZ decrements to loop) with the number of bits to read (which is 8)
0239-023B
CTB0CALL 0241HCALL CTBITCD 41 02Go read a bit from the cassette recorder (the resulting byte from the 8 bits will be accumulated into the A register)
023EPOP HLE1Get the value from the STACK and put it in Register Pair HL
023FPOP BCC1Get the value from the STACK and put it in Register Pair BC
0240RETC9RETurn 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
↳ CTBITPUSH BCC5Save the value in Register Pair BC on the STACK
0242PUSH AFF5Save the value in Register A on the STACK
0243-0244
CB0IN A,(0FFH)IN A,(CASIO$)DB FFRead a bit from the cassette port (waiting for a clock pulse)
0245RLA17Rotate 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
0246-0247JR NC,0243HJR NC,CB030 FBIf NC is set, we do not yet have the start bit, so loop back to 0243H until the start bit is found
0248-0249LD B,40H06 41No that we have the start bit we need a delay, so load Register B with the delay count of 40H
*0248H-0249LD B,60HIn ROM v1.2, load B with a delay count of 60H (=476/703 microseconds) instead of 40H
024A-024B
CB1Loop (don’t jump unless B is zero; with a decrement of B each time) for delay
024F-0250LD B,76H06 76We need a delay so load Register B with the delay count of 76H
*024FH-0250LD B,85HIn ROM v1.2, load B with a delay count of 85H (865/975 microseconds) instead of 76H
0253-0254IN A,(0FFH)DB FFRead a bit from the cassette port
0255LD B,A47Since we are going to need to use A, load A’s value (the bit read from the cassette port) into Register B
0256POP AFF1Get the value from the STACK (which was the bits already read for this byte) and put it in Register A
0257-0258RLC BCB 10Shift the that data bit read in Register B into the Carry flag
0259RLA17Shift the value in the Carry flag (which was B) into Register A
025APUSH AFF5Save the value in Register A on the STACK because its gonna get erased by the next step
025EPOP AFF1Get the value from the STACK and put it in Register A
025FPOP BCC1Get the value from the STACK and put it in Register Pair BC
0260RETC9RETurn to CALLer
0261H-0263H – CASSETTE ROUTINE– “TWOCSO”
0261-0263
↳ TWOCSOCALL 0264HCALL CASOUTCD 64 02Write 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
↳ CASOUTPUSH HLE5Save the value in Register Pair HL on the STACK
0265PUSH BCC5Save the value in Register Pair BC on the STACK
0266PUSH DED5Save the value in Register Pair DE on the STACK
0267PUSH AFF5Save the value in Register A on the STACK
0268-0269LD C,08H0E 08Load Register C with the number of bits to be written (i.e., 8)
026ALD D,A57Load Register D with the byte to be written from Register A because A is about to get used in the next line
026ELD A,D7ALoad Register A with the byte to be written in Register D
026FRLCA07Shift the bit to be written into the Carry flag
0270LD D,A57Save the adjusted byte to be written in Register A in Register D
0276
BYT1DEC C0DDecrement the counter in Register C
0279POP AFF1Get the value from the STACK and put it in Register A
027APOP DED1Get the value from the STACK and put it in Register Pair DE
027BPOP BCC1Get the value from the STACK and put it in Register Pair BC
027CPOP HLE1Get the value from the STACK and put it in Register Pair HL
027DRETC9RETurn to CALLer
027EH-0283H – CASSETTE ROUTINE
027E-027F
↳ BYT2LD B,87H06 87Load Register B with the delay count of 87H (Decimal: 135)
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.
0284-0286
↳ CWONWLCALL 01FEHCALL CTONCD FE 01Go evaluate the cassette drive number and turn on that cassette drive’s motor
0287-0288LD B,0FFH06 FFWrites tape leader and the A5H sync byte. DE and HL are unchanged.
Load Register B with the number of bytes to be written
0289XOR AAFZero Register A
028A-028C
CSAV1CALL 0264HCALL CASOUTCD 64 02Calls 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)
028F-0290LD A,A5H3E A5Load Register A with the sync byte value
0291-0292JR 0264HJR CASOUT18 D1Calls 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”
0293-0295
↳ CSRDONCALL 01FEHCALL CTONCD FE 01Go 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+3PUSH HLE5Reads 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
0297XOR AAFZero Register A and status flags
0298-029A
CLOD1CALL 0241HCALL CTBITCD 41 02Top of a loop. GOSUB to read a byte from the cassette and return with it in Register A
029B-029CCP A5HFE A5Check to see if the byte read from the cassette in Register A is a sync byte
029FH-02A8H – CASSETTE ROUTINE
Places the double asterisk in the right top corner to show that the sync byte has been found
029F-02A0LD A,2FH LD A,”*”3E 2APlaces the double asterisk in the right top corner to show that the sync byte has been found.
Load Register A with a *character. (“*” is 2AH)
02A1-02A3LD (3C3EH),A32 3E 3CDisplay the *character in Register A on the video display at location 15422
02A4-02A6LD (3C3FH),A32 3F 3CDisplay the *character in Register A on the video display at location 15423
02A7POP HLE1Get the current BASIC program pointer from the STACK and put it in Register Pair HL
02A8RETC9RETurn to CALLer
02A9H-0329H – LEVEL II SYSTEM ROUTINE-ENTRY POINT– “ENBLK”
02A9-02AB
↳ ENBLKCALL 0314HCALL CADRINCD 14 03Go read 2 bytes from the cassette, which should be the start/execution address, and return with it in Register Pair HL
02AC-02AELD (40DFH),HLLD (TEMP),HL22 DF 40Save the just read execution address from HL into 40DFH.
Note: 40DFH-40E0H is also used by DOS
02B2-02B4
↳ SYSTEMCALL 41E2HCALL SYSOUTCD E2 41Go call the DOS link at 41E2H.
In NEWDOS 2.1, this is called during a SYSTEM operation
This location passes control to the routine used by the BASIC command SYSTEM
02B5-02B7LD SP,4288HLD SP,BUFINI+16031 88 42Set the STACK pointer to 4288H (which is the assumed load address). This location passes control to the routine used by the BASIC command SYSTEM
02B8-02BACALL 20FEHCALL CRDOCD FE 20GOSUB to display a carriage return on the video display if necessary
02BB-02BCLD A,2AHLD A,”*”3E 2ALoad Register A with an *character (which will form the next prompt)
02C3-02C5JP C,06CCHJP C,RESETRDA CC 06If a BREAKkey was hit (because the Carry flag is now on), go to the Level II BASIC READY routine
02C6Since 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.
02CA-02CBCP 2FHLD A,”/”FE 2FCheck to see if the character at the location of the input buffer pointer in Register A is a /character
02CC-02CDJR Z,031DHJR Z, GODO28 4FJump to 031DH if the character at the location of the input buffer pointer in Register A is a /
02D1-02D3
LOPHDCALL 0235HCALL CASINCD 35 02Top 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-02D5CP 55HFE 55Check to see if the byte read from the cassette in Register A is a header byte (=55H)
02D8-02D9LD B,06H06 06If were here, we got the header byte, so load Register B with the length of the filename to read from the cassette (which is 6 characters)
LD A,(HL)7ELoad Register A with the character at the location of the current input buffer pointer in Register Pair HL
02DBOR AB7Check to see if the character at the location of the current input buffer pointer in Register A is an end of input character
02DC-02DDJR Z,02E7HJR Z,GETDT28 09Jump 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
02DE-02E0CALL 0235HCALL CASINCD 35 02Calls 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)
02E1CP (HL)BECheck 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
02E2INC HL23Increment the input buffer pointer in Register Pair HL
02E3-02E4Jump 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
*02E2-02E3Jump 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
*02E4INC HLIncrement the input buffer pointer in Register Pair HL
02E5-02E6Loop until the whole of the filename has been read from the cassette and checked against the user response
02E7-02E9
GETDTCALL 022CHCALL BCASINCD 2C 02Call the BLINK ASTERISK routine at 022CH which alternatively displays and clears an asterisk in the upper right hand corner of the video display
02EA-02EC
↳ GETDT2CALL 0235HCALL CASINCD 35 02Calls 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-02EECP 78HFE 78Check to see if the byte read from the cassette in Register A is an execution address header byte (which is 78H)
02EF-02F0JR Z,02A9HJR Z,ENBLK28 B8Jump if the byte read from the cassette in Register A is an execution address header byte
02F1-02F2CP 3CHFE 3CCheck to see if the byte read from the cassette in Register A is a file block header byte (which is 3CH)
02F3-02F4Loop until either an execution address header byte or a file block header byte is read from the cassette
02F5-02F7CALL 0235HCALL CASINCD 35 02Calls 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)
02F8LD B,A47Load Register B with the count of bytes to be loaded in Register A
02F9-02FBCALL 0314HCALL CADRINCD 14 03Read the file block’s starting address from the cassette and return with it in Register Pair HL
02FCADD A,L85For 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
02FDLD C,A4FLoad Register C with the file block’s starting checksum in Register A
02FE-0300
↳ LDATINCALL 0235HCALL CASINCD 35 02Calls 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)
0301LD (HL),A77Save the byte read from the cassette in Register A at the location of the memory pointer in Register Pair HL
0302INC HL23Increment the memory pointer in Register Pair HL
0303ADD A,C81Add the value of the current checksum in Register C to the value in Register A
0304LD C,A4FLoad Register C with the updated checksum in Register A
0307-0309CALL 0235HCALL CASINCD 35 02Calls 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
030ACP CB9Check to see if the computed checksum in Register C is the same as the checksum read from the cassette in Register A
030B-030CJR Z,02E7HJR Z,GETDT28 DAIf its the same, jump to 02E7H because the next instructions are for bad checksums
030D-030ELD A,43H3E 43Load Register A with a Ccharacter
030F-0311LD (3C3EH),A32 3E 3CDisplay the Ccharacter in Register A on the video display (at 15422)
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
0314
↳ CADRINCALL 0235HCALL CASINCD 35 02Calls 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)
0317LD L,A6FLoad Register L with the byte read from the cassette in Register A (which is the LSB of the 16 bit value)
0318-031ACALL 0235HCALL CASINCD 35 02Calls 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)
031BLD H,A67Load Register H with the byte read from the cassette in Register A (which is the MSB of the 16 bit value)
031CRETC9RETurn to CALLer
031DH – Execute the Cassette Program which was Loaded– “GODO”
031D
↳ GODOEX DE,HLEBLoad Register Pair DE with the pointer to the BASIC command line being processed (held in Register Pair HL)
031E-0320LD HL,(40DFH)LD HL,(TEMP)2A DF 40Load Register Pair HL with the execution address (which is stored at 40DFH).
Note: 40DFH-40E0H is also used by DOS
0321EX DE,HLEBSo 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
0322Since 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.
0323-0325CALL NZ,1E5AHCALL NZ,LINGETC4 5A 1ECall 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
0326-0327Jump if it turns out there weren’t any digits (i.e., bad input) in the input
0328EX DE,HLEBSince 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
0329JP (HL)E9Jump 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
↳ OUTDOPUSH BCC5We are going to need to use Register C, so push Register Pair BC into the STACK
032BLD C,A4FLoad Register C with the character to be output in Register A
032C-032ECALL 41C1HCALL EXOUTCCD C1 41Go call the DOS link at 41ClH.
In NEWDOS 2.1, this writes to the system output device
032F-0331LD A,(409CH)LD A,(PRTFLG)3A 9C 40Load 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
0332OR AB7Since LDdoesn’t set flags, in order to be able to test Register A using flags we need to execute an OR Afirst. This will enable us to set the flags according to the current output device number in Register A
0333LD A,C79Load Register A with the character to be output in Register C
0334POP BCC1Get 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
0335-0337JP M,0264HJP M,CASOUTFA 64 02If 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)
0338-0339Jump 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
↳ OUT2DPUSH DED5If 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.
Save the value in Register Pair DE on the STACK
033B-033DCALL 0033HCALL $DSPCD 33 00Call the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
033EPUSH AFF5Save the character in Register A on the STACK
033F-0341CALL 0348HCALL DSPPOSCD 48 03Go update the current cursor position and test to see if the display memory is full
0342-0344LD (40A6H),ALD (TTYPOS),A32 A6 40Save the current cursor line position stored in 40A6H to Register A.
Note: 40A6H holds the current cursor line position
0345POP AFF1Get the character from the STACK and put it in Register A
0346POP DED1Get the value from the STACK and put it in Register Pair DE
0347RETC9RETurn to CALLer
0348H-0357H – VIDEO ROUTINE– “DSPPOS”
0348-034A
↳ DSPPOSLD A,(403DH)LD A,(CAST$)3A 3D 40Load 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-034CAND 08HAND 0000 1000E6 08Mask Register A against 00001000 to isolate Bit 3 (the 32/64 character per line flag) in Register A
034D-034FLD A,(4020H)LD A,(CURSOR)3A 20 40Load Register A with the LSB of the current cursor position.
Note: 4020H-4021H holds Video DCB – Cursor location
0350-0351If 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
0352RRCA0FDivide the LSB of the current cursor position in Register A by two
0353-0354AND 1FHAND 0001 1111E6 1FMask 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
AND 3FHAND 0011 1111E6 3FMask 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
0357RETC9RETurn 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
035BPUSH DED5Since the next routine uses DE, save the value in Register Pair DE on the STACK
035FPOP DED1Get the value from the STACK and put it in Register Pair DE
0360RETC9RETurn 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
↳ INLINXOR AAFZero Register A to clear the buffered character
0362-0364LD (4099H),ALD (CHARC),A32 99 40Save the value in Register A as the last key pressed (which is stored in 4099H).
Note: 4099H holds the Last key pressed
0365-0367LD (40A6H),ALD (TTYPOS),A32 A6 40Save the value in Register A as the current cursor line position (which is stored in 40A6H).
Note: 40A6H holds the current cursor line position
0368-036ACALL 41AFHCALL INLINECD AF 41Go call the DOS link at 41AFH.
In NEWDOS 2.1, this is the satrt of keyboard input
036BPUSH BCC5Save Register Pair BC on the STACK
036C-036ELD HL,(40A7H)LD HL,(BUFPNT)2A A7 40Load Register Pair HL with the starting address of the input buffer (which is stored in 40A7H).
Note: 40A7H-40A8H holds the input Buffer pointer
036F-0370LD B,0F0H06 F0Load Register B with the length of the input buffer (which is 240)
0371-0373CALL 05D9HCALL KEYINCD D9 05“WAIT FOR NEXT LINE” keyboard input routine at 05D9H (which takes keyboard entry until a carriage return, a break, or buffer overrun occurs)
0374PUSH AFF5Save the flags on the STACK
0375LD C,B48Load Register C with the length of the input in Register B
0376-0377LD B,00H06 00Zero Register B so that Register Pair BC will have the length of the input
0378ADD HL,BC09Add the length of the input in Register Pair BC to the starting address of the input buffer in Register Pair HL
0379-037ALD (HL),00H36 00Save an end of the input character at the location of the end of input pointer in Register Pair HL
037B-037DLD HL,(40A7H)LD HL,(BUFPNT)2A A7 40Load Register Pair HL with the starting address of the input buffer (which is 40A7H).
Note: 40A7H-40A8H holds the input Buffer pointer
037EPOP AFF1Get the flags from the STACK
037FPOP BCC1Get the value from the STACK and put it in Register Pair BC
0380DEC HL2BDecrement the input buffer pointer in Register Pair HL (so that HL is the input area pointer – 1)
0381RET CD8Return if the BREAKkey was pressed
0382XOR AAFOtherwise (i.e., the BREAKkey was not pressed), zero all the status flags
0383RETC9RETurn to CALLer
0384H-038AH – KEYBOARD ROUTINE– “INCHR”
Waits for keypress
0387OR AB7Check to see if a key was pressed
0388RET NZC0Return if a key was pressed (meaning OR A was set to NZ)
038BH-039BH – PRINTER ROUTINE – “FINLPT”
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
↳ FINLPTXOR AAFZero Register A, which then means it contains the device code for VIDEO
038C-038ELD (409CH),ALD (PRTFLG),A32 9C 40Save 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-0391LD A,(409BH)LD A,(LPTPOS)3A 9B 40Load Register A with the current printer carriage position (which is stored at 409BH).
Note: 409BH holds the printer carriage position
0392OR AB7Set the flags for the carriage position in Register A
0393RET ZC8Return if the carriage position in Register A is equal to zero
0394-0395LD A,0DHLD A,ENTER3E 0DLoad Register A with a “CARRIAGE RETURN“
0396PUSH DED5Save the value in Register Pair DE on the STACK
0397-0399CALL 039CHCALL OUTPUTCD 9C 03Go send the carriage return character in Register A to the printer
039APOP DED1Get the value from the STACK and put it in Register Pair DE
039BRETC9RETurn to CALLer
039CH-03C1H – PRINTER ROUTINE– “OUTLPT”
This is the LPRINT routine. All registers are saved. The byte to be printed should be in the A register.
039C
↳ OUTLPTPUSH AFF5Save the value in Register Pair AF on the STACK
039DPUSH DED5Save the value in Register Pair DE on the STACK
039EPUSH BCC5Save the value in Register Pair BC on the STACK
039FLD C,A4FLoad Register C with the character to be sent to the printer in Register A
03A0-03A1LD E,00H1E 00Zero Register E (which will ultimately hold the new character/line count of 0CH, 0DH, or 0AH)
03A2-03A3
↳ OUTDOCP 0CHFE 0CCheck to see if the character to be sent to the printer in Register A is equal to 0CH (which is ‘skip to next line’)
03A4-03A5Jump to 03B6H if the character to be sent to the printer in Register A is equal to 0CH
03A6-03A7CP 0AHLD A,LINE FEEDFE 0ACheck to see if the character to be sent to the printer in Register A is a line feed character (i.e., 0AH)
03A8-03A9Jump to 03ADH if the character to be sent to the printer in Register A isn’t a line feed character
03AA-03ABLD A,0DHLD A,CARRIAGE RETURN3E 0DLoad Register A with a carriage return character (i.e., 0DH)
03ACLD C,A4FLoad Register C with the character to be sent to printer in Register A
03AD-03AE
↳ LZRNOTCP 0DHFE 0DCheck to see if the character to be sent to the printer in Register A is a carriage return character
03AF-03B0Jump to 03B6H if the character to be sent to the printer in Register A is a carriage return character
03B1-03B3LD A,(409BH)LD A,(LPTPOS)3A 9B 40Load Register A with the current printer carriage position (stored in 409BH).
Note: 409BH holds the printer carriage position
03B4INC A3CIncrement the current carriage position in Register A
03B5LD E,A5FLoad Register E with the current carriage position in Register A
03B6
↳ LZRPOSLD A,E7BLoad Register A with the current carriage position in Register E. Why do this since its obviously already done? Becasuse this is a jump point!
03B7-03B9LD (409BH),ALD (LPTPOS),A32 9B 40Save the current carriage position (which is stored in 409BH) in Register A.
Note: 409BH holds the printer carriage position
03BALD A,C79Load Register A with the character to be sent to the printer in Register C
03BB-03BDCALL 003BHCALL $PRTCD 3B 00Call the PRINT CHARACTER routine at 003B (which sends the character in the C Register to the printer)
03BEPOP BCC1Get the value from the STACK and put it in Register Pair BC
03BFPOP DED1Get the value from the STACK and put it in Register Pair DE
03C0POP AFF1Get the value from the STACK and put it in Register Pair AF
03C1RETC9RETurn 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
↳ CIOPUSH HLE5Save Register Pair HL on the STACK
03C3-03C4PUSH IXDD E5Save the value in Register Pair IX on the STACK
03C5PUSH DED5Save the starting address of the device control block in Register Pair DE on the STACK
03C6-03C7POP IXDD E1Get the starting address of the device control block from the STACK and put it in Register Pair IX, so now IX = DCB + 0
03C8PUSH DED5Save the value in Register Pair DE on the STACK
03C9-03CBLD HL,03DDHLD HL,CIORTN21 DD 03Load Register Pair HL with a return address of 03DDH
03CCPUSH HLE5Save the return address in Register Pair HL on the STACK
03CDLD C,A4FSave the character to process (current held in Register A) to Register C so we can use Register A
03CELD A,(DE)1ALoad Register A with the device type code (stored the memory location pointed to by DE)
03CFAND BA0Isolate the device code bits in A by AND’ing with the device codes in B
03D0CP BB8Check to see if the updated device type code in Register A is the same as the driver entry code in Register B
03D1-03D3JP NZ,4033HJP NZ,CIO$C2 33 40Jump 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-03D5CP 02HFE 02At 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-03D8LD L,(IX+01H)DD 6E 01Load 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-03DBLD H,(IX+02H)DD 66 02Load 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
03DCJP (HL)E9Jump to the driver entry address in Register Pair HL
03DD
↳ CIORTNPOP DED1Get the value from the STACK and put it in Register Pair DE
03DE-03DFPOP IXDD E1Get the value from the STACK and put it in Register Pair IX
03E0POP HLE1Get the value from the STACK and put it in Register Pair HL
03E1POP BCC1Get the value from the STACK and put it in Register Pair BC
03E2RETC9RETurn 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
↳ KEYLD HL,4036HLD HL,KYBT$21 36 40Load Register Pair HL with the keyboard work area’s starting address (which is 4036H).
Note: 4036H-403CH is the keyboard work area
03E6-03E8LD BC,3801HLD BC,KEYAD$+101 01 38Load Register Pair BC with the keyboard memory’s starting address (which is 3801H)
03E9-03EALD D,00H16 00Zero Register D, which will be used to track the keyboard code
LD A,(BC)0ALoad Register A with the value at the location of the keyboard memory pointer in Register Pair BC (which is row N)
03ECLD E,A5FLoad Register E with the keyboard memory value in Register A (8 column bits)
03EDXOR (HL)AECheck 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
03EELD (HL),E73Save the keyboard memory value (the column bits) in Register E at the location of the keyboard work area pointer in Register Pair HL
03EFAND EA3Test 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
03F2INC D14Increment the keyboard row counter in Register D
03F3INC L2CIncrement the keyboard work area pointer in Register Pair HL
03F4-03F5RLC CCB 01Adjust the keyboard memory pointer in Register Pair BC by stepping it from 3801 to 3840 by rotating the bits left (RLC)
03F6-03F8JP P,03EBHJP P,KEYLPF2 EB 03Jump 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
03F9RETC9RETurn to CALLer
03FAH-040AH – Accept a Keyboard Downstroke and Convert it to ASCII– “KEYDWN”
03FA
↳ KEYDWNLD E,A5FSave the column number from the new keypress in Register A in Register E
03FBLD A,D7ALoad Register A with the row counter in Register D (this is going to cycle from 0 through 6)
03FCRLCA07Multiply the row counter in Register A by two
03FDRLCA07Multiply the row counter in Register A by two
*03FB-03FDJP 011CHFor ROM v1.2, jump to the new keyboard debounce routine. That routine includes the now deleted RCLA, RCLAinstructions which had to be killed to make room for this 3 byte jump opcode
03FERLCA07Multiply the row counter in Register A by two
03FFLD D,A57Load Register D with the row counter in Register A
0400-0401LD C,01H0E 01Load Register C with the starting column counter (as bit 0)
0402
↳ KEYDLPLD A,C79Load Register A with the column counter in Register C (as a mask)
0403AND EA3Turn 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
0404-0405Jump to 040BH if the column counter in Register A is the same as the active column number in Register E
0406INC D14Increment the column number (which is held in Register D)
0407-0408RLC CCB 01Adjust the column counter in Register C left 1 bit to align the mask
0409-040AJR 0402HJR KEYDLP18 F7Loop back to 0402H until the value in Register D is adjusted for its column
040BH-0428H – Part of the Keyboard routine– “KEYFND”
We now have identified the key. Next we need to see if it is shifted
040B-040D
↳ KEYFNDLD A,(3880H)LD A,(KEYAD$+80H)3A 80 38Load Register A with the value of the SHIFT but from memory location 3880H
040ELD B,A47Load Register B with the SHIFT FLAG value in Register A
040FLD A,D7ALoad Register A with the value in Register D (Row * 8 + column 0-7)
0410-0411ADD 40HC6 40Adjust the ASCII value in Register A (Row * 8 + column (0-7) + 64 decimal)
0412-0413CP 60HFE 60Check to see if the value in Register A is alphabetic by testing for the 4 rows (@, A-Z)
0416-0417RRC BCB 08Put the SHIFT value in Register B in the carry flag (as the RRC command rotates right, and puts bit 0 into the carry bit)
0418-0419Jump if the SHIFT key wasn’t pressed (i.e., the rotated right Register B’s bit zero wasn’t a zero)
041A-041BADD 20HC6 20Adjust the value in Register A for lower case (by ADDing 20H to it)
041CLD D,A57Load Register D with the adjusted character in Register A
041D-041FLD A,(3840H)LD A,(KEYAD$+40H)3A 40 38Load Register A with the value at keyboard memory row six
0420-0421AND 10HAND 0001 0000E6 10Turn off some bits so we can check to see if the down arrow key (or ENTER) was pressed (by ANDing it against 0001 0000)
0424LD A,D7ALoad Register A with the character in Register D
0425-0426SUB 60HD6 60Adjust the value of the character in Register A down by 96, possibly to make it a CONTROL KEY
0429H-043CH – Part of the Keyboard routine– “KEYNAL”
If we are here, the character was not alphanumeric, so need to check for special and/or shift
0429-042A
↳ KEYNALSUB 70HD6 70Adjust the value of the character in Register A down by 112 (for a special key, like ENTER or SPACE)
042B-042CJump to 043DH if the character in Register A is for keyboard row six
042D-042EADD 40HC6 40Readjust the value of character in Register A by adding 64 to adjust to rows 4-5
042F-0430CP 3CHFE 3CCheck to see if the character in Register A is a 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, “:”, “;”, or “,” ,character
0431-0432Jump to 0435H for a simple inversion if the character in Register A is one of those characters
0433-0434XOR 10HXOR 0001 0000EE 10Adjust the character in Register A by XORing against 0001 0000 to invert bit 5
0439-043AXOR 10HEE 10Adjust the character in Register A by XORing against 0001 0000 to reinvert bit 5
043DH-044AH – Part of the Keyboard routine– “KEYSPL”
This routine does a special key conversion via a table
043D
↳ KEYSPLRLCA07Adjust the value in Register A to be (ROW*8 + COLUMN-48) * 2)
043E-043FRRC BCB 08Put the SHIFT value in Register B into the Carry flag
0442INC A3CIncrement the value in Register A to turn (ROW*8 + COLUMN-48) * 2) into (COLUMN*2+1)
0443-0445
↳ KEYNSFLD HL,0050HLD HL,KEYTAB21 50 00Load Register Pair HL with the starting address of the keyboard lookup table at 50H, which is the last row
0446LD C,A4FLoad Register C with the offset in Register A (which is either 43D or 442)
0447-0448LD B,00H06 00Zero Register B
0449ADD HL,BC09Add the offset in Register Pair BC to the starting address of the keyboard lookup table in Register Pair HL
044ALD A,(HL)7ELoad Register A with the ASCII value at the location of the keyboard lookup table pointer in Register Pair HL
044BH-044BH – Part of the Keyboard routine– “KEYRTN”
This routine will debounce the keyboard downstroke and return
044B
↳ KEYRTNLD D,A57Load Register D with the ASCII value for the key pressed in Register A
LD BC,0DACHLD BC,350001 AC 0DLoad Register Pair BC with the delay count (which is 3500)
044F-0451CALL 0060HCALL $PAUSECD 60 00Call the delay routine at 0060H (which will delay BC times 14.65)
0452LD A,D7ALoad Register A with the ASCII value for the key pressed (saved in Register D)
0453-0454CP 01HFE 01Check to see if the BREAK key was pressed
0455RET NZC0Return if the BREAK key wasn’t pressed
0456RST 28HEFIf 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)
044BRET57RETurn 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
↳ DSPLD L,(IX+03H)DD 6E 03Load 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-045DLD H,(IX+04H)DD 66 04Load 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
0460-0462LD A,(IX+05H)DD 7E 05Load Register A with the cursor on/off flag (which is stored at the location of the video device control block pointer in Register Pair IX plus five)
0463OR AB7Check to see if the cursor is on or off
0466LD (HL),A77Display the character in Register A at the location of the current cursor position in Register Pair HL since the cursor is on
0467
↳ DSPGRPLD A,C79Load Register A with the character to be displayed from Register C
0468-0469CP 20HFE 20Check to see if the character to be displayed in Register A is a control code
046A-046CJP C,0506HJP C,DSPCTLDA 06 05Since 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-046ECP 80HFE 80Check to see if the character to be displayed in Register A is a graphic character or space compression code
046F-0470Since 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-0472CP 40HFE 40Check to see if the character to be displayed in Register A is an alphabetic character
0473-0474Since 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-0476SUB 40HSUB “@”D6 40If 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-0478CP 20HFE 20Check to see if the character to be displayed in Register A is a lower case character
0479-047ASince 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-047CSUB 20HD6 20Convert the lower case character in Register A to upper case by subtracting 32
CALL 0541HCALL DSPOUTCD 41 05Go to 0541H display the character in Register A (and scroll the screen if necessary)
0481-0482AND 03HE6 03Turn off some bits in Register A so that it will be in the range of video memory (3C00H-3FFFH) by ANDing it against 0000 0011
0483-0484OR 3CHOR 0011 1100F6 3CTurn on some bits in Register A to force the MSB of the buffer to be 3C-3Fh (i.e., video memory) by ORing it against 0011 1100
0485LD H,A67Load Register H with the updated MSB value (which was forced to be within video memory) in Register A
0486LD D,(HL)56Load Register D with the value at the location of the current cursor position in Register Pair HL
0487-0489LD A,(IX+05H)DD 7E 05Load Register A with the cursor on/off flag at the location of the video device control block pointer in Register Pair IX plus five
048AOR AB7Check to see if the cursor is on or off
048D-048FLD (IX+05H),DDD 72 05Since 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-0491LD (HL),5FHLD (HL),CURCHR36 5FDisplay the cursor character (of 5FH) at the current location of the cursor in Register Pair HL
0492-0494
↳ DSPRTNLD (IX+03H),LDD 75Save 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-0497LD (IX+04H),HDD 74Save 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
0498LD A,C79Load Register A with the character that was displayed in Register C
0499RETC9RETurn to CALLer
049AH – Read the character at the current position of the display– “DSPRD”
049A-049C
↳ DSPRDLD A,(IX+05H)DD 7E 05Load Register A with the cursor on/off flag
049DOR AB7Check to see if the cursor is on or off
049ERET NZC0Return if the cursor is on and is therefore covering the character
049FLD A,(HL)7EIf 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
04A0RETC9RETurn to CALLer
04A1H – Go to the beginning of the line– “DSPBOL”
04A1
↳ DSPBOLLD A,L7DLoad Register A with the LSB of the current position in Register L
04A2-04A3AND 0C0HAND 1100 0000E6 C0Turn 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
04A4LD L,A6FLoad Register L with the updated value in Register A
04A5RETC9Return with the new video buffer address stored in HL
04A6H – Handle graphic characters– “DSPHRC”
04A6-04A7
↳ DSPGRCCP C0HFE C0Check to see if the character to be displayed in Register A is a space compression character
04A8-04A9Since 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-04ABSUB C0HD6 C0Now 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
04AELD B,A47Now 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”
04B1-04B3CALL 0541HCALL DSPOUTCD 41 05Gosub to 0541H to display the blank character in Register A (and scroll the screen if necessary)
04B8H – Turn the cursor on– “DSPCON”
04B8
↳ DSPCONLD A,(HL)7ELoad Register A with the character being displayed at the location of the current cursor position in Register Pair HL
LD (IX+05H),ADD 77 05Save the value in Register A as the cursor on/off flag at the location of the video device control block pointer in Register Pair IX plus five
04BCRETC9RETurn to CALLer
04BDH – Turn the cursor off– “DSPCOF”
04BD
↳ DSPCOFXOR AAFZero Register A to turn the cursor flag off
04C0H – Home the cursor– “DSPHOM”
04C0-04C2
↳ DSPHOMLD HL,3C00HLD HL,DSPAD$21 00 3CLoad Register Pair HL with the starting address of video memory (which is 3C00H)
04C3-04C5LD A,(403DH)LD A,(CAST$)3A 3D 40Load 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-04C7AND 0F7HAND 1111 0111E6 F7Mask Register A against 1111 0111, forcing Bit 3 to OFF to denote 64 characters per line
04C8-04CALD (403DH),ALD (CAST$),A32 3D 40Put the masked Register A back into 403DH
04CB-04CCOUT (0FFH),AD3 FFSend the value in Register A out port 255 which is the video/cassette port
04CDRETC9RETurn to CALLer
04CEH – Backspace– “DSPBSP”
04CE
↳ DSPBSPDEC HL2BDecrement the current cursor position (i.e., backspace) in Register Pair HL
04CF-04D1LD A,(CAST$)LD A,(403DH)3A 3D 40Load Register A with the 32/64 character per line flag (which is at 403DH).
Note: 403DH-4040H is used by DOS
04D2-04D3AND 08HAND 0000 1000E6 08Turn off some bits so we can check to see if it’s 32 or 64 characters per line by ANDing it against 0000 1000
04D4-04D5Since 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
04D6DEC HL2BRight 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
LD (HL),20HLD (HL),” “36 20Display a space character at the location of current cursor position in Register Pair HL
04D9RETC9RETurn to CALLer
04DAH – Cursor Left– “DSPLFT”
04DA-04DC
↳ DSPLFTLD A,(403DH)LD A,(CAST$)3A 3D 40Load Register A with the 32/64 character per line flag (which is at 403DH).
Note: 403DH-4040H is used by DOS
04DD-04DEAND 08HAND 0000 1000E6 08Mask Register A against 0000 1000 to isolate Bit 3 and set the flags Z and NZ based on that bit
04DF-04E1CALL NZ,04E2HCALL NZ,DSPLF2C4 E2 04If 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
LD A,L7DWe are actually here regardless of what Bit 3 was. Load Register A with the LSB of the current cursor position in Register L
04E3-04E4AND 3FHAND 0011 1111E6 3FMask the value in Register A by ANDing it against 0011 1111 to backspace LSB of curstor to the previous line and then ..
04E5DEC HL2BBackspace the cursor by 1 by decrementing the current cursor position in Register Pair HL
04E6RET NZC0Return 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
↳ DSPDWNLD DE,0040H11 40 00We 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)
04EAADD HL,DE19Skip 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
04EBRETC9RETurn to CALLer
04ECH – Cursor Right– “DSPRHT”
04EC
↳ DSPRHTINC HL23Increment the current cursor position in Register Pair HL
04EDLD A,L7DLoad Register A with the LSB of the current cursor position in Register L
04EE-04EFAND 3FHAND 0011 1111E6 3FTurn 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
04F0RET NZC0Return 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
↳ DSPUPLD DE,0FFC0H11 C0 FFNow 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)
04F4ADD HL,DE19Add 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
04F5RETC9RETurn to CALLer
04F6H – Set up 32-Character mode– “DSPETB”
04F6-04F8
↳ DSPETBLD A,(403DH)LD A,(CAST$)3A 3D 40This 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-04FAOR 08HOR 0000 1000F6 08Turn 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-04FDLD (403DH),ALD (CAST$),A32 3D 40Save the resulting value in Register A back into 403DH (the 32/64 character per line flag).
Note: 403DH-4040H is used by DOS
04FE-04FFOUT (0FFH),AD3 FFSend the value in Register A out the port 255 (the video/cassette)
0500INC HL23Increment the current cursor position in Register Pair HL
0501LD A,L7DLoad Register A with the LSB of the current cursor position in Register L
0502-0503AND 0FEHE6 FETurn 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
0504LD L,A6FLoad Register L with the updated value in Register A
0505RETC9RETurn to CALLer
0506H – Process control characters– “DSPCTL”
0506-0508
↳ DSPCTLLD DE,0480HLD DE,DSPSKP11 80 04Load Register Pair DE with the return address
0509PUSH DED5Save the return address in Register Pair DE on the STACK
050A-050BCP 08HFE 08Check to see if the character in Register A is a backspace and erase character (i.e., 08H)
050C-050DSince 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-050FCP 0AHFE 0ACheck to see if the character in Register A is less than a line feed character
0510RET CD8Return if the character to be displayed in Register A is less than a line feed character so we can ignore them
0511-0512CP 0EHFE 0ECheck to see if the character to be displayed in Register A is less than or equal to a turn on the cursor character
0513-0514JR C,0564HJR C,DSPCR38 4FJump if the character to be displayed in Register A is less than a turn on the cursor character so that a carriage return will be displayed
0515-0516Jump if the character to be displayed in Register A is a turn on the cursor character
0517-0518CP 0FHFE 0FCheck to see if the character in Register A is a turn off the cursor character
0519-051AJump if the character to be displayed in Register A is a turn off the cursor character
051B-051CCP 17HFE 17Check to see if the character to be displayed in Register A is a turn on the 32 character per line mode character
051D-051EJump if the character to be displayed in Register A is a turn on the 32 character per line mode character
051F-0520CP 18HFE 18Check to see if the character to be displayed in Register A is a left arrow character
0521-0522Jump if the character to be displayed in Register A is a left arrow character
0523-0524CP 19HFE 19Check to see if the character to be displayed in Register A is a right arrow character
0525-0526Jump if the character to be displayed in Register A is a right arrow character
0527-0528CP 1AHFE 1ACheck to see if the character to be displayed in Register A is a down arrow character
0529-052AJump if the character to be displayed in Register A is a down arrow character
052B-052CCP 1BHFE 1BCheck to see if the character to be displayed in Register A is an up arrow character
052D-052EJR Z,04F1HJR Z,DSPUP28 C2Jump if the character to be displayed in Register A is an up arrow character
052F-0530CP 1CHFE 1CCheck to see if the character to be displayed in Register A is a home the cursor character
0531-0532Jump if the character to be displayed in Register A is a home the cursor character
0533-0534CP 1DHFE 1DCheck to see if the character to be displayed in Register A is a backspace to the beginning of the line character
0535-0537JP Z,04A1HJP Z,DSPBOLCA A1 04Jump if the character to be displayed in Register A is a backspace to the beginning of the line character
0538-0539CP 1EHFE 1ECheck to see if the character to be displayed in Register A is an erase to the end of the line character
053A-053BJump if the character to be displayed in Register A is an erase to the end of the line character
053C-053DCP 1FHFE 1FCheck to see if the character to be displayed in Register A is an erase to the end of the screen character
053E-053FJump if the character to be displayed in Register A is an erase to the end of the screen character
0540RETC9Return if the character to be displayed isn’t any of the above control codes
0541H – Part of the Display routine– “DSPOUT”
Output the character held in Register A and move the cursor, scrolling the screen if necessary
0541
↳ DSPOUTLD (HL),A77Put the character stored in A into the memory location stored in HL
0542INC HL23Increment the current cursor position in Register Pair HL
0543-0545LD A,(403DH)LD A,(CAST$)3A 3D 40Load Register A with the 32/64 character per line flag (stored in 403DH).
Note: 403DH-4040H is used by DOS
0546-0547AND 08HAND 0000 1000E6 08Turn off some bits so we can check to see if it’s 32 or 64 characters per line by ANDing it against 0000 1000
054AINC HL23Increment the current cursor position in Register Pair HL
054B
↳ DSPOT2LD A,H7CLoad Register A with the MSB of the current cursor position in Register H
054C-054DCP 40HFE 40Check to see if the end of video memory plus one has been reached
054ERET NZC0Return if the end of video memory plus one hasn’t been reached
054F-0551LD DE,FFC0H11 C0 FFLoad Register Pair DE with a negative length of a line on the video display (i.e., -64)
0552ADD HL,DE19Move 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
0553PUSH HLE5Save 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
↳ DSPROLLD DE,3C00HLD DE,DSPAD$11 00 3CLoad Register Pair DE with the starting address of video memory
0557-0559LD HL,3C40HLD HL,DSPAD$+6421 40 3CLoad Register Pair HL with the starting address of the second line of the video memory
055APUSH BCC5Save the value in Register Pair BC on the STACK
055B-055DLD BC,03C0HLD BC,1024 – 6401 C0 03Load Register Pair BC with the length of video memory to be moved (which is 15 lines or 960 bytes)
055E-055FLDIRED B0Move the last fifteen lines of video memory to the first fifteen lines of video memory to scroll 1 line
0560POP BCC1Get the value from the STACK and put it in Register Pair BC
0561EX DE,HLEBLoad Register Pair HL with the starting address of the sixteenth line of video memory
0564H – Part of the Display routine– “DSPCR”
Display a carriage return / line feed
0564
↳ DSPCRLD A,L7DLoad Register A with the LSB of the current cursor position in Register L
0565-0566AND C0HE6 C0Turn off some bits in Register A so that it’s the start of the current line by ANDing it with 1100 0000
0567LD L,A6FLoad Register L with the updated value in Register A
0568PUSH HLE5Save the current cursor position in Register Pair HL on the STACK
0569-056BLD DE,0040H11 40 00Load Register Pair DE with the length of a line on the video display (which is 40H or 64 Decimal)
056CADD HL,DE19Add the length of a line on the video display in Register Pair DE to the current cursor position in Register Pair HL
056DLD A,H7CLoad Register A with the MSB of the current cursor position in Register H
056E-056FCP 40HFE 40Check to see if the end of video memory plus one has been reached (which is 40H or 64 Decimal)
0570-0571Jump to 0554H (to scroll the screen) if the end of video memory plus one has been reached
0572POP DED1Throw away the entry at the top of the STACK
0573H – Part of the Display routine– “DSPEOL”
Erase to the end of the line
0573
DSPEOLPUSH HLE5Save the new cursor position in Register Pair HL on the STACK
0574LD D,H54Load Register D with the MSB of the current cursor position in Register H
0575LD A,L7DLoad Register A with the LSB of the current cursor position in Register L
0576-0577OR 3FHOR 0011 1111F6 3FTurn on some bits in Register A to isolate the screen line by masking the value in Register A by ORing it against 0011 1111
0578LD E,A5FSave the updated value in Register A in Register E
0579INC DE13Increment the adjusted cursor position in Register Pair DE
057CH – Part of the Display routine– “DSPEOF”
Erase to the end of the frame
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
↳ DSPEOFPUSH HLE5Save 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
↳ DSPERFLD DE,4000HLD DE,DSPAD$+102411 00 40Load Register Pair DE with the end of video memory plus one (which is 4000H)
0580-0581
↳ DSPERALD (HL),20HLD (HL),” “36 20Display a space (which is 20H) at the video memory pointer in Register Pair HL
0582INC HL23Increment the video memory pointer in Register Pair HL
0583LD A,H7CLoad Register A with the MSB of the video memory pointer held in Register H
0584CP DBACheck 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
0585-0586LOOP 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
0587LD A,L7DLoad Register A with the LSB of the video memory pointer in Register L
0588CP EBBCheck 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
0589-058ALOOP 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
058BPOP HLE1Get the current cursor position from the STACK and put it in Register Pair HL
058CRETC9RETurn 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
↳ PRTLD A,C79Load Register A with the character to be sent to the printer in Register C
058EOR AB7Set the status flags and test A to see if it is zero
058F-0590Jump to 05D1H (get the printer status and return) if the character to be sent to the printer in Register A is equal to zero
0591-0592CP 0BHFE 0BCheck to see if the character to be sent to the printer in Register A is a skip to the top of the form character which is CHR$(11)
0593-0594JR Z,059FHJR Z,PRTVT28 0AJump to 059FH if the character to be sent to the printer in Register A is a skip to the top of the form character (a/k/a a vertical tab)
0595-0596CP 0CHFE 0CCheck to see if the character to be sent to the printer in Register A is a conditional skip to the top of the form character which is CHR$(12)
0597-0598Jump 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)
0599XOR AAFZero Register A (which causes the null character to be printed)
059A-059COR (IX+03H)DD B6 03Check 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
059F-05A1LD A,(IX+03H)DD 7E 03Load 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
↳ PRTVTSUB (IX+04H)DD 96 04Subtract 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
05A5LD B,A47A 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)
05A6-05A8
↳ PRTFFCALL 05D1HCALL PRTSTACD D1 05Call 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)
05AB-05ACLD A,0AH3E 0ANow the printer is ready and B has the number of lines to skip. Load Register A with a line feed character which is CHR$(10)
05AD-05AFLD (37E8H),ALD (PRTAD$),A32 E8 37Send the value in Register A to the printer by loading it into memory location 37E8H
05B2-05B3JR PRTOPJR 05CCH18 18Now all the lines have been skipped, so jump to 05CCH to reset the line counter and return
05B4
↳ PRTITPUSH AFF5Save the character to be sent to the printer in Register A on the STACK
05B5-05B7
↳ PRTIT2CALL 05D1HCALL PRTSTACD D1 05Call 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)
05BAPOP AFF1Get the character to be sent to the printer from the STACK and put it in Register A
05BB-05BDLD (37E8H),ALD (PRTAD$),A32 E8 37Send the character in Register A to the printer by putting the character into 37E8H
05BE-05BFCP 0DHFE 0DCheck to see if the character which was just sent to the printer was a carriage return
05C0RET NZC0If the character sent to the printer in Register A isn’t a carriage return, return out of the print routine
05C1-05C3INC (IX+04H)DD 34 04Now 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-05C6LD A,(IX+04H)DD 7E 04Load 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-05C9CP (IX+03H)DD BE 03Check 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
05CALD A,C79Load Register A with the character sent to the printer in Register C
05CBRET NZC0Return if the number of lines printed so far isn’t the same as the number of lines per page
05CC-05CF
↳ PRTOPLD (IX+04H),04HDD 36 04 00Zero the number of lines printed so far at the location of the printer device control block pointer in Register Pair IX plus four
05D0RETC9RETurn 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-05D3LD A,(37E8H)LD A,(PRTAD$)3A E8 37Get the status value from the printer (which is held in 37E8H) and put it in Register A
05D4-05D5AND F0HAND 1111 0000E6 F0Since 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-05D7CP 30HFE 30Check 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)
05D8RETC9RETurn 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
↳ KEYINPUSH HLE5Save the start of the input buffer area pointer in Register Pair HL on the STACK
05DA-05DBLD A,0EH3E 0ELoad Register A with a turn on the cursor character (which is 14)
05DC-05DECALL 0033HCALL $DSPCD 33 00Display a cursor by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
05DFLD C,B48Load Register C with the size of the input buffer in Register B
05E0-05E2
↳ KLNNXTCALL 0049HCALL $KEYCD 49 00Call the “WAIT FOR KEYBOARD INPUT” routine at 0049H, so as to wait until a key is pressed
05E3-05E4CP 20HCP ” “FE 20Check to see if the key that was pressed in Register A is greater than a SPACE
05E5-05E6Jump if the key that was pressed in Register A is displayable (i.e., greater than or equal to a SPACE)
05E7-05E8CP 0DHFE 0DCheck to see if the key that was pressed in Register A is a CARRIAGE RETURN
05E9-05EBJP Z,0662HJP Z,KLNCRCA 62 06Jump if the key that was pressed in Register A is a CARRIAGE RETURN
05EC-05EDCP 1FHFE 1FCheck to see if the key that was pressed in Register A is the CLEARkey
05F0-05F1CP 01HFE 01Check to see if the key that was pressed in Register A is the BREAKkey
05F4-05F6LD DE,05E0HLD DE,KLNNXT11 E0 05Load Register Pair DE with the return address of 05E0H
05F7PUSH DED5Save the return address in Register Pair DE on the STACK
05F8-05F9CP 08HFE 08Check to see if the key that was pressed in Register A is a backspace (which is 08) the cursor and erase character
05FA-05FBJump if the key was pressed in Register A is a backspace the cursor and erase character
05FC-05FDCP 18HFE 18Check to see if the key that was pressed in Register A is a backspace character
05FE-05FFJump if the key that was pressed in Register A is a backspace character
0600-0601CP 09HFE 09Check to see if the key that was pressed in Register A is a tab character
0604-0605CP 19HFE 19Check to see if the key that was pressed in Register A is a turn on the 32 character per line mode character
0606-0607Jump if the key that was pressed in Register A is a turn on the 32 character per line mode character
0608-0609CP 0AHFE 0ACheck to see if the key that was pressed in Register A is a line feed character of CHR$(10)
060ARET NZC0Return (to 05E0H) if the key that was pressed in Register A isn’t a line feed character
060BPOP DED1Get the return address from the STACK and put it in Register Pair DE (so that it isn’t 05E0H anymore)
060C
↳ KLNCHRLD (HL),A77We 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
060DLD A,B78Load Register A with the length of the buffer remaining in Register B
060EOR AB7Check to see if there is any more of the input buffer remaining (and set status)
0611LD A,(HL)7ENow 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
0612INC HL23Increment the input buffer pointer in Register Pair HL
0613-0615CALL 0033HCALL $DSPCD 33 00Display the character by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
0616DEC B05Decrement the number of bytes remaining in the input buffer area in Register B
0619H – Part of the Display routine– “KLNCLR”
Clear the screen
0619H-061B
↳ KLNCLRCALL 01C9HCALL CLSCD C9 01Call the CLEAR SCREEN routine at 01C9H (which clears the screen, changes to 64 characters, and homes the screen)
061CLD B,C41Load Register B with the length of the input buffer in Register C (which resets the counter of characters transmitted)
061DPOP HLE1Get the starting address for the input buffer area from the STACK and put it in Register Pair HL (which resets the buffer address)
061EPUSH HLE5Save the starting address for the input buffer area in Register Pair HL on the STACK
061F-0621JP KLNNXTJP 05E0HC3 E0 05Jump to 05E0H (to get the next character, which is now the first character in the buffer)
0622H – Part of the Display routine– “KLNCNL”
Cancel the accumulated line
0622-0624
↳ KLNCNLCALL 0630HCALL KLNBSPCD 30 06Gosub to wait for the next key and back up the input buffer pointer in Register Pair HL if necessary
0625DEC HL2BBackup to the previous character (the one before the CARRIAGE RETURN) by decrementing the input buffer pointer in Register Pair HL
0626LD A,(HL)7ELoad Register A with the character at the location of the input buffer pointer in Register Pair HL
0627INC HL23Increment the input buffer pointer in Register Pair HL to the net availabile position
0628-0629CP 0AHFE 0ACheck to see if the character in Register A is the line feed character of CHR$(10)
062ARET ZC8Return if the character in Register A is a line feed character
062B
↳ KLNCANLD A,B78Now 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
062CCP CB9Check 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
062FRETC9The 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
↳ KLNBSPLD A,B78Load Register A with the number of bytes remaining in the input buffer area in Register B
0631CP CB9Compare 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
0632RET ZC8Return if the input buffer area is full
0633DEC HL2BDecrement the input buffer area pointer in Register Pair HL to backspace the previous character .
0634LD A,(HL)7E… and then get that character into Register A
0635-0636CP 0AHFE 0ACheck to see if the character in Register A is the line feed character of CHR$(10)
0637INC HL23Increment the input buffer area pointer in Register Pair HL
0638RET ZC8Return if the character in Register A is a line feed character
0639DEC HL2BDecrement the input buffer area pointer in Register Pair HL to backspace the previous character in the buffer .
063A-063BLD A,08H3E 08Load Register A with a backspace of CHR$(08) and then .
063C-063ECALL 0033HCALL $DSPCD 33 00Effectuate the backspace by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
063FINC B04Increment the number of characters remaining in the input buffer area in Register B
0640RETC9RETurn to CALLer
0641H – Part of the Display routine– “KLNETB”
Turn on 32 Character Mode
0641-0642
↳ KLNETBLD A,17HLD A,0001 01113E 17Load Register A with mask of 00010111 so as to turn on the 32 character per line mode character
0643-0645JP 0033HJP $DSPC3 33 00Call 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
0646H – Part of the Display routine– “KLNHT”
Process a horizontal tab
0646-0648
↳ KLNHTCALL DSPPOSCALL 0348HCD 48 03Go get the cursor line position and return with it in Register A
0649-064AAND 07H<ADD 0000 0111span class=”opcode2″>E6 07Turn off some bits so we can mask the cursor line position in Register A by ANDing it against 0000 0111
064BCPL2FInverse the value in Register A
064CINC A3CIncrement the value in Register A so that it is 1 <= A <= 8
064D-064EADD 08HC6 08Clear the upper bits of the counter
064FLD E,A5FLoad Register E with the number of spaces to be added in Register A
0650
↳ KLNHTLLD A,B78Load Register A with the number of bytes remaining in the input buffer area in Register B
0651OR AB7Check to see if the buffer is full
0652RET ZC8Return if the input buffer is full
0653-0654LD A,20HLD A,” “3E 20Load Register A with a space character
0655LD (HL),A77Save the space character in Register A at the location of the input buffer area pointer in Register Pair HL
0656INC HL23Increment the input buffer area pointer in Register Pair HL
0657PUSH DED5Save the value in Register Pair DE on the STACK
0658-065ACALL 0033HCALL $DSPCD 33 00Display the space by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
065BPOP DED1Get the value from the STACK and put it in Register Pair DE
065CDEC B05Since you just displayed one of the spaces, decrement the number of bytes remaining in the input buffer area in Register B .
065DDEC E1D… and decrement the number of spaces to be added in Register E
065ERET ZC8Return if the number of spaces has been added to the input buffer
065F-0660JR KLNHTLJR 0650H18 EFLoop back to 0650H until all the spaces have been added to the input buffer
0661H – Part of the Display routine– “KLNBRK”
Process a Carriage Return and Automatic Line Feed
0661
↳ KLNBRKSCF37Set the Carry flag. This is done because the routine is going to exit with the CARRY flag set as an indication that BREAK was hit
0662
↳ KLNCRPUSH AFF5Save the value in Register Pair AF on the STACK, which saves the CARRY flag
0663-0664LD A,0DH3E 0DLoad Register A with a carriage return character
0665LD (HL),A77Save the carriage return character in Register A at the location of the input buffer area pointer in Register Pair HL
0666-0668CALL 0033HCALL $DSPCD 33 00Display 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-066ALD A,0FH3E 0FLoad Register A with a turn off the cursor character
066B-066DCALL 0033HCALL $DSPCD 33 00Turn off the cursor by calling the DISPLAY A CHARACTER routine at 0033H (which puts the character in Register A on the video screen)
066ELD A,C79Load Register A with the length of the input (=buffer size) in Register C
066FSUB B90Subtract the number of bytes remaining in the input buffer area in Register B from the length of the input buffer area in Register A
0670LD B,A47Load Register B with the number of characters in the input buffer area in Register A
0671POP AFF1Get 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
0672POP HLE1Get the starting address of the input buffer area from the STACK and put it in Register Pair HL
0673RETC9RETurn to CALLer
0674H-06D1H – INITIALIZATION ROUTINE– “INIT”
0674-0675
↳ INITOUT (0FFH),AD3 FFSend 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-0678LD HL,06D2HLD HL,INITR21 D2 06Load Register Pair HL with the starting address of the RST’s and the video/keyboard/printer DCB’s
0679-067BLD DE,4000HLD DE,RST1$11 00 40Load Register Pair DE with the starting address of the communications region
067C-067ELD BC,0036HLD BC,INITRL01 36 00Load Register Pair BC with the length of the ROM area to be moved (which is 54 bytes)
067F-0680LDIRED B0Move the RST’s and DCB’s (06D2H-0707H) to the RAM vector area of 4000H-4035H
0681DEC A3DDecrement the value in Register A
0682DEC A3DDecrement the value in Register A
0683-0684Loop back to 0674H until the block move has occurred 128 times, jump to 0674H (which sends a click to the cassette port)
*0683-0684Loop back to 0676H until the block move has occurred 128 times, jump to 0676H (which runs the routine AFTER the click, so no more click)
0685-0686LD B,27HLD B,SIZRM$06 27Load Register B with the number of bytes of memory to be zeroed (which is 39)
0687
↳ CLRAMLD (DE),A12Save the zero in Register A at the location of the memory pointer in Register Pair DE
0688INC DE13Increment the destination pointer in Register Pair DE
0689-068ADJNZ CLRAMDJNZ 0687H10 FCLoop back to 0687H until all of the memory locations between 4036H and 4062H have been zeroed
068BH – INITIALIZATION ROUTINE– “CHKMAN”
Check for a manual override
068B-068D
↳ CHKMANLD A,(3840H)LD A,(KEYAD$+40H)3A 40 38Load Register A with the location of keyboard memory for the BREAK key (which is 14400)
068E-068FAND 04H<AND 0000 0100E6 04Turn off some bits so we can check to see if the BREAK key is being pressed
0690-0692JP NZ,0075HJP NZ,INIT2C2 75 00Jump to 0075H if the BREAK key was pressed, and flow down if it wasn’t
This is the beginning of the routine which boots a diskette
0693-0695LD SP,407DHLD SP,TSTK$31 7D 40Set the STACK pointer to 407DH
0696-0698LD A,(37ECH)LD A,(FDCAD$)3A EC 37Load Register A with the status of the disk controller (which is stored in 37ECH)
0699INC A3CIncrement the value in Register A so that we can .
069A-069BCP 02HFE 02… check to see if the disk controller is present
069C-069EJP C,0075HJP C,INIT2DA 75 00Jump to the Level II Initialization Routine at 0075H if the disk controller isn’t present
069FH – Bootstrap from Diskette– “BOOT”
069F-06A0
↳ BOOTLD A,01H3E 01So now we know there is a disk controller, so lets load Register A with the unit select mask for Drive :0
06A1-06A3LD (37E1H),ALD (DSEL$),A32 E1 37Select Drive :0 and turn the motor on by loading 01H into 37E1H
06A4-06A6LD HL,37ECHLD HL,FDCAD$21 EC 37Load Register Pair HL with the address of the disk command/status Register of 37ECH
06A7-06A9LD DE,37EFHLD DE,FDCAD$+311 EF 37Load Register Pair DE with the address of the disk data Register of 37EFH
06AA-06ABLD (HL),03H36 03Load 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)
06AC-06AELD BC,0000H01 00 00Load Register Pair BC with the delay count
06AF-06B1CALL 0060HCALL $PAUSECD 60 00Call the delay routine at 0060H (which will delay BC times 14.65; about 3 seconds)
06B2-06B3
↳ BOOTDLBIT 0,(HL)CB 46Top 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
06B4-06B5Loop back to that test in 06B2H until the disk is no longer busy
06B6XOR AAFZero Register A
This routine loads Drive 0, Track 0, Sector 0 into 4200H-4455H, and then jumps to 4200H
06B7-06B9LD (37EEH),ALD (FDCAD$+2),A32 EE 37Save the value in Register A in the disk sector Register (at 37EEH)
06BA-06BCLD BC,4200HLD BC,MEM$01 00 42Load Register Pair BC with the address in memory to place the sector read (which is 4200H)
06BD-06BELD A,8CH3E 8CLoad 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
06BFLD (HL),A77Put 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
↳ BOOTLPBIT 1,(HL)CB 4ETop of a loop to check the disk command/status Register of 37ECH to see if there is data available
06C4LD A,(DE)1ASo now we know there is data available, so we load Register A with the byte read from the disk (i.e., the data in disk data Register of 37EFH)
06C5LD (BC),A02Save the value in Register A at the location of the memory pointer in Register Pair BC (4200H-4455H)
06C6INC C0CIncrement the LSB of the memory pointer in Register C
06C7-06C8Loop back to 06C0H until the whole 256 bytes of the sector has been read into 4200H-4455H
06C9-06CBJP 4200HJP MEM$C3 00 42Now 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 1A19H as the latter sometimes does strange things to any resident BASIC program
06CC-06CE
↳ RESETRLD BC,1A18HLD BC,STPRDY01 18 1ALoad Register Pair BC with the starting address of the Level II BASIC READY routine (which is kept at 1A18H)
06D2H-0707H – ROM STORAGE LOCATION FOR DATA TO BE MOVED TO RAM BY THE INITIALIZATION PROCESS– “INITR”
06D2
↳ INITRJP 1C96HJP SYNCHRC3 96 1CThis will be 4000H – it is a jump to the RST 08H routine (COMPARE SYMBOL routine). DOS will overwrite this value
06D5JP 1D78HJP CHRGTRC3 78 1DThis will be 4003H – it is a jump to RST 10H (get the next character). DOS will overwrite this value
06D8JP 1C90HJP DCOMPRC3 90 1CThis will be 4006H – it is a jump to RST 18H (compare DE and HL). DOS will overwrite this value
06DBJP 25D9HJP GETYPRC3 D9 25This will be 4009H – it is a jump to RST 20H (tests for data type). DOS will overwrite this value
06DERETC9This will be 400CH – is a RETurn from RST 28H (which is a jump to 4BA2H for DOS)
06DFNOP
06E000NOP
06E1RETC9This will be 400FH – it is a RETurn from RST 30H (which is a jump to 44B4H for DOS)
06E2NOP
06E300NOPThis will be 4012H – RST 38H vector DI/RET (JP 4518H for DOS)
06E4EIFBThis is the interrupt entry point vector
06E5RET
06E600NOP
This is the keyboard DCB
06E701Keyboard DCB + 0
06E803 E3Keyboard DCB + 1 – Driver Address
06EA00Keyboard DCB + 3
06EB00Keyboard DCB + 4
06EC00Keyboard DCB + 5
06ED“K”Keyboard DCB + 6
06EE“I”Keyboard DCB + 7
This is the display DCB
06EF07Display DCB + 0
06F0-06F158 04Display DCB + 1,2 – Driver Address
06F2-06F300 3CDisplay DCB + 3,4 – Cursor Position Address
06F40Display DCB + 5 – Cursor Character
06F5“D”Display DCB + 6
06F6“O”Display DCB + 7
This is the printer DCB
06F76Printer DCB + 0
06F8-06F98D 05Printer DCB + 1,2 – Driver Address
06FA43Printer DCB + 3 – Lines per page
06FB0Printer DCB + 4 – Line Counter
06FC0Printer DCB + 5
06FD“P”Printer DCB + 6
06FE“R”Printer DCB + 7
06FFJP 5000HC3 00 50This will be 402DH, and SYS 0 will change this to JP 4400H
0702RST 00HC7This will be 4030H, and SYS 0 will change this to LD A,A3H
0703NOP
070400NOPThis will be 4032H, and SYS 0 will change this to RST 28H
0705LD A,00H3E 00This will be 4033H, and SYS 0 will change this to 44BBH
0707RET
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).
According to Vernon Hester, there are a whole lot of errors in ROM code for processing math. I have no hope of finding the code so I will put them here:
When a number is just under specific decimal magnitudes, the ROM prints a colon instead of 10
Example: 9999999999999999 / 1D12 + 3D-13 returns :000
070BH-070FH – SINGLE PRECISION ADDITION, ACCumulator = (HL) + ACCumulator– “FADDH”
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 13LD HL,1380HLD HL,FHALFLoad 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.
070B-070D
↳ FADDSCALL 09C2HCALL MOVRMCD C2 09Move 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)
070E-070FJR 0716HJR FADD18 06Actually 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
0710H-0712H – SINGLE PRECISION SUBTRACTION, ACCumulator = (HL) – ACCumulator
“FSUBS”
Single-precision subtraction (ACC=(HL)-ACC). This loads the BCDE registers with the value from (HL), then passes control to 713H.
0710-0712
↳ FSUBSCALL 09C2HCALL MOVRMCD C2 09Load a SINGLE PRECISION value pointed to by Register Pair HL into Register Pairs BC and DE via a GOSUB to MOVRM
0713H-0715H – SINGLE PRECISION SUBTRACTION, ACCumulator = BCDE – ACCumulator– “FSUB”
Single-precision subtraction (ACCumulator=BCDE-ACCumulator). The routine actually inverts ACCumulator (i.e., 4121H-4122H) and adds it to the contents of the BCDE registers which, in effect, is a subtraction. The result will be stored in the ACCumulator (i.e., 4121H-4122H).
Single Precision Subtract: Subtracts the single precision value in (BC/DE) from the single precision value in the ACCumulator. The difference is left in the ACCumulator
Single-precision subtraction (ACC=BCDE-ACC). The routine actually inverts the ACC and adds it to the contents of the BCDE registers which, in effect, is a subtraction. The result will be stored in the arithmetic work area (ACC)
Note: If you wanted to subtract two single precision numbers, store the minuend in the BCDE registers and store the subtrahend in 4121H-4124H and then CALL 0713H. The result (in single precision format) is in 4121H-4124H in approximately 670 microseconds.
0713-0715
↳ FSUBCALL 0982HCALL NEGCD 82 09Go reverse the sign of the single precision value in Register Pairs BC and DE so that the addition routine just below can be used.
0716H-0752H – SINGLE PRECISION ADDITION, ACCumulator = BCDE + ACCumulator– “FADD”
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
↳ FADDLD A,B78First 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
0717OR AB7Set 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
0718RET ZC8Return if the single precision value in Register Pairs BC and DE is equal to zero because the result is already in the ACCumulator.
0719-071BLD A,(4124H)LD A,(FAC)3A 24 41Next, 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)
071COR AB7Set the flags based on the exponent (now in A) is equal to zero
071D-071FJP Z,09B4HJP Z,MOVFRCA B4 09If 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.
0720SUB B90Subtract 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.
0721-0722If 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.
0723CPL2FIf 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)
0724INC A3CIncrement the difference in the exponents in Register A so that it will be the correct positive number
0725EX DE,HLEBSwap the ACCumulator and the Registers
0726-0728CALL 09A4HCALL PUSHFCD A4 09Call 09A4 which moves the SINGLE PRECISION value in the ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
0729EX DE,HLEBLoad Register Pair DE with the 16-bit value in Register Pair HL
072A-072CCALL 09B4HCALL MOVFRCD B4 09Call 09B4H which moves the SINGLE PRECISION value in DC/DE into ACCumulator
072DPOP BCC1Next 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
072EPOP DED1Get 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
↳ FADD1CP 19HFE 19The 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
0731RET NCD0If the math is going to exceed 24 bits, then we need to fail and RETurn
0732PUSH AFF5Save the shift count (the difference in the exponents in Register A) on the STACK
0733-0735CALL 09DFHCALL UNPACKCD DF 09Set the sign bits for the single precision values and return with the equality of the sign bits in Register A
0736LD H,A67Save the sign bits (the subtraction flag) into Register A
0737POP AFF1Get shift count (the difference of the exponents) back from the STACK and put it in Register A
0738-073ACALL 07D7HCALL SHIFTRCD D7 07Shift 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.
073BOR HB4Get the subtraction flag to see if the sign bits are equal
073C-073ELD HL,4121HLD HL,FACLO21 21 41Load Register Pair HL with the starting address of ACCumulator
0742-0744CALL 07B7HCALL FADDACD B7 07Otherwise, we add the single precision value in BCDE to the single precision value in the ACCumulator
0748INC HL23If 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
0749INC (HL)34Increment the exponent in the ACCumulator at the location of the memory pointer in Register Pair HL
074A-074CJP Z,07B2HJP Z,OVERRCA B2 07Check 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-074ELD L,01H2E 01Prepare 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
074F-0751CALL 07EBHCALL SHRADDCD EB 07Go shift the single precision result in Register Pairs BC and DE
0754H-077CH – SINGLE PRECISION MATH ROUTINE– “FADD3”
This routine will subtract CDEB from ((HL)+0,1,2),0.
0754
↳ FADD3XOR AAFZero Register A to negate the unflow byte and subtract the numbers.
0755SUB B90Subtract the 8-bit value in Register B from the value in Register A
0756LD B,A47Save the result into Register A
0757LD A,(HL)7EPrepare to subtract the low order numbers. First, load Register A with the value at the memory pointer in Register Pair HL
0758SBC A,E9BSubtract the value in Register E from the value in Register A
0759LD E,A5FLoad Register E with the result in Register A
075AINC HL23Increment the memory pointer in Register Pair HL to point to the next byte (the middle order numbers) to deal with
075BLD A,(HL)7EPrepare to subtract the middle order numbers. First, load Register A with the value at the location of the memory pointer in Register Pair HL
075CSBC A,D9ASubtract the value in Register D from the value in Register A
075DLD D,A57Load Register D with the result in Register A
075EINC HL23Increment the memory pointer in Register Pair HL to point to the next byte (the high order numbers) to deal with
075FLD A,(HL)7ELoad Register A with the value at the location of the memory pointer in Register Pair HL
0760SBC A,C99Subtract the value in Register C from the value in Register A
0761LD C,A4FLoad Register C with the result in Register A
With that out the way, we need to make sure we have a positive mantissa (or else we will need to negate the number).
0762-0764
↳ FADFLTCALL C,07C3HCALL C,NEGRDC C3 07If the Carry flag is set (which is to indicate that the number was negative), go convert the single precision value to a positive number
This next routine normalizes CDEB. In doing so, ABCDE and HL are all modified. This routine shifts the mantissa left until the MSB is a 1.
0765
↳ NORMALLD L,B68Put the lowest two bytes into (HL)
0766LD H,E63Load Register H with the LSB of the single precision value in Register E
0767XOR AAFZero Register A so that Register B can track the shift count.
0769LD A,C79Load Register A with the MSB of the single precision value in Register C
076AOR AB7Check to see if the value in Register A is equal to zero
076DLD C,D4AShift the NMSB into the MSB by loading Register C with the value in Register D
076ELD D,H54Shift the LSB into the NMSB by loading Register D with the value in Register H
076FLD H,L65Load Register H with the value in Register L
0770LD L,A6FLoad Register L with the value in Register A
0771LD A,B78Load Register A with the new shift count (exponent counter) in Register B
0772-0773SUB 08HD6 08Subtract the number of bits just shifted from the new exponent counter in Register A
0774-0775CP E0HFE E0Check to see if we shifted 4 bytes of zeroes. If no (NZ) we will need to shift over 8 more.
0776-0777If 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
↳ ZEROXOR AAFZero Register A
0779-077B
↳ ZERO0LD (4124H),ALD (FAC),A32 24 41Make 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.
077CRETC9Return with a single precision value of zero in the ACCumulator
077DH-07A7H – SINGLE PRECISION MATH SUPPORT ROUTINE– “NORM2”
077D
↳ NORM2DEC B05Decrement the shift count (exponent counter) in Register B
077EADD HL,HL29Rotate (HL) left by 1 and shift in a 0
077FLD A,D7ARotate the next higher order (NMSB) left 1 as well.
0780RLA17Shift the NMSB in Register A left one bit and shift a bit from Register Pair HL if necessary
0781LD D,A57Save the adjusted NMSB in Register A into Register D
0782LD A,C79Load Register A with the MSB in Register C
0783ADC A,A8FShift the MSB in Register A left one bit and shift a bit from Register D if necessary. The flags will get set as well.
0784LD C,A4FLoad Register C with the adjusted value in Register A
JP P,077DHJP P,NORM2F2 7D 07IF the P FLAG is set, then we have more normalization to do so loop until the most significant bit of the single precision value is equal to one
If we are here, then we have a fully normalized result, so let us continue.
0788LD A,B78Load Register A with the new shift count (exponent counter) in Register B
0789LD E,H5CLoad Register E with the LSB of the low order part of the single precision value
078ALD B,L45Load Register B with the MSB of the low order part of the single precision value
078BOR AB7Check to see if there were any bits shifted
078E-0790LD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
0791ADD A,(HL)86Add the value of the original exponent at the location of the memory pointer in Register Pair HL to the number of bits shifted in Register A
0792LD (HL),A77Save the new exponent in Register A at the location of the memory pointer in Register Pair HL
0793-0794JR NC,0778HR NC,ZERO30 E3Jump 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
0795RET ZC8Return 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.
Vernong Hester has flagged an error in the rounding of math routines. In base 10, rounding to k-digits examines digit k+1. If digit k+1 is 5 through 9, then digit k is adjusted up by one and carries to the most significant digit, if necessary. If digit k+1 is less than 5, then digit k is not adjusted. This should not get muddled with the conversion of base 2 to base 10. Nevertheless, four divided by nine should be: .444444 and not .444445
0796
↳ ROUNDLD A,B78Load Register A with the LSB of the single precision value in Register B
0797-0799
↳ ROUNDBLD HL,4124HLD HL,FAC21 24 41Load 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.
079AOR AB7Set 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.
079B-079DCALL M,07A8HCALL M,ROUNDAFC A8 07If the M FLAG is set (if the most significant bit in the value in Register A is set), GOSUB to round up
079ELD B,(HL)46Put the exponent (modified or not), whcih is currently held in the RAM location pointed to by HL, into Register B.
079FINC HL23Increment the memory pointer in Register Pair HL to now point to the sign
07A0LD A,(HL)7ELoad Register A with the value of the sign at the location of the memory pointer in Register Pair HL
07A1-07A2AND 80HAND 1000 0000E6 80Turn off some bits so we can mask the sign bit in Register A (1000 0000)
07A3XOR CA9Set the sign bit in Register A
07A4LD C,A4FLoad Register C with the sign
07A5-07A7JP 09B4HJP MOVFRC3 B4 09Save 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
↳ ROUNDAINC E1CIncrement the LSB of the single precision value (which is stored in Register E). Note: This is the entry point from QUINT.
07A9RET NZC0If the NZ FLAG is set, then we have no overflow, so we are done!
07ABRET NZC0If the NZ FLAG is set, then we have no overflow, so we are done!
07ADRET NZC0If the NZ FLAG is set, then we have no overflow, so we are done!
LD C,800E 80If 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 …
07B0INC (HL)34… update the exponent (which is stored in the RAM location pointed to by HL)
07B1RET NZC0If 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.
07B2H – ?OV ERROR entry point– “OVERR”
07B2H-07B3
↳ OVERRLD E,0AHLD E,ERROV1E 0ALoad Register E with an ?OV ERROR code.
07B4-07B6JP 19A2HJP ERRORC3 A2 19Go 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
↳ FADDALD A,(HL)7ELoad Register A with the LSB of the single precision value in the ACCumulator (pointed to by Register Pair HL)
07B8ADD A,E83Add 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
07B9LD E,A5F… and put that sum into Register E
07BAINC HL23Onto the middle number/NMSB. Increment the memory pointer in Register Pair HL to point to the NMSB (ACCumulator + 1).
07BBLD A,(HL)7ELoad Register A with the NMSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
07BCADC A,D8AAdd the NMSB of the single precision value in Register D to the NMSB of the single precision value in Register A
07BDLD D,A57Load Register D with the result in Register A
07BEINC HL23Onto the high order number/MSB. Increment the memory pointer in Register Pair HL to point to the MSB (ACCumulator + 2).
07BFLD A,(HL)7ELoad Register A with the MSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
07C0ADC A,C89Add the MSB of the single precision value in Register C to the MSB of the single precision value in Register A
07C1LD C,A4FLoad Register C with the result in Register A
07C2RETC9RETurn 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
↳ NEGRLD HL,4125HLD HL,FAC+121 25 41Load Register Pair HL with the address of the sign flag storage location.
07C6LD A,(HL)7ELoad Register A with the value of the sign flag at the location of the memory pointer in Register Pair HL
07C7CPL2FComplement the sign flag in Register A
07C8LD (HL),A77Save the adjusted sign flag in Register A back to (FAC+1)
07C9XOR AAFZero Register A. This will allow us to zero Register L and to do negative math
07CALD L,A6FLoad 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.
07CBSUB B90NEGate the low order/LSB number by subtracting Register B from zero (held in Register A)
07CCLD B,A47Save that negated Register B back to Register B
07CDLD A,L7DLoad Register A with zero
07CESBC A,E9BNEGate the next highest order number by subtracting Register E from zero (held in Register A)
07CFLD E,A5FSave that negated Register E back to Register E
07D0LD A,L7DLoad Register A with zero
07D1SBC A,D9ANEGate the next highest order number by subtracting Register D from zero (held in Register A)
07D2LD D,A57Save that negated Register D back to Register D
07D3LD A,L7DLoad Register A with zero
07D4SBC A,C99NEGate the highest order number/MSB by subtracting Register C from zero (held in Register A)
07D5LD C,A4FSave that negated Register C back to Register C
07D6RETC9RETurn 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
↳ SHIFTRLD B,00H06 00Load Register B, which will hold the overflow byte, with zero to reset the overflow byte
07D9-07DA
↳ SHFTR1SUB 08HD6 08Top 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
07DB-07DCIf 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 …
07DDLD B,E43Load Register B with the LSB of the single precision value in Register E
07DELD E,D5ALoad Register E with the NMSB of the single precision value in Register D
07DFLD D,C51Load Register D with the MSB of the single precision value in Register C
07E0-07E1LD C,00H0E 00Load Register C with zero
07E4 – SINGLE PRECISION MATH ROUTINE– “SHFTR2”
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
↳ SHFTR2ADD 09HC6 09Adjust the shift counter in Register A to its correct value for working with individual bits instead of bytes
07E6LD L,A6FLoad Register L with the shift counter in Register A so that L will hold the counter for shifts at the single bit level
07E7
↳ SHFTR3XOR AAFTop of a loop. Clear the CARRY FLAG.
07E8DEC L2DDecrement the bit shift counter (held in Register L)
07E9RET ZC8Return if there are no more bits to be shifted. This is the routine’s exit.
LD A,C79If 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
RRA1FShift 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.
07ECLD C,A4FSave the bit shifted MSB (held in Register A) back into Register C
07EDLD A,D7ALoad Register A with the NMSB of the single precision value in Register D
07EERRA1FShift 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.
07EFLD D,A57Save the bit shifted NMSB (held in Register A) back into Register D
07F0LD A,E7BLoad Register A with the LSB of the single precision value in Register E
07F1RRA1FShift 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.
07F2LD E,A5FSave the bit shifted LSB (held in Register A) back into Register D
07F3LD A,B78Load Register A with the overflow byte (held in Register B)
07F4RRA1FShift 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.
07F5LD B,A47Save the bit shifted overflow byte (held in Register A) back into Register B
07F8H-07FBH – SINGLE PRECISION CONSTANT STORAGE LOCATION
– “FONE”
07F8-07FB
↳ FONE00 00 00 8100A single precision constant equal to 1.0 is stored here
07FCH-0808H – SINGLE PRECISION CONSTANTS STORAGE LOCATION 2– “LOGCN2”
07FC
↳ LOGCN20303The number of single precision constants which follows is stored here
07FD-0800AA 56 19 80AAA single precision constant equal to 0.598978650 is stored here
0801-0804F1 22 76 80F1A single precision constant equal to 0.961470632 is stored here
0805-080845 AA 38 8245A single precision constant equal to 2.88539129 is stored here
0809H-0846H – LEVEL II BASIC LOGROUTINE– “LOG”
The LOG(n) routine, (ACCumulator=LOG (ACCumulator)). This routine finds the natural log (base E) of the single precision value in the ACCumulator area.
The result is returned as a single precision value in the ACCumulator
To use a ROM call to find LOG(n), where X is a positive single precision variable, store the value of n in 4121H-4124H and then CALL 0809H. The result (in single precision format) is in 4121H-4124Hin approximately 19 milliseconds. NOTE: A fatal error occurs if the value of the input variable is zero or negative.
Vernon Hester has identified a bug in the LOG() routine. Regardless of the base, if the argument is 1 then the logarithm is zero, if the argument is >1 then the logarithm is positive, and if the argument is >0 and < 1 then the logarithm is negative. However, if the argument is just under 1, the ROM’s LOG function produces a positive value. e.g., 10 PRINT LOG(.99999994)
0809-080B
↳ LOGCALL 0955HCALL SIGNCD 55 09Go check the sign (or zero value) of the single precision value in the ACCumulator
080COR AB7Set the flags.
080D-080FJP PE,1E4AHJP PE,FCERREA 4A 1EIf 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-0812LD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
0813LD A,(HL)7ELoad 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-0816LD BC,8035H01 35 80Load Register BC with the exponent and the MSB of a single precision constant (which is 32821)
0817-0819LD DE,04F3H11 F3 04Load 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
081ASUB B90Remove the excess 80H (held in Register B) from the exponent of the n-value (of LOG (n)) held in Register A
081BPUSH AFF5Save the modified exponent to the the STACK for later
081CLD (HL),B70Set the exponent to 80H
The next two instructions save SQR(.5) to the STACK
081DPUSH DED5Save the NMSB and the LSB of the single precision value in Register Pair DE on the STACK
081EPUSH BCC5Save the exponent and the MSB of the single value in Register Pair BC on the STACK
081F-0831CALL 0716HCALL FADDCD 16 07Calculate (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
0822POP BCC1Get the exponent and the MSB of the single precision value from the STACK and put it in Register Pair BC
0823POP DED1Get 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)
0824INC B04Multiply the single precision value in Register Pairs BC and DE by two by bumping the exponent in Register B
0825-0827CALL 08A2HCALL FDIVCD A2 08Go divide the single precision value in Register Pairs BC and DE by the x-value in the ACCumulator and return with the result in the ACCumulator
0828-082ALD HL,07F8HLD HL,FONE21 F8 07Load Register Pair HL with the starting address of a single precision constant (which is at 2040)
082B-082DCALL 0710HCALL FSUBSCD 10 07Go 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-0830LD HL,07FCHLD HL,LOGCN221 FC 07Load Register Pair HL with the starting address of a storage location for the single precision constants of a “approximation polynomial” to be used.
0831-0833CALL 149AHCALL POLYXCD 9A 14Go do a series of computations and return with the result in the ACCumulator
The next two instructions are commented in the original ROM source code as: Get -1/2
0834-0836LD BC,8080H01 80 80Load Register BC with the exponent and the MSB of a single precision constant
0837-0839LD DE,0000H11 00 00Load Register Pair DE with the NMSB and the LSB of a single precision. Register Pairs BC and DE are now equal to a single precision of -0.5
083A-083CCALL 0716HCALL FADDCD 16 07Add 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)
083DPOP AFF1Retrieve the original exponent from the STACK and put it in Register A
083E-0840CALL 0F89HCALL FINLOGCD 89 0FGo 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
↳ MULLN2LD BC,8031H01 31 80Load Register Pair BC with the exponent and the MSB of a single precision constant
0844-0846LD DE,7218H11 18 72Load 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
This routine alters every Register.
0847-0849
↳ FMULTCALL 0955HCALL SIGNCD 55 09Go check to see if the single precision value in the ACCumulator is equal to zero
084ARET ZC8Return if the single precision value in the ACCumulator is equal to zero
LD L,00H2E 00Since we don’t have a zero, the next step is to add the two exponents using L as a flag, so load Register L with 0
084D-084FCALL 0914HCALL MULDIVCD 14 09Next we need to fix up the exponents and save the numbers in the registers for faster addition.
0850LD A,C79Load Register A with the single precision value’s High Order/MSB in Register C
0851-0853LD (414FH),ALD (FMLTT1),A32 4F 41Save the MSB of the single precision value in Register A at memory location 414FH
0854EX DE,HLEBLoad Register Pair HL with the NMSB and the LSB of the single precision value in Register Pair DE
0855-0857LD (4150H),HLLD (FMLTT2),HL22 50 41Save the NMSB and the LSB of the single precision value in Register Pair HL at memory locations 4150H and 4151H
0858-085ALD BC,0000H01 00 00Load Register Pair BC with a zero, which we will also put into Register D and Register E
085BLD D,B50Load Register D with the value in Register B
085CLD E,B58Load Register E with the value in Register B
085D-085FLD HL,0765HLD HL,NORMAL21 65 07Load Register Pair HL with the return address
0860PUSH HLE5Save the return address in Register Pair HL on the STACK
0861-0863LD HL,0869HLD HL,FMULT221 69 08Load Register Pair HL with the return address
0864PUSH HLE5Save the return address in Register Pair HL on the STACK
0865PUSH HLE5Save the return address in Register Pair HL on the STACK
0866-0868LD HL,4121HLD HL,FACLO21 21 41Load Register Pair HL with the low order/LSB address of the single precision value in the ACCumulator
0869
↳ FMULT2LD A,(HL)7ELoad Register A with the byte to multiply by (on entry its the LSB of the single precision value in the ACCumulator)
086AINC HL23Increment the memory pointer in Register Pair HL to point to the next byte of the number in the ACCumulator
086BOR AB7Check to see if the LSB of the single precision value in the ACCumulator in Register A is equal to zero
086C-086DJump if the LSB of the single precision value in the ACCumulator is equal to zero
086EPUSH HLE5Save the memory pointer to the number in the ACCumulator (tracked by Register HL) on the STACK
086F-0870LD L,08H2E 08Load 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
↳ FMULT4RRA1FShift 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.
0872LD H,A67Load Register H with the adjusted LSB in Register A
0873LD A,C79Load Register A with the MSB of the single precision value in Register C
0874-0875If the bit was zero, don’t add in any numbers and, instead, jump forward to 0881H
0876PUSH HLE5Save the counters (tracked in Register Pair HL) to the STACK
0877-0879LD HL,(4150H)LD HL,(FMLTT2)2A 50 41Load Register Pair HL with the NMSB and the LSB of the original value in Register Pairs BC and DE
087AADD HL,DE19Add 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
087BEX DE,HLEBLoad Register Pair DE with the adjusted total in Register Pair HL
087CPOP HLE1Get the counters back from the STACK and put it in Register Pair HL
087D-087FLD A,(414FH)LD A,(FMLTT1)3A 4F 41Load Register A with the MSB of the original value in Register Pairs BC and DE
0880ADC A,C89Add the MSB of the original value in Register A to the MSB of the total figured so far in Register C
0881
↳ FMULT5RRA1FShift 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.
0882LD C,A4FLoad Register C with the adjusted MSB of the total in Register A
0883LD A,D7ALoad Register A with the NMSB of the total in Register D
0884RRA1FShift 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.
0885LD D,A57Load Register D with the adjusted NMSB of the total in Register A
0886LD A,E7BLoad Register A with the LSB of the total in Register E
0887RRA1FShift 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.
0888LD E,A5FLoad Register E with the adjusted LSB of the total in Register A
0889LD A,B78Load Register A with the value in Register B
088ARRA1FShift 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.
088BLD B,A47Load Register B with the adjusted value in Register A
088CDEC L2DDecrement the bit counter in Register L and set the flags accordingly
088DLD A,H7CLoad Register A with the LSB of the number we are multiplying
0890
↳ POPHRTPOP HLE1Get the memory pointer to the number to multiply by from the STACK and put it in Register Pair HL
0891RETC9RETurn to CALLer
0892H-0896H – SINGLE PRECISION MATH ROUTINE– “FMULT3”
This is accomplished by a circular shift of BC/DE one byte – B is lost, C is replaced by A
This is a multiply by zero, where we just shift everything 8 bits to the right.
0892
↳ FMULT3LD B,E43Load Register B with the LSB of the single precision value in Register E
0893LD E,D5ALoad Register E with the NMSB of the single precision value in Register D
0894LD D,C51Load Register D with the MSB of the single precision value in Register C
0895LD C,A4FLoad Register C with the value in Register A (which should be all 0’s, which will now be on the left)
0896RETC9RETurn to CALLer
0897H-08A1H – SINGLE PRECISION MATH ROUTINE
– “DIV10”
This routine divides the ACCumulator by 10. Every Register is used.
0897-0899
↳ DIV10CALL 09A4HCALL PUSHFCD A4 09Save the number via a GOSUB to 09A4 which moves the SINGLE PRECISION value in the ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
089A-089CLD HL,0DD8HLD HL,FTEN21 D8 0DLoad Register Pair HL with the starting address of a single precision constant equal to 10
089D-089FCALL 09B1HCALL MOVFMCD B1 09Move the “10” into the ACCUulator via a call to 09B1H (which moves a SINGLE PRECISION number pointed to by HL to ACCumulator)
08A0
↳ FDIVTPOP BCC1Get the exponent and the MSB of the single precision value on the STACK and put it in Register Pair BC
08A1POP DED1Get 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.
08A2-08A4
↳ FDIVCALL 0955HCALL SIGNCD 55 09Go check to see if the single precision value in the ACCumulator is equal to zero so as to process that error.
08A5-08A7JP Z,199AHJP Z,DV0ERRCA 9A 19If 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-08A9LD L,FFH2E FFLoad Register L with a flag for use when subtracting the two exponents.
08AD
08AEINC (HL)
INC (HL)34Add two to the exponent pointed to by (HL) to correct scaling
08AFDEC HL2BDecrement 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
08B0LD A,(HL)7ELoad Register A with the MSB of the single precision value in the ACCumulator
08B1-08B3LD (4089H),ALD (FDIVA+1),A32 89 40Save the MSB of the single precision value in the ACCumulator in Register A at memory location 4089H
08B4DEC HL2BDecrement 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
08B5LD A,(HL)7ELoad 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-08B8LD (4085H),ALD (FDIVB+1),A32 85 40Save the NMSB of the single precision value in the ACCumulator in Register A at memory location 4085H
08B9DEC HL2BDecrement 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
08BALD A,(HL)7ELoad 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-08BDLD (4081H),ALD (FDIVC+1),A32 81 40Save 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.
08BELD B,C41First, 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)
08BFEX DE,HLEBThen, get the NMSB and LSB of the dividend from DE into Register Pair HL
08C0XOR AAFNext, we need to zero out C, D, E, and the Highest Order
08C1LD C,A4FZero the MSB of the total by loading Register C with the value in Register A
08C2LD D,A57Zero the NMSB of the total by loading Register D with the value in Register A
08C3LD E,A5FZero the LSB of the total by loading Register E with the value in Register A
08C4-08C6LD (408CH),ALD (FDIVG+1),A32 8C 40Zero memory location 408CH (which is holding the highest order)
08C7
↳ FDIV1PUSH HLE5Save the NMSB and LSB of the single precision dividend (held in Register Pair HL) on the STACK
08C8PUSH BCC5Save the MSB of the dividend in Register B on the STACK
08C9LD A,L7DNext 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
08CA-08CCCALL 4080HCALL FDIVCCD 80 40Go 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-08CESBC 00HDE 00Subtract the CARRY FLAG from it
08CFCCF3FSet the CARRY FLAG to correspond to the next quotient bit
08D0-08D1If 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-08D4LD (408CH),ALD (FDIVG+1),A32 8C 40Update the highest order number held at FDIVG+1
08D5POP AFF1We want to clear the previous number off the stack since the subtraction didn’t cause an error
08D6POP AFF1And again
08D7SCF37Set the CARRY FLAG so that the next bit in the quotient is a 1 to indicate that the subtraction was good
08D9
↳ FDIV2POP BCC1If 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
08DAPOP HLE1… and get the old number back into Register Pair HL
08DBLD A,C79We 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
08DDINC A
DEC A3CIncrement and then Decrement the MSB of the total in Register A. This will set the SIGN FLAG without affecting the CARRY FLAG
08DERRA1FShift 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.
08E2RLA17If we are here, then we aren’t done. First, we need to get the old CARRY FLAG back via a RLA
08E3LD A,E7BNext, we are going to rotate EVERYTHING left 1 bit. Load Register A with the LSB of the total in Register E
08E4RLA17Rotate the next bit of the quotient in
08E5LD E,A5FLoad Register E with the adjusted LSB of the total in Register A
08E6LD A,D7ALoad Register A with the NMSB of the total in Register D
08E7RLA17Rotate the next bit of the quotient in
08E8LD D,A57Load Register D with the adjusted NMSB of the total in Register A
08E9LD A,C79Load Register A with the MSB of the total in Register C
08EARLA17Rotate the next bit of the quotient in
08EBLD C,A4FLoad Register C with the adjusted MSB of the total in Register A
08ECADD HL,HL29Almost done! Rotate a zero into the right end of the number
08EDLD A,B78Next, rotate the High Order/MSB of the dividend in Register B
08EERLA17Rotate the next bit of the quotient in
08EFLD B,A47Load Register B with the adjusted MSB of the dividend in Register A
08F0-08F2LD A,(408CH)LD A,(FDIVG+1)3A 8C 40Next, rotate the HIGHEST order. Load Register A with the value at memory location 408CH
08F3RLA17Rotate the next bit of the quotient in
08F4-08F6LD (408CH),ALD (FDIVG+1),A32 8C 40Save the adjusted value in Register A at memory location 408CH
08F7LD A,C79Next we need to add one to the exponent if the first subtraction didn’t work. To do so, first load Register A with the MSB of the total in Register C
08F8OR DB2Combine the NMSB of the total in Register D with the value in Register A
08F9OR EB3Combine the LSB of the total in Register E with the value in Register A
08FCPUSH HLE5Save the NMSB and the LSB of the dividend in Register Pair HL on the STACK
08FD-08FFLD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
0900DEC (HL)35Decrement the exponent in the ACCumulator at the location of the memory pointer in Register Pair HL
0901POP HLE1Get the NMSB and the LSB of the dividend from the STACK and put it in Register Pair HL
0902-0903Keep dividing if there was no overflow by JUMPING back to 08C7H if the exponent in the ACCumulator isn’t equal to zero
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
↳ MULDVSLD A,FFH3E FFThis 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
0909LD L,0AFH2E AFZ-80 Trick. If we are passing through, the Register L will change, but the XOR in 090A will not trigger.
090A
↳ MULDVAXOR AAFThis 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-090DLD HL,412DHLD HL,ARG-121 2D 41Load 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)
090ELD C,(HL)4ELoad Register C with the High Order/MSB and the sign of the value in ARG (a/k/a REG 2) for unpacking
090FINC HL23Increment the value of the memory pointer in Register Pair HL to now point to the exponent
0910XOR (HL)AEGet the exponent by XORing the mask in Register A (which varied based on where this routine was entered from)
0911LD B,A47Save the adjusted exponent into Register B for processing below
0912-0913LD L,00H2E 00Load 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
↳ MULDIVLD A,B78First we should test to make sure that the number isn’t zero, so Load Register A with the exponent in Register B
0915OR AB7Check to see if the exponent in Register A is equal to zero
0916-0917If 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
0918LD A,L7DNext, 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-091BLD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
091CXOR (HL)AECombine 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)
091DADD A,B80Add the value of the exponent in Register B to the value of the exponent in Register A
091ELD B,A47Load Register B with the combined exponents (currently held in Register A)
091FRRA1FShift 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.
0920XOR BA8Check 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.
0921LD A,B78Load Register A with the combined/summed exponents value in Register B
0925-0926ADD 80HC6 80If we don’t have an overflow, then we need to make an exponent in excess of 80H (and turn on bit 8)
0927LD (HL),A77Save the value of the combined exponent in Register A as the exponent in the ACCumulator at the location of the memory pointer in Register Pair HL
0928-092AJP Z,0890HJP Z,POPHRTCA 90 08If the ADD 80H triggered a ZERO FLAG, then we have an underflow! Jump to POPHRT to put the numbers back and RETurn
092B-092DCALL 09DFHCALL UNPACKCD DF 09Unpack the arguments by a GODUB to UNPACK, which will turn on the sign bit of the MSB in the ACCumulator and Register B and save the sign bits
092ELD (HL),A77Save the new sign (held in Register A) to the ACCumulator at the location of the memory pointer in Register Pair HL
092F
↳ DCXHRTDEC HL2BDecrement the memory pointer in Register Pair HL so that it points to the exponent in the ACCumulator
0930RETC9RETurn to CALLer, with the HIGH ORDER/MSB in Register A
0931H-093DH – SINGLE PRECISION MATH ROUTINE– “MLDVEX”
This routine is called from EXP. If jumped here will checks if ACC=0. If so, the Z flag will be set
0931-0933
↳ MLDVEXCALL 0955HCALL SIGNCD 55 09Go check the value of the sign bit for the value in the ACCumulator and choose UNDERFLOW if negative
0934CPL2FPick OVERFLOW if it was positive
0935POP HLE1Get the value from the STACK and put it in Register HL
0936
↳ MULDV1OR AB7Weneed to test to see if the error was an OVERFLOW or an UNDERFLOW, so set the flags according to the value of the sign bit test
0938-093AJP P,0778HJP P,ZEROF2 78 07If the value in the ACCumulator is negative, JUMP to 0778H to handle the underflow
093B-093DJP 07B2HJP OVERRC3 B2 07If its not negative, jump to 07B2H to throw an error because we have an overflow
093EH-0954H – SINGLE PRECISION MATH ROUTINE– “MUL10”
This routine multiplies the ACCumulator by 10. Every register is modified.
093E-0940
↳ MUL10CALL 09BFHCALL MOVRFCD BF 09Call 09BF which loads the SINGLE PRECISION value in the ACCumulator into Register Pair BC/DE
0941LD A,B78Load Register A with the value of the exponent (from Register B)
0942OR AB7Check to see if the exponent in Register A is equal to zero, because if the exponent is 0 then so is the number!
0943RET ZC8If the single precision value in Register Pairs BC and DE is equal to zero, then RETurn
ADD 02HC6 02Multiply the value of the exponent in Register A by four (by adding 2 to the exponent)
0946-0948JP C,07B2HJP C,OVERRDA B2 07Display an ?OV ERRORif the adjusted exponent in Register A is too large
0949LD B,A47Put the exponent back into Register B
094A-094CCALL 0716HCALL FADDCD 16 07Multiply 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-094FLD HL,4124HLD HL,FAC21 24 41Prepare 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
0950INC (HL)34Increment 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
0951RET NZC0Return if the new value in the ACCumulator is in an acceptable range
0952-0954JP 07B2HJP OVERRC3 B2 07Display an ?OV ERRORif 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
↳ SIGNLD A,(4124H)LD A,(FAC)3A 24 41Prepare 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
0958OR AB7Check to see if the exponent in Register A is equal to zero
0959RET ZC8Return if the single precision value in the ACCumulator is equal to zero
LD A,(4123H)LD A,(FAC-1)3A 23 41Load Register A with the SIGN of the ACCumulator
095D-095ECP 2FHFE 2FZ-80 Trick. If passing through, this will check the value of Register A and skip the next CPL instruction.
095E
↳ FCOMPSCPL2FComplement the sign. This is ignored if passing through and proceesed only if specifically jumped to.
095F
↳ ICOMPSRLA17Put the value of the sign bit in Register A into the CARRY FLAG
0960
“SIGNS”SBC A,A9FIf 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
0961RET NZC0If the CARRY FLAG was 1, then the number is negative, and we want to RETurn
INC A3CIncrement the value in Register A so that Register A will be equal to 1 if the single precision value in the ACCumulator is positive
0963RETC9RETurn 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
↳ FLOATLD B,88H06 88Load Register B with an exponent for an integer value
0966-0968LD DE,0000H11 00 00Load Register Pair DE with zero
This routine will float the singed number in B/A/D/E. All registers are modified.
LD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
096CLD C,A4FLoad Register C with the High Order/MSB of the integer value
096DLD (HL),B70Save the exponent in Register B into the ACCumulator at the location of the memory pointer in Register Pair HL
096E-096FLD B,00H06 00Load Register B with zero to zero the overflow byte
0970INC HL23Increment the memory pointer in Register Pair HL to now point to the sign of the number in the ACCumulator
0971-0972LD (HL),80H36 80Assume a positive number by putting an 80H there
0973RLA17Shift the value of the sign bit into the CARRY FLAG
0977H-0989H – LEVEL II BASIC ABS()ROUTINE– “ABS”
ABSroutine (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
0977-0979
↳ ABSCALL 0994HCALL VSIGNCD 94 09GOSUB to VSIGN to get the SGN of the ACCumulator into Register A
097ARET PF0If that sign is POSITIVE, then I guess we are done, so just RETurn
This routine will negate any value in the ACCumulator. Every Register is affected.
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
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
↳ NEGLD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the MSB (which holds the SIGN bit) in the ACCumulator.
0985LD A,(HL)7ELoad 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-0987XOR 80HXOR 1000 0000EE 80Complement the sign bit in the MSB in Register A. Since we know the number is negative, this is really just switching it to positive.
0988LD (HL),A77Save 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
0989RETC9RETurn to CALLer
098AH-0993H – LEVEL II BASIC SGN()ROUTINE– “SGN”
SGNfunction (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
↳ CONIALD L,A6FLoad Register L with the result of the sign test in Register A
098ERLA17Shift the sign bit in Register A into the Carry flag
098FSBC A,A9FAdjust 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
0990LD H,A67Save the adjusted value in Register A in Register H
0994H-09A3H – LEVEL II BASIC MATH ROUTINE– “VSIGN”
This routine checks the sign of the ACCumulator. NTF must be set. After execution A register=00 if ACCumulator=0, A=01 if ACC > 0 or A=FFH if A < 1. The Flags are also valid.
0994
↳ VSIGNWe 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
0998-099AJP P,0955HJP P,SIGNF2 55 09Since 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-099DLD HL,(4121H)LD HL,(FACLO)2A 21 41At 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
↳ ISIGNLD A,H7CLoad Register A with the MSB (which holds the SIGN bit) of the integer value in Register H
099FOR LB5Check to see if the integer value in the ACCumulator is equal to zero
09A0RET ZC8Return if the integer value in the ACCumulator is equal to zero
LD A,H7CIf 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
↳ PUSHFEX DE,HLEBPreserve (HL) by swapping HL and DE
09A5-09A7LD HL,(4121H)LD HL,(FACLO)2A 21 41Load Register Pair HL with the LSB and the NMSB of the single precision value in the ACCumulator
09A8EX (SP),HLE3Swap (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
09A9PUSH HLE5Save the return address in Register Pair HL on the STACK
09AA-09ACLD HL,(4123H)LD HL,(FAC-1)2A 23 41Load Register Pair HL with the exponent and the High Order/MSB of the single precision value in the ACCumulator
09ADEX (SP),HLE3Swap (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
09AEPUSH HLE5Save the return address in Register Pair HL on the STACK
09AFEX DE,HLEBRestore the original Register Pair HL from DE
09B0RETC9RETurn 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.
09B1-09B3
↳ MOVFMCALL 09C2HCALL MOVRMCD C2 09Load 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
↳ MOVFREX DE,HLEBLoad Register Pair HL with the NMSB and the LSB of the single precision value in Register Pair DE.
09B5-09B7LD (4121H),HLLD (FACLO),HL22 21 41Save the NMSB and the LSB of the single precision value into the ACCumulator (at the locations pointed to by Register Pair HL)
09B8LD H,B60Let HL = BC (so the High Orders/MSB + Exponent) … part 1 …
09B9LD L,C59… part 2
09BA-09BCLD (4123H),HLLD (FAC-1),HL22 23 41Save the exponent and the MSB of the single precision value into the ACCumulator pointed to by Register Pair HL
09BDEX DE,HLEBRestore the original HL from DE
09BERETC9RETurn 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
↳ MOVRMLD E,(HL)5ELoad 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
09C3INC HL23Increment the value of the memory pointer in Register Pair HL to point to the middle order/NMSB number
09C4
↳ GETBCDLD D,(HL)56Load Register D with the NMSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
09C5INC HL23Increment the value of the memory pointer in Register Pair HL to point to the high order/MSB number
09C6LD C,(HL)4ELoad Register C with the MSB of the single precision value in the ACCumulator at the location of the memory pointer in Register Pair HL
09C7INC HL23Increment the value of the memory pointer in Register Pair HL to point to the exponent
09C8LD B,(HL)46Load 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
↳ INXHRTINC HL23Increment the value of the memory pointer in Register Pair HL so that it points to the beginning of the next number
09CARETC9RETurn 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
↳ MOVMFLD DE,4121HLD DE,FACLO11 21 41Load 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
↳ MOVELD B,04H06 04Load Register B with the number of bytes to be moved for a single precision value so that B will act as a counter.
09D0-09D1JR 09D7HJR MOVE118 05Jump 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
↳ MOVVFMEX DE,HLEBExchange 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
↳ VMOVELD A,(40AFH)LD A,(VALTYP)3A AF 40Load 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!
09D6LD B,A47Load 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
↳ MOVE1LD A,(DE)1ATop 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)
09D8LD (HL),A77and then Save the value in Register A at the location of the memory pointer in Register Pair HL
09D9INC DE13Increment the value of the memory pointer in Register Pair DE
09DAINC HL23Increment the value of the memory pointer in Register Pair HL
09DBDEC B05Decrement the value of the byte counter in Register B
09DERETC9RETurn to CALLer
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
↳ UNPACKLD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the MSB (including the SIGN) of the value in the ACCumulator
09E2LD A,(HL)7ELoad Register A with the MSB (and SIGN) of the value in the ACCumulator at the location of the memory pointer in Register Pair HL
09E3RLCA07Duplicate the sign into the CARRY and the LSB
09E4SCF37Set the Carry flag to restore the hidden “1” for the mantissa
09E5RRA1FTurn 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.
09E6LD (HL),A77Save the adjusted High Order/MSB+Sign in Register A in the ACCumulator at the location of the memory pointer in Register Pair HL
09E7CCF3FInvert the value of the sign bit in the Carry flag
09E8RRA1FMove 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 HL23Increment the value of the memory pointer in Register Pair HL twice to now point to the temporary sign byte
09EBLD (HL),A77Save the complemented sign (in Register A) to the location of the memory pointer in Register Pair HL
09ECLD A,C79Load Register A with the MSB+SIGN of the single precision value in Register C
09EDRLCA07Duplicate the sign in both the CARRY FLAG and the LSB
09EESCF37Set the Carry flag to restore the hidden “1” for the mantissa
09EFRRA1FRestore 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.
09F0LD C,A4FLoad Register C with the adjusted High Order (MSB+Sign) in Register A
09F1RRA1FMove 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.
09F2XOR (HL)AECombine the value of the sign bit of the ACCUMULATOR and the SIGN BIT of the Registers
09F3RETC9RETurn 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
↳ VMOVFALD HL,4127HLD HL,ARGLO21 27 41This 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
LD DE,09D2HLD DE,MOVVFM11 D2 09Load 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.
LD HL,4127HLD HL,ARGLO21 27 41Entered 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
LD DE,09D3HLD DE,VMOVE11 D3 09When entered from here, we need to load Register Pair DE with the return address of the MOVE routine.
PUSH DED5When entered here, save Register Pair DE (which, if passed through, is a return address) on the STACK
LD DE,4121HLD DE,FACLO11 21 41Entered 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
0A06We 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
0A07RET CD8If that test is anything other than double precision, return out of this subroutine to the address which was fed in
LD DE,411DHLD DE,DFACLO11 1D 41If 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.
0A0BRETC9RETurn 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
↳ FCOMPLD A,B78First we need to check to see if ARG is zero, so load Register A with the value of the exponent in Register B
0A0DOR AB7Set the flags based on Register A
0A0E-0A10JP Z,0955HJP Z,SIGNCA 55 09If the exponent in Register A is equal to zero, then JUMP to SIGN
0A11-0A13LD HL,095EHLD HL,FCOMPS21 5E 09Set up the destination address to use on a RETurn by first loading Register Pair HL with the address to the FCOMPS routine
0A14PUSH HLE5Save the return address in Register Pair HL on the STACK
0A18LD A,C79If 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
0A19RET ZC8If the ACCumulator was zero, RETurn with Register A holding Register C
LD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the MSB+SIGN in Register A
0A1DXOR (HL)AECheck to see if the signs of the ACCumulator and the ARG are the same via a XOR
0A1ELD A,C79If 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
0A1FRET MF8If the signs are different, RETurn
0A20-0A22CALL 0A26HCALL FCOMP2CD 26 0ANow that we have resolved the signs, JUMP to FCOMP2 to check the rest of the numbers
0A23
↳ FCOMPDRRA1FIf 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.
0A24XOR CA9Combine the value of the MSB/SIGN of the single precision value in Register C with the value in Register A
0A26H-0A38H – Part of the SINGLE PRECISION COMPARISON ROUTINE– “FCOMP2”
0A26INC HL23Increment 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
0A27LD A,B78Load Register A with the value of the exponent for the single precision value held in ARG (stored in Register B)
0A28CP (HL)BECheck 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
0A29RET NZC0If 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
DEC HL2BDecrement 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
0A2BLD A,C79Load Register A with the HIGH ORDER/MSB of the single precision number in ARG (stored in Register C)
0A2CCP (HL)BECheck 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
0A2DRET NZC0If 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
DEC HL2BDecrement 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
0A2FLD A,D7ALoad Register A with the MIDDLE ORDER/NMSB of the single precision number in ARG (stored in Register D)
0A30CP (HL)BECheck 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
0A31RET NZC0If 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
DEC HL2BDecrement 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
0A33LD A,E7BLoad Register A with the LOW ORDER/LSB of the single precision number in ARG (stored in Register E)
0A34SUB (HL)96Use 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.
0A35RET NZC0Return if the value of the LSB in the ACCumulator isn’t the same as the value of the LSB in Register A
POP HLE1If we are here then the numbers are the same so we need to get the extra data off of the STACK
0A37POP HLE1Clear the stack
0A3BRET7CAll done, so RET to CALLer
0A39H-0A48H – INTEGER COMPARISON ROUTINE
– “ICOMP”
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
↳ ICOMPLD A,D7AFirst we test the signs, so load Register A with the SIGN of the integer value in Register D
0A3AXOR HACCheck 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
0A3BLD A,H7CIf 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.
0A3C-0A3EJP M,095FHJP M,ICOMPSFA 5F 09If the sign bits are NOT the same, JUMP to ICOMPS to check the numbers
0A3FCP DBAIf 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
0A40-0A42JP NZ,0960HJP NZ,SIGNSC2 60 09if 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
0A33LD A,L7BNext, 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
0A44SUB E93Use 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
0A45-0A47JP NZ,0960HJP NZ,SIGNSC2 60 09If 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
0A48RETC9If we are here, then two things. First, they are the same. Second, A is zero. So RETurn to CALLer
0A49H-0A77H – DOUBLE PRECISION COMPARISON ROUTINE– “DCOMPD”
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
↳ DCOMPDLD HL,4127HLD HL,ARGLO21 27 41Load 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)
0A4C-0A4ECALL 09D3HCALL VMOVECD D3 09Go move the double precision value pointed to by Register Pair DE to ARG (a/k/a REG 2)
LD DE,412EHLD DE,ARG11 2E 41Load Register Pair DE with the address of the exponent in ARG (a/k/a REG 2)
0A52LD A,(DE)1ALoad Register A with the exponent for the double precision value in ARG (a/k/a REG 2) at the location of the memory pointer in Register Pair DE
0A53OR AB7Check to see if the double precision value in ARG (a/k/a REG 2) is equal to zero
0A54-0A56JP Z,0955HJP Z,SIGNCA 55 09If 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-0A59LD HL,095EHLD HL,FCOMPS21 5E 09Load Register Pair HL with a return address to the FCOMPS routine
0A5APUSH HLE5Save the return address in Register Pair HL on the STACK
0A5B-0A5DCALL 0955HCALL SIGNCD 55 09Go check to see if the double precision value in the ACCumulator is equal to zero
0A5EDEC DE1BDecrement 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)
0A5FLD A,(DE)1ALoad 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
0A60LD C,A4FPresetve the MSB+SIGN of the double precision value in ARG (a/k/a REG 2) into Register C
0A61RET ZC8If the number in the ACCumulator = 0, then the sign of the result is the sign of ARG, so RETurn wto FCOMPS
LD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the SIGN of the double precision value in the ACCumulator
0A65XOR (HL)AECheck 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
0A66LD A,C79In case they are the same, get the sign from C into Register A.
0A67RET MF8If they are NOT the same, RETurn to FCOMPS to set set Register A
INC DE13Increment the value of the memory pointer in Register Pair DE so that DE now points to the exponent of ARG
0A69INC HL23Increment the value of the memory pointer in Register Pair HL so that HL now points to the exponent of the ACCumulator
0A6A-OA6BLD B,08H06 08Load Register B with the number of bytes to be compared, as B will act as a counter
0A6C
↳ DCOMP1LD A,(DE)1ALoad Register A with a byte from the double precision number in ARG (pointed to by Register Pair DE)
0A6DSUB (HL)96Use subtraction to compare that byte from ARG with the correspondible byte from the ACCumulator (pointed to by Register Pair HL)
0A6E-0A70JP NZ,0A23HJP NZ,FCOMPDC2 23 0AIf the NZ is set, then the numbers are different o JUMP to FCOMPD to set up Register A
0A71DEC DE1BIf we are here, then they are the same, so we need to move to the next byte of ARG (so decrement the value of the memory pointer in Register Pair DE)
0A72DEC HL2Band the next byte of the ACCumulator (so decrement the value of the memory pointer in Register Pair HL)
0A73DEC B05and to decrease the byte counter (so Decrement the number of bytes remaining to be compared in Register B)
0A74-0A75The 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
0A76POP BCC1If 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
0A77RETC9RETurn 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.
0A78-0A7A
↳ DCOMPCALL 0A4FHCALL XDCOMPCD 4F 0AGOSUB to compare the double precision value in ARG (a/k/a REG 2) to the double precision value in the ACCumulator
0A7B-0A7DJP NZ,095EHJP NZ,FCOMPSC2 5E 09If 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
0A7ERETC9RETurn to CALLer
0A7FH-0AB0H – LEVEL II BASIC CINTROUTINE– “FRCINT”
CINTroutine. 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 CINTroutine, store the single precision input variable in 4121H-4124H and then call to 0A8AH and bypass all the foregoing. After the call, the integer result would be in 4121H-4122H and in the HL Register Pair. Too big a number will generate a ?OV Error.
0A7F
↳ FRCINTWe 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-0A82LD HL,(4121H)LD HL,(FACLO)2A 21 41Just 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)
0A83RET MF8If that test showed we have an INTEGER, then return out of this subroutine
0A84-0A86JP Z,0AF6HJP Z,TMERRCA F6 0AIf that test showed we have a STRING, Display a ?TM ERRORmessage
0A87-0A89CALL NC,0AB9HCALL NC,CONSDD4 B9 0AIf that test shows we have DOUBLE PRECISION, call 0AB9H to convert the number to single precision
0A8A-0A8CLD HL,07B2HLD HL,OVERR21 B2 07Just in case the number is too big, pre-load HL with the RETurn address to the ?OV ERROR routine
0A8DPUSH HLE5Save the return address in Register Pair HL on the STACK and fall into the “CONIS” routine to continue.
0A8EH – LEVEL II BASIC CONVERSION ROUTINE– “CONIS”
This routine will convert a single precision number to an integer. Every register is affected.
0A8E-0A90
↳ CONISLD A,(4124H)LD A,(FAC)3A 24 41Load Register A with the exponent for the single precision value in the ACCumulator
0A91-0A92CP 90HFE 90Check to see if the exponent for the single precision value in the ACCumulator in Register A indicates more than 16 bits of precision
0A93-0A94If 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
0A95-0A97CALL 0AFBHCALL QINTCD FB 0AIf 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
0A98EX DE,HLEBLoad Register Pair HL with the integer value that was put into Register Pair DE by QINT
0A99
↳ CONIS1POP DED1Get 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
LD (4121H),HLLD (FACLO),HL22 21 41Save the integer value in Register Pair HL as the current value in the ACCumulator.
0A9D-0A9E
↳ VALINTLD A,02H3E 02Load Register A with an integer number type flag.
0A9F-0AA1
↳ CONISDLD (40AFH),ALD (VALTYP),A32 AF 40Save 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
0AA2RETC9RETurn to CALLer
0AA3H – LEVEL II BASIC CONVERSION ROUTINE– “CONIS2”
0AA3-0AA5
↳ CONIS2LD BC,9080H01 80 90This 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-0AA8LD DE,0000H11 00 00Load 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
0AA9-0AABCALL 0A0CHCALL FCOMPCD 0C 0ACall 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.
0AACRET NZC0If 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
0AADLD H,C61If 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
0AAELD L,D6ALoad Register L with the NMSB of the single precision value in Register D
0AAF-0AB0JR 0A99HJR CONIS118 E8Jump to 0A99H to store (HL) into the ACCumulator and set the VALTYPE accordingly.
0AB1H-0ACBH – LEVEL II BASIC CSNGROUTINE– “FRCSNG”
Force the number in the ACCumulator to be a single-precision number. Every register is affected.
CSNGroutine. Takes value from ACC and converts it to single-precision. The result is put in ACC and NTF contains 4.
CSNG routine. Takes value from ACC and converts it to single-precision. The result is put in ACC and NTF contains 4
Integer To Single: The contents of ACCumulator are converted from integer or double precision to single precision. All registers are used
0AB1
↳ FRCSNGWe 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
0AB2RET POE0IF PO is set, then we have SINGLE PRECISION number already, so nothing to do! RETurn out of this subroutine
0AB3-0AB5JP M,0ACCHJP M,CONSIFA CC 0AIf that test shows we have an INTEGER, jump to 0ACCH to convert it
0AB6-0AB8JP Z,0AF6HJP Z,TMERRCA F6 0AIf that test shows we have a STRING, display a ?TM ERROR. Otherwise, fall into the DOUBLE PRECISION routine, located just after to avoid a JUMP to it.
0AB9 – LEVEL II BASIC NUMBER CONVERSION ROUTINE– “CONSD”
Convert a double-prevision number to single-precision. Every register is affected.
0AB9-0ABB
↳ CONSDCALL 09BFHCALL MOVRFCD BF 09Move 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
0ABFLD A,B78Next we need to see if the number is zero, so load Register A with the exponent of the double precision value in Register B
0AC0OR AB7Check to see if the exponent in the ACCumulator is equal to zero
0AC1RET ZC8If the exponent is zero, then the number is zero, so RETurn
0AC2-0AC4CALL 09DFHCALL UNPACKCD DF 09We 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-0AC7LD HL,4120HLD HL,FACLO-121 20 41Load 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)
0AC8LD B,(HL)46Loaded Register B with the chopped number, as that is where the ROUND routine expects the number to be
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
↳ CONSILD HL,(4121H)LD HL,(FACLO)2A 21 41Load Register Pair HL with the integer value from the ACCumulator
0ACF-0AD1
↳ CONSIHCALL 0AEFHCALL VALSNGCD EF 0AGo set the current number type flag to single precision
0AD2LD A,H7CNow we need to prepare the registers for the FLOATR routine. First, load Register A with the MSB of the integer value in Register H
0AD3LD D,L55Load Register D with the LSB of the integer value in Register L
0AD4-0AD5LD E,00H1E 00Zero Register E
0AD6-0AD7LD B,90H06 90Load Register B with the initial maximum exponent
0ADBH-0AEDH – LEVEL II BASIC CDBL ROUTINE– “FRCDBL”
CDBLroutine. Takes a value from ACCumulator (regardless of integer or single precision) and convert it to double-precision. The result will be in ACC and NTF will be 8.
0ADB
↳ FRCDBLWe 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
0ADCRET NCD0If that test shows we have already a DOUBLE PRECISION number, then we are done, so RETurn out of the subroutine
0ADD-0ADFJP Z,0AF6HJP Z,TMERRCA F6 0AIf that test shows we have a STRING, Display a TM ERROR message
0AE0-0AE2CALL M,0ACCHCALL M,CONSIFC CC 0AIf 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
↳ CONDSLD HL,0000H21 00 00Load Register Pair HL with zero so we can zero out the ACCumulator
0AE6-0AE8LD (411DH),HLLD (DFACLO),HL22 1D 41Zero out the first and second bytes of the double precision number in the ACCumulator.
Note: 411DH-4124H holds ACCumulator
0AE9-0AEBLD (411FH),HLLD (DFACLO+2),HL22 1F 41Zero out the third and fourth bytes of the double precision number in the ACCumulator
0AEC-0AED
↳ VALDBLLD A,08H3E 08Load Register A with a double precision number type flag
0AEEH-0AF3H – LEVEL II BASIC MATH ROUTINE– “VALSNG”
0AEELD BC,043EH01 3E 04Z-80 Trick. If passing through to this routine, BC will be modified but the next instruction will be skipped.
0AEF-0AF0
↳ VALSNGLD A,04H
3E 04Load Register A with a single precision number type flag (of 4)
0AF1-0AF3JP 0A9FHJP CONISDC3 9F 0AHowever we got here, Register A now holds the desired VALTYPE, so jump away to 0A9FH to save the value in Register A as the VALTYPE and RETurn
0AF4H-0AFAH – LEVEL II BASIC MATH ROUTINE– “CHKSTR”
This routine will force the ACCUmlator to be a STRING. Only Register A is modified.
0AF4
↳ CHKSTRWe 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
0AF5RET ZC8If 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.
0AF6 – ?TM Error Routine– “TMERR”
0AF6-0AF7
↳ TMERRLD E,18H1E 18Load Register E with a ?TM ERROR code.
This is the entry point for the TM ERROR
0AF8-0AFAJP 19A2HJP ERRORC3 A2 19Display 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
↳ QINTLD B,A47Load 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.
0AFCLD C,A4FLoad Register C with the exponent of the single precision number in Register A
0AFDLD D,A57Load Register D with the exponent of the single precision number in Register A
0AFELD E,A5FLoad Register E with the exponent of the single precision number in Register A
0AFFOR AB7Check to see if the single precision number in the ACCumulator is equal to zero
0B00RET ZC8If 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.
0B01PUSH HLE5Save the value in Register Pair HL on the STACK
0B02-0B04CALL 09BFHCALL MOVRFCD BF 09Call 09BF which loads the SINGLE PRECISION value in the ACCumulator into Register Pair BC/DE
0B05-0B07CALL 09DFHCALL UNPACKCD DF 09Go turn on the sign bit of the single precision value in Register Pairs BC and DE
0B08XOR (HL)AESet the sign bit according to the sign of the value at the location of the memory pointer in Register Pair HL
0B09LD H,A67Preserve the sign of the numbers into Register H
0B0A-0B0CCALL M,0B1FHCALL M,QINTAFC 1F 0BIf the number was negative, we need to substract 1 from the LOW ORDER/LSB and to do that we GOSUB to QINTA
0B0D-0B0ELD A,98H3E 98Next 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
0B0FSUB B90and then subtract the exponent in Register B from the exponent in Register A
0B10-0B12CALL 07D7HCALL SHIFTRCD D7 07Shift the single precision value in Register Pairs BC and DE to get rid of any fractional bits via a GOSUB to SHIFTR.
0B13LD A,H7CRestore the SIGN back into Register A from Register H
0B14RLA17Put the sign bit into the Carry flag so that it won’t get changed.
0B15-0B17CALL C,07A8HCALL C,ROUNDADC A8 07If the original number was negative (and thus the CARRY FLAG is set), GOSUB to ROUNDA to bump the value in Register Pairs BC and DE by 1
0B18-0B19LD B,00H06 00Clear our Register B
0B1A-0B1CCALL C,07C3HCALL C,NEGRDC C3 07If the original number was negative, we need to negate the number because we need a signed mantissa
0B1DPOP HLE1Restore HL from the STACK where it was saved at the top of this routine
0B1ERETC9RETurn to CALLer
0BlFH-0B25H – LEVEL II BASIC MATH ROUTINE– “QINTA”
0B1F
↳ QINTADEC DE1BDecrement C/D/E by 1
0B20LD A,D7ANow 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
0B21AND EA3Combine the LSB of the single precision value in Register E with the NMSB of the single precision value in Register A
0B22INC A3CIncrement the combined value in Register A
0B23RET NZC0If both D and E were -1 (i.e., DE was FFFFH) then RETurn
0B24
↳ DCXBRTDEC BC0BDecrement 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.
0B25RETC9RETurn to CALLer
0B26H-0B58H – LEVEL II BASIC FIXROUTINE
– “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 FIXroutine via a ROM call, you would store the single-precision input variable in 4121H-4124H, then put a 4 into 40AFH to flag as single precision, and then call to 0B26H. If the result can be an integer, it will be in 4121H-4122H and in the HL Register Pair. If single precision, the result will be in 4121H-4124H. If double precision, in 411DH-4124H. In all cases 40AFH will have the data mode flag as 2, 4, or 8, accordingly.
FIX routine. Takes a value from ACC and converts it to an integer value. The result will be in ACC. NTF will be 2 if value is smaller than 32767 else it will be 4. An error will be generated if NTF=3 (string)
Floating To Integer: Unconditionally truncates the fractional part of a floating point number in the ACCumulator. The result is stored in the ACCumulator and the type flag is set to integer
0B26
↳ FIXWe 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
0B27RET MF8If that test shows we have an INTEGER then we are all done, so RETurn to the caller
0B2B-0B2DJP P,0B37HJP P,VINTF2 37 0BIf 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.)
0B2E-0B30CALL 0982HCALL NEGCD 82 09If 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
0B31-0B33CALL 0B37HCALL VINTCD 37 0BNow 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.)
0B34-0B36JP 097BHJP VNEGC3 7B 09Since 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 INTroutine via a ROM call, you would store the single precision input variable in 4121H-4124H, put a 4 into 40AFH (to flag as single precision), and then call 0B3DH and bypass all the foregoing. After the call, the integer result would be in 4121H-4122H and in the HL Register Pair IF the absolute value of the input did not exceed 32767. Otherwise it will be in 4121H-4124H in single precision format, and 40AF will be a 2 for integer or 4 for single precision
According to Vernon Hester, there is are a number of bugs in this routine.
First, INT(value) should produce a result equal to or less than value. However, if the value is double-precision (by definition), the ROM rounds value to single-precision first, then performs the INT function. e.g., PRINT INT(2.9999999) produces 3 instead of 2.
Next, INT(value) should never overflow. However, if the value is double-precision 32767.9999#, the ROM overflows.
Next, INT(value) should produce a result equal to or less than value. However, if the value is double-precision equal to ?2″n+2″(n-7) where n is an integer >14, the ROM produces an incorrect value. e.g., PRINT INT(?44800#) produces ?45056
0B37
↳ VINTWe 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
0B38RET MF8If that test shows we have an INTEGER then we are done, so RETurn to CALLer.
0B39-0B3AIf the NC FLAG is set, then we have a double density number, so JUMP to DINT to handle the conversion.
0B3B-0B3CJR Z,0AF6HJP Z,TMERR28 B9Display a ?TM ERRORif the current value in the ACCumulator isa string
0B3D-0B3FCALL 0A8EHCALL CONISCD 8E 0ANow 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
↳ INTLD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
0B43LD A,(HL)7ELoad Register A with the value of the exponent in the ACCumulator (held at the location of the memory pointer in Register Pair HL)
0B44-0B45CP 98HFE 98Check 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-0B48LD A,(4121H)LD A,(FACLO)3A 21 41Load Register A with the LSB of the single precision number in the ACCumulator
0B49RET NCD0If there are no fractional bits, then we are done, so RETurn with Register A holding the single precision value in the ACCumulator
0B4ALD A,(HL)7ELoad Register A with the exponent of the single precision number in the ACCumulator
0B4B-0B4DCALL 0AFBHCALL QINTCD FB 0AIf we are here, then there were fractional bits, so GOSUB to QINT to convert the single precision number in the ACCumulator to an integer
0B4E-0B4FLD (HL),98H36 98Adjust the exponent to be a correct one post-normalization
0B50LD A,E7BLoad Register A with the LSB of the integer value in Register E
0B51PUSH AFF5Save the LSB of the integer value in Register A on the STACK
0B52LD A,C79If the number was negative then we need to negate it, so first load Register A with the value in Register C
0B53RLA17Move the sign bit in Register A into the CARRY FLAG
0B57POP AFF1Get the LSB of the single precision value from the STACK and put it in Register A
0B58RETC9RETurn to CALLer
0B59H-0B9DH – LEVEL II BASIC MATH ROUTINE– “DINT”
Greated Integer function for double-precision numbers. All registers are affected.
0B59-0B5B
↳ DINTLD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
0B5CLD A,(HL)7ELoad 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-0B5ECP 90HFE 90Check to see if the double precision number in the ACCumulator uses more or less than 16 bits of precision
0B5F-0B61JP C,0A7FHJP C,FRCINTDA 7F 0AIf 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)
0B62-0B63If 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
0B64LD C,A4FIf 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
0B65DEC HL2BDecrement the value of the memory pointer in Register Pair HL to point to the HIGH ORDER (MSB+SIGN) portion of the double precision number
0B66LD A,(HL)7ELoad 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-0B68XOR 80HXOR 1000 0000EE 80Complement the value of the sign bit in Register A (which is 1000 0000)
0B69-0B6ALD B,06H06 06Next 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
↳ DINT1DEC HL2BTop of a loop. Decrement the value of the memory pointer in Register Pair HL to point to the next byte of the number
0B6COR (HL)B6Combine 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
0B6DDEC B05Decrement the byte counter in Register B
0B70OR AB7The above loop kept ORing A with bits, so now we need to see what A actually holds, so set the flag.
0B71-0B73LD HL,8000H21 00 80Just in case, put -32768 into Register Pair HL. Note that -32768 is negative 0 in double precision
0B74-0B76JP Z,0A9AHJP Z,MAKINTCA 9A 0AIf the P flag is set, then Register A was zero (-32768), so JUMP to 0A9AH to deal with it
0B77LD A,C79Register A wasn’t zero, so let’s keep calcuating. Load Register A with the exponent for the double precision value in Register C
0B78-0B79
↳ DINT2CP B8HFE B8Check to see if there are fractional bits in for the double precision value in the ACCumulator
0B7ARET NCD0If the NO CARRY FLAG is set, then there are no fractional bits so we already have an integer! With this, RETurn
0B7B
↳ DINTFO
↳ “DINTFO”PUSH AFF5Save the exponent in Register A on the STACK. This is the entry point from FOUT, and if that’s the case, the CARRY FLAG will be set.
0B7C-0B7ECALL 09BFHCALL MOVRFCD BF 09Gosub to 09BF which loads the HIGH ORDER (the most significant four bytes) of the double precision value in the ACCumulator into Register Pair BC/DE
0B7F-0B81CALL 09DFHCALL UNPACKCD DF 09Gosub to 09DFH to turn on the sign bit and return with the value of the sign
0B82XOR (HL)AEGet the sign back by XORing A against (HL)
0B83DEC HL2BDecrement the value of the memory pointer in Register Pair HL to point to the exponent of the double-precision number
0B84-0B85LD (HL),B8H36 B8Save an exponent at the location of the memory pointer in Register HL for post-normalization
0B86PUSH AFF5Save the value of the sign test in Register A on the STACK
0B87-0B89CALL M,0BA0HCALL M,DINTAFC A0 0BIf the number was negative, then the M FLAG will be set, in which case GOSUB to DINTA to subtract 1 from the LSB of the ACCumulato
0B8A-0B8CLD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the HIGH ORDER/MSB in the ACCumulator
0B8D-0B8ELD A,B8H3E B8Next we need to see how many bits we need to shift, so start off with Register A being the the maximum value of an exponent
0B8FSUB B90Subtract the value of the exponent at the location of the memory pointer in Register Pair HL from the value in Register A
0B93POP AFF1Get the value of the sign test from the STACK and put it in Register A
0B94-0B96CALL M,0D20HCALL M,DROUNAFC 20 0DIf the sign is negative, GOSUB to DROUNA to add 1 to the value in the ACCumulator
0B97XOR AAFZero 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-0B9ALD (411CH),ALD (DFACLO-1),A32 1C 41Put a ZERO into the starting address of ACCumulator minus one
0B9BPOP AFF1Get 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
0B9CRET NCD0IF called from FOUT, then RETurn to skip re-floating the number.
0BA0H-0BA9H – LEVEL II BASIC MATH ROUTINE– “DINTA”
0BA0-0BA2
↳ DINTALD HL,411DHLD HL,DFACLO21 1D 41If we enter from DINTA, our purpose is to subtract 1 from the ACCumulator, so we point HL to the LSB of the ACCumulator.
0BA3
↳ DINTA1LD A,(HL)7ERegardless of how we enter this routine, the purpose now is to subtract 1 from (HL). To do that, first get the value into A
0BA4DEC (HL)35Decrement the value at the location of the memory pointer in Register Pair HL by 1
0BA5OR AB7We really only want to continue if the byte uCheck to see if the byte used to be ZERO, so test the byte
0BA6INC HL23Increment the value of the memory pointer in Register Pair HL to point to the next byte
0BA7-0BA8Loop until the value at the location of the memory pointer in Register Pair HL is equal to a nonzero value
0BA9RETC9RETurn 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
↳ UMULTPUSH HLE5Save the value in Register Pair HL on the STACK
0BAB-0BADLD HL,0000H21 00 00Load Register Pair HL with zero to zero the product registers
0BAE
0BAFLD A,B
OR C78 B1First 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
0BB0-0BB1If BC is already zero, then just return, since HL is already zero
0BB2-0BB3LD A,10H3E 10Load Register A with the counter value (which is 16)
0BB4
↳ UMULT1ADD HL,HL29Top of a loop. Multiply the result in Register Pair HL by two
0BB5-0BB7JP C,273DHJP C,BSERRDA 3D 27If the CARRY FLAG was set, then we have an overflow, which we handle by displaying a ?BS ERROR message
0BB8EX DE,HLEBSave the product so far into Register Pair DE
0BB9ADD HL,HL29Multiply the integer value in Register Pair HL by two
0BBAEX DE,HLEBSwap DE and HL so DE now holds HL * 4 and HL holds HL * 2
0BBB-0BBCIf the HIGH ORDER/MSB from the HL addition was 1, then we need to add in (BC) so JUMP to UMULT2 to do that
0BBDADD HL,BC09Add the integer value in Register Pair BC to the result in Register Pair HL
0BBE-0BC0JP C,273DHJP C,BSERRDA 3D 27Display a BS ERROR message if the result in Register Pair HL has overflowed
0BC1
↳ UMULT2DEC A3DDecrement the counter in Register A
0BC4
↳ MULRETEX DE,HLEBSwap so that the return result is in DE. We don’t care about HL because …
0BC5POP HLE1… restore the original HL from the STACK
0BC6RETC9RETurn 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
↳ ISUBLD A,H7CThe 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
0BC8RLA17Rotate the value of the sign bit into the CARRY FLAG
0BC9SBC A,A9FAdjust Register A according to the value of the sign bit
0BCALD B,A47Load Register B with the result of the sign test
0BCELD A,C79Load Register A with zero
0BCFSBC A,B98Negate the sign
0BD2H-0BF1H – INTEGER ADDITION– “IADD”
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
↳ IADDLD A,H7CThe 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
0BD3RLA17Rotate the value of the sign bit into the CARRY FLAG
0BD4SBC A,A9FAdjust Register A according to the value of the sign bit
0BD5
↳ IADDSLD B,A47Load Register B with the result of the sign test
0BD6PUSH HLE5Save the second argument (held in Register Pair HL) to the the STACK in case we have an overflow
0BD7LD A,D7AThe 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
0BD8RLA17Rotate the value of the sign bit into the CARRY FLAG
0BD9SBC A,A9FAdjust Register A according to the value of the sign bit
0BDAADD HL,DE19Add the two LSBs, result in Register Pair HL
0BDBADC A,B88Add 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
0BDCRRCA0FThe 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
0BDDXOR HACCombine the value of the sign bit for the result in Register H with the value in Register A
0BDE-0BE0JP P,0A99HJP P,CONIS1F2 99 0AIf 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.
0BE1PUSH BCC5If we are here then we have an overflow. First, save the extended sign of (HL) (held in Register B) to the STACK
0BE2EX DE,HLEBLoad Register Pair HL with the integer value in Register Pair DE
0BE3-0BE5CALL 0ACFHCALL CONSIHCD CF 0AGo float the Register value in Register Pair HL to single precision and return with the result in the ACCumulator
0BE6POP AFF1Get the sign of (HL) from the STACK and put it in Register A
0BE7POP HLE1Get the old (HL) back from the STACK
0BE8-0BEACALL 09A4HCALL PUSHFCD A4 09Call 09A4 which moves the SINGLE PRECISION value in the ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
0BEBEX DE,HLEBLoad Register Pair DE with the integer value in Register Pair HL, as FLOATR needs DE to hold the value
0BEC-0BEECALL 0C6BHCALL INEGADCD 6B 0CGo float the integer value in Register Pair DE to single precision and return with the result in the ACCumulator
0BEF-0BF1JP 0F8FHJP FADDTC3 8F 0FAt 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
↳ IMULTLD A,H7CLoad Register A with the MSB of the integer value in Register H
0BF3OR LB5Combine the LSB of the integer value in Register L with the MSB of the integer value in Register A
0BF4-0BF6JP Z,0A9AHJP Z,MAKINTCA 9A 0AIf the ZERO flag is set, then HL is zero, and if so, just return
0BF7PUSH HLE5In case of an overflow, we are going to need our original arguments. Save the integer value in Register Pair HL on the STACK
0BF8PUSH DED5Save the integer value in Register Pair DE on the STACK
0BF9-0BFBCALL 0C45HCALL IMULDVCD 45 0CGo convert any negative integer values to positive and return with Register B set according to the value of the sign bits
0BFCPUSH BCC5Save the value of the sign bit test in Register B on the STACK
0BFD
0BFELD B,H
LD C,L44Copy the second argument from HL into BC
0BFF-0C01LD HL,0000H21 00 00Start Register Pair HL at zero, as the result will go into HL
0C02-0C03LD A,10H3E 10Load Register A with the counter value (which is 16)
0C04
↳ IMULT1ADD HL,HL29Multiply the result in Register Pair HL by two
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.
0C07EX DE,HLEBExchange the integer value in Register Pair DE with the integer result in Register Pair HL
0C08ADD HL,HL29Multiply the integer value in Register Pair HL by two
0C09EX DE,HLEBExchange the integer result in Register Pair DE with the integer value in Register Pair HL
0C0A-0C0BIf the NC FLAG is set, then skip the next instructions which add in BC
0C0CADD HL,BC09Add the integer value in Register Pair BC to the integer result in Register Pair HL
0C10
↳ IMULT2DEC A3DDecrement the value of the counter in Register A
0C13POP BCC1At this point we are done, so we need to finish up. First, get the value of the sign test from the STACK and put it in Register B
0C14POP DED1Get the original FIRST argument from the STACK and put it in Register Pair DE
This is the entry from IDIV. The next instructions test to see if the result is => 32768 or is -32768.
0C15
↳ IMLDIVLD A,H7CLoad Register A with the MSB of the result in Register H
0C16OR AB7Test Register H
0C17-0C19JP M,0C1FHJP M,IMULT3FA 1F 0CIf the M FLAG is set, then the result is =gt; 32768, so JUMP to IMULT3 to make sure it isn’t -32768.
0C1APOP DED1If we are here, then the number is OK, so get the SECOND argument off the stack and into Register Pair DE
0C1BLD A,B78Load Register A with the value of the sign test in Register B
0C1FH-0C34H – LEVEL II BASIC MATH ROUTINE– “IMULT3”
0C1F-0C20
↳ IMULT3XOR 80HEE 80Clear the sign bit for the MSB of the integer value in Register A which is 1000 0000
0C21OR LB5Combine the value of the LSB for the integer value in Register L with the adjusted MSB of the integer value in Register A
0C22-0C23If the Z FLAG is set, then the result is 32768, so JUMP to IMULT4
0C24EX DE,HLEBIf we are hre, then it is > 32768 giving us an overflow, so Load Register Pair HL with the integer value in Register Pair DE
0C26
↳ IMULT5POP BCCFGet the value of the sign test from the STACK and put it in Register B
0C27POP HL0AGet the original FIRST argument from the STACK and put it in Register Pair HL
0C28-0C2ACALL 0ACFHCALL CONSIHCD CF 0AGo float the FIRST argument (held in in Register Pair HL) to single precision and return with the result in the ACCumulator
0C2BPOP HLE1Get the original SECOND argument from the STACK and put it in Register Pair HL
0C2C-0C2ECALL 09A4HCALL PUSHFCD A4 09Save 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)
0C2F-0C31CALL 0ACFHCALL CONSIHCD CF 0AGo float the SECOND argument (held in in Register Pair HL) to single precision and return with the result in the ACCumulator
0C32
↳ FMULTTPOP BCC1Get the FIRST argument off the stack and put it in Register Pair BC. POLYX jumps here.
0C33POP DED1Get the NMSB and the LSB of the single precision value from the STACK and put it in Register Pair DE
0C34-0C36JP 0847HJP FMULTC3 47 08Multiply 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
↳ IMULT4LD A,B78We need to see if the result is +/- 32768. First, load Register A with the result of the sign test in Register B
0C38OR AB7Check the result
0C39POP BCC1Discard the original SECOND argument from the STACK
0C3DPUSH DED5If we are here, then the result is positive. Save the remainder for MOD to the STACK
0C41POP DED1Get the MOD’s remainder from the STACK and put it in Register Pair DE
0C45H-0C5AH – LEVEL II BASIC MATH ROUTINE– “IMULDV”
This is the integer division routine HL = DE / HL. The remainder will be left in DE and the quotient will be left in HL. Every register is affected.
0C45
↳ IMULDVLD A,H7CLoad Register A with the MSB+SIGN of the integer value in Register H
0C46XOR DAACombine the MSB of the integer value in Register D with the MSB+SIGN of the integer value in Register A
0C47LD B,A47Save the result of the combined signs in Register A into Register B
0C48-0C4ACALL 0C4CHCALL INEGHCD 4C 0CIf necessary, NEGate the SECOND argument (i.e., the value in Register Pair HL) to positive
0C4BEX DE,HLEBPresetve the contents of Register DE into Register Pair HL, and fall through to the negation routine below.
0C4C
↳ INEGHLD A,H7CLoad Register A with the MSB+SIGN of the integer value in Register H
0C4D
↳ INEGAOR AB7Set the condition codes so we can see the sign of HL
0C4E-0C50JP P,0A9AHJP P,MAKINTF2 9A 0AIf 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
↳ INEGHLXOR AAFZero Register A.
0C52LD C,A4FLoad Register C with the ZERO held in Register A
0C53SUB L95Subtract the LSB of the integer value in Register L from the ZERO in Register A
0C54LD L,A6FSave the adjusted value in Register A in Register L
0C55LD A,C79Load Register A with a ZERO
0C56SBC A,H9CSubtract the HIGH ORDER (MSB+SIGN) of the integer value in Register H from the value in Register A
0C57LD H,A67Save the adjusted value in Register A into Register H
0C58-0C5AJP 0A9AHJP MAKINTC3 9A 0AJump to 0A9AH to save the result into the ACCumulator for when the operations jump back here.
0C5BH-0C6FH – LEVEL II BASIC MATH ROUTINE– “INEG”
Integer Negation Routine. All registers are altered.
0C5B-0C5D
↳ INEGLD HL,(4121H)LD HL,(FACLO)2A 21 41Load Register Pair HL with the integer value in the ACCumulator
0C5E-0C60CALL 0C51HCALL INEGHLCD 51 0CGo convert the integer value in Register Pair HL to positive if it’s negative
0C61LD A,H7CLoad Register A with the HIGH ORDER (i.e., MSB+SIGN) of the integer value in Register H
0C62-0C63XOR 80HXOR 1000 0000EE 80Invert the value of the sign bit in Register A which is 1000 0000 so that we can check for the special case of -32768
0C64OR LB5Combine the LSB of the integer value in Register L with the adjusted MSB of the integer value in Register A
0C65RET NZC0Return if the integer value in the ACCumulator isn’t equal to -32768
EX DE,HLEBIf we are here, the magic -32768 was found, so we need to float it. First, load Register Pair DE with the integer value in Register Pair HL
0C6AXOR AAFZero Register A, which we will use for the HIGH ORDER
0C6B-0C6C
↳ INEGADLD B,98H06 98Load Register B with an exponent. IADD jumps here.
DOUBLE PRECISION ROUTINES
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
0C70H-0C76H – DOUBLE PRECISION SUBTRACTION– “DSUB”
Double-precision subtraction (ACCumulator = ACCumulator – ARG).
Subtracts the double precision value in ARG (a/k/a REG 2) from the value in the ACCumulator. The difference is left in the ACCumulator.
Note: If you wanted to subtract two double precision numbers, store the minuend in 411DH-4124H and the subtrahend in 4127H-412EH, and CALL 0C70H. The result (in double precision format) is in 411DH-4124H in approximately 1.3 milliseconds.
Vernon Hester has flagged a bug. Double-precision subtraction should produce an difference accurate to 16 digits. However, the difference resulting from doubleprecision subtraction is erroneous when the smaller operand’s value is significantly less than the larger operand’s value
Example: In the code Y# = .20# : X# = 1D16 : J# = X# – Y# : PRINT J# – X#J# is incorrect and J#-X# shows a positive result when it is negative.
0C70-0C72
↳ DSUBLD HL,412DHLD HL,ARG-121 2D 41Since 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)
0C73LD A,(HL)7ELoad 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-0C75XOR 80HXOR 1000 0000EE 80Invert the value of the sign bit for the MSB of the double precision value in Register A which is 1000 0000
0C76LD (HL),A77Save 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
↳ DADDLD HL,412EHLD HL,ARG21 2E 41Load Register Pair HL with the address of the exponent in the FIRST argument held at ARG (a/k/a REG 2)
0C7ALD A,(HL)7EPrepare 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
0C7BOR AB7Check to see if the double precision value in ARG (a/k/a REG 2) is equal to zero
0C7CRET ZC8Return 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
0C7DLD B,A47Preserve the exponent for the double precision value in Register A into Register C
0C7EDEC HL2BDecrement the value of the memory pointer in Register Pair HL to now point to the HIGH ORDER (i.e., MSB + SIGN) for unpacking
0C7FLD C,(HL)4ELoad 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-0C82LD DE,4124HLD DE,FAC11 24 41Load Register Pair DE with the address of the exponent of the SECOND argument (held in the ACCumulator)
0C83LD A,(DE)1AFetch the value of the exponent of the double precision value in the ACCumulator at the location of the memory pointer in Register Pair DE
0C84OR AB7Set the flags to see if the double precision value in the ACCumulator is equal to zero
0C85-0C87JP Z,09F4HJP Z,MOVFACA F4 09If 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.
0C88SUB B90Now 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
0C89-0C8AIf the NC FLAG is set, then the we need to put the smaller number into the ACCumulator, so JUMP to DADD2 to do that
0C8BCPL2FNegate the shift count held in Register A
0C8CINC A3CIncrement the value of the difference for the exponents in Register A so that Register A will hold the positive difference
0C8DPUSH AFF5Save 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-0C8FLD C,08H0E 08Load Register C with a counter value which is 8
0C90INC HL23Increment 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)
0C91PUSH HLE5Save the value of the memory pointer in Register Pair HL (which is pointing to ARG) on the STACK
0C92
↳ DADD1LD A,(DE)1ATop of a loop. Load Register A with the value of the ACCumulator pointed to by Register Pair DE
0C93LD B,(HL)46Load Register B with the value of ARG (a/k/a REG 2) pointed to by Register Pair DE
0C94LD (HL),A77Save the ACCumulator value into the corresponding ARG (a/k/a REG 2) byte.
0C95LD A,B78Load Register A with the ARG byte (held in Register B)
0C96LD (DE),A12Save the ARG byte (held in Register A) into the corresopnding ACCumulator byte (pointed to by Register Pair DE)
0C97DEC DE1BDecrement the value of the memory pointer in Register Pair DE to the next lower byte of the ACCumulator
0C98DEC HL2BDecrement the value of the memory pointer in Register Pair HL to the next lower byte in ARG
0C99DEC C0DDecrement the value of the counter in Register C
0C9A-0C9BLoop until the double precision values in the ACCumulator and ARG (a/k/a REG 2) have been exchanged
0C9CPOP HLE1Get the HIGH ORDER back from the stack into Register Pair HL
0C9DLD B,(HL)46Fetch the exponent for the double precision value in ARG (a/k/a REG 2) pointed to by Register Pair HL
0C9EDEC HL2BDecrement the value of the memory pointer in Register Pair HL to now point to the HIGH ORDER (MSB + SIGN)
0C9FLD C,(HL)4ELoad 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
0CA0POP AFF1Get the shift count (i.e., difference for the exponents) back into Register A
0CA1-0CA2
↳ DADD2CP 39HFE 39Check to see if the difference between the two exponents is greater than 56 bits
0CA3RET NCD0Return if the difference between the two exponents is greater than 56 bits
0CA4PUSH AFF5Save the shift count (i.e., difference for the exponents) from Register A onto the STACK
0CA8INC HL23Increment the value of the memory pointer in Register Pair HL to now point to ARGLO-1
0CA9-0CAALD (HL),00H36 00Zero the temporary LSB (held at the location of the memory pointer in Register Pair HL)
0CABLD B,A47Preserve the sign test into Register B
0CACPOP AFF1Restore the shift count (i.e., difference for the exponents) from the STACK into Register A
0CAD-0CAFLD HL,412DHLD HL,ARG-121 2D 41Load Register Pair HL with the address of the HIGH ORDER in ARG (a/k/a REG 2)
0CB0-0CB2CALL 0D69HCALL DSHFTRCD 69 0DGo shift the double precision value in ARG (a/k/a REG 2) until it lines up with the double precision value in the ACCumulator
0CB3-0CB5LD A,(4126H)LD A,(ARGLO-1)3A 26 41We next need to transfer the OVERFLOW byte from ARG to ACCumulator, so first put it in Register A
0CB6-0CB8LD (411CH),ALD (DFACLO-1),A32 1C 41Save the value in Register A to ARG
0CB9LD A,B78Load Register A with the value of the sign test in Register B
0CBAOR AB7Check to see if the signs are equal
0CBB-0CBDJP P,0CCFHJP P,DADD3F2 CF 0CIf the P FLAG is set, then the signs of the numbers are different, so JUMP to DADD3 to subtract the values
0CBE-0CC0CALL 0D33HCALL DADDAACD 33 0DOtherwise (i.e., the signs are the same) GOSUB to DADDAA to add the numbers
0CC1-0CC3JP NC,0D0EHJP NC,DROUNDD2 0E 0DIf that didn’t trigger a NC FLAG, then JUMP to 0D0EH to ROUND the result and continue on.
0CC4EX DE,HLEBIf that DID trigger the NC FLAG, then put the pointer to the exponent of the ACCumulator into HL
0CC5INC (HL)34Add one to the exponent (since we had an overflow)
0CC6-0CC8JP Z,07B2HJP Z,OVERRCA B2 07Check for OVERFLOW because of that too! If the Z FLAG is set, then display an ?OV ERRORif the exponent for the double precision result in the ACCumulator is too large
0CC9-0CCBCALL 0D90HCALL DSHFRBCD 90 0DIf we still have no overflow, then we need to shift the number right one so as to shift in the CARRY FLAG. To do this we GOSUB to DSHFRB
0CCFH-0D1FH – DOUBLE PRECISION MATH ROUTINE– “DADD3”
0CD2-0CD4LD HL,4125HLD HL,FAC+121 25 41Right 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.
0CD5-0CD7CALL C,0D57HCALL C,DNEGRDC 57 0DGo complement the result in the ACCumulator if the Carry flag is set
0CD8H – DOUBLE PRECISION MATH ROUTINE– “DNORML” and “DNORM1”
0CD8
↳ DNORMLXOR AAFZero Register A, which will act as a byte shift counter
0CDA-0CDCLD A,(4123H)LD A,(FAC-1)3A 23 41Load Register A with the value of the HIGH ORDER (i.e., MSB+SIGN) of the double precision result in the ACCumulator
0CDDOR AB7Check to see if we can shift 8 numbers to the left
0CDE-0CDFIf the NZ FLAG is set, then we cannot shift 8 numbers left, so we need to JUMP to see if the number is already normalized.
0CE0-0CE2LD HL,411CHLD HL,DFACLO-121 1C 41If we are here then we CAN shift 8 numbers left, so first load Register Pair HL with the starting address of ACCumulator minus one.
0CE3-0CE4LD C,08H0E 08Load Register C with the number of bytes to be shifted (i.e., 8)
0CE5
↳ DNORM2LD D,(HL)56Top of a loop. Load Register D with a byte from the ACCumulator (pointed to by Register Pair HL)
0CE6LD (HL),A77Save the value in Register A to the newly vacated location at the memory pointer in Register Pair HL. Note that on the FIRST loop, this is a zero.
0CE7LD A,D7APut the current byte from the ACCumulator (preserved in D) into Register A for writing on the next iteration
0CE8INC HL23Increment the value of the memory pointer in registerpair HL
0CE9DEC C0DDecrement the number of bytes to be shifted in Register C
0CEA-0CEBLoop until all of the bytes in the double precision value have been shifted
0CECLD A,B78Now that we did an 8 byte shift, we need to subtract 8 from the shift counter. First, load Register A with the number of bits shifted in Register B
0CED-0CEESUB 08HD6 08Subtract the number of bits just shifted from the shift counter in Register A
0CEF-0CF0CP C0HFE C0Check to see if the whole of the double precision value has been shifted
0CF1-0CF2If the whole of the double precision value hasn’t been shifted, JUMP back to DNORM1 to shift some more
0CF6H – Part of the “DNORML” and “DNORM1” Routine
0CF6
↳ DNORM3DEC B05Decrement the shift counter held in Register B
0CF7-0CF9LD HL,411CHLD HL,DFACLO-121 1C 41Load Register Pair HL with the starting address of ACCumulator minus one.
0CFA-0CFCCALL 0D97HCALL DSHFLCCD 97 0DShift the double precision value in the ACCumulator once to the left
0CFDOR AB7Check to see if the number has been normalized yet
0CFE-0D00
↳ DNORM5JP P,0CF6HJP P,DNORM3F2 F6 0CIf the P FLAG is set, then we are not yet normalized, so LOOP back to DNORM3 and keep shifting
0D01LD A,B78Load Register A with the value of the shift counter from Register B
0D02OR AB7Check to see if the shift counter in Register A is equal to zero
0D03-0D04If the shift counter is zero, then proceed to round the number and finish up by JUMPing to DROUND
0D05-0D07LD HL,4124HLD HL,FAC21 24 41Load Register Pair HL with the address of the exponent in the ACCumulator
0D08ADD A,(HL)86Add 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
0D09LD (HL),A77Save the adjusted exponent for the double precision value in Register A at the location of the memory pointer in Register Pair HL
0D0A-0D0CJP NC,0778HP NC,ZEROD2 78 07If the NC FLAG was triggered, then we have an UNDERFLOW, so JUMP to ZERO
0D0DRET ZC8If the Z FLAG is set, then the result is already zero and we are done, so FALL into the DROUND routine and round the result.
0D0EH – DOUBLE PRECISION MATH ROUTINE– “DROUND” and “DROUNB”
This routine will round the ACCumulator. Registers A, B, H, and L are affected.
0D0E-0D10
↳ DROUNDLD A,(411CH)LD A,(DFACLO-1)3A 1C 41Load Register A with the value of the rounding byte at the location of the starting address of ACCumulator minus one.
0D11
↳ DROUNBOR AB7Check to see if there is a bit to be shifted into the double precision value in the ACCumulator
0D12-0D14CALL M,0D20HCALL M,DROUNAFC 20 0DGo move the bit into the double precision value if necessary
0D15-0D17LD HL,4125HLD HL,FAC+121 25 41Load Register Pair HL with the address of the unpacked sign for the result.
0D18LD A,(HL)7ELoad Register A with the value of the sign for the result at the location of the memory pointer in Register Pair HL
0D19-0D1AAND 80HAND 1000 0000E6 80Turn off some bits so we can mask the value of the sign for the result in Register A which is 1000 0000 to isolate the sign bit.
0D1B
0D1CDEC HL
DEC HL2BDecrement the value of the memory pointer in Register Pair HL twice so that it points to HIGH ORDER (MSB) byte in the ACCumulator
0D1DXOR (HL)AEPack the SIGN and the MSB together
0D1ELD (HL),A77Save the packed sign and MSB combination byte to the ACCumulator at the location of the memory pointer in Register Pair HL
0D1FRETC9RETurn to CALLer
0D20H-0D32H – DOUBLE PRECISION MATH support routine– “DROUNA”
0D20-0D22
↳ DROUNALD HL,411DHLD HL,DFACLO21 1D 41Set up HL to point to the LSB of the the ACCumulator.
Note: 411DH-4124H holds ACCumulator
0D23-0D24LD B,07H06 07Load Register B with the number of bytes to be bumped for the double precision value in the ACCumulator
0D25
↳ DRON1INC (HL)34Top of a loop. Increment a byte of the ACCumulator at the location of the memory pointer in Register Pair HL
0D26RET NZC0Return if the value at the location of the memory pointer in Register Pair HL isn’t equal to zero
0D27INC HL23Increment the value of the memory pointer in Register Pair HL to point to the next highest order in the ACCumulator
0D28DEC B05Decrement the value of the byte counter in Register B
0D2BINC (HL)34We have bumped all the bytes, so now we need to increment the value of the exponent at the location of the memory pointer in Register Pair HL
0D2C-0D2EJP Z,07B2HJP Z,OVERRCA B2 07Check 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
0D2FDEC HL2BDecrement the value of the memory pointer in Register Pair HL to point to the HIGH ORDER
0D30-0D31LD (HL),80H36 80Save a new MSB+SIGN at the location of the memory pointer in Register Pair HL
0D32RETC9RETurn to CALLer
0D33H-0D44H – DOUBLE PRECISION MATH ROUTINE– “DADDAA” and “DADDA”
0D33-0D35
↳ DADDAALD HL,4127HLD HL,ARGLO21 27 41DADD 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
↳ DADDFOLD DE,411DHLD DE,DFACLO11 1D 41FOUT 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
↳ DADDSLD C,07H0E 07Load Register C with the number of bytes to be added
0D3BXOR AAFClear the Carry flag
0D3C
↳ DADDLSLD A,(DE)1ATop of a loop. Load Register A with the value in the ACCumulator at the location of the memory pointer in Register Pair DE
0D3DADC A,(HL)8EAdd the value in ARG (a/k/a REG 2) at the location of the memory value in Register A
0D3ELD (DE),A12Save the result of that addition into the ACCumulator at the location of the memory pointer in Register Pair DE
0D3FINC DE13Increment the value of the memory pointer in Register Pair DE
0D40INC HL23Increment the value of the memory pointer in Register Pair HL
0D41DEC C0DDecrement the number of bytes to be added in Register C
0D42-0D43Loop until all of the bytes for the double precision values have been added
0D44RETC9RETurn 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
↳ DADDASLD HL,4127HLD HL,ARGLO21 27 41DADD 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
↳ DADDFSLD DE,411DHLD DE,DFACLO11 1D 41FOUT 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
↳ DADDSSLD C,07H0E 07Load Register C with the number of bytes to be subtracted
0D4DXOR AAFClear the Carry flag
0D4E
↳ DADDLSLD A,(DE)1ATop of a loop. Load Register A with the value in the ACCumulator at the location of the memory pointer in Register Pair DE
0D4FSBC A,(HL)9ESubtract 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
0D50LD (DE),A12Save the result in Register A in the ACCumulator at the location of the memory pointer in Register Pair DE
0D51INC DE13Increment the value of the memory pointer in Register Pair DE
0D52INC HL23Increment the value of the memory pointer in Register Pair HL
0D53DEC C0DDecrement the number of bytes to be subtracted for the double precision values in Register C
0D54-0D55Loop until all of the bytes for the double precision values have been subtracted
0D56RETC9RETurn to CALLer
0D57H-0D68H – DOUBLE PRECISION MATH ROUTINE– “DNEGR”
This routine will negate the signed number held in the ACCumulator. Registers A, B, C, H, and L are affected. This routine is called by DADD and DINT.
0D57
↳ DNEGRLD A,(HL)7ELoad Register A with the value of the sign from the ACCumulator at the location of the memory pointer in Register Pair HL
0D58CPL2FComplement the value of the sign in Register A
0D59LD (HL),A77Save the value of the sign in Register A at the location of the memory pointer in Register Pair HL
0D5A-0D5CLD HL,411CHLD HL,DFACLO-121 1C 41Load Register Pair HL with the starting address of ACCumulator minus one.
0D5D-0D5ELD B,08H06 08Load Register B with the number of bytes to be reversed
0D5FXOR AAFZero Register A and clear the CARRY FLAG
0D60LD C,A4FLoad Register C with the ZERO
0D61
↳ DNEGR1LD A,C79Top of a loop. Load Register A with the value in Register C
0D62SBC A,(HL)9ENEGate a byte to the ACCumulator
0D63LD (HL),A77Save the NEGated value in Register A back to the location of the memory pointer in Register Pair HL
0D64INC HL23Increment the value of the memory pointer in Register Pair HL
0D65DEC B05Decrement the number of bytes to be reversed in Register B
0D66-0D67Loop until all of the bytes for the double precision number in the ACCumulator have been reversed
0D68RETC9RETurn to CALLer
0D69H-0D8FH – DOUBLE PRECISION MATH ROUTINE– “DSHFTR”
This routine wwill shift the double precision value held in the ACCumulator to the right once.
0D69
↳ DSHFTRLD (HL),C71Save the unpacked MSB of the double precision value in Register C at the location of the memory pointer in Register Pair HL
0D6APUSH HLE5Save the value of the memory pointer in Register Pair HL on the STACK
0D6B-0D6C
↳ DSHFR1SUB 08HD6 08Subtract 8 from the number of bits to be shifted from the number of bits to be shifted in Register A
0D6D-0D6EIf 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.
0D6FPOP HLE1Get the value of the memory pointer from the STACK and put it in Register Pair HL
0D70
↳ DSHFRMPUSH HLE5Save the value of the memory pointer in Register Pair HL on the STACK. This is the entry point from DMULT.
0D71-0D73LD DE,0800H11 00 08This LD command shifts a zero into the HIGH ORDER byte and sets up a counter
0D74
↳ DSHFR2LD C,(HL)4ETop of a loop. Preserve a byte of the ACCumulator into Register C
0D75LD (HL),E73Overwrite that location with the last byte (held in Register E)
0D76LD E,C59Load Register E with the value in Register C so that THIS is the byte to write next.
0D77DEC HL2BDecrement the value of the memory pointer in Register Pair HL to point to the next lower order byte
0D78DEC D15Decrement the number of bits shifted in Register D
0D69H-0D8FH – DOUBLE PRECISION MATH ROUTINE– “DSHFR3”
0D7D-0D7E
↳ DSHFR3ADD 09HC6 09At this point, we cannot shift 8 bytes at once and need to do them individually. First, set a corrected shift counter
0D7FLD D,A57Preserve the adjusted shift counter into Register D
0D80
↳ DSHFR4XOR AAFClear the CARRY FLAG
0D81POP HLE1Restore the pointer to the HIGH ORDER byte into Register Pair HL
0D82DEC D15Decrement the number of bits to be shifted in Register D
0D83RET ZC8Return if all of the bits have been shifted
0D84
↳ DSHFRAPUSH HLE5If 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-0D86LD E,08H1E 08Load Register E with the counter of the number of bytes to be shifted
0D87
↳ DSHFR5LD A,(HL)7ETop of a loop. Load Register A with a byte from the ACCumulator pointed to by HL
0D88RRA1FShift 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.
0D89LD (HL),A77Put the rotated byte back
0D8ADEC HL2BDecrement the value of the memory pointer in Register Pair HL so we deal with the next lower order byte
0D8BDEC E1DDecrement the number of bytes to be shifted in Register E
0D90H-0D96H – DOUBLE PRECISION MATH ROUTINE– “DSHFRB”
This is the entry from DADD and DMULT.
0D90-0D92
↳ DSHFRBLD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the HIGH PRDER (MSB) of the double precision value in the ACCumulator
0D93-0D94LD D,01H16 01Load Register D with the number of bits to be shifted
0D97H-0DA0H – DOUBLE PRECISION MATH ROUTINE– “DSHFLC”
This routine will rotate the ACCumulator left one. Register A, C, H, and L are affected.
0D97-0D98
↳ DSHFLCLD C,08H0E 08Load Register C with the number of bytes to be shifted
0D99
↳ DSHFTLLD A,(HL)7ETop of a loop. Load Register A with a byte from the ACCumulator pointed to by HL
0D9ARLA17Rotate that byte left one bit
0D9BLD (HL),A77Save the shifted byte (held in Register A) back to the ACCumulator at the location of the memory pointer in Register Pair HL
0D9CINC HL23Increment the value of the memory pointer in Register Pair HL to point to the next higher order byte
0D9DDEC C0DDecrement the byte counter in Register C
0DA0RETC9RETurn to CALLer
0DA1H-0DD3H – DOUBLE PRECISION MULTIPLICATION– “DMULT”
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.
0DA1-0DA3
↳ DMULTCALL 0955HCALL SIGNCD 55 09As 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
0DA4RET ZC8If 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
0DA5-0DA7CALL 090AHCALL MULDVACD 0A 09Add the exponents and take care of processing the signs of the numbers via a GOSUB to MULDVA
0DA8-0DAACALL 0E39HCALL DMULDVCD 39 0EZero out the ACCumulator and put the move the double precision value in the ACCumulator to a temporary work area via a GOSUB to DMULDV
0DABLD (HL),C71Put the unpacked HIGH ORDER byte (pointed to by Register Pair HL in ARG) into Register C
0DACINC DE13Increment Register Pair DE so that it points to the LSB of the double precision value in ARG
0DAD-0DAELD B,07H06 07Load Register B with the number of bytes to be figured
0DAF
↳ DMULT2LD A,(DE)1ATop of a big loop. Fetch a byte of ARG (at the location pointed to by DE) to multiply by into Register A
0DB0INC DE13Increment the value of the memory pointer in Register Pair DE to point to the next higher byte.
0DB1OR AB7Check to see if the value in Register A is equal to zero
0DB2PUSH DED5Save the value of the memory pointer to ARG (held in Register Pair DE) to the STACK
0DB3-0DB4If Register A is zero, then we are multiplying by ZERO, so JUMP to DMULT5
0DB5-0DB6LD C,08H0E 08Otherwise, we need to set up for another loop for bit rotation. First, load Register C with the numberof bits to be shifted
0DB7
↳ DMULT3PUSH BCC5Top of a loop. Save the counters (held in Register Pair BC) to the STACK
0DB8RRA1FShift 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.
0DB9LD B,A47Preserve the shifted multiplier byte into Register B
0DBA-0DBCCALL C,0D33HCALL C,DADDAADC 33 0DIf 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
0DBD-0DBFCALL 0D90HCALL DSHFRBCD 90 0DRotate the production right one bit via a GOSUB to DSHFRB which shifts the value of the total in the ACCumulator
0DC0LD A,B78Restore the shifted multiplier byte (held in Register B) back into Register A
0DC1POP BCC1Restore the counters from the STACK into Register Pair BC
0DC2DEC C0DDecrement the number of bits to be shifted from the ARG (tracked in Register C)
0DC3-0DC4If 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
↳ DMULT4POP DED1If 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
0DC6DEC B05Decrement the number of bytes to be figured (tracked in Register B)
0DC7-0DC8Loop back to 0DAFH to multiply by the next higher order byte in ARG until all of the bytes in ARG have been figured
0DC9-0DCBJP 0CD8HJP DNORMLC3 D8 0CIf 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
↳ DMULT5LD HL,4123HLD HL,FAC-121 23 41Load Register Pair HL with the address of the HIGH ORDER/MSB of the ACCumulator
0DCF-0DD1CALL 0D70HCALL DSHFRMCD 70 0DGo shift the double precision total in the ACCumulator right one byte
0DD4H-0DDBH – DOUBLE PRECISION CONSTANT STORAGE AREA– “DTEN” and “FTEN”
0DD4-0DDB
↳ DTEN00 00 00 00 00 00 20 8400A double precision constant equal to 10 is stored here. Note: 0DD8 is also a reference point.
0DD8-0DDB
↳ FTEN00 00 20 8400A double precision constant equal to 10.0 is stored here. Note: 0DD8 is also a reference point.
0DDCH-0DE4H – DOUBLE PRECISION MATH ROUTINE– “DDIV10”
Double precision divide routine. Divides the ACCumulator by 10. All registers are affected.
0DDC-0DDE
↳ DDIV10LD DE,0DD4HLD DE,DTEN11 D4 0DLoad Register Pair DE with the starting address of the double precision constant for 10
0DDF-0DE1LD HL,4127HLD HL,ARGLO21 27 41Load Register Pair HL with the starting address of ARG (a/k/a REG 2).
Note: 4127H-412EH holds ARG (a/k/a REG 2)
0DE2-0DE4CALL 09D3HCALL VMOVECD D3 09GOSUB to VMOVE to move the 10 into ARG and then fall through to the DDIV routine to divide by 10.
0DE5H-0E38H – DOUBLE PRECISION DIVISION– “DDIV”
Double-precision division (ACCumulator=ACC / ARG).
Divides the double precision value in the ACCumulator by the value in ARG (a/k/a REG 2). The quotient is left in the ACCumulator. All registers are affected
To use a ROM call to divide two double precision numbers, store the dividend in 411DH-4124H, and the divisor in 4127H-412EH and then CALL 0DE5H. The result (in double precision format) is in 411DH-4124H and then pproximately 42 milliseconds. Overflow or /0 will error out and return to Level II.
According to Vernon Hester, there is a bug this routine. Double-precision division should return a zero quotient when the dividend is zero. However, when the dividend is zero and the divisor is less than .25#, the ROM’s double-precision division produces an non-zero quotient. e.g., PRINT 0 / .24# produces a quotient of 1.171859195766034D-38. If the divisor is 2.938735877055719D-39 then the quotient is .5
Another bug is that double-precision division should perform correctly for absolute values that are from 2.938735877055719D-39 to 1.701411834604692D+38. If the divisor is the minimum magnitude or the minimum magnitude times 2, then double-precision division errors.
10 Z# = 1 / (2^125 + 2^125) * .25 ‘This values Z# with 2.938735877055719D-39
20 PRINT 1 / Z# ‘displays 2.938735877055719D-39 instead of overflow
0DE5-0DE7
↳ DDIVLD A,(412EH)LD A,(ARG)3A 2E 41As 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)
0DE8OR AB7Check to see if the double precision value in ARG (a/k/a REG 2) is equal to zero
0DE9-0DEBJP Z,199AHJP Z,DV0ERRCA 9A 19Display a ?/0 ERROR message if the double precision value in ARG (a/k/a REG 2) is equal to zero
0DEC-0DEECALL 0907HCALL MULDVSCD 07 09Subtract the exponents and check the signs via a GOSUB to MULDVS
0DEF
0DF0INC (HL)
INC (HL)34Increment the value of the exponent in the ACCumulator TWICE to correct the scaling
0DF1-0DF3CALL 0E39HCALL DMULDVCD 39 0EZero the ACCumulator and move the double precision value from ACCumulator into ARG (a/k/a REG 2)
0DF4-0DF6LD HL,4151HLD HL,FBUFFR+3421 51 41Load Register Pair HL with the address of the extra HIGH ORDER byte we will use in ARG
0DF7LD (HL),C71Zero the that byte
0DF8LD B,C41Zero Register B, which will be the flag that tells us when start dividing
0DF9-0DFB
↳ DDIV1LD DE,414AHLD DE,FBUFFR+2711 4A 41Top of a large loop. First, get the pointer to the end of the BUFFR into Register Pair DE
0DFC-0DFELD HL,4127HLD HL,ARGLO21 27 41Load Register Pair HL with the address of the END of the double precision value in ARG (a/k/a REG 2)
0E02LD A,(DE)1APrepare 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
0E03SBC A,C99Subtract the value in Register C from the value in Register A
0E04CCF3FIf 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
0E05-0E06JR C,0E12HJR C,DDIV238 0BIf 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-0E09LD DE,414AHLD DE,FBUFFR+2711 4A 41Put the pointer to the end of the BUFFR into Register Pair DE
0E0A-0E0CLD HL,4127HLD HL,ARGLO21 27 41Load Register Pair HL with the address of the END of the double precision value in ARG (a/k/a REG 2)
0E0D-0E0FCALL 0D39HCALL DADDSCD 39 0DGo add the double precision value in ARG (a/k/a REG 2) to the double precision value in FBUFFR
0E10XOR AAFClear the CARRY FLAG for the Z-80 trick in the next instruction.
0E11-0E13JP C,0412HDA 12 04Z-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
↳ DDIV2LD (DE),A12If this line is executed (i.e., JUMPed to, but not passed down to), put the new highest order byte into Register A
0E13INC B04If 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-0E16LD A,(4123H)LD A,(FAC-1)3A 23 41Prepare the check to see if we are finished dividing. First, getch the byte at FAC-1
0E17
0E18INC A
DEC A3CINCrement and DECrement Register A so that the SIGN FLAG will be set without chaning the status of the CARRY FLAG
0E19RRA1FIn 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.
0E1A-0E1CJP M,0D11HJP M,DROUNBFA 11 0DIf the M FLAG is set, then we are done and have 57 bits of accuracy, so JUMP to DROUNB to finish up.
0E1DRLA17Restore the CARRY BIT to where it belongs
0E1E-0E20LD HL,411DHLD HL,DFACLO21 1D 41Load 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-0E22LD C,07H0E 07Load Register C with the number of bytes to be shifted
0E23-0E25CALL 0D99HCALL DSHFTLCD 99 0DGOSUB to DSHFTL to shit in the next bit in the quotient (held in the ACCumulator)
0E26-0E28LD HL,414AHLD HL,FBUFFR+2721 4A 41Load Register Pair HL with the pointo the LOW ORDER byte in FBUFFR
0E29R-0E2BHCALL 0D97HCALL DSHFLCCD 97 0DGo shift the double precision value dividend (in FBUFFR) one to the left
0E2CLD A,B78Test 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
0E2DOR AB7Set the flags based on Register B
0E2E-0E2FIf Register B is not ZERO, then we have more to go so LOOP back up to DDIV1
0E30-0E32LD HL,4124HLD HL,FAC21 24 41If 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
0E33DEC (HL)35Decrement 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!
0E34-0E35Continue dividing so long as we don’t have an overflow by LOOPING back to DDIV
0E36-0E38JP 07B2HJP OVERRC3 B2 07Display an ?OV ERRORif 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
↳ DMULDVLD A,C79We 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-0E3CLD (412DH),ALD (ARG-1),A32 2D 41Save the MSB of the double precision value in ARG (a/k/a REG 2) in Register A
0E3DDEC HL2BDecrement the value of the memory pointer in Register Pair HL to now point to the HIGH ORDER of the ACCumulator
0E3E-0E40LD DE,4150HLD DE,FMLTT211 50 41Load Register Pair DE with the end of FBUFFR
0E41-0E43LD BC,0700H01 00 07Load Register B with the number of bytes to be moved (which is 7) and put a zero into Register C
0E44
↳ DMLDV1LD A,(HL)7ETop of a loop. Fetch a byte from the ACCumulator (tracked by Register Pair HL) into Register A
0E45LD (DE),A12Save that byte into FBUFFR (tracked by Register Pair DE)
0E46LD (HL),C71Zero out that location in the ACCumulator
0E47DEC DE1BDecrement the value of the memory pointer to FBUFFR (tracked by Register Pair DE)
0E48DEC HL2BDecrement the value of the memory pointer to the ACCumulator (tracked by Register Pair HL)
0E49DEC B05Decrement the value of the byte counter in Register B to see if we are done
0E4A-0E4BLoop until the double precision value has been moved from the ACCumulator to FBUFFR
0E4CRETC9RETurn 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
0E4D-0E4F
↳ DMUL10CALL 09FCHCALL VMOVAFCD FC 09Go move the value in the ACCumulator to ARG (a/k/a REG 2)
0E50EX DE,HLEBSince VMOVAF exits with DE pointing to ACCumulator + 1 we need to swap those so that HL points to the ACCumulator
0E51DEC HL2BAs 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
0E52LD A,(HL)7EFetch the exponent from the ACCumulator
0E53OR AB7Check to see if the value in the ACCumulator is equal to zero
0E54RET ZC8Return if the value in the ACCumulator is equal to zero
0E55-0E56ADD 02HC6 02Add two to the exponent which is the same as multiplying the ACCumulator by 4
0E57-0E59JP C,07B2HJP C,OVERRDA B2 07Display an ?OV ERRORif the adjusted exponent in Register A is too large
0E5ALD (HL),A77Save the adjusted exponent back into the ACCumulator at the location of the memory pointer in Register Pair HL
0E5BPUSH HLE5Save pointer to the ACCumulator onto the STACK
0E5C-0E5ECALL 0C77HCALL DADDCD 77 0CAdd 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)
0E5FPOP HLE1Get the memory pointer to the ACCumulator from the STACK and put it in Register Pair HL
0E60INC (HL)34Add 1 to the exponent, thus doubling the number.
0E61RET NZC0Return if overflow didn’t occur
0E62-0E64JP 07B2HJP OVERRC3 B2 07Display an ?OV ERRORif 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.
0E6BOR 0AFH
F6 AFF6 AFPart 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
↳ FINXOR AAFZero Register A for the purpose of triggering the GOSUB to MAKINT at 0E73H
0E6D
↳ FINCHREX DE,HLEBLoad Register Pair DE with the pointer to the current BASIC line being interpreted
0E6E-0E70LD BC,00FFH01 FF 00Load Register Pair BC with a zero and a negative one. Register B will track the decimal point location and C will be a flag.
0E71LD H,B60Load Register H with zero
0E72LD L,B68Load Register L with zero. Now HL is zero.
0E73-0E75CALL Z,0A9AHCALL Z,MAKINTCC 9A 0AA CALL to MAKINT will clear the ACCumulator and force VALTYP into Integer
0E76EX DE,HLEBRestore the pointer to the BASIC line being interpreted into HL and zero out Register Pair DE
0E77LD A,(HL)7ERetrieve the first character at at the location of the current input buffer pointer in Register Pair HL
0E78-0E79CP 2DHCP “-“FE 2DCheck to see if the character at the current position in the string being interpreted is a –
0E7APUSH AFF5Save the sign in Register Pair AF on the STACK
0E7B-0E7DJP Z,0E83HJP Z,FINCCA 83 0EIf the character at the current position in the string being interpreted is a –then JUMP to FINC to ignore it
0E7E-0E7FCP 2BHCP “+”FE 2BCheck to see if the character at the current position in the string being interpreted is a +
0E80-0E81JR Z,0E83HJR Z,FINC28 01If the character at the current position in the string being interpreted is a +then JUMP to FINC to process it
0E82DEC HL2BDecrement the value of the current input buffer pointer in Register Pair HL to point to the first character in the string being interpreted
0E83
↳ FINCSince 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)
0E84-0E86JP C,0F29HJP C,FINDIGDA 29 0FIf the character at the location of the current input buffer pointer in Register A is numeric then JUMP to FINDIG
0E87-0E88CP 2EHCP “.”FE 2ECheck to see if the character at the location of the current input buffer pointer in Register A is a .
0E89-0E8BJP Z,0EE4HJP Z,FINDPCA E4 0EJump if the character at the location of the current input buffer pointer in Register A is a .
0E8C-0E8DCP 45HCP “E”FE 45Check to see if the character at the location of the current input buffer pointer in Register A is an E(which is a single precision exponent)
0E8E-0E8FJR Z,0EA4HJR Z,FINEX28 14Jump if the character at the location of the current input buffer pointer in Register A is an E
0E90-0E91CP 25HCP “%”FE 25Check to see if the character at the location of the current input buffer pointer in Register A is a %
0E92-0E94JP Z,0EEEHJP Z,FININTCA EE 0EJump to FININT (since this HAS to be an integer) if the character at the location of the current input buffer pointer in Register A is a %
0E95-0E96CP 23HCP “#”FE 23Check to see if the character at the location of the current input buffer pointer in Register A is a #
0E97-0E99JP Z,0EF5HJP Z,FINDBFCA F5 0EJump 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-0E9BCP 21HCP “!”FE 21Check to see if the character at the location of the current input buffer pointer in Register A is a !
0E9C-0E9EJP Z,0EF6HJP Z,FINSNFCA F6 0EJump 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-0EA0CP 44HCP “D”FE 44Check to see if the character at the location of the current input buffer pointer in Register A is a D
0EA1-0EA2If the character ISN’T a D, then we must be finished with the number, so JUMP to FINE
0EA3
↳ FINEX1OR AB7Set the flags according to the value of the character at the location of the current input buffer pointer in Register A
0EA4-0EA6
↳ FINEXCALL 0EFBHCALL FINFRCCD FB 0EConvert the current value in the ACCumulator to either single precision or double precision
0EA7PUSH HLE5Save the current input buffer pointer to the string being processed (tracked in Register Pair HL) to the STACK
0EA8-0EAALD HL,0EBDHLD HL,FINEC21 BD 0ELoad Register Pair HL with the return address to the FINEC routine
0EABEX (SP),HLE3Swap (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
0EACNext 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.
0EADDEC D15Decrement the value in Register D to turn the sign of the exponent to NEGATIVE
0EAE-0EAFCP 0CEHCP “-“FE CECheck to see if the character at the location of the current input buffer pointer in Register A is a –token
0EB0RET ZC8If the character at the location of the current input buffer pointer in Register A is a minus sign token then RET
0EB1-0EB2CP 2DHCP “-“FE 2DCheck to see if the character at the location of the current input buffer pointer in Register A is a –sign (not token)
0EB3RET ZC8If the character at the location of the current input buffer pointer in Register A is a minus sign then RET
0EB4INC D14If 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-0EB6CP 0CDHCP “+”FE CDCheck to see if the character at the location of the current input buffer pointer in Register A is a +token (0CDH)
0EB7RET ZC8Return if the character at the location of the current input buffer pointer in Register A is a +token (CDH)
0EB8-0EB9CP 2BHCP “+”FE 2BCheck to see if the character at the location of the current input buffer pointer in Register A is a +
0EBBRET Z2BReturn if the character at the location of the current input buffer pointer in Register A is a +
0EBADEC HLC8If 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
0EBCPOP AFF1Discard 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.
0EBE-0EC0JP C,0F94HJP C,FINEDGDA 94 0FIf 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
0EC1INC D14If 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
0EC2-0EC3So long as the exponent isn’t a ZERO, JUMP to FINE to skip over the handling of a negative exponent
0EC4XOR AAFIf we are here, then the exponent is negative. Zero Register A
0EC5SUB E93NEGate the value of the exponent in Register E (i.e., A = 0 – E)
0EC6LD E,A5FLoad Register E with the negated version of itself
0EC7
↳ FINEPUSH HLE5Save the current input buffer pointer to the string being converted (tracked in Register Pair HL) to the STACK
0EC8LD A,E7BLoad Register A with the value of the exponent in Register E
0EC9SUB B90Subtract the value in Register B from the exponent in Register A to get the number of times we have to multiply or divide by 10
This “FINE2” routine will multiply or divide by 10 the correct number of times. If A=0 the number is an integer.
0ECA-0ECC
↳ FINE2CALL P,0F0AHCALL P,FINMLTF4 0A 0FIf the P FLAG is set, then we need to multiply. So multiply the current value by ten
0ECD-0ECFCALL M,0F18HCALL M,FINDIVFC 18 0FIf the M FLAG is set, then we need to divide. So multiply the current value by ten
0ED0-0ED1Whichever of those two routines applied, if they returned a NZ then we need to do it again … so Loop until the value is adjusted correctly
Next we need to put the correct sign on the number.
0ED2POP HLE1Get the value of the current input buffer pointer of the string being parsed from the STACK and put it in Register Pair HL
0ED3POP AFF1Get the sign value from the STACK and put it in Register A
0ED4PUSH HLE5Save the value of the current input buffer pointer of the string being parsed in Register Pair HL on the STACK
0ED5-0ED7CALL Z,097BHCALL Z,VNEGCC 7B 09If the Z FLAG is set, then convert the current value to negative
0ED8POP HLE1Get the value of the current input buffer pointer of the string being parsed from the STACK and put it in Register Pair HL
Next we want -32768 to be an integer (it would be single precision at this point)
0ED9We 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.
0EDARET PEE8If that test shows we have anything other than a SINGLE PRECISION number, then we do no thave -32678, so RETurn
0EDBPUSH HLE5If 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-0EDELD HL,0890HLD HL,POPHRT21 90 08Load Register Pair HL with the return address of the POPHRT routine because CONIS2 does funny things to the stack.
0EDFPUSH HLE5Save the value of the return address in Register Pair HL on the STACK
0EE0-0EE2CALL 0AA3HCALL CONIS2CD A3 0ACheck to see if we have -32768 via a GOSUB to CONIS2 which will convert the current value in the ACCumulator to an integer if possible
0EE3RETC9RETurn to CALLer. If we didn’t have -32768 then this will RETurn to POPHRT
0EE4 – Math Routine– “FINDP”
This routine checks to see if we have seen TWO decimal points and to set the decimal point flag. We jumped here when we found a single decimal point.
0EE4
↳ FINDPWe 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
0EE5INC C0CIncrement the value in Register C to adjust the flag
0EE6-0EE7If the INC C is NOT ZERO then we have 2 decimal points, so we are DONE.
0EE8-0EEACALL C,0EFBHCALL C,FINFRCDC FB 0EIf 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
0EEE – Math Routine– “FININT”
0EEE
↳ FININTWe 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
0EEF-0EF1JP P,1997HJP P,SNERRF2 97 19If that test shows anything but an INTEGER, jump to the Level II BASIC error routine and display a ?SN ERROR message
0EF2
↳ INFINEINC HL23Top 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 %).
0EF5
↳ FINDBFOR AB7If we are here then we need to force double precision, so set the NZ FLAG
0EF6-0EF8
↳ FINSNFCALL 0EFBHCALL FINFRCCD FB 0EForce 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)
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
↳ FINFRCPUSH HLE5Save the value of the current input buffer pointer of the string being parsed in Register Pair HL on the STACK
0EFCPUSH DED5Save the exponent (held in Register Pair DE) to the STACK
0EFDPUSH BCC5Save the decimal point information (held in Register Pair BC) to the STACK
0EFEPUSH AFF5Save the sp/dp value flag for the conversion (held in Register A) to the STACK
0EFF-0F01CALL Z,0AB1HCALL Z,FRCSNGCC B1 0AIf 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)
0F02POP AFF1Restore the sp/dp value flag for the conversion from the STACK and put it in Register Pair AF
0F03-0F05CALL NZ,0ADBHCALL NZ,FRCDBLC4 DB 0AIf 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)
0F06POP BCC1Restore the decimal point information from the STACK and put it in Register Pair BC
0F07POP DED1Restore the exponent from the STACK and put it in Register Pair DE
0F08POP HLE1Restore the value of the current input buffer pointer of the string being parsed from the STACK and put it in Register Pair HL
0F09RETC9RETurn 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
↳ FINMULRET ZC8If the exponent is ZERO then exit right back out
0F0B
↳ FINMLTPUSH AFF5Save the exponent (held in Register Pair AF) to the STACK. FOUT enters the routine here.
0F0CWe 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
0F0DPUSH AFF5Save exponent and the value type from AF to the STACK
0F0E-0F10CALL PO,093EHCALL PO,MUL10E4 3E 09If that test shows SINGLE PRECISION, go to 093EH to multiply the current value in the ACCumulator by “10.0”
0F11POP AFF1Get the exponent and the value type from the STACK back into AF
0F12-0F14CALL PE,0E4DHCALL PE,DMUL10EC 4D 0EIf that test shows DOUBLE PRECISION, go to 0E4DH to multiply the current value in the ACCumulator by “10D0”
0F15POP AFF1Get the exponent and the value type from the STACK and put it in Register Pair AF
0F16
↳ DCRARTDEC A3DDecrement the exponent (held in Register A) since we have now multiplied by 1 since (x^10 = 10x^9).
0F17RETC9RETurn to CALLer
0F18 – Math Routine– “FINDIV”
This subroutine divides a number by 10 once. FIN and FOUT use this routine. Registers A, B, and C are affected.
0F18
↳ FINDIVPUSH DED5Preserve DE to the STACK for POPing at the end
0F19PUSH HLE5Preserve HL to the STACK for POPing at the end
0F1APUSH AFF5Since we need to divide we need to preserve the exponent, so save the value in Register A on the STACK
0F1BWe 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
0F1CPUSH AFF5Save the value of the FLAGS from the RST 20H call to the STACK
0F1D-0F1FCALL PO,0897HCALL PO,DIV10E4 97 08If that test shows SINGLE PRECISION, go to 0897H to divide the current value in the ACCumulator by “10.0”
0F20POP AFF1Get the value from the STACK and put it in Register Pair AF
0F21-0F23CALL PE,0DDCHCALL PE,DDIV10EC DC 0DIf that test shows DOUBLE PRECISION, go to 0DDCH to divide the current value in the ACCumulator by “10D0”
0F24POP AFF1Restore the flags from the STACK and put it in Register Pair F
0F25POP HLE1Get the value from the STACK and put it in Register Pair HL
0F26POP DED1Get the value from the STACK and put it in Register Pair DE
0F27INC A3CIncrement the exponent (stored in Register A) since 10x^9 = x^10
0F28RETC9RETurn 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
↳ FINDIGPUSH DED5Save the exponent (held in Register Pair DE) on the STACK
0F2ALD A,B78We need to check where the decimal point is, so load Register A with the value in Register B
0F2BADC A,C89Increement the decimal place count if we are past the decimal point by adding the value in Register C to the value in Register A
0F2CLD B,A47Save the revised decimal point location (tracked in Register B)
0F2DPUSH BCC5Save the decimal point information (tracked in Register Pair BC) on the STACK
0F2EPUSH HLE5Save the value of the current input buffer pointer of the string being parsed in Register Pair HL on the STACK
0F2FLD A,(HL)7EFetch the digit we want to pack at the location of the current input buffer pointer in Register Pair HL
0F30-0F31SUB 30HSUB “0”D6 30Subtract 30H from the ASCII value in Register A so that it will be binary
0F32PUSH AFF5Save the adjusted value in the digit (held in Register A) to the STACK
0F33We 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
0F34-0FJ6JP P,0F5DHJP P,FINDGVF2 5D 0FIf that test shows we have anything but an INTEGER, jump to FINDGV to handle the cases of a a single precision or double precision number
If we are here, then we re packing the next digit of an integer.
0F37-0FJ9LD HL,(4121H)LD HL,(FACLO)2A 21 41Now that we know we have an integer, put it into the ACCumulator at (HL)
0F3A-0FJCLD DE,0CCDH11 CD 0CLoad Register Pair DE with 3277 to see if we will overflow
0F3DNow 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)
0F3E-0F3FIf 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
0F41LD D,H
LD E,L54Let DE = HL
0F42ADD HL,HL29Multiply the integer value in Register Pair HL by two
0F43ADD HL,HL29Multiply the integer value in Register Pair HL by two. Register Pair HL now holds the original integer value times four
0F44ADD HL,DE19Add 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
0F45ADD HL,HL29Multiply 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.
0F46POP AFF1Get the binary value for the number we want to pack in from the STACK and put it in Register A
0F47LD C,A4FLoad Register C with the value of the character in Register A. Why C? The DAD routine needs it there and B is already zero.
0F48ADD HL,BC09Add the value of the character in Register Pair BC to the newly shifted integer value in Register Pair HL
0F49LD A,H7CWe next need to test for an overflow, so load Register A with the MSB of the integer value in Register H
0F4AOR AB7Set the flags based on the MSB
0F4B-0F4DJP M,0F57HJP M,FINDG1FA 57 0FIf the M FLAG is set, then we have overflowed, so JUMP to FINDG1
0F4E-0F50LD (4121H),HLLD (FACLO),HL22 21 41If we are here, then we did not overflow so save the new integer back into the ACCumulator
POP HLE1Restore the value of the current input buffer pointer of the string being parsed into Register Pair HL
0F52POP BCC1Restore the the decimal point information (tracked in Register Pair BC) from the STACK
0F53POP DED1Restore the exponent (held in Register Pair DE) from the STACK
0F57 – Math Routine– “FINDG1”
This routine handles 32768 and 32769
0F57
↳ FINDG1LD A,C79Load Register A with the binary value of the character in Register C
0F58PUSH AFF5Save the value in Register A on the STACK
0F59 – Math Routine– “FINDG2”
Convert integer digits into single precision digits
0F59-0F5B
↳ FINDG2CALL 0ACCHCALL CONSICD CC 0AGo convert the current value in the ACCumulator to single precision
0F5CSCF37Set the Carry flag to avoid the next instruction jumping away
0F5D – Math Routine– “FINDGV”
Determine if we have a single precision or a double prevision number
0F5D-0F5E
↳ FINDGVIf 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-0F61LD BC,9474H01 74 94Load Register Pair BC with the exponent and the MSB of a single precision constant
0F62-0F64LD DE,2400H11 00 24Load 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
0F65-0F67CALL 0A0CHCALL FCOMPCD 0C 0ACall 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:- A=0 if ACCumulator = BCDE
- A=1 if ACCumulator>BCDE; and
- A=FFH if ACCumulator<BCDE.
0F68-0F6AJP P,0F74HJP P,FINDG3F2 74 0FIf 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.
0F6B-0F6DCALL 093EHCALL MUL10CD 3E 09Go multiply the single precision value in the ACCumulator by 10
0F6EPOP AFF1Get the binary value of the number we want to pack in from the STACK and put it in Register A
0F6F-0F71CALL 0F89HCALL FINLOGCD 89 0FAdd the value in Register A to the single precision value in the ACCumulator
0F74 – Math Routine– “FINDG3” and “FINDGD”
The routine will convert a 7 digit single precision number into a double precision number
0F74-0F76
↳ FINDG3CALL 0AE3HCALL CONDSCD E3 0AGo convert the single precision value in the ACCumulator to double precision
This routine will pack in a digit into a double precision number
0F77-0F79
↳ FINDGDCALL 0E4DHCALL DMUL10CD 4D 0EGo multiply the double precision value in the ACCumulator by ten
0F7A-0F7CCALL 09FCHCALL VMOVAFCD FC 09Go move the double precision value in the ACCumulator to ARG (a/k/a REG 2)
0F7DPOP AFF1Get the binary value for the number to pack in from the STACK and put it in Register A
0F84-0F86CALL 0C77HCALL DADDCD 77 0CCall 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)
0F89H-0F93H – SINGLE PRECISION MATH ROUTINE– “FINLOG”
This is a subroutine for FIN and for LOG
0F89-0F8B
↳ FINLOGCALL 09A4HCALL PUSHFCD A4 09Call 09A4 which moves the SINGLE PRECISION value in the ACCumulator to the STACK (stored in LSB/MSB/Exponent order)
0F8C-0F8ECALL 0964HCALL FLOATCD 64 09Go convert the value in Register A to a single precision floating number and return with the result in the ACCumulator
0F8FPOP BCC1Clear off the stack
0F90POP DED1Clear off the stack
0F91-0F93JP 0716HJP FADDC3 16 07Jump 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
↳ FINEDGLD A,E7BLoad Register A with the value of the exponent in Register E
0F95-0F96CP 0AHFE 0ATest 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
0F97-0F98If 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
0F99RLCA07Multiply the value in Register A by two
0F9ARLCA07Multiply the value in Register A by two. Register A now holds the original value of the exponent times four
0F9BADD A,E83Add the original value of the exponent in Register E to the adjusted value of the exponent in Register A
0F9CRLCA07Multiply the value in Register A by two. Register A now holds the original value of the exponent times ten
0F9DADD A,(HL)86Add 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-0F9FSUB 30HSUB “0”D6 30Convert the adjusted value in Register A to it’s binary equivalent (which is subtracting 0011 0000)
0FA0LD E,A5FSave the adjusted exponent into Register E
0FA1-0FA3JP M,321EHFA 1E 32Z-80 TRICK. If passing through, this will never trigger, but neither will the next instruction!
0FA2-0FA3
↳ FINEDOLD E,32H1E 32If 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
0FA7H-0FAEH – DISPLAY MESSAGE ROUTINE– “INPRT”
This routine is to output a floating point number.
0FA7
↳ INPRTPUSH HLE5Save the line number (held in Register Pair HL) to the STACK
0FA8-0FAALD HL,1924HLD HL,INTXT21 24 19Load Register Pair HL with the starting address of the ” IN ” +00H message (which is 1924H)
0FAB-0FADCALL 28A7HCALL STROUTCD A7 28Call the WRITE MESSAGE routine at 28A7H..
NOTE:
- 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).
0FAEPOP HLE1Get 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.
0FAF-0FB1
↳ LINPRTCALL 0A9AHCALL MAKINTCD 9A 0AGo save the line number (held in the ACCumulator) as an integer into Register Pair HL
0FB2XOR AAFZero Register A to indicate that the output should be a free format
0FB3-0FB5CALL 1034HCALL FOUINICD 34 10Go initialize the input buffer for the ASCII conversion. This will set up the sign.
0FB6OR (HL)B6Turn off the Z FLAG.
0FB7-0FB9CALL 0FD9HCALL FOUT2CD D9 0FGo convert the integer value in the ACCumulator to an ASCII string. Return with Register Pair HL pointing to the result
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
↳ FOUTXOR AAFZero 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.
0FBE-0FC0
↳ PUFOUTCALL 1034HCALL FOUINICD 34 10Save 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-0FC2AND 08HE6 08Turn 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
0FC3-0FC4JR Z,0FC7HJR Z,FOUT128 02If a plus sign is NOT required to be added to the ASCII output string, then Jump to 0FC7H
0FC5-0FC6LD (HL),2BHLD (HL),”+”36 2BIf we are here, then it is required, so put a +into the buffer pointed to by Register Pair HL
EX DE,HLEBLoad Register Pair DE with the value of the buffer pointer (held in Register Pair HL)
0FC8-0FCACALL 0994HCALL VSIGNCD 94 09Go determine the value of the sign for the current value in the ACCumulator
0FCBEX DE,HLEBRestore the buffer pointer back to HL
0FCC-0FCEJP P,0FD9HJP P,FOUT2F2 D9 0FIf the P FLAG is set then we have a negative number, so we need to negate it by JUMPing to FOUT2
0FCF-0FD0LD (HL),2DHLD (HL),”-“36 2DSave a minus sign (-) at the location of the buffer pointer in Register Pair HL
0FD1PUSH BCC5Save the field length specifications held in B and C to the STACK
0FD2PUSH HLE5Save the buffer pointer to the STACK
0FD3-0FD5CALL 097BHCALL VNEGCD 7B 09GOSUB to 097BH to convert the negative value in the ACCumulator to its positive equivalent
0FD6POP HLE1Restore the buffer pointer from the STACK into HL
0FD7POP BCC1Restore the field length specifications from the STACK into B and C
0FD8OR HB4Turn off the Z FLAG. This relies on the fact that FBUFR is never on page 0
INC HL23Increment the buffer pointer in Register Pair HL to where the next character will be placed
0FDA-0FDBLD (HL),30H36 30Save 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-0FDELD A,(40D8H)LD A,(TEMP3)3A D8 40Load Register A with the format specification (held in a temporary storage location)
0FDFLD D,A57Preserve the format specification into Register D
0FE0RLA17Move the “free format” or “fixed format” bit into the Carry flag
0FE1-0FE3LD A,(40AFH)LD A,(VALTYP)3A AF 40Since VNEG may have changed VALTYP, re-fetch it (as -32768 is and integer but 32768 is single-precision).
0FE4-0FE6JP C,109AHJP C,FOUTFXDA 9A 10The comment in the original source says to JUMP to FOUTFX because “the man wants fixed formatted output here to print numbers in free format”
0FEA-0FEBCP 04HFE 04Check to see if the current value in the ACCumulator is single or double precision
0FEC-0FEEJP NC,103DHJP NC,FOUFRVD2 3D 10If the current value in the ACCumulator is single or double precision JUMP to FOUFRV
0FEF-0FF1LD BC,0000H01 00 00If we are here (and didn’t jump away) then we are dealing with an INTEGER. First, set the decimal point counter and comma counter to ZERO
0FF2-0FF4CALL 132FHCALL FOUTCICD 2F 13Call 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
↳ FOUTZSLD HL,4130HLD HL,FBUFFR+121 30 41Load Register Pair HL with the starting address of the buffer, which will hold the SIGN
0FF8LD B,(HL)46Load Register B with the sign (i.e., the character at the location of the buffer pointer in Register Pair HL)
0FF9-0FFALD C,20HLD C,” “0E 20Load Register C with a SPACE
0FFB-0FFDLD A,(40D8H)LD A,(TEMP3)3A D8 40Load Register A with format specifications
0FFELD E,A5FPut the format specifications into Register E
0FFF-1000AND 20HAND 0010 0000E6 20MASK the format specifications (by AND against 0010 0000) to see an asterisk fill is required
↳ CTSTAT
↳ CTCHG
Note: 403DH-4040H is used by DOS
Note: 403DH-4040H is used by DOS
Alternately displays and clears an asterisk in the upper right hand comer. Uses all registers.
↳ BCASIN
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.
CASIN
CTB0
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
↳ CTBIT
CB0
CB1
↳ TWOCSO
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.
↳ CASOUT
BYT1
↳ BYT2
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.
↳ CWONWL
Load Register B with the number of bytes to be written
CSAV1
↳ CSRDON
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
↳ CSRDON+3
Save the current BASIC program pointer in Register Pair HL on the STACK
CLOD1
Places the double asterisk in the right top corner to show that the sync byte has been found
Load Register A with a *character. (“*” is 2AH)
↳ ENBLK
Note: 40DFH-40E0H is also used by DOS
↳ SYSTEM
In NEWDOS 2.1, this is called during a SYSTEM operation
This location passes control to the routine used by the BASIC command SYSTEM
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.
LOPHD
GETDT
↳ GETDT2
↳ LDATIN
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
↳ CADRIN
↳ GODO
Note: 40DFH-40E0H is also used by DOS
The RST 10H routine parses the characters starting at HL+1 for the first non-SPACE,non-09H,non-0BH character it finds. On exit, Register A will hold that character, and the C FLAG is set if its alphabetic, and NC FLAG if its alphanumeric. All strings must have a 00H at the end.
This 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.
↳ OUTDO
In NEWDOS 2.1, this writes to the system output device
Note: 409CH holds the current output device flag: -1=cassette, 0=video and 1=printer
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
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
↳ OUT2D
Save the value in Register Pair DE on the STACK
Note: 40A6H holds the current cursor line position
↳ DSPPOS
Note: 403DH-4040H is used by DOS
Note: 4020H-4021H holds Video DCB – Cursor location
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
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.
↳ INLIN
Note: 4099H holds the Last key pressed
Note: 40A6H holds the current cursor line position
In NEWDOS 2.1, this is the satrt of keyboard input
Note: 40A7H-40A8H holds the input Buffer pointer
Note: 40A7H-40A8H holds the input Buffer pointer
Waits for keypress
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.
↳ FINLPT
Note: 409CH holds the current output device flag: -1=cassette, 0=video and 1=printer
Note: 409BH holds the printer carriage position
This is the LPRINT routine. All registers are saved. The byte to be printed should be in the A register.
↳ OUTLPT
↳ OUTDO
↳ LZRNOT
Note: 409BH holds the printer carriage position
↳ LZRPOS
Note: 409BH holds the printer carriage position
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.
↳ CIO
↳ CIORTN
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”
↳ KEY
Note: 4036H-403CH is the keyboard work area
↳ KEYDWN
↳ KEYDLP
We now have identified the key. Next we need to see if it is shifted
↳ KEYFND
If we are here, the character was not alphanumeric, so need to check for special and/or shift
↳ KEYNAL
This routine does a special key conversion via a table
↳ KEYSPL
↳ KEYNSF
This routine will debounce the keyboard downstroke and return
↳ KEYRTN
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”
↳ DSP
↳ DSPGRP
↳ DSPRTN
↳ DSPRD
↳ DSPBOL
↳ DSPGRC
↳ DSPCON
↳ DSPCOF
↳ DSPHOM
Note: 403DH-4040H is used by DOS
↳ DSPBSP
Note: 403DH-4040H is used by DOS
↳ DSPLFT
Note: 403DH-4040H is used by DOS
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
↳ DSPDWN
↳ DSPRHT
Same trick as dealing with CURSOR DOWN if there was an overflow, this does a CURSOR UP if you back up too far
↳ DSPUP
↳ DSPETB
Note: 403DH-4040H is used by DOS
Note: 403DH-4040H is used by DOS
↳ DSPCTL
Output the character held in Register A and move the cursor, scrolling the screen if necessary
↳ DSPOUT
Note: 403DH-4040H is used by DOS
↳ DSPOT2
Scroll the screen upward by one line
↳ DSPROL
Display a carriage return / line feed
↳ DSPCR
Erase to the end of the line
DSPEOL
Erase to the end of the frame
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
↳ DSPEOF
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
↳ DSPERF
↳ DSPERA
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”
↳ PRT
↳ PRTVT
↳ PRTFF
↳ PRTIT
↳ PRTIT2
↳ PRTOP
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.
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
↳ KEYIN
↳ KLNNXT
↳ KLNCHR
Clear the screen
↳ KLNCLR
Cancel the accumulated line
↳ KLNCNL
↳ KLNCAN
Backspace one character. On entry Register B to hold the number of characters received, and Register C to hold the size of the buffer
↳ KLNBSP
Turn on 32 Character Mode
↳ KLNETB
Process a horizontal tab
↳ KLNHT
↳ KLNHTL
Process a Carriage Return and Automatic Line Feed
↳ KLNBRK
↳ KLNCR
↳ INIT
↳ CLRAM
Check for a manual override
↳ CHKMAN
This is the beginning of the routine which boots a diskette
↳ BOOT
↳ BOOTDL
This routine loads Drive 0, Track 0, Sector 0 into 4200H-4455H, and then jumps to 4200H
↳ BOOTLP
This is an alternative re-entry point into BASIC. A JP 6CCH is often better than a jump to 1A19H as the latter sometimes does strange things to any resident BASIC program
↳ RESETR
↳ INITR
This is the keyboard DCB
This is the display DCB
This is the printer DCB
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).
According to Vernon Hester, there are a whole lot of errors in ROM code for processing math. I have no hope of finding the code so I will put them here:
When a number is just under specific decimal magnitudes, the ROM prints a colon instead of 10
Example: 9999999999999999 / 1D12 + 3D-13 returns :000
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.
↳ FADDH21 80 13
↳ FADDS
“FSUBS”
Single-precision subtraction (ACC=(HL)-ACC). This loads the BCDE registers with the value from (HL), then passes control to 713H.
↳ FSUBS
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.
↳ FSUB
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
↳ FADD
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.
At this point, the smaller number is in ABCD, so we proceed with the math.
↳ FADD1
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.
This routine will subtract CDEB from ((HL)+0,1,2),0.
↳ FADD3
With that out the way, we need to make sure we have a positive mantissa (or else we will need to negate the number).
↳ FADFLT
This next routine normalizes CDEB. In doing so, ABCDE and HL are all modified. This routine shifts the mantissa left until the MSB is a 1.
↳ NORMAL
This routine will ZERO out the ACCumulator, changing only Register A in the process. A will exit as 0.
↳ ZERO
↳ ZERO0
↳ NORM2
If we are here, then we have a fully normalized result, so let us continue.
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.
Vernong Hester has flagged an error in the rounding of math routines. In base 10, rounding to k-digits examines digit k+1. If digit k+1 is 5 through 9, then digit k is adjusted up by one and carries to the most significant digit, if necessary. If digit k+1 is less than 5, then digit k is not adjusted. This should not get muddled with the conversion of base 2 to base 10. Nevertheless, four divided by nine should be: .444444 and not .444445
↳ ROUND
↳ ROUNDB
This is a subroutine within the ROUND round. This will add one to C/D/E.
↳ ROUNDA
07B2H – ?OV ERROR entry point– “OVERR”
↳ OVERR
This routine adds (HL+2),)(HL+1),(HL+0) to C,D,E. This is called by FADD and FOUT.
↳ FADDA
This routine negates the number in C/D/E/B. CALLd by FADD and QUINT. Alters everything except Register H.
↳ NEGR
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.
↳ SHIFTR
↳ SHFTR1
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 …
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.
↳ SHFTR2
↳ SHFTR3
– “FONE”
↳ FONE
↳ LOGCN2
The LOG(n) routine, (ACCumulator=LOG (ACCumulator)). This routine finds the natural log (base E) of the single precision value in the ACCumulator area.
The result is returned as a single precision value in the ACCumulator
To use a ROM call to find LOG(n), where X is a positive single precision variable, store the value of n in 4121H-4124H and then CALL 0809H. The result (in single precision format) is in 4121H-4124Hin approximately 19 milliseconds. NOTE: A fatal error occurs if the value of the input variable is zero or negative.
Vernon Hester has identified a bug in the LOG() routine. Regardless of the base, if the argument is 1 then the logarithm is zero, if the argument is >1 then the logarithm is positive, and if the argument is >0 and < 1 then the logarithm is negative. However, if the argument is just under 1, the ROM’s LOG function produces a positive value. e.g., 10 PRINT LOG(.99999994)
↳ LOG
The next two instructions are commented in the original ROM source code as: Get SQR(.5)
The next two instructions save SQR(.5) to the STACK
The next two instructions restore SQR(.5) from the STACK
The next two instructions get SQR(2)
The next two instructions are commented in the original ROM source code as: Get -1/2
The instructions are commented in the original ROM source code as: Get LN(2)
↳ MULLN2
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.
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
This routine alters every Register.
↳ FMULT
↳ FMULT2
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.
↳ FMULT4
↳ FMULT5
↳ POPHRT
This is accomplished by a circular shift of BC/DE one byte – B is lost, C is replaced by A
This is a multiply by zero, where we just shift everything 8 bits to the right.
↳ FMULT3
– “DIV10”
This routine divides the ACCumulator by 10. Every Register is used.
↳ DIV10
↳ FDIVT
With the numbers in their places, we now just fall into the floating division routine.
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.
↳ FDIV
08AE
INC (HL)34
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.
↳ FDIV1
↳ FDIV2
08DD
DEC A3C
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.
↳ MULDVS
↳ MULDVA
↳ MULDIV
↳ DCXHRT
This routine is called from EXP. If jumped here will checks if ACC=0. If so, the Z flag will be set
↳ MLDVEX
↳ MULDV1
This routine multiplies the ACCumulator by 10. Every register is modified.
↳ MUL10
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.
↳ SIGN
↳ FCOMPS
↳ ICOMPS
“SIGNS”
This routine will take a signed integer held in Register A and turn it into a floating point number. All registers are modified.
↳ FLOAT
This routine will float the singed number in B/A/D/E. All registers are modified.
ABSroutine (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
↳ ABS
This routine will negate any value in the ACCumulator. Every Register is affected.
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 |
This routine will negate the single or double precision number in the ACCumulator. Registers A, H, and L are affected.
↳ NEG
SGNfunction (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.
↳ CONIA
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.
↳ VSIGN
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 |
This routine finds the sign of the value held at (HL). Only Register A is altered.
↳ ISIGN
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.
↳ PUSHF
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.
↳ MOVFM
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!
↳ MOVFR
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.
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
↳ MOVRM
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
↳ GETBCD
↳ INXHRT
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
↳ MOVMF
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
↳ MOVE
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))
↳ MOVVFM
This routine is similar to 9D2H above. The only difference is that it moves data in the opposite direction. ((HL) = (DE))
↳ VMOVE
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))
↳ MOVE1
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)
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.
↳ UNPACK
INC HL23
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.
↳ VMOVFA
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.
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 |
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.
↳ FCOMP
↳ FCOMPD
– “ICOMP”
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
↳ ICOMP
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.
↳ DCOMPD
Note: 4127H-412EH holds ARG (a/k/a REG 2)
↳ DCOMP1
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.
↳ DCOMP
CINTroutine. 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 CINTroutine, 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.
↳ FRCINT
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 |
This routine will convert a single precision number to an integer. Every register is affected.
↳ CONIS
↳ CONIS1
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
↳ VALINT
↳ CONISD
Note: 40AFH holds Current number type flag. This is the entry point from the CONDS routine
↳ CONIS2
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.
Force the number in the ACCumulator to be a single-precision number. Every register is affected.
CSNGroutine. Takes value from ACC and converts it to single-precision. The result is put in ACC and NTF contains 4.
CSNG routine. Takes value from ACC and converts it to single-precision. The result is put in ACC and NTF contains 4
Integer To Single: The contents of ACCumulator are converted from integer or double precision to single precision. All registers are used
↳ FRCSNG
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 |
Convert a double-prevision number to single-precision. Every register is affected.
↳ CONSD
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.
↳ CONSI
↳ CONSIH
CDBLroutine. 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.
↳ FRCDBL
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 |
Convert a single precision number to double precisions. Modifies Registers A, H, and L.
↳ CONDS
Note: 411DH-4124H holds ACCumulator
↳ VALDBL
↳ VALSNG
3E 04
This routine will force the ACCUmlator to be a STRING. Only Register A is modified.
↳ CHKSTR
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 |
↳ TMERR
This is the entry point for the TM ERROR
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).
↳ QINT
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.
↳ QINTA
↳ DCXBRT
– “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 FIXroutine 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
↳ FIX
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 |
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 INTroutine via a ROM call, you would store the single precision input variable in 4121H-4124H, put a 4 into 40AFH (to flag as single precision), and then call 0B3DH and bypass all the foregoing. After the call, the integer result would be in 4121H-4122H and in the HL Register Pair IF the absolute value of the input did not exceed 32767. Otherwise it will be in 4121H-4124H in single precision format, and 40AF will be a 2 for integer or 4 for single precision
According to Vernon Hester, there is are a number of bugs in this routine.
First, INT(value) should produce a result equal to or less than value. However, if the value is double-precision (by definition), the ROM rounds value to single-precision first, then performs the INT function. e.g., PRINT INT(2.9999999) produces 3 instead of 2.
Next, INT(value) should never overflow. However, if the value is double-precision 32767.9999#, the ROM overflows.
Next, INT(value) should produce a result equal to or less than value. However, if the value is double-precision equal to ?2″n+2″(n-7) where n is an integer >14, the ROM produces an incorrect value. e.g., PRINT INT(?44800#) produces ?45056
↳ VINT
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 |
↳ INT
Greated Integer function for double-precision numbers. All registers are affected.
↳ DINT
↳ DINT1
↳ DINT2
↳ DINTFO
↳ “DINTFO”
↳ DINTA
↳ DINTA1
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.
↳ UMULT
0BAF
OR C78 B1
↳ UMULT1
↳ UMULT2
↳ MULRET
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
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
↳ ISUB
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).
↳ IADD
↳ IADDS
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.
↳ IMULT
0BFE
LD C,L44
↳ IMULT1
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.
↳ IMULT2
This is the entry from IDIV. The next instructions test to see if the result is => 32768 or is -32768.
↳ IMLDIV
↳ IMULT3
↳ IMULT5
↳ FMULTT
↳ IMULT4
This is the integer division routine HL = DE / HL. The remainder will be left in DE and the quotient will be left in HL. Every register is affected.
↳ IMULDV
↳ INEGH
↳ INEGA
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.
↳ INEGHL
Integer Negation Routine. All registers are altered.
↳ INEG
↳ INEGAD
The next bunch of routines are the double precision arithmetic routines. According to the original ROM source code, the conventions are.
- Double prevision numbers are 8 bytes long: The first 4 bytes are 32 low order bits of precision and the last 4 bytes are are in the same format as single precision numbers. The lowest order byte comes first in RAM.
- For one argument gunctions: The argument is in the ACCumulator, and the results is put there too.
- For two argument operations, the first argument is in the ACCumulator and the second argument is in ARG-7,-6,-5,-4,-3,-2,-1,-0. ARGLO=ARG-7. The result is left in the ACCumulator.
- Note that the order of the numbers is reversed from integers and single precisions values
Double-precision subtraction (ACCumulator = ACCumulator – ARG).
Subtracts the double precision value in ARG (a/k/a REG 2) from the value in the ACCumulator. The difference is left in the ACCumulator.
Note: If you wanted to subtract two double precision numbers, store the minuend in 411DH-4124H and the subtrahend in 4127H-412EH, and CALL 0C70H. The result (in double precision format) is in 411DH-4124H in approximately 1.3 milliseconds.
Vernon Hester has flagged a bug. Double-precision subtraction should produce an difference accurate to 16 digits. However, the difference resulting from doubleprecision subtraction is erroneous when the smaller operand’s value is significantly less than the larger operand’s value
Example: In the code Y# = .20# : X# = 1D16 : J# = X# – Y# : PRINT J# – X#J# is incorrect and J#-X# shows a positive result when it is negative.
↳ DSUB
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.
↳ DADD
Next we are going to switch ARG and the ACCumulator.
↳ DADD1
↳ DADD2
↳ DNORML
↳ DNORM2
↳ DNORM3
↳ DNORM5
This routine will round the ACCumulator. Registers A, B, H, and L are affected.
↳ DROUND
↳ DROUNB
0D1C
DEC HL2B
↳ DROUNA
Note: 411DH-4124H holds ACCumulator
↳ DRON1
↳ DADDAA
Note: 4127H-412EH holds ARG (a/k/a REG 2)
↳ DADDFO
Note: 411DH-4124H holds ACCumulator
↳ DADDS
↳ DADDLS
This routine subtracts numbers in the pure version. This needs to be done in two subroutines since the ROM cannot be modified.
↳ DADDAS
Note: 4127H-412EH holds ARG (a/k/a REG 2)
↳ DADDFS
Note: 411DH-4124H holds ACCumulator
↳ DADDSS
↳ DADDLS
This routine will negate the signed number held in the ACCumulator. Registers A, B, C, H, and L are affected. This routine is called by DADD and DINT.
↳ DNEGR
↳ DNEGR1
This routine wwill shift the double precision value held in the ACCumulator to the right once.
↳ DSHFTR
↳ DSHFR1
↳ DSHFRM
↳ DSHFR2
↳ DSHFR3
↳ DSHFR4
↳ DSHFRA
↳ DSHFR5
This is the entry from DADD and DMULT.
↳ DSHFRB
This routine will rotate the ACCumulator left one. Register A, C, H, and L are affected.
↳ DSHFLC
↳ DSHFTL
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.
↳ DMULT
↳ DMULT2
↳ DMULT3
↳ DMULT4
This routine handles multiplying by zero.
↳ DMULT5
↳ DTEN
↳ FTEN
Double precision divide routine. Divides the ACCumulator by 10. All registers are affected.
↳ DDIV10
Note: 4127H-412EH holds ARG (a/k/a REG 2)
Double-precision division (ACCumulator=ACC / ARG).
Divides the double precision value in the ACCumulator by the value in ARG (a/k/a REG 2). The quotient is left in the ACCumulator. All registers are affected
To use a ROM call to divide two double precision numbers, store the dividend in 411DH-4124H, and the divisor in 4127H-412EH and then CALL 0DE5H. The result (in double precision format) is in 411DH-4124H and then pproximately 42 milliseconds. Overflow or /0 will error out and return to Level II.
According to Vernon Hester, there is a bug this routine. Double-precision division should return a zero quotient when the dividend is zero. However, when the dividend is zero and the divisor is less than .25#, the ROM’s double-precision division produces an non-zero quotient. e.g., PRINT 0 / .24# produces a quotient of 1.171859195766034D-38. If the divisor is 2.938735877055719D-39 then the quotient is .5
Another bug is that double-precision division should perform correctly for absolute values that are from 2.938735877055719D-39 to 1.701411834604692D+38. If the divisor is the minimum magnitude or the minimum magnitude times 2, then double-precision division errors.
10 Z# = 1 / (2^125 + 2^125) * .25 ‘This values Z# with 2.938735877055719D-39
20 PRINT 1 / Z# ‘displays 2.938735877055719D-39 instead of overflow
↳ DDIV
0DF0
INC (HL)34
↳ DDIV1
↳ DDIV2
0E18
DEC A3C
Note: 411DH-4124H holds ACCumulator
This routine will transfer the double prevision number held in the ACCumulator to FBUFFR for the DMULT and DDIV routines. All registers are affected.
↳ DMULDV
↳ DMLDV1
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
↳ DMUL10
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.
F6 AFF6 AF
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).
↳ FIN
↳ FINCHR
↳ FINC
↳ FINEX1
↳ FINEX
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.
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.
↳ FINE
This “FINE2” routine will multiply or divide by 10 the correct number of times. If A=0 the number is an integer.
↳ FINE2
Next we need to put the correct sign on the number.
Next we want -32768 to be an integer (it would be single precision at this point)
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. |
This routine checks to see if we have seen TWO decimal points and to set the decimal point flag. We jumped here when we found a single decimal point.
↳ FINDP
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 |
↳ FININT
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 |
↳ INFINE
↳ FINDBF
↳ FINSNF
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.
↳ FINFRC
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.
↳ FINMUL
↳ FINMLT
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 |
↳ DCRART
This subroutine divides a number by 10 once. FIN and FOUT use this routine. Registers A, B, and C are affected.
↳ FINDIV
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 |
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.
↳ FINDIG
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 |
If we are here, then we re packing the next digit of an integer.
0F41
LD E,L54
At this point, the number has shifted over to make room for the new digit in the ones place.
This routine handles 32768 and 32769
↳ FINDG1
Convert integer digits into single precision digits
↳ FINDG2
Determine if we have a single precision or a double prevision number
↳ FINDGV
These next 2 instruction set up BCDE to hold “1000000”
- A=0 if ACCumulator = BCDE
- A=1 if ACCumulator>BCDE; and
- A=FFH if ACCumulator<BCDE.
The routine will convert a 7 digit single precision number into a double precision number
↳ FINDG3
This routine will pack in a digit into a double precision number
↳ FINDGD
This is a subroutine for FIN and for LOG
↳ FINLOG
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.
↳ FINEDG
↳ FINEDO
This routine is to output a floating point number.
↳ INPRT
NOTE:
- 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).
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.
↳ LINPRT
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.
- Bit 7:
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.
↳ FOUT
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.
↳ PUFOUT
This routine will zero suppress the digits in FBUFFR and asterisk fill and zero suppress if necessary.
↳ FOUTZS