; ===========================================================
; An Assembly Listing of the Operating System of the ZX81 ROM
; ===========================================================

; Last updated: 13-DEC-2004 ; 2011 Updated to remove -, +, /, *, &, ; characters from labels (which confuse assemblers) ; ; 2011 Updated for conditional assembly of ORIGINAL or "Shoulders of Giants" ROM ; ; 2014-08-01 Updated to add CHARS_PER_LINE_WINDOW which is normally 32. ; ; The ideal pixel rates for square pixels on a PAL system are ; 14.75 MHz (interlaced) and ; 7.375 MHz (non-interlaced, which the ZX80/ZX81 are). ; These are not commonly available but fortunately one can buy ; baud-rate generator frequencies such as ; 14.7456 and 7.3728 MHz that are only 0.03% low ; which is more than close enough. ; ; ZX video normally has 6.5 MHz pixel rate, ; so 32 characters take 256 pixels in 39.4 microseconds. ; A 7.3728 MHz clock and 40 characters produces ; 320 pixels in 43.4 microseconds. ; ; ZX80 video generation is software defined so it is ; easy to get square pixels simply by subtracting 8 from the bytes ; at hex addresses 287, 2AA and 2B8. ; The video will appear to the left of the screen but ; the characters will be square and a diagonal graphic line ; will be at 45 degrees. ; ; ZX81 video generation in fast mode exactly the same as the ZX80. ; ; ZX81 video generation in slow mode is problematic, in that ; the NMI generator expects a 3.25 MHz CPU clock ; (3.25MHz / 208 = 15.625 kHz = 64 microsecond line period) ; It is inside the ULA where it cannot be modified. ; ; Simply fitting a 7.3728 MHz crystal would reduce the line period to ; 57.3 microseconds. Slow mode would require the CPU clock to be ; divided by 236. ; ; Square pixels on NTSC requires 11+3/11 = 11.272... MHz (interlaced) ; or 5.63.. non-interlaced which is slower than the original 6.5 MHz. ; The NTSC line period is still 64 microseconds, so 256 pixels ; stretch over 45 microseconds, and 320 pixels over 56 microseconds. ; Thus it is possible to get square pixels on an NTSC display, ; it is not possible to get 40 column text as well. ; That would require the PAL clock, but pixels would not be square. ; ; The ZX printer is fixed in hardware. ; It will not work in 40-column mode. ; ; ; ; PIXEL_CLOCK equ 7372500 ; ; on-line assembler complains about the line above ; ; CHARS_PER_LINE_WINDOW always 32 for 6.5 MHz pixel rate ; always 40 for 7.375 MHz PAL square pixel rate ; CHARS_PER_LINE_WINDOW equ 40 ; 32 originally ; ; CHARS_PER_LINE always 32 for 6.5 MHz pixel rate ; but 32 or 40 if using PAL square pixel rate ; CHARS_HORIZONTAL equ 40 ; 32 originally CHARS_VERTICAL equ 24 ; ; 2014-08-01 ; Largely working but some bugs remain. ; Working: ; You can type text and it takes 40 characters before new line. ; 40 characters are nicely centred in the screen. ; PLOT X,Y accepts X from 0 to 79. ; Faulty: ; System crashing in an authentic ZX81 fashion, ; I don't know if this is due to software bugs ; or socket joint disturbance from key presses. ; ; ; 2018-01-09 add org ; Assembles using on-line assembler "zasm" at: ; ; http://k1.spdns.de/cgi-bin/zasm.cgi ; org 0 FALSE equ 0 ORIGINAL equ 0 NOT_BODGED equ 1 ; 2018-02-09 CHARS_HORIZONTAL placed in SCROLL routine. ; Thanks to Adam Klotblixt for testing code and spotting this bug. ; Also added to some G007 routines. ;
; ; Work in progress. ; This file will cross-assemble an original version of the "Improved" ; ZX81 ROM. ; The file can be modified to change the behaviour of the ROM ; when used in emulators although there is no spare space available. ; ; The documentation is incomplete and if you can find a copy ; of "The Complete Spectrum ROM Disassembly" then many routines ; such as POINTERS and most of the mathematical routines are ; similar and often identical. ; ; I've used the labels from the above book in this file and also ; some from the more elusive Complete ZX81 ROM Disassembly ; by the same publishers, Melbourne House. #if 0 ; zasm does not understand these: #define DEFB .BYTE ; TASM cross-assembler definitions #define DEFW .WORD #define EQU .EQU #endif ; define stuff sensibly: ; ; I/O locations: ; IO_PORT_TAPE equ $FF ; write IO_PORT_SCREEN equ $FF ; write IO_PORT_KEYBOARD_RD equ $FE ; A0 low IO_PORT_NMI_GEN_ON equ $FE ; A0 low IO_PORT_NMI_GEN_OFF equ $FD ; A1 low IO_PORT_PRINTER equ $FB ; A2 low
; ; System variables: ; RAMBASE equ $4000 ERR_NR equ $4000 ; The report code. Incremented before printing. FLAGS equ $4001 ; Bit 0: Suppression of leading space. ; Bit 1: Control Flag for the printer. ; Bit 2: Selects K or F mode; or, F or G ; Bit 6: FP no. or string parameters. ; Bit 7: Reset during syntax checking." ERR_SP equ $4002 ; Pointer to the GOSUB stack. RAMTOP equ $4004 ; The top of available RAM, or as specified. MODE equ $4006 ; Holds the code for K or F PPC equ $4007 ; Line number of the current statement. PPC_hi equ PPC+1 VERSN equ $4009 ; Marks the start of the RAM that is saved. E_PPC equ $400A ; The BASIC line with the cursor D_FILE equ $400C ; Pointer to Display file DF_CC equ $400E ; Address for PRINT AT position VARS equ $4010 ; Pointer to variable area DEST equ $4012 ; Address of current variable in program area E_LINE equ $4014 ; Pointer to workspace E_LINE_hi equ E_LINE+1 CH_ADD equ $4016 ; Pointer for scanning a line, in program or workspace X_PTR equ $4018 ; Pointer to syntax error. X_PTR_lo equ X_PTR X_PTR_hi equ X_PTR+1 STKBOT equ $401A ; Pointer to calculator stack bottom. STKEND equ $401C ; Pointer to calculator stack end. BERG equ $401E ; Used for many different counting purposes MEM equ $401F ; Pointer to base of table of fp. nos, either in calc. stack or variable area. ; ; Unused by ZX BASIC. Or FLAG Y for G007 DF_SZ equ $4022 ; Number of lines in the lower screen S_TOP equ $4023 ; Current line number of automatic listing LAST_K equ $4025 ; Last Key pressed DEBOUNCE_VAR equ $4027 ; The de-bounce status MARGIN equ $4028 ; Adjusts for differing TV standards NXTLIN equ $4029 ; Next BASIC line to be interpreted OLDPPC equ $402B ; Last line number, in case needed. FLAGX equ $402D ; Bit 0: Reset indicates an arrayed variable ; Bit 1: Reset indicates a given variable exists ; Bit 5: Set during INPUT mode ; Bit 7: Set when INPUT is to be numeric STRLEN equ $402E ; Length of a string, or a BASIC line STRLEN_lo equ STRLEN ; T_ADDR equ $4030 ; Pointer to parameter table. & distinguishes between PLOT & UNPLOT SEED equ $4032 ; For RANDOM function FRAMES equ $4034 ; Frame counter FRAMES_hi equ FRAMES+1 ; COORDS equ $4036 ; X & Y for PLOT COORDS_x equ COORDS ; PR_CC equ $4038 ; Print buffer counter S_POSN equ $4039 ; Line & Column for PRINT AT S_POSN_x equ $4039 ; S_POSN_y equ $403A ; CDFLAG equ $403B ; Bit 6 = the true fast/slow flag ; Bit 7 = copy of the fast/slow flag. RESET when FAST needed PRBUFF equ $403C ; Printer buffer PRBUFF_END equ $405C ; MEM_0_1st equ $405D ; room for 5 floating point numbers (meme_0 to mem_ 5???) ; $407B ; unused. Or RESTART to G007 ; $407D ; The BASIC program starts here ; equ $40 ; equ $40 ; equ $40 ; First byte after system variables: USER_RAM equ $407D MAX_RAM equ $7FFF
;=============================== ; ZX81 constants: ;=============================== ; ZX characters (not the same as ASCII) ;------------------------------- ZX_SPACE equ $00 ; ZX_graphic equ $01 ; ZX_graphic equ $02 ; ZX_graphic equ $03 ; ZX_graphic equ $04 ; ZX_graphic equ $05 ; ZX_graphic equ $06 ; ZX_graphic equ $07 ; ZX_graphic equ $08 ; ZX_graphic equ $09 ; ZX_graphic equ $0A ZX_QUOTE equ $0B ZX_POUND equ $0C ZX_DOLLAR equ $0D ZX_COLON equ $0E ZX_QUERY equ $0F ZX_BRACKET_LEFT equ $10 ZX_BRACKET_RIGHT equ $11 ZX_GREATER_THAN equ $12 ZX_LESS_THAN equ $13 ZX_EQUAL equ $14 ZX_PLUS equ $15 ZX_MINUS equ $16 ZX_STAR equ $17 ZX_SLASH equ $18 ZX_SEMICOLON equ $19 ZX_COMMA equ $1A ZX_PERIOD equ $1B ZX_0 equ $1C ZX_1 equ $1D ZX_2 equ $1E ZX_3 equ $1F ZX_4 equ $20 ZX_5 equ $21 ZX_6 equ $22 ZX_7 equ $23 ZX_8 equ $24 ZX_9 equ $25 ZX_A equ $26 ZX_B equ $27 ZX_C equ $28 ZX_D equ $29 ZX_E equ $2A ZX_F equ $2B ZX_G equ $2C ZX_H equ $2D ZX_I equ $2E ZX_J equ $2F ZX_K equ $30 ZX_L equ $31 ZX_M equ $32 ZX_N equ $33 ZX_O equ $34 ZX_P equ $35 ZX_Q equ $36 ZX_R equ $37 ZX_S equ $38 ZX_T equ $39 ZX_U equ $3A ZX_V equ $3B ZX_W equ $3C ZX_X equ $3D ZX_Y equ $3E ZX_Z equ $3F ZX_RND equ $40 ZX_INKEY_STR equ $41 ZX_PI equ $42 ; ; $43 to $6F not used ; ZX_cursor_up equ $70 ZX_cursor_down equ $71 ZX_cursor_left equ $72 ZX_cursor_right equ $73 ZX_GRAPHICS equ $74 ZX_EDIT equ $75 ZX_NEWLINE equ $76 ZX_RUBOUT equ $77 ZX_KL equ $78 ZX_FUNCTION equ $79 ; ; $7A to $7F not used ; ZX_CURSOR equ $7F ; ; $80 to $BF are inverses of $00 to $3F ; ; ZX_graphic equ $80 ; inverse space ; ZX_graphic equ $81 ; ZX_graphic equ $82 ; ZX_graphic equ $83 ; ZX_graphic equ $84 ; ZX_graphic equ $85 ; ZX_graphic equ $86 ; ZX_graphic equ $87 ; ZX_graphic equ $88 ; ZX_graphic equ $89 ; ZX_graphic equ $8A ZX_INV_QUOTE equ $8B ZX_INV_POUND equ $8C ZX_INV_DOLLAR equ $8D ZX_INV_COLON equ $8E ZX_INV_QUERY equ $8F ZX_INV_BRACKET_RIGHT equ $90 ZX_INV_BRACKET_LEFT equ $91 ZX_INV_GT equ $92 ZX_INV_PLUS equ $95 ZX_INV_MINUS equ $96 ZX_INV_K equ $B0 ZX_INV_S equ $B8 ZX_DOUBLE_QUOTE equ $C0 ZX_AT equ $C1 ZX_TAB equ $C2 ; not used equ $C3 ZX_CODE equ $C4 ZX_VAL equ $C5 ZX_LEN equ $C6 ZX_SIN equ $C7 ZX_COS equ $C8 ZX_TAN equ $C9 ZX_ASN equ $CA ZX_ACS equ $CB ZX_ATN equ $CC ZX_LN equ $CD ZX_EXP equ $CE ZX_INT equ $CF ZX_SQR equ $D0 ZX_SGN equ $D1 ZX_ABS equ $D2 ZX_PEEK equ $D3 ZX_USR equ $D4 ZX_STR_STR equ $D5 ; STR$ ZX_CHR_STR equ $D6 ; CHR$ ZX_NOT equ $D7 ZX_POWER equ $D8 ZX_OR equ $D9 ZX_AND equ $DA ZX_LESS_OR_EQUAL equ $DB ZX_GREATER_OR_EQUAL equ $DC ZX_NOT_EQUAL equ $DD ZX_THEN equ $DE ZX_TO equ $DF ZX_STEP equ $E0 ZX_LPRINT equ $E1 ZX_LLIST equ $E2 ZX_STOP equ $E3 ZX_SLOW equ $E4 ZX_FAST equ $E5 ZX_NEW equ $E6 ZX_SCROLL equ $E7 ZX_CONT equ $E8 ZX_DIM equ $E9 ZX_REM equ $EA ZX_FOR equ $EB ZX_GOTO equ $EC ZX_GOSUB equ $ED ZX_INPUT equ $EE ZX_LOAD equ $EF ZX_LIST equ $F0 ZX_LET equ $F1 ZX_PAUSE equ $F2 ZX_NEXT equ $F3 ZX_POKE equ $F4 ZX_PRINT equ $F5 ZX_PLOT equ $F6 ZX_RUN equ $F7 ZX_SAVE equ $F8 ZX_RAND equ $F9 ZX_IF equ $FA ZX_CLS equ $FB ZX_UNPLOT equ $FC ZX_CLEAR equ $FD ZX_RETURN equ $FE ZX_COPY equ $FF
; _CLASS_00 equ 0 _CLASS_01 equ 1 _CLASS_02 equ 2 _CLASS_03 equ 3 _CLASS_04 equ 4 _CLASS_05 equ 5 _CLASS_06 equ 6
; These values taken from BASIC manual ; ; ERROR_CODE_SUCCESS equ 0 ERROR_CODE_CONTROL_VARIABLE equ 1 ERROR_CODE_UNDEFINED_VARIABLE equ 2 ERROR_CODE_SUBSCRIPT_OUT_OF_RANGE equ 3 ERROR_CODE_NOT_ENOUGH_MEMORY equ 4 ERROR_CODE_NO_ROOM_ON_SCREEN equ 5 ERROR_CODE_ARITHMETIC_OVERFLOW equ 6 ERROR_CODE_RETURN_WITHOUT_GOSUB equ 7 ERROR_CODE_INPUT_AS_A_COMMAND equ 8 ERROR_CODE_STOP equ 9 ERROR_CODE_INVALID_ARGUMENT equ 10 ERROR_CODE_INTEGER_OUT_OF_RANGE equ 11 ERROR_CODE_VAL_STRING_INVALID equ 12 ERROR_CODE_BREAK equ 13 ERROR_CODE_EMPTY_PROGRAM_NAME equ 15
; ; codes for Forth-like calculator ; __jump_true equ $00 __exchange equ $01 __delete equ $02 __subtract equ $03 __multiply equ $04 __division equ $05 __to_power equ $06 __or equ $07 __boolean_num_and_num equ $08 __num_l_eql equ $09 __num_gr_eql equ $0A __nums_neql equ $0B __num_grtr equ $0C __num_less equ $0D __nums_eql equ $0E __addition equ $0F __strs_and_num equ $10 __str_l_eql equ $11 __str_gr_eql equ $12 __strs_neql equ $13 __str_grtr equ $14 __str_less equ $15 __strs_eql equ $16 __strs_add equ $17 __negate equ $18 __code equ $19 __val equ $1A __len equ $1B __sin equ $1C __cos equ $1D __tan equ $1E __asn equ $1F __acs equ $20 __atn equ $21 __ln equ $22 __exp equ $23 __int equ $24 __sqr equ $25 __sgn equ $26 __abs equ $27 __peek equ $28 __usr_num equ $29 __str_dollar equ $2A __chr_dollar equ $2B __not equ $2C __duplicate equ $2D __n_mod_m equ $2E __jump equ $2F __stk_data equ $30 __dec_jr_nz equ $31 __less_0 equ $32 __greater_0 equ $33 __end_calc equ $34 __get_argt equ $35 __truncate equ $36 __fp_calc_2 equ $37 __e_to_fp equ $38 ; ; __series_xx equ $39 : $80__$9F. ; tells the stack machine to push ; 0 to 31 floating-point values on the stack. ; __series_06 equ $86 __series_08 equ $88 __series_0C equ $8C ; __stk_const_xx equ $3A : $A0__$BF. ; __st_mem_xx equ $3B : $C0__$DF. ; __get_mem_xx equ $3C : $E0__$FF. __st_mem_0 equ $C0 __st_mem_1 equ $C1 __st_mem_2 equ $C2 __st_mem_3 equ $C3 __st_mem_4 equ $C4 __st_mem_5 equ $C5 __st_mem_6 equ $C6 __st_mem_7 equ $C7 __get_mem_0 equ $E0 __get_mem_1 equ $E1 __get_mem_2 equ $E2 __get_mem_3 equ $E3 __get_mem_4 equ $E4 __stk_zero equ $A0 __stk_one equ $A1 __stk_half equ $A2 __stk_half_pi equ $A3 __stk_ten equ $A4
;***************************************** ;** Part 1. RESTART ROUTINES AND TABLES ** ;*****************************************
; THE 'START'
; All Z80 chips start at location zero. ; At start-up the Interrupt Mode is 0, ZX computers use Interrupt Mode 1. ; Interrupts are disabled . mark_0000: START: OUT (IO_PORT_NMI_GEN_OFF),A ; Turn off the NMI generator if this ROM is ; running in ZX81 hardware. This does nothing ; if this ROM is running within an upgraded ; ZX80. LD BC,MAX_RAM ; Set BC to the top of possible RAM. ; The higher unpopulated addresses are used for ; video generation. JP RAM_CHECK ; Jump forward to RAM_CHECK.
; THE 'ERROR' RESTART
; The error restart deals immediately with an error. ; ZX computers execute the same code in runtime as when checking syntax. ; If the error occurred while running a program ; then a brief report is produced. ; If the error occurred while entering a BASIC line or in input etc., ; then the error marker indicates the exact point at which the error lies. mark_0008: ERROR_1: LD HL,(CH_ADD) ; fetch character address from CH_ADD. LD (X_PTR),HL ; and set the error pointer X_PTR. JR ERROR_2 ; forward to continue at ERROR_2.
; THE 'PRINT A CHARACTER' RESTART
; This restart prints the character in the accumulator using the alternate ; register set so there is no requirement to save the main registers. ; There is sufficient room available to separate a space (zero) from other ; characters as leading spaces need not be considered with a space. mark_0010: PRINT_A: AND A ; test for zero - space. JP NZ,PRINT_CH ; jump forward if not to PRINT_CH. JP PRINT_SP ; jump forward to PRINT_SP. ; ___ #if ORIGINAL DEFB $FF ; unused location. #else DEFB $01 ;+ unused location. Version. PRINT PEEK 23 #endif
; THE 'COLLECT A CHARACTER' RESTART
; The character addressed by the system variable CH_ADD is fetched and if it ; is a non-space, non-cursor character it is returned else CH_ADD is ; incremented and the new addressed character tested until it is not a space. mark_0018: GET_CHAR: LD HL,(CH_ADD) ; set HL to character address CH_ADD. LD A,(HL) ; fetch addressed character to A. TEST_SP: AND A ; test for space. RET NZ ; return if not a space NOP ; else trickle through NOP ; to the next routine.
; THE 'COLLECT NEXT CHARACTER' RESTART
; The character address is incremented and the new addressed character is ; returned if not a space, or cursor, else the process is repeated. mark_0020:
NEXT_CHAR: CALL CH_ADD_PLUS_1 ; gets next immediate ; character. JR TEST_SP ; back ; ___ DEFB $FF, $FF, $FF ; unused locations.
; THE 'FLOATING POINT CALCULATOR' RESTART
; this restart jumps to the recursive floating-point calculator. ; the ZX81's internal, FORTH-like, stack-based language. ; ; In the five remaining bytes there is, appropriately, enough room for the ; end-calc literal - the instruction which exits the calculator. mark_0028: FP_CALC: #if ORIGINAL JP CALCULATE ; jump immediately to the CALCULATE routine. #else JP CALCULATE ;+ jump to the NEW calculate routine address. #endif mark_002B: end_calc: POP AF ; drop the calculator return address RE_ENTRY EXX ; switch to the other set. EX (SP),HL ; transfer H'L' to machine stack for the ; return address. ; when exiting recursion then the previous ; pointer is transferred to H'L'. EXX ; back to main set. RET ; return.
; THE 'MAKE BC SPACES' RESTART
; This restart is used eight times to create, in workspace, the number of ; spaces passed in the BC register. mark_0030:
BC_SPACES: PUSH BC ; push number of spaces on stack. LD HL,(E_LINE) ; fetch edit line location from E_LINE. PUSH HL ; save this value on stack. JP RESERVE ; jump forward to continue at RESERVE.
_START equ $00 _ERROR_1 equ $08 _PRINT_A equ $10 _GET_CHAR equ $18 _NEXT_CHAR equ $20 _FP_CALC equ $28 _BC_SPACES equ $30
; THE 'INTERRUPT' RESTART
; The Mode 1 Interrupt routine is concerned solely with generating the central ; television picture. ; On the ZX81 interrupts are enabled only during the interrupt routine, ; although the interrupt ; ; This Interrupt Service Routine automatically disables interrupts at the ; outset and the last interrupt in a cascade exits before the interrupts are ; enabled. ; ; There is no DI instruction in the ZX81 ROM. ; ; A maskable interrupt is triggered when bit 6 of the Z80's Refresh register ; changes from set to reset. ; ; The Z80 will always be executing a HALT (NEWLINE) when the interrupt occurs. ; A HALT instruction repeatedly executes NOPS but the seven lower bits ; of the Refresh register are incremented each time as they are when any ; simple instruction is executed. (The lower 7 bits are incremented twice for ; a prefixed instruction) ; ; This is controlled by the Sinclair Computer Logic Chip - manufactured from ; a Ferranti Uncommitted Logic Array. ; ; When a Mode 1 Interrupt occurs the Program Counter, which is the address in ; the upper echo display following the NEWLINE/HALT instruction, goes on the ; machine stack. 193 interrupts are required to generate the last part of ; the 56th border line and then the 192 lines of the central TV picture and, ; although each interrupt interrupts the previous one, there are no stack ; problems as the 'return address' is discarded each time. ; ; The scan line counter in C counts down from 8 to 1 within the generation of ; each text line. For the first interrupt in a cascade the initial value of ; C is set to 1 for the last border line. ; Timing is of the utmost importance as the RH border, horizontal retrace ; and LH border are mostly generated in the 58 clock cycles this routine ; takes .
MARK_0038: INTERRUPT: DEC C ; (4) decrement C - the scan line counter. JP NZ,SCAN_LINE ; (10/10) JUMP forward if not zero to SCAN_LINE POP HL ; (10) point to start of next row in display ; file. DEC B ; (4) decrement the row counter. (4) RET Z ; (11/5) return when picture complete to R_IX_1_LAST_NEWLINE ; with interrupts disabled. SET 3,C ; (8) Load the scan line counter with eight. ; Note. LD C,$08 is 7 clock cycles which ; is way too fast. ; -> mark_0041: WAIT_INT: ; ; NB $DD is for 32-column display ; LD R,A ; (9) Load R with initial rising value $DD. EI ; (4) Enable Interrupts. [ R is now $DE ]. JP (HL) ; (4) jump to the echo display file in upper ; memory and execute characters $00 - $3F ; as NOP instructions. The video hardware ; is able to read these characters and, ; with the I register is able to convert ; the character bitmaps in this ROM into a ; line of bytes. Eventually the NEWLINE/HALT ; will be encountered before R reaches $FF. ; It is however the transition from $FF to ; $80 that triggers the next interrupt. ; [ The Refresh register is now $DF ] ; ___ mark_0045: SCAN_LINE: POP DE ; (10) discard the address after NEWLINE as the ; same text line has to be done again ; eight times. RET Z ; (5) Harmless Nonsensical Timing. ; (condition never met) JR WAIT_INT ; (12) back to WAIT_INT ; Note. that a computer with less than 4K or RAM will have a collapsed ; display file and the above mechanism deals with both types of display. ; ; With a full display, the 32 characters in the line are treated as NOPS ; and the Refresh register rises from $E0 to $FF and, at the next instruction ; - HALT, the interrupt occurs. ; With a collapsed display and an initial NEWLINE/HALT, it is the NOPs ; generated by the HALT that cause the Refresh value to rise from $E0 to $FF, ; triggering an Interrupt on the next transition. ; This works happily for all display lines between these extremes and the ; generation of the 32 character, 1 pixel high, line will always take 128 ; clock cycles.
; THE 'INCREMENT CH_ADD' SUBROUTINE
; This is the subroutine that increments the character address system variable ; and returns if it is not the cursor character. The ZX81 has an actual ; character at the cursor position rather than a pointer system variable ; as is the case with prior and subsequent ZX computers. mark_0049: CH_ADD_PLUS_1: LD HL,(CH_ADD) ; fetch character address to CH_ADD. mark_004C: TEMP_PTR1: INC HL ; address next immediate location. mark_004D: TEMP_PTR2: LD (CH_ADD),HL ; update system variable CH_ADD. LD A,(HL) ; fetch the character. CP ZX_CURSOR ; compare to cursor character. RET NZ ; return if not the cursor. JR TEMP_PTR1 ; back for next character to TEMP_PTR1.
; THE 'ERROR_2' BRANCH
; This is a continuation of the error restart. ; If the error occurred in runtime then the error stack pointer will probably ; lead to an error report being printed unless it occurred during input. ; If the error occurred when checking syntax then the error stack pointer ; will be an editing routine and the position of the error will be shown ; when the lower screen is reprinted. mark_0056: ERROR_2: POP HL ; pop the return address which points to the ; DEFB, error code, after the RST 08. LD L,(HL) ; load L with the error code. HL is not needed ; anymore. mark_0058: ERROR_3: LD (IY+ERR_NR-RAMBASE),L ; place error code in system variable ERR_NR LD SP,(ERR_SP) ; set the stack pointer from ERR_SP CALL SLOW_FAST ; selects slow mode. JP SET_MIN ; exit to address on stack via routine SET_MIN. ; ___ DEFB $FF ; unused.
; THE 'NON MASKABLE INTERRUPT' ROUTINE
; Jim Westwood's technical dodge using Non-Maskable Interrupts solved the ; flicker problem of the ZX80 and gave the ZX81 a multi-tasking SLOW mode ; with a steady display. Note that the AF' register is reserved for this ; function and its interaction with the display routines. When counting ; TV lines, the NMI makes no use of the main registers. ; The circuitry for the NMI generator is contained within the SCL (Sinclair ; Computer Logic) chip. ; ( It takes 32 clock cycles while incrementing towards zero ). mark_0066: NMI: EX AF,AF' ; (4) switch in the NMI's copy of the ; accumulator. INC A ; (4) increment. JP M,NMI_RET ; (10/10) jump, if minus, to NMI_RET as this is ; part of a test to see if the NMI ; generation is working or an intermediate ; value for the ascending negated blank ; line counter. JR Z,NMI_CONT ; (12) forward to NMI_CONT ; when line count has incremented to zero. ; Note. the synchronizing NMI when A increments from zero to one takes this ; 7 clock cycle route making 39 clock cycles in all. mark_006D: NMI_RET: EX AF,AF' ; (4) switch out the incremented line counter ; or test result $80 RET ; (10) return to User application for a while. ; ___ ; This branch is taken when the 55 (or 31) lines have been drawn. mark_006F: NMI_CONT: EX AF,AF' ; (4) restore the main accumulator. PUSH AF ; (11) * Save Main Registers PUSH BC ; (11) ** PUSH DE ; (11) *** PUSH HL ; (11) **** ; the next set-up procedure is only really applicable when the top set of ; blank lines have been generated. LD HL,(D_FILE) ; (16) fetch start of Display File from D_FILE ; points to the HALT at beginning. SET 7,H ; (8) point to upper 32K 'echo display file' HALT ; (1) HALT synchronizes with NMI. ; Used with special hardware connected to the ; Z80 HALT and WAIT lines to take 1 clock cycle.
; the NMI has been generated - start counting. ; The cathode ray is at the RH side of the TV. ; ; First the NMI servicing, similar to CALL = 17 clock cycles. ; Then the time taken by the NMI for zero-to-one path = 39 cycles ; The HALT above = 01 cycles. ; The two instructions below = 19 cycles. ; The code at
R_IX_1 up to and including the CALL = 43 cycles. ; The Called routine at DISPLAY_5 = 24 cycles. ; -------------------------------------- --- ; Total Z80 instructions = 143 cycles. ; ; Meanwhile in TV world, ; Horizontal retrace = 15 cycles. ; Left blanking border 8 character positions = 32 cycles ; Generation of 75% scanline from the first NEWLINE = 96 cycles ; --------------------------------------- --- ; = 143 cycles ; ; Since at the time the first JP (HL) is encountered to execute the echo ; display another 8 character positions have to be put out, then the ; Refresh register need to hold $F8. Working back and counteracting ; the fact that every instruction increments the Refresh register then ; the value that is loaded into R needs to be $F5. :-) ; ; OUT (IO_PORT_NMI_GEN_OFF),A ; (11) Stop the NMI generator. JP (IX) ; (8) forward to R_IX_1 (after top) or R_IX_2 ; **************** ; ** KEY TABLES ** ; ****************
; THE 'UNSHIFTED' CHARACTER CODES
mark_007E: K_UNSHIFT: DEFB ZX_Z DEFB ZX_X DEFB ZX_C DEFB ZX_V DEFB ZX_A DEFB ZX_S DEFB ZX_D DEFB ZX_F DEFB ZX_G DEFB ZX_Q DEFB ZX_W DEFB ZX_E DEFB ZX_R DEFB ZX_T DEFB ZX_1 DEFB ZX_2 DEFB ZX_3 DEFB ZX_4 DEFB ZX_5 DEFB ZX_0 DEFB ZX_9 DEFB ZX_8 DEFB ZX_7 DEFB ZX_6 DEFB ZX_P DEFB ZX_O DEFB ZX_I DEFB ZX_U DEFB ZX_Y DEFB ZX_NEWLINE DEFB ZX_L DEFB ZX_K DEFB ZX_J DEFB ZX_H DEFB ZX_SPACE DEFB ZX_PERIOD DEFB ZX_M DEFB ZX_N DEFB ZX_B
; THE 'SHIFTED' CHARACTER CODES
mark_00A5:
K_SHIFT: DEFB ZX_COLON ; : DEFB ZX_SEMICOLON ; ; DEFB ZX_QUERY ; ? DEFB ZX_SLASH ; / DEFB ZX_STOP DEFB ZX_LPRINT DEFB ZX_SLOW DEFB ZX_FAST DEFB ZX_LLIST DEFB $C0 ; "" DEFB ZX_OR DEFB ZX_STEP DEFB $DB ; <= DEFB $DD ; <> DEFB ZX_EDIT DEFB ZX_AND DEFB ZX_THEN DEFB ZX_TO DEFB $72 ; cursor-left DEFB ZX_RUBOUT DEFB ZX_GRAPHICS DEFB $73 ; cursor-right DEFB $70 ; cursor-up DEFB $71 ; cursor-down DEFB ZX_QUOTE ; " DEFB $11 ; ) DEFB $10 ; ( DEFB ZX_DOLLAR ; $ DEFB $DC ; >= DEFB ZX_FUNCTION DEFB ZX_EQUAL DEFB ZX_PLUS DEFB ZX_MINUS DEFB ZX_POWER ; ** DEFB ZX_POUND ; £ DEFB ZX_COMMA ; , DEFB ZX_GREATER_THAN ; > DEFB ZX_LESS_THAN ; < DEFB ZX_STAR ; *
; THE 'FUNCTION' CHARACTER CODES
mark_00CC:
K_FUNCT: DEFB ZX_LN DEFB ZX_EXP DEFB ZX_AT DEFB ZX_KL DEFB ZX_ASN DEFB ZX_ACS DEFB ZX_ATN DEFB ZX_SGN DEFB ZX_ABS DEFB ZX_SIN DEFB ZX_COS DEFB ZX_TAN DEFB ZX_INT DEFB ZX_RND DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_KL DEFB ZX_TAB DEFB ZX_PEEK DEFB ZX_CODE DEFB ZX_CHR_STR ; CHR$ DEFB ZX_STR_STR ; STR$ DEFB ZX_KL DEFB ZX_USR DEFB ZX_LEN DEFB ZX_VAL DEFB ZX_SQR DEFB ZX_KL DEFB ZX_KL DEFB ZX_PI DEFB ZX_NOT DEFB ZX_INKEY_STR
; THE 'GRAPHIC' CHARACTER CODES
mark_00F3:
K_GRAPH: DEFB $08 ; graphic DEFB $0A ; graphic DEFB $09 ; graphic DEFB $8A ; graphic DEFB $89 ; graphic DEFB $81 ; graphic DEFB $82 ; graphic DEFB $07 ; graphic DEFB $84 ; graphic DEFB $06 ; graphic DEFB $01 ; graphic DEFB $02 ; graphic DEFB $87 ; graphic DEFB $04 ; graphic DEFB $05 ; graphic DEFB ZX_RUBOUT DEFB ZX_KL DEFB $85 ; graphic DEFB $03 ; graphic DEFB $83 ; graphic DEFB $8B ; graphic DEFB $91 ; inverse ) DEFB $90 ; inverse ( DEFB $8D ; inverse $ DEFB $86 ; graphic DEFB ZX_KL DEFB $92 ; inverse > DEFB $95 ; inverse + DEFB $96 ; inverse - DEFB $88 ; graphic
; THE 'TOKEN' TABLES
mark_0111:
TOKEN_TABLE: DEFB ZX_QUERY +$80; '?' DEFB ZX_QUOTE, ZX_QUOTE +$80; "" DEFB ZX_A, ZX_T +$80; AT DEFB ZX_T, ZX_A, ZX_B +$80; TAB DEFB ZX_QUERY +$80; '?' DEFB ZX_C, ZX_O, ZX_D, ZX_E +$80; CODE DEFB ZX_V, ZX_A, ZX_L +$80; VAL DEFB ZX_L, ZX_E, ZX_N +$80; LEN DEFB ZX_S, ZX_I, ZX_N +$80; SIN DEFB ZX_C, ZX_O, ZX_S +$80; COS DEFB ZX_T, ZX_A, ZX_N +$80; TAN DEFB ZX_A, ZX_S, ZX_N +$80; ASN DEFB ZX_A, ZX_C, ZX_S +$80; ACS DEFB ZX_A, ZX_T, ZX_N +$80; ATN DEFB ZX_L, ZX_N +$80; LN DEFB ZX_E, ZX_X, ZX_P +$80; EXP DEFB ZX_I, ZX_N, ZX_T +$80; INT DEFB ZX_S, ZX_Q, ZX_R +$80; SQR DEFB ZX_S, ZX_G, ZX_N +$80; SGN DEFB ZX_A, ZX_B, ZX_S +$80; ABS DEFB ZX_P, ZX_E, ZX_E, ZX_K +$80; PEEK DEFB ZX_U, ZX_S, ZX_R +$80; USR DEFB ZX_S, ZX_T, ZX_R, ZX_DOLLAR +$80; STR$ DEFB ZX_C, ZX_H, ZX_R, ZX_DOLLAR +$80; CHR$ DEFB ZX_N, ZX_O, ZX_T +$80; NOT DEFB ZX_STAR, ZX_STAR +$80; ** DEFB ZX_O, ZX_R +$80; OR DEFB ZX_A, ZX_N, ZX_D +$80; AND DEFB ZX_LESS_THAN, ZX_EQUAL +$80; >= DEFB ZX_GREATER_THAN, ZX_EQUAL +$80; <= DEFB ZX_LESS_THAN, ZX_GREATER_THAN +$80; >< DEFB ZX_T, ZX_H, ZX_E, ZX_N +$80; THEN DEFB ZX_T, ZX_O +$80; TO DEFB ZX_S, ZX_T, ZX_E, ZX_P +$80; STEP DEFB ZX_L, ZX_P, ZX_R, ZX_I, ZX_N, ZX_T +$80; LPRINT DEFB ZX_L, ZX_L, ZX_I, ZX_S, ZX_T +$80; LLIST DEFB ZX_S, ZX_T, ZX_O, ZX_P +$80; STOP DEFB ZX_S, ZX_L, ZX_O, ZX_W +$80; SLOW DEFB ZX_F, ZX_A, ZX_S, ZX_T +$80; FAST DEFB ZX_N, ZX_E, ZX_W +$80; NEW DEFB ZX_S, ZX_C, ZX_R, ZX_O, ZX_L, ZX_L +$80; SCROLL DEFB ZX_C, ZX_O, ZX_N, ZX_T +$80; CONT DEFB ZX_D, ZX_I, ZX_M +$80; DIM DEFB ZX_R, ZX_E, ZX_M +$80; REM DEFB ZX_F, ZX_O, ZX_R +$80; FOR DEFB ZX_G, ZX_O, ZX_T, ZX_O +$80; GOTO DEFB ZX_G, ZX_O, ZX_S, ZX_U, ZX_B +$80; GOSUB DEFB ZX_I, ZX_N, ZX_P, ZX_U, ZX_T +$80; INPUT DEFB ZX_L, ZX_O, ZX_A, ZX_D +$80; LOAD DEFB ZX_L, ZX_I, ZX_S, ZX_T +$80; LIST DEFB ZX_L, ZX_E, ZX_T +$80; LET DEFB ZX_P, ZX_A, ZX_U, ZX_S, ZX_E +$80; PAUSE DEFB ZX_N, ZX_E, ZX_X, ZX_T +$80; NEXT DEFB ZX_P, ZX_O, ZX_K, ZX_E +$80; POKE DEFB ZX_P, ZX_R, ZX_I, ZX_N, ZX_T +$80; PRINT DEFB ZX_P, ZX_L, ZX_O, ZX_T +$80; PLOT DEFB ZX_R, ZX_U, ZX_N +$80; RUN DEFB ZX_S, ZX_A, ZX_V, ZX_E +$80; SAVE DEFB ZX_R, ZX_A, ZX_N, ZX_D +$80; RAND DEFB ZX_I, ZX_F +$80; IF DEFB ZX_C, ZX_L, ZX_S +$80; CLS DEFB ZX_U, ZX_N, ZX_P, ZX_L, ZX_O, ZX_T +$80; UNPLOT DEFB ZX_C, ZX_L, ZX_E, ZX_A, ZX_R +$80; CLEAR DEFB ZX_R, ZX_E, ZX_T, ZX_U, ZX_R, ZX_N +$80; RETURN DEFB ZX_C, ZX_O, ZX_P, ZX_Y +$80; COPY DEFB ZX_R, ZX_N, ZX_D +$80; RND DEFB ZX_I, ZX_N, ZX_K, ZX_E, ZX_Y, ZX_DOLLAR +$80; INKEY$ DEFB ZX_P, ZX_I +$80; PI
; THE 'LOAD_SAVE UPDATE' ROUTINE
; ; mark_01FC:
LOAD_SAVE: INC HL ; EX DE,HL ; LD HL,(E_LINE) ; system variable edit line E_LINE. SCF ; set carry flag SBC HL,DE ; EX DE,HL ; RET NC ; return if more bytes to LOAD_SAVE. POP HL ; else drop return address
; THE 'DISPLAY' ROUTINES
; ; mark_0207:
SLOW_FAST: LD HL,CDFLAG ; Address the system variable CDFLAG. LD A,(HL) ; Load value to the accumulator. RLA ; rotate bit 6 to position 7. XOR (HL) ; exclusive or with original bit 7. RLA ; rotate result out to carry. RET NC ; return if both bits were the same. ; Now test if this really is a ZX81 or a ZX80 running the upgraded ROM. ; The standard ZX80 did not have an NMI generator. LD A,$7F ; Load accumulator with %011111111 EX AF,AF' ; save in AF' LD B,17 ; A counter within which an NMI should occur ; if this is a ZX81. OUT (IO_PORT_NMI_GEN_ON),A ; start the NMI generator. ; Note that if this is a ZX81 then the NMI will increment AF'. mark_0216: LOOP_11: DJNZ LOOP_11 ; self loop to give the NMI a chance to kick in. ; = 16*13 clock cycles + 8 = 216 clock cycles. OUT (IO_PORT_NMI_GEN_OFF),A ; Turn off the NMI generator. EX AF,AF' ; bring back the AF' value. RLA ; test bit 7. JR NC,NO_SLOW ; forward, if bit 7 is still reset, to NO_SLOW. ; If the AF' was incremented then the NMI generator works and SLOW mode can ; be set. SET 7,(HL) ; Indicate SLOW mode - Compute and Display. PUSH AF ; * Save Main Registers PUSH BC ; ** PUSH DE ; *** PUSH HL ; **** JR DISPLAY_1 ; skip forward - to DISPLAY_1. ; ___ mark_0226: NO_SLOW: RES 6,(HL) ; reset bit 6 of CDFLAG. RET ; return.
; THE 'MAIN DISPLAY' LOOP
; This routine is executed once for every frame displayed. mark_0229:
DISPLAY_1: LD HL,(FRAMES) ; fetch two-byte system variable FRAMES. DEC HL ; decrement frames counter. mark_022D: DISPLAY_P: LD A,$7F ; prepare a mask AND H ; pick up bits 6-0 of H. OR L ; and any bits of L. LD A,H ; reload A with all bits of H for PAUSE test. ; Note both branches must take the same time. JR NZ,ANOTHER ; (12/7) forward if bits 14-0 are not zero ; to ANOTHER RLA ; (4) test bit 15 of FRAMES. JR OVER_NC ; (12) forward with result to OVER_NC ; ___ mark_0237: ANOTHER: LD B,(HL) ; (7) Note. Harmless Nonsensical Timing weight. SCF ; (4) Set Carry Flag. ; Note. the branch to here takes either (12)(7)(4) cyles or (7)(4)(12) cycles. mark_0239: OVER_NC: LD H,A ; (4) set H to zero LD (FRAMES),HL ; (16) update system variable FRAMES RET NC ; (11/5) return if FRAMES is in use by PAUSE ; command. mark_023E: DISPLAY_2: CALL KEYBOARD ; gets the key row in H and the column in L. ; Reading the ports also starts ; the TV frame synchronization pulse. (VSYNC) LD BC,(LAST_K) ; fetch the last key values LD (LAST_K),HL ; update LAST_K with new values. LD A,B ; load A with previous column - will be $FF if ; there was no key. ADD A,2 ; adding two will set carry if no previous key. SBC HL,BC ; subtract with the carry the two key values. ; If the same key value has been returned twice then HL will be zero. LD A,(DEBOUNCE_VAR) OR H ; and OR with both bytes of the difference OR L ; setting the zero flag for the upcoming branch. LD E,B ; transfer the column value to E LD B,11 ; and load B with eleven LD HL,CDFLAG ; address system variable CDFLAG RES 0,(HL) ; reset the rightmost bit of CDFLAG JR NZ,NO_KEY ; skip forward if debounce/diff >0 to NO_KEY BIT 7,(HL) ; test compute and display bit of CDFLAG SET 0,(HL) ; set the rightmost bit of CDFLAG. RET Z ; return if bit 7 indicated fast mode. DEC B ; (4) decrement the counter. NOP ; (4) Timing - 4 clock cycles. ?? SCF ; (4) Set Carry Flag mark_0264: NO_KEY: LD HL,DEBOUNCE_VAR ; CCF ; Complement Carry Flag RL B ; rotate left B picking up carry ; C<-76543210<-C mark_026A: LOOP_B: DJNZ LOOP_B ; self-loop while B>0 to LOOP_B LD B,(HL) ; fetch value of DEBOUNCE_VAR to B LD A,E ; transfer column value CP $FE ; SBC A,A ; A = A-A-C = 0-Carry #if 1 ; I think this truncating DEBOUNCE_VAR ; which would explain why the VSYNC time didn't match ; my calculations that assumed debouncing for 255 loops. ; ; LD B,$1F ; binary 000 11111 OR (HL) ; AND B ; truncate column, 0 to 31 #endif RRA ; LD (HL),A ; OUT (IO_PORT_SCREEN),A ; end the TV frame synchronization pulse. LD HL,(D_FILE) ; (12) set HL to the Display File from D_FILE SET 7,H ; (8) set bit 15 to address the echo display. CALL DISPLAY_3 ; (17) routine DISPLAY_3 displays the top set ; of blank lines.
; THE 'VIDEO_1' ROUTINE
mark_0281: R_IX_1: LD A,R ; (9) Harmless Nonsensical Timing ; or something very clever? LD BC,25*256+1 ; (10) 25 lines, 1 scanline in first. ($1901) ; 32 characters, use $F5 (i.e. minus 11) ; 40 characters, use $ED (i.e. minus 19) ; mark_0286: LD A,277-CHARS_PER_LINE_WINDOW ; $F5 for 6.5MHz clocked machines ; (7) This value will be loaded into R and ; ensures that the cycle starts at the right ; part of the display - after last character ; position. CALL DISPLAY_5 ; (17) routine DISPLAY_5 completes the current ; blank line and then generates the display of ; the live picture using INT interrupts ; The final interrupt returns to the next ; address. R_IX_1_LAST_NEWLINE: DEC HL ; point HL to the last NEWLINE/HALT. CALL DISPLAY_3 ; displays the bottom set of blank lines. ; ___ mark_028F: R_IX_2: JP DISPLAY_1 ; JUMP back to DISPLAY_1
; THE 'DISPLAY BLANK LINES' ROUTINE
; This subroutine is called twice (see above) to generate first the blank ; lines at the top of the television display and then the blank lines at the ; bottom of the display. ; ; It is actually pretty bad. ; PAL or NTSC = 312 or ; 1 to 5 = 5 long and 5 short sync ; 6 to 23 = blank ; 24 to 309 = image ; 310 to 312 = 6 short sync ; ; The ZX80 generates either 62 or 110 blank lines ; ; 262 - 31 - 31 = 200 ; 312 - 55 - 55 = 202 ; ; This does not include 'VSYNC' line periods. ; mark_0292: DISPLAY_3: POP IX ; pop the return address to IX register. ; will be either R_IX_1 or R_IX_2 - see above. LD C,(IY+MARGIN-RAMBASE) ; load C with value of system constant MARGIN. BIT 7,(IY+CDFLAG-RAMBASE) ; test CDFLAG for compute and display. JR Z,DISPLAY_4 ; forward, with FAST mode, to DISPLAY_4 LD A,C ; move MARGIN to A - 31d or 55d. NEG ; Negate INC A ; EX AF,AF' ; place negative count of blank lines in A' OUT (IO_PORT_NMI_GEN_ON),A ; enable the NMI generator. POP HL ; **** POP DE ; *** POP BC ; ** POP AF ; * Restore Main Registers RET ; return - end of interrupt. Return is to ; user's program - BASIC or machine code. ; which will be interrupted by every NMI.
; THE 'FAST MODE' ROUTINES
mark_02A9: DISPLAY_4: LD A,284-CHARS_PER_LINE_WINDOW ; $FC for 6.5MHz clocked machines ; (7) load A with first R delay value LD B,1 ; (7) one row only. CALL DISPLAY_5 ; (17) routine DISPLAY_5 DEC HL ; (6) point back to the HALT. EX (SP),HL ; (19) Harmless Nonsensical Timing if paired. EX (SP),HL ; (19) Harmless Nonsensical Timing. JP (IX) ; (8) to R_IX_1 or R_IX_2
; THE 'DISPLAY_5' SUBROUTINE
; This subroutine is called from SLOW mode and FAST mode to generate the ; central TV picture. With SLOW mode the R register is incremented, with ; each instruction, to $F7 by the time it completes. With fast mode, the ; final R value will be $FF and an interrupt will occur as soon as the ; Program Counter reaches the HALT. (24 clock cycles) mark_02B5: DISPLAY_5: LD R,A ; (9) Load R from A. R = slow: $F5 fast: $FC ;; Original, for 32 column display: ;; ;; LD A,$DD ; (7) load future R value. $F6 $FD ;; ;; For other display widths, ;; need to count down three instructions then the number of characters ;; LD A,256-3-CHARS_PER_LINE_WINDOW ; (7) load future R value. $F6 $FD EI ; (4) Enable Interrupts $F7 $FE JP (HL) ; (4) jump to the echo display. $F8 $FF
; THE 'KEYBOARD SCANNING' SUBROUTINE
; The keyboard is read during the vertical sync interval while no video is ; being displayed. Reading a port with address bit 0 low i.e. $FE starts the ; vertical sync pulse. mark_02BB:
KEYBOARD: LD HL,$FFFF ; (16) prepare a buffer to take key. LD BC,$FEFE ; (20) set BC to port $FEFE. The B register, ; with its single reset bit also acts as ; an 8-counter. IN A,(C) ; (11) read the port - all 16 bits are put on ; the address bus. Start VSYNC pulse. OR $01 ; (7) set the rightmost bit so as to ignore ; the SHIFT key. mark_02C5: EACH_LINE: OR $E0 ; [7] OR %11100000 LD D,A ; [4] transfer to D. CPL ; [4] complement - only bits 4-0 meaningful now. CP 1 ; [7] sets carry if A is zero. SBC A,A ; [4] $FF if $00 else zero. OR B ; [7] $FF or port FE,FD,FB.... AND L ; [4] unless more than one key, L will still be ; $FF. if more than one key is pressed then A is ; now invalid. LD L,A ; [4] transfer to L. ; now consider the column identifier. LD A,H ; [4] will be $FF if no previous keys. AND D ; [4] 111xxxxx LD H,A ; [4] transfer A to H ; since only one key may be pressed, H will, if valid, be one of ; 11111110, 11111101, 11111011, 11110111, 11101111 ; reading from the outer column, say Q, to the inner column, say T. RLC B ; [8] rotate the 8-counter/port address. ; sets carry if more to do. IN A,(C) ; [10] read another half-row. ; all five bits this time. JR C,EACH_LINE ; [12](7) loop back, until done, to EACH_LINE ; The last row read is SHIFT,Z,X,C,V for the second time. RRA ; (4) test the shift key - carry will be reset ; if the key is pressed. RL H ; (8) rotate left H picking up the carry giving ; column values - ; $FD, $FB, $F7, $EF, $DF. ; or $FC, $FA, $F6, $EE, $DE if shifted. ; We now have H identifying the column and L identifying the row in the ; keyboard matrix. ; This is a good time to test if this is an American or British machine. ; The US machine has an extra diode that causes bit 6 of a byte read from ; a port to be reset. RLA ; (4) compensate for the shift test. RLA ; (4) rotate bit 7 out. RLA ; (4) test bit 6. SBC A,A ; (4) $FF or 0 {USA} AND $18 ; (7) 24 or 0 ADD A,31 ; (7) 55 or 31 ; result is either 31 (USA) or 55 (UK) blank lines above and below the TV ; picture. LD (MARGIN),A ; (13) update system variable MARGIN RET ; (10) return
; THE 'SET FAST MODE' SUBROUTINE
; ; mark_02E7: SET_FAST: BIT 7,(IY+CDFLAG-RAMBASE) RET Z ; HALT ; Wait for Interrupt OUT (IO_PORT_NMI_GEN_OFF),A ; RES 7,(IY+CDFLAG-RAMBASE) RET ; return.
; THE 'REPORT_F'
mark_02F4:
REPORT_F: RST _ERROR_1 DEFB $0E ; Error Report: No Program Name supplied.
; THE 'SAVE COMMAND' ROUTINE
; ; mark_02F6:
SAVE: CALL NAME JR C,REPORT_F ; back with null name EX DE,HL ; ; ; The next 6 bytes differ ; #if NOT_BODGED ; what ZASM assembled: ; 02FC: 11CB12 LD DE,$12CB ; five seconds timing value (4811 decimal) ; 02FF: CD460F mark_02FF: HEADER: CALL BREAK_1 #else ; what the SG ROM disassembled to: ; 02FC ED;FD LDIR ; Patch tape SAVE ; 02FE C3;07;02 JP SLOW_FAST ; to $0207 ; 0301 0F RRCA #endif mark_0302: JR NC,BREAK_2 mark_0304: DELAY_1: DJNZ DELAY_1 DEC DE ; LD A,D ; OR E ; JR NZ,HEADER ; back for delay to HEADER mark_030B: OUT_NAME: CALL OUT_BYTE BIT 7,(HL) ; test for inverted bit. INC HL ; address next character of name. JR Z,OUT_NAME ; back if not inverted to OUT_NAME ; now start saving the system variables onwards. LD HL,VERSN ; set start of area to VERSN thereby ; preserving RAMTOP etc. mark_0316: OUT_PROG: CALL OUT_BYTE CALL LOAD_SAVE ; >> JR OUT_PROG ; loop back
; THE 'OUT_BYTE' SUBROUTINE
; This subroutine outputs a byte a bit at a time to a domestic tape recorder. mark_031E: OUT_BYTE: LD E,(HL) ; fetch byte to be saved. SCF ; set carry flag - as a marker. mark_0320: EACH_BIT: RL E ; C < 76543210 < C RET Z ; return when the marker bit has passed ; right through. >> SBC A,A ; $FF if set bit or $00 with no carry. AND $05 ; $05 " " " " $00 ADD A,$04 ; $09 " " " " $04 LD C,A ; transfer timer to C. a set bit has a longer ; pulse than a reset bit. mark_0329: PULSES: OUT (IO_PORT_TAPE),A ; pulse to cassette. LD B,$23 ; set timing constant mark_032D: DELAY_2: DJNZ DELAY_2 ; self-loop CALL BREAK_1 ; test for BREAK key. mark_0332: BREAK_2: JR NC,REPORT_D ; forward with break to REPORT_D LD B,$1E ; set timing value. mark_0336: DELAY_3: DJNZ DELAY_3 ; self-loop DEC C ; decrement counter JR NZ,PULSES ; loop back mark_033B: DELAY_4: AND A ; clear carry for next bit test. DJNZ DELAY_4 ; self loop (B is zero - 256) JR EACH_BIT ; loop back
; THE 'LOAD COMMAND' ROUTINE
mark_0340: LOAD: CALL NAME ; DE points to start of name in RAM. RL D ; pick up carry RRC D ; carry now in bit 7. mark_0347: #if NOT_BODGED LNEXT_PROG: CALL IN_BYTE JR LNEXT_PROG ; loop
; THE 'IN_BYTE' SUBROUTINE
mark_034C: IN_BYTE: LD C,$01 ; prepare an eight counter 00000001. mark_034E: NEXT_BIT: LD B,$00 ; set counter to 256 #else ; what the SG ROM has: ;0347 EB EX DE,HL ; NEXT-PROG ;0348 ED;FC LDIR ; Patch tape LOAD ;034A C3;07;02 JP SLOW_FAST ;034D 01;06;00 LD BC,6 #endif mark_0350: BREAK_3: LD A,$7F ; read the keyboard row IN A,(IO_PORT_KEYBOARD_RD) ; with the SPACE key. OUT (IO_PORT_SCREEN),A ; output signal to screen. RRA ; test for SPACE pressed. JR NC,BREAK_4 ; forward if so to BREAK_4 RLA ; reverse above rotation RLA ; test tape bit. JR C,GET_BIT ; forward if set to GET_BIT DJNZ BREAK_3 ; loop back POP AF ; drop the return address. CP D ; ugh. mark_0361: RESTART: JP NC,INITIAL ; jump forward to INITIAL if D is zero ; to reset the system ; if the tape signal has timed out for example ; if the tape is stopped. Not just a simple ; report as some system variables will have ; been overwritten. LD H,D ; else transfer the start of name LD L,E ; to the HL register mark_0366: IN_NAME: CALL IN_BYTE ; is sort of recursion for name ; part. received byte in C. BIT 7,D ; is name the null string ? LD A,C ; transfer byte to A. JR NZ,MATCHING ; forward with null string CP (HL) ; else compare with string in memory. JR NZ,LNEXT_PROG ; back with mis-match ; (seemingly out of subroutine but return ; address has been dropped). mark_0371: MATCHING: INC HL ; address next character of name RLA ; test for inverted bit. JR NC,IN_NAME ; back if not ; the name has been matched in full. ; proceed to load the data but first increment the high byte of E_LINE, which ; is one of the system variables to be loaded in. Since the low byte is loaded ; before the high byte, it is possible that, at the in-between stage, a false ; value could cause the load to end prematurely - see LOAD_SAVE check. INC (IY+E_LINE_hi-RAMBASE) ; increment E_LINE_hi. LD HL,VERSN ; start loading at VERSN. mark_037B: IN_PROG: LD D,B ; set D to zero as indicator. CALL IN_BYTE ; loads a byte LD (HL),C ; insert assembled byte in memory. CALL LOAD_SAVE ; >> JR IN_PROG ; loop back ; ___ ; this branch assembles a full byte before exiting normally ; from the IN_BYTE subroutine. mark_0385: GET_BIT: PUSH DE ; save the LD E,$94 ; timing value. mark_0388: TRAILER: LD B,26 ; counter to twenty six. mark_038A: COUNTER: DEC E ; decrement the measuring timer. IN A,(IO_PORT_KEYBOARD_RD) ; read the tape input RLA ; BIT 7,E ; LD A,E ; JR C,TRAILER ; loop back with carry to TRAILER DJNZ COUNTER POP DE ; JR NZ,BIT_DONE CP $56 ; JR NC,NEXT_BIT mark_039C: BIT_DONE: CCF ; complement carry flag RL C ; JR NC,NEXT_BIT RET ; return with full byte. ; ___ ; if break is pressed while loading data then perform a reset. ; if break pressed while waiting for program on tape then OK to break. mark_03A2: BREAK_4: LD A,D ; transfer indicator to A. AND A ; test for zero. JR Z,RESTART ; back if so mark_03A6: REPORT_D: RST _ERROR_1 DEFB $0C ; Error Report: BREAK - CONT repeats
; THE 'PROGRAM NAME' SUBROUTINE
mark_03A8:
NAME: CALL SCANNING LD A,(FLAGS) ; sv ADD A,A ; JP M,REPORT_C POP HL ; RET NC ; PUSH HL ; CALL SET_FAST CALL STK_FETCH LD H,D ; LD L,E ; DEC C ; RET M ; ADD HL,BC ; SET 7,(HL) ; RET ;
; THE 'NEW' COMMAND ROUTINE
mark_03C3: NEW: CALL SET_FAST LD BC,(RAMTOP) ; fetch value of system variable RAMTOP DEC BC ; point to last system byte.
; THE 'RAM CHECK' ROUTINE
mark_03CB: RAM_CHECK: LD H,B ; LD L,C ; LD A,$3F ; mark_03CF: RAM_FILL: LD (HL),$02 ; DEC HL ; CP H ; JR NZ,RAM_FILL mark_03D5: RAM_READ: AND A ; SBC HL,BC ; ADD HL,BC ; INC HL ; JR NC,SET_TOP DEC (HL) ; JR Z,SET_TOP DEC (HL) ; JR Z,RAM_READ mark_03E2: SET_TOP: LD (RAMTOP),HL ; set system variable RAMTOP to first byte ; above the BASIC system area.
; THE 'INITIALIZATION' ROUTINE
mark_03E5:
INITIAL: LD HL,(RAMTOP) ; fetch system variable RAMTOP. DEC HL ; point to last system byte. LD (HL),$3E ; make GO SUB end-marker $3E - too high for ; high order byte of line number. ; (was $3F on ZX80) DEC HL ; point to unimportant low-order byte. LD SP,HL ; and initialize the stack-pointer to this ; location. DEC HL ; point to first location on the machine stack DEC HL ; which will be filled by next CALL/PUSH. LD (ERR_SP),HL ; set the error stack pointer ERR_SP to ; the base of the now empty machine stack. ; Now set the I register so that the video hardware knows where to find the ; character set. This ROM only uses the character set when printing to ; the ZX Printer. The TV picture is formed by the external video hardware. ; Consider also, that this 8K ROM can be retro-fitted to the ZX80 instead of ; its original 4K ROM so the video hardware could be on the ZX80. LD A,$1E ; address for this ROM is $1E00. LD I,A ; set I register from A. IM 1 ; select Z80 Interrupt Mode 1. LD IY,ERR_NR ; set IY to the start of RAM so that the ; system variables can be indexed. LD (IY+CDFLAG-RAMBASE),%01000000 ; Bit 6 indicates Compute and Display required. LD HL,USER_RAM ; The first location after System Variables - ; 16509 decimal. LD (D_FILE),HL ; set system variable D_FILE to this value. LD B,$19 ; prepare minimal screen of 24 NEWLINEs ; following an initial NEWLINE. mark_0408: LINE: LD (HL),ZX_NEWLINE ; insert NEWLINE (HALT instruction) INC HL ; point to next location. DJNZ LINE ; loop back for all twenty five to LINE LD (VARS),HL ; set system variable VARS to next location CALL CLEAR ; sets $80 end-marker and the ; dynamic memory pointers E_LINE, STKBOT and ; STKEND. mark_0413: N_L_ONLY: CALL CURSOR_IN ; inserts the cursor and ; end-marker in the Edit Line also setting ; size of lower display to two lines. CALL SLOW_FAST ; selects COMPUTE and DISPLAY
; THE 'BASIC LISTING' SECTION
mark_0419: UPPER: CALL CLS LD HL,(E_PPC) ; sv LD DE,(S_TOP) ; sv AND A ; SBC HL,DE ; EX DE,HL ; JR NC,ADDR_TOP ADD HL,DE ; LD (S_TOP),HL ; sv mark_042D: ADDR_TOP: CALL LINE_ADDR JR Z,LIST_TOP EX DE,HL ; mark_0433: LIST_TOP: CALL LIST_PROG DEC (IY+BERG-RAMBASE) JR NZ,LOWER LD HL,(E_PPC) ; sv CALL LINE_ADDR LD HL,(CH_ADD) ; sv SCF ; Set Carry Flag SBC HL,DE ; LD HL,S_TOP ; sv JR NC,INC_LINE EX DE,HL ; LD A,(HL) ; INC HL ; LDI ; LD (DE),A ; JR UPPER ; ___ mark_0454: DOWN_KEY: LD HL,E_PPC ; sv mark_0457: INC_LINE: LD E,(HL) ; INC HL ; LD D,(HL) ; PUSH HL ; EX DE,HL ; INC HL ; CALL LINE_ADDR CALL LINE_NUM POP HL ; mark_0464: KEY_INPUT: BIT 5,(IY+FLAGX-RAMBASE) JR NZ,LOWER ; forward LD (HL),D ; DEC HL ; LD (HL),E ; JR UPPER
; THE 'EDIT LINE COPY' SECTION
; This routine sets the edit line to just the cursor when ; 1) There is not enough memory to edit a BASIC line. ; 2) The edit key is used during input. ; The entry point LOWER mark_046F: EDIT_INP: CALL CURSOR_IN ; sets cursor only edit line. ; -> mark_0472: LOWER: LD HL,(E_LINE) ; fetch edit line start from E_LINE. mark_0475: EACH_CHAR: LD A,(HL) ; fetch a character from edit line. CP $7E ; compare to the number marker. JR NZ,END_LINE ; forward if not LD BC,6 ; else six invisible bytes to be removed. CALL RECLAIM_2 JR EACH_CHAR ; back ; ___ mark_0482: END_LINE: CP ZX_NEWLINE ; INC HL ; JR NZ,EACH_CHAR mark_0487: EDIT_LINE: CALL CURSOR ; sets cursor K or L. mark_048A: EDIT_ROOM: CALL LINE_ENDS LD HL,(E_LINE) ; sv LD (IY+ERR_NR-RAMBASE),$FF CALL COPY_LINE BIT 7,(IY+ERR_NR-RAMBASE) JR NZ,DISPLAY_6 LD A,(DF_SZ) ; CP CHARS_VERTICAL ; $18 = 24 JR NC,DISPLAY_6 INC A ; LD (DF_SZ),A ; LD B,A ; LD C,1 ; CALL LOC_ADDR LD D,H ; LD E,L ; LD A,(HL) ; mark_04B1: FREE_LINE: DEC HL ; CP (HL) ; JR NZ,FREE_LINE INC HL ; EX DE,HL ; LD A,(RAMTOP+1) ; sv RAMTOP_hi CP $4D ; CALL C,RECLAIM_1 JR EDIT_ROOM
; THE 'WAIT FOR KEY' SECTION
mark_04C1: DISPLAY_6: LD HL,$0000 ; LD (X_PTR),HL ; sv LD HL,CDFLAG ; system variable CDFLAG #if NOT_BODGED BIT 7,(HL) ; CALL Z,DISPLAY_1 mark_04CF: SLOW_DISP: BIT 0,(HL) ; JR Z,SLOW_DISP #else ; 04CA D3;00 OUT ($00),A ; PORT 0 ; 04CC CB;46 L04CC: BIT 0,(HL) ; 04CE 28;FC JR Z,L04CC ; 04D0 D3;01 OUT ($01),A ; PORT 1 ; 04D2 00 NOP #endif LD BC,(LAST_K) ; sv CALL DEBOUNCE CALL DECODE JR NC,LOWER ; back
; THE 'KEYBOARD DECODING' SECTION
; The decoded key value is in E and HL points to the position in the ; key table. D contains zero. mark_04DF: K_DECODE: LD A,(MODE) ; Fetch value of system variable MODE DEC A ; test the three values together JP M,FETCH_2 ; forward, if was zero JR NZ,FETCH_1 ; forward, if was 2 ; The original value was one and is now zero. LD (MODE),A ; update the system variable MODE DEC E ; reduce E to range $00 - $7F LD A,E ; place in A SUB 39 ; subtract 39 setting carry if range 00 - 38 JR C,FUNC_BASE ; forward, if so LD E,A ; else set E to reduced value mark_04F2: FUNC_BASE: LD HL,K_FUNCT ; address of K_FUNCT table for function keys. JR TABLE_ADD ; forward ; ___ mark_04F7: FETCH_1: LD A,(HL) ; CP ZX_NEWLINE ; JR Z,K_L_KEY CP ZX_RND ; $40 SET 7,A ; JR C,ENTER LD HL,$00C7 ; (expr reqd) mark_0505: TABLE_ADD: ADD HL,DE ; JR FETCH_3 ; ___ mark_0508: FETCH_2: LD A,(HL) ; BIT 2,(IY+FLAGS-RAMBASE) ; K or L mode ? JR NZ,TEST_CURS ADD A,$C0 ; CP $E6 ; JR NC,TEST_CURS mark_0515: FETCH_3: LD A,(HL) ; mark_0516: TEST_CURS: CP $F0 ; JP PE,KEY_SORT mark_051B: ENTER: LD E,A ; CALL CURSOR LD A,E ; CALL ADD_CHAR mark_0523: BACK_NEXT: JP LOWER ; back
; THE 'ADD CHARACTER' SUBROUTINE
mark_0526: ADD_CHAR: CALL ONE_SPACE LD (DE),A ; RET ;
; THE 'CURSOR KEYS' ROUTINE
mark_052B: K_L_KEY: LD A,ZX_KL ; mark_052D: KEY_SORT: LD E,A ; LD HL,$0482 ; base address of ED_KEYS (exp reqd) ADD HL,DE ; ADD HL,DE ; LD C,(HL) ; INC HL ; LD B,(HL) ; PUSH BC ; mark_0537: CURSOR: LD HL,(E_LINE) ; sv BIT 5,(IY+FLAGX-RAMBASE) JR NZ,L_MODE mark_0540: K_MODE: RES 2,(IY+FLAGS-RAMBASE) ; Signal use K mode mark_0544: TEST_CHAR: LD A,(HL) ; CP ZX_CURSOR ; RET Z ; return INC HL ; CALL NUMBER JR Z,TEST_CHAR CP ZX_A ; $26 JR C,TEST_CHAR CP $DE ; ZX_THEN ?? JR Z,K_MODE mark_0556: L_MODE: SET 2,(IY+FLAGS-RAMBASE) ; Signal use L mode JR TEST_CHAR
; THE 'CLEAR_ONE' SUBROUTINE
mark_055C: CLEAR_ONE: LD BC,$0001 ; JP RECLAIM_2
; THE 'EDITING KEYS' TABLE
mark_0562: ED_KEYS: DEFW UP_KEY DEFW DOWN_KEY DEFW LEFT_KEY DEFW RIGHT_KEY DEFW FUNCTION DEFW EDIT_KEY DEFW N_L_KEY DEFW RUBOUT DEFW FUNCTION DEFW FUNCTION
; THE 'CURSOR LEFT' ROUTINE
; ; mark_LEFT_KEY: LEFT_KEY: CALL LEFT_EDGE LD A,(HL) ; LD (HL),ZX_CURSOR ; INC HL ; JR GET_CODE
; THE 'CURSOR RIGHT' ROUTINE
mark_RIGHT_KEY: RIGHT_KEY: INC HL ; LD A,(HL) ; CP ZX_NEWLINE ; JR Z,ENDED_2 LD (HL),ZX_CURSOR ; DEC HL ; mark_0588: GET_CODE: LD (HL),A ; mark_0589: ENDED_1: JR BACK_NEXT
; THE 'RUBOUT' ROUTINE
mark_058B: RUBOUT: CALL LEFT_EDGE CALL CLEAR_ONE JR ENDED_1
; THE 'ED_EDGE' SUBROUTINE
; ; mark_0593: LEFT_EDGE: DEC HL ; LD DE,(E_LINE) ; sv LD A,(DE) ; CP ZX_CURSOR ; RET NZ ; POP DE ; mark_059D: ENDED_2: JR ENDED_1
; THE 'CURSOR UP' ROUTINE
; ; mark_059F: UP_KEY: LD HL,(E_PPC) ; sv CALL LINE_ADDR EX DE,HL ; CALL LINE_NUM LD HL,E_PPC+1 ; point to system variable E_PPC_hi JP KEY_INPUT ; jump back
; THE 'FUNCTION KEY' ROUTINE
; ; mark_FUNCTION: FUNCTION: LD A,E ; AND $07 ; LD (MODE),A ; sv JR ENDED_2 ; back
; THE 'COLLECT LINE NUMBER' SUBROUTINE
mark_05B7: ZERO_DE: EX DE,HL ; LD DE,DISPLAY_6 + 1 ; $04C2 - a location addressing two zeros. ; -> mark_05BB: LINE_NUM: LD A,(HL) ; AND $C0 ; JR NZ,ZERO_DE LD D,(HL) ; INC HL ; LD E,(HL) ; RET ;
; THE 'EDIT KEY' ROUTINE
mark_EDIT_KEY: EDIT_KEY: CALL LINE_ENDS ; clears lower display. LD HL,EDIT_INP ; Address: EDIT_INP PUSH HL ; ** is pushed as an error looping address. BIT 5,(IY+FLAGX-RAMBASE) ; test FLAGX RET NZ ; indirect jump if in input mode ; to EDIT_INP (begin again). ; LD HL,(E_LINE) ; fetch E_LINE LD (DF_CC),HL ; and use to update the screen cursor DF_CC ; so now RST $10 will print the line numbers to the edit line instead of screen. ; first make sure that no newline/out of screen can occur while sprinting the ; line numbers to the edit line. ; prepare line 0, column 0. LD HL,256*CHARS_VERTICAL + CHARS_HORIZONTAL + 1 ; LD (S_POSN),HL ; update S_POSN with these dummy values. LD HL,(E_PPC) ; fetch current line from E_PPC may be a ; non-existent line e.g. last line deleted. CALL LINE_ADDR ; gets address or that of ; the following line. CALL LINE_NUM ; gets line number if any in DE ; leaving HL pointing at second low byte. LD A,D ; test the line number for zero. OR E ; RET Z ; return if no line number - no program to edit. DEC HL ; point to high byte. CALL OUT_NO ; writes number to edit line. INC HL ; point to length bytes. LD C,(HL) ; low byte to C. INC HL ; LD B,(HL) ; high byte to B. INC HL ; point to first character in line. LD DE,(DF_CC) ; fetch display file cursor DF_CC LD A,ZX_CURSOR ; prepare the cursor character. LD (DE),A ; and insert in edit line. INC DE ; increment intended destination. PUSH HL ; * save start of BASIC. LD HL,29 ; set an overhead of 29 bytes. ADD HL,DE ; add in the address of cursor. ADD HL,BC ; add the length of the line. SBC HL,SP ; subtract the stack pointer. POP HL ; * restore pointer to start of BASIC. RET NC ; return if not enough room to EDIT_INP EDIT_INP. ; the edit key appears not to work. LDIR ; else copy bytes from program to edit line. ; Note. hidden floating point forms are also ; copied to edit line. EX DE,HL ; transfer free location pointer to HL POP DE ; ** remove address EDIT_INP from stack. CALL SET_STK_B ; sets STKEND from HL. JR ENDED_2 ; back to ENDED_2 and after 3 more jumps ; to LOWER, LOWER. ; Note. The LOWER routine removes the hidden ; floating-point numbers from the edit line.
; THE 'NEWLINE KEY' ROUTINE
mark_060C: N_L_KEY: CALL LINE_ENDS LD HL,LOWER ; prepare address: LOWER BIT 5,(IY+FLAGX-RAMBASE) JR NZ,NOW_SCAN LD HL,(E_LINE) ; sv LD A,(HL) ; CP $FF ; JR Z,STK_UPPER CALL CLEAR_PRB CALL CLS mark_0626: STK_UPPER: LD HL,UPPER ; Address: UPPER mark_0629: NOW_SCAN: PUSH HL ; push routine address (LOWER or UPPER). CALL LINE_SCAN POP HL ; CALL CURSOR CALL CLEAR_ONE CALL E_LINE_NUM JR NZ,N_L_INP LD A,B ; OR C ; JP NZ,N_L_LINE DEC BC ; DEC BC ; LD (PPC),BC ; sv LD (IY+DF_SZ-RAMBASE),2 LD DE,(D_FILE) ; sv JR TEST_NULL ; forward ; ___ mark_064E: N_L_INP: CP ZX_NEWLINE ; JR Z,N_L_NULL LD BC,(T_ADDR) ; CALL LOC_ADDR LD DE,(NXTLIN) ; LD (IY+DF_SZ-RAMBASE),2 mark_0661: TEST_NULL: RST _GET_CHAR CP ZX_NEWLINE ; mark_0664: N_L_NULL: JP Z,N_L_ONLY LD (IY+FLAGS-RAMBASE),$80 EX DE,HL ; mark_066C: NEXT_LINE: LD (NXTLIN),HL ; EX DE,HL ; CALL TEMP_PTR2 CALL LINE_RUN RES 1,(IY+FLAGS-RAMBASE) ; Signal printer not in use LD A,$C0 ; ;; LD (IY+X_PTR_lo-RAMBASE),A ;; ERROR IN htm SOURCE! IY+$19 is X_PTR_hi LD (IY+X_PTR_hi-RAMBASE),A CALL X_TEMP RES 5,(IY+FLAGX-RAMBASE) BIT 7,(IY+ERR_NR-RAMBASE) JR Z,STOP_LINE LD HL,(NXTLIN) ; AND (HL) ; JR NZ,STOP_LINE LD D,(HL) ; INC HL ; LD E,(HL) ; LD (PPC),DE ; INC HL ; LD E,(HL) ; INC HL ; LD D,(HL) ; INC HL ; EX DE,HL ; ADD HL,DE ; CALL BREAK_1 JR C,NEXT_LINE LD HL,ERR_NR BIT 7,(HL) JR Z,STOP_LINE LD (HL),$0C mark_06AE: STOP_LINE: BIT 7,(IY+PR_CC-RAMBASE) CALL Z,COPY_BUFF ; #if 0 LD BC,$0121 ; #else LD BC,256*1 + CHARS_HORIZONTAL + 1 #endif ; ; CALL LOC_ADDR LD A,(ERR_NR) LD BC,(PPC) INC A JR Z,REPORT CP $09 JR NZ,CONTINUE INC BC mark_06CA: CONTINUE: LD (OLDPPC),BC ; JR NZ,REPORT DEC BC ; mark_06D1: REPORT: CALL OUT_CODE LD A,ZX_SLASH RST _PRINT_A CALL OUT_NUM CALL CURSOR_IN JP DISPLAY_6 ; ___ mark_06E0: N_L_LINE: LD (E_PPC),BC ; LD HL,(CH_ADD) ; EX DE,HL ; LD HL,N_L_ONLY PUSH HL ; LD HL,(STKBOT) ; SBC HL,DE ; PUSH HL ; PUSH BC ; CALL SET_FAST CALL CLS POP HL ; CALL LINE_ADDR JR NZ,COPY_OVER CALL NEXT_ONE CALL RECLAIM_2 mark_0705: COPY_OVER: POP BC ; LD A,C ; DEC A ; OR B ; RET Z ; PUSH BC ; INC BC ; INC BC ; INC BC ; INC BC ; DEC HL ; CALL MAKE_ROOM CALL SLOW_FAST POP BC ; PUSH BC ; INC DE ; LD HL,(STKBOT) ; DEC HL ; LDDR ; copy bytes LD HL,(E_PPC) ; EX DE,HL ; POP BC ; LD (HL),B ; DEC HL ; LD (HL),C ; DEC HL ; LD (HL),E ; DEC HL ; LD (HL),D ; RET ; return.
; THE 'LIST' AND 'LLIST' COMMAND ROUTINES
mark_072C: LLIST: SET 1,(IY+FLAGS-RAMBASE) ; signal printer in use mark_0730: LIST: CALL FIND_INT LD A,B ; fetch high byte of user-supplied line number. AND $3F ; and crudely limit to range 1-16383. LD H,A ; LD L,C ; LD (E_PPC),HL ; CALL LINE_ADDR mark_073E: LIST_PROG: LD E,$00 ; mark_0740: UNTIL_END: CALL OUT_LINE ; lists one line of BASIC ; making an early return when the screen is ; full or the end of program is reached. JR UNTIL_END ; loop back to UNTIL_END
; THE 'PRINT A BASIC LINE' SUBROUTINE
mark_0745: OUT_LINE: LD BC,(E_PPC) ; sv CALL CP_LINES LD D,$92 ; JR Z,TEST_END LD DE,$0000 ; RL E ; mark_0755: TEST_END: LD (IY+BERG-RAMBASE),E LD A,(HL) ; CP $40 ; POP BC ; RET NC ; PUSH BC ; CALL OUT_NO INC HL ; LD A,D ; RST _PRINT_A INC HL ; INC HL ; mark_0766: COPY_LINE: LD (CH_ADD),HL ; SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space mark_076D: MORE_LINE: LD BC,(X_PTR) ; LD HL,(CH_ADD) ; AND A ; SBC HL,BC ; JR NZ,TEST_NUM LD A,ZX_INV_S ; $B8 ; RST _PRINT_A mark_077C: TEST_NUM: LD HL,(CH_ADD) ; LD A,(HL) ; INC HL ; CALL NUMBER LD (CH_ADD),HL ; JR Z,MORE_LINE CP ZX_CURSOR ; JR Z,OUT_CURS CP ZX_NEWLINE ; JR Z,OUT_CH BIT 6,A ; JR Z,NOT_TOKEN CALL TOKENS JR MORE_LINE ; ___ mark_079A: NOT_TOKEN: RST _PRINT_A JR MORE_LINE ; ___ mark_079D: OUT_CURS: LD A,(MODE) ; Fetch value of system variable MODE LD B,$AB ; Prepare an inverse [F] for function cursor. AND A ; Test for zero - JR NZ,FLAGS_2 ; forward if not to FLAGS_2 LD A,(FLAGS) ; Fetch system variable FLAGS. LD B,ZX_INV_K ; Prepare an inverse [K] for keyword cursor. mark_07AA: FLAGS_2: RRA ; 00000?00 -> 000000?0 RRA ; 000000?0 -> 0000000? AND $01 ; 0000000? 0000000x ADD A,B ; Possibly [F] -> [G] or [K] -> [L] CALL PRINT_SP JR MORE_LINE
; THE 'NUMBER' SUBROUTINE
mark_07B4: NUMBER: CP $7E ; RET NZ ; INC HL ; INC HL ; INC HL ; INC HL ; INC HL ; RET ;
; THE 'KEYBOARD DECODE' SUBROUTINE
mark_07BD:
DECODE: LD D,0 ; SRA B ; shift bit from B to Carry SBC A,A ; A = 0 - Carry OR $26 ; %00100110 LD L,5 ; SUB L ; mark_07C7: KEY_LINE: ADD A,L ; SCF ; Set Carry Flag RR C ; JR C,KEY_LINE INC C ; RET NZ ; LD C,B ; DEC L ; LD L,1 ; JR NZ,KEY_LINE LD HL,$007D ; (expr reqd) LD E,A ; ADD HL,DE ; SCF ; Set Carry Flag RET ;
; THE 'PRINTING' SUBROUTINE
mark_07DC: LEAD_SP: LD A,E ; AND A ; RET M ; JR PRINT_CH ; ___ ; HL is typically -10000, -1000, -100, -10 ; and repeatedly subtracted from BC ; i.e. it print ; ; mark_07E1: OUT_DIGIT: XOR A ; assume the digit is zero to begin with mark_07E2: DIGIT_INC: ADD HL,BC ; HL += -ve number INC A ; JR C,DIGIT_INC ; loop SBC HL,BC ; undo last iteration DEC A ; undo last iteration JR Z,LEAD_SP ; leading zeros shown as spaces mark_07EB: OUT_CODE: LD E,ZX_0 ; $1C ADD A,E ; mark_07EE: OUT_CH: AND A ; JR Z,PRINT_SP mark_07F1: PRINT_CH: RES 0,(IY+FLAGS-RAMBASE) ; signal leading space permitted mark_07F5: PRINT_SP: EXX ; PUSH HL ; BIT 1,(IY+FLAGS-RAMBASE) ; is printer in use ? JR NZ,LPRINT_A CALL ENTER_CH JR PRINT_EXX ; ___ mark_0802: LPRINT_A: CALL LPRINT_CH mark_0805: PRINT_EXX: POP HL ; EXX ; RET ; ; ___ mark_0808: ENTER_CH: LD D,A ; LD BC,(S_POSN) ; LD A,C ; CP CHARS_HORIZONTAL+1 ; JR Z,TEST_LOW mark_0812: TEST_N_L: LD A,ZX_NEWLINE ; CP D ; JR Z,WRITE_N_L LD HL,(DF_CC) ; CP (HL) ; LD A,D ; JR NZ,WRITE_CH DEC C ; JR NZ,EXPAND_1 INC HL ; LD (DF_CC),HL ; LD C,CHARS_HORIZONTAL+1 ; $21 = 33 normally DEC B ; LD (S_POSN),BC ; mark_082C: TEST_LOW: LD A,B ; CP (IY+DF_SZ-RAMBASE) JR Z,REPORT_5 AND A ; JR NZ,TEST_N_L mark_0835: REPORT_5: LD L,4 ; 'No more room on screen' JP ERROR_3 ; ___ mark_083A: EXPAND_1: CALL ONE_SPACE EX DE,HL ; mark_083E: WRITE_CH: LD (HL),A ; INC HL ; LD (DF_CC),HL ; DEC (IY+S_POSN_x-RAMBASE) RET ; ; ___ mark_0847: WRITE_N_L: LD C,CHARS_HORIZONTAL+1 ; $21 = 33 DEC B ; SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space JP LOC_ADDR
; THE 'LPRINT_CH' SUBROUTINE
; This routine sends a character to the ZX-Printer placing the code for the ; character in the Printer Buffer. ; Note. PR_CC contains the low byte of the buffer address. The high order byte ; is always constant. mark_0851: LPRINT_CH: CP ZX_NEWLINE ; compare to NEWLINE. JR Z,COPY_BUFF ; forward if so LD C,A ; take a copy of the character in C. LD A,(PR_CC) ; fetch print location from PR_CC AND $7F ; ignore bit 7 to form true position. CP $5C ; compare to 33rd location LD L,A ; form low-order byte. LD H,$40 ; the high-order byte is fixed. CALL Z,COPY_BUFF ; to send full buffer to ; the printer if first 32 bytes full. ; (this will reset HL to start.) LD (HL),C ; place character at location. INC L ; increment - will not cross a 256 boundary. LD (IY+PR_CC-RAMBASE),L ; update system variable PR_CC ; automatically resetting bit 7 to show that ; the buffer is not empty. RET ; return.
; THE 'COPY' COMMAND ROUTINE
; The full character-mapped screen is copied to the ZX-Printer. ; All twenty-four text/graphic lines are printed. mark_0869: COPY: ; ; check - is this $16==22 or 24? ; ;; LD D,$16 ; prepare to copy twenty four text lines. LD D,22 ; prepare to copy twenty four text lines. LD HL,(D_FILE) ; set HL to start of display file from D_FILE. INC HL ; JR COPY_D ; forward ; ___ ; A single character-mapped printer buffer is copied to the ZX-Printer. mark_0871: COPY_BUFF: LD D,1 ; prepare to copy a single text line. LD HL,PRBUFF ; set HL to start of printer buffer PRBUFF. ; both paths converge here. mark_0876: COPY_D: CALL SET_FAST PUSH BC ; *** preserve BC throughout. ; a pending character may be present ; in C from LPRINT_CH mark_087A: COPY_LOOP: PUSH HL ; save first character of line pointer. (*) XOR A ; clear accumulator. LD E,A ; set pixel line count, range 0-7, to zero. ; this inner loop deals with each horizontal pixel line. mark_087D: COPY_TIME: OUT (IO_PORT_PRINTER),A ; bit 2 reset starts the printer motor ; with an inactive stylus - bit 7 reset. POP HL ; pick up first character of line pointer (*) ; on inner loop. mark_0880: COPY_BRK: CALL BREAK_1 JR C,COPY_CONT ; forward with no keypress to COPY_CONT ; else A will hold 11111111 0 RRA ; 0111 1111 OUT (IO_PORT_PRINTER),A ; stop ZX printer motor, de-activate stylus. mark_0888: REPORT_D2: RST _ERROR_1 DEFB $0C ; Error Report: BREAK - CONT repeats ; ___ mark_088A: COPY_CONT: IN A,(IO_PORT_PRINTER) ; read from printer port. ADD A,A ; test bit 6 and 7 JP M,COPY_END ; jump forward with no printer to COPY_END JR NC,COPY_BRK ; back if stylus not in position to COPY_BRK PUSH HL ; save first character of line pointer (*) PUSH DE ; ** preserve character line and pixel line. LD A,D ; text line count to A? CP 2 ; sets carry if last line. SBC A,A ; now $FF if last line else zero. ; now cleverly prepare a printer control mask setting bit 2 (later moved to 1) ; of D to slow printer for the last two pixel lines ( E = 6 and 7) AND E ; and with pixel line offset 0-7 RLCA ; shift to left. AND E ; and again. LD D,A ; store control mask in D. mark_089C: COPY_NEXT: LD C,(HL) ; load character from screen or buffer. LD A,C ; save a copy in C for later inverse test. INC HL ; update pointer for next time. CP ZX_NEWLINE ; is character a NEWLINE ? JR Z,COPY_N_L ; forward, if so, to COPY_N_L PUSH HL ; * else preserve the character pointer. SLA A ; (?) multiply by two ADD A,A ; multiply by four ADD A,A ; multiply by eight LD H,$0F ; load H with half the address of character set. RL H ; now $1E or $1F (with carry) ADD A,E ; add byte offset 0-7 LD L,A ; now HL addresses character source byte RL C ; test character, setting carry if inverse. SBC A,A ; accumulator now $00 if normal, $FF if inverse. XOR (HL) ; combine with bit pattern at end or ROM. LD C,A ; transfer the byte to C. LD B,8 ; count eight bits to output. mark_08B5: COPY_BITS: LD A,D ; fetch speed control mask from D. RLC C ; rotate a bit from output byte to carry. RRA ; pick up in bit 7, speed bit to bit 1 LD H,A ; store aligned mask in H register. mark_08BA: COPY_WAIT: IN A,(IO_PORT_PRINTER) ; read the printer port RRA ; test for alignment signal from encoder. JR NC,COPY_WAIT ; loop if not present to COPY_WAIT LD A,H ; control byte to A. OUT (IO_PORT_PRINTER),A ; and output to printer port. DJNZ COPY_BITS ; loop for all eight bits to COPY_BITS POP HL ; * restore character pointer. JR COPY_NEXT ; back for adjacent character line to COPY_NEXT ; ___ ; A NEWLINE has been encountered either following a text line or as the ; first character of the screen or printer line. mark_08C7: COPY_N_L: IN A,(IO_PORT_PRINTER) ; read printer port. RRA ; wait for encoder signal. JR NC,COPY_N_L ; loop back if not to COPY_N_L LD A,D ; transfer speed mask to A. RRCA ; rotate speed bit to bit 1. ; bit 7, stylus control is reset. OUT (IO_PORT_PRINTER),A ; set the printer speed. POP DE ; ** restore character line and pixel line. INC E ; increment pixel line 0-7. BIT 3,E ; test if value eight reached. JR Z,COPY_TIME ; back if not ; eight pixel lines, a text line have been completed. POP BC ; lose the now redundant first character ; pointer DEC D ; decrease text line count. JR NZ,COPY_LOOP ; back if not zero LD A,$04 ; stop the already slowed printer motor. OUT (IO_PORT_PRINTER),A ; output to printer port. mark_08DE: COPY_END: CALL SLOW_FAST POP BC ; *** restore preserved BC.
; THE 'CLEAR PRINTER BUFFER' SUBROUTINE
; This subroutine sets 32 bytes of the printer buffer to zero (space) and ; the 33rd character is set to a NEWLINE. ; This occurs after the printer buffer is sent to the printer but in addition ; after the 24 lines of the screen are sent to the printer. ; Note. This is a logic error as the last operation does not involve the ; buffer at all. Logically one should be able to use ; 10 LPRINT "HELLO "; ; 20 COPY ; 30 LPRINT ; "WORLD" ; and expect to see the entire greeting emerge from the printer. ; Surprisingly this logic error was never discovered and although one can argue ; if the above is a bug, the repetition of this error on the Spectrum was most ; definitely a bug. ; Since the printer buffer is fixed at the end of the system variables, and ; the print position is in the range $3C - $5C, then bit 7 of the system ; variable is set to show the buffer is empty and automatically reset when ; the variable is updated with any print position - neat. mark_08E2: CLEAR_PRB: LD HL,PRBUFF_END ; address fixed end of PRBUFF LD (HL),ZX_NEWLINE ; place a newline at last position. LD B,32 ; prepare to blank 32 preceding characters. ; ; NB the printer is fixed at 32 characters, maybe it can be tweaked ??? ; mark_08E9: PRB_BYTES: DEC HL ; decrement address - could be DEC L. LD (HL),0 ; place a zero byte. DJNZ PRB_BYTES ; loop for all thirty-two LD A,L ; fetch character print position. SET 7,A ; signal the printer buffer is clear. LD (PR_CC),A ; update one-byte system variable PR_CC RET ; return.
; THE 'PRINT AT' SUBROUTINE
; ; ; mark_08F5: PRINT_AT: LD A,CHARS_VERTICAL-1 ; originally 23 SUB B ; JR C,WRONG_VAL mark_08FA: TEST_VAL: CP (IY+DF_SZ-RAMBASE) JP C,REPORT_5 INC A ; LD B,A ; LD A,CHARS_HORIZONTAL-1 ; originally 31 SUB C ; mark_0905: WRONG_VAL: JP C,REPORT_B ADD A,2 ; LD C,A ; mark_090B: SET_FIELD: BIT 1,(IY+FLAGS-RAMBASE) ; Is printer in use? JR Z,LOC_ADDR LD A,$5D ; SUB C ; LD (PR_CC),A ; RET ;
; THE 'LOCATE ADDRESS' ROUTINE
; ; I'm guessing this locates the address of a character at X,Y ; on the screen, with 0,0 being on the bottom left? ; S_POSN_x equ $4039 ; S_POSN_y equ $403A ; so when BC is stored there, B is Y and C is X ; mark_0918: LOC_ADDR: LD (S_POSN),BC ; LD HL,(VARS) ; LD D,C ; LD A,CHARS_HORIZONTAL+2 ; $22 == 34 originally. SUB C ; LD C,A ; LD A,ZX_NEWLINE ; INC B ; mark_0927: LOOK_BACK: DEC HL ; CP (HL) ; JR NZ,LOOK_BACK DJNZ LOOK_BACK INC HL ; CPIR ; DEC HL ; LD (DF_CC),HL ; SCF ; Set Carry Flag RET PO ; DEC D ; RET Z ; PUSH BC ; CALL MAKE_ROOM POP BC ; LD B,C ; LD H,D ; HL := DE LD L,E ; mark_0940: EXPAND_2: ; ; Writes B spaces to HL-- ; LD (HL),ZX_SPACE ; DEC HL ; DJNZ EXPAND_2 EX DE,HL ; restore HL INC HL ; LD (DF_CC),HL ; RET ;
; THE 'EXPAND TOKENS' SUBROUTINE
mark_094B: TOKENS: PUSH AF ; CALL TOKEN_ADD JR NC,ALL_CHARS BIT 0,(IY+FLAGS-RAMBASE) ; Leading space if set JR NZ,ALL_CHARS XOR A ; A = 0 = ZX_SPACE RST _PRINT_A mark_0959: ALL_CHARS: LD A,(BC) ; AND $3F ; truncate to printable values ??? RST _PRINT_A LD A,(BC) ; INC BC ; ADD A,A ; JR NC,ALL_CHARS POP BC ; BIT 7,B ; RET Z ; CP ZX_COMMA ; $1A == 26 JR Z,TRAIL_SP CP ZX_S ; $38 == 56 RET C ; mark_096D: TRAIL_SP: XOR A ; SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space JP PRINT_SP ; ___ mark_0975: TOKEN_ADD: PUSH HL ; LD HL,TOKEN_TABLE BIT 7,A ; JR Z,TEST_HIGH AND $3F ; mark_097F: TEST_HIGH: CP $43 ; JR NC,FOUND LD B,A ; INC B ; mark_0985: WORDS: BIT 7,(HL) ; INC HL ; JR Z,WORDS DJNZ WORDS BIT 6,A ; JR NZ,COMP_FLAG CP $18 ; mark_0992: COMP_FLAG: CCF ; Complement Carry Flag mark_0993: FOUND: LD B,H ; LD C,L ; POP HL ; RET NC ; LD A,(BC) ; ADD A,$E4 ; RET ;
; THE 'ONE_SPACE' SUBROUTINE
mark_099B:
ONE_SPACE: LD BC,$0001 ;
; THE 'MAKE ROOM' SUBROUTINE
; ; mark_099E:
MAKE_ROOM: PUSH HL ; CALL TEST_ROOM POP HL ; CALL POINTERS LD HL,(STKEND) ; EX DE,HL ; LDDR ; Copy Bytes RET ;
; THE 'POINTERS' SUBROUTINE
mark_09AD: POINTERS: PUSH AF ; PUSH HL ; LD HL,D_FILE ; LD A,$09 ; mark_09B4: NEXT_PTR: LD E,(HL) ; INC HL ; LD D,(HL) ; EX (SP),HL ; AND A ; SBC HL,DE ; ADD HL,DE ; EX (SP),HL ; JR NC,PTR_DONE PUSH DE ; EX DE,HL ; ADD HL,BC ; EX DE,HL ; LD (HL),D ; DEC HL ; LD (HL),E ; INC HL ; POP DE ; mark_09C8: PTR_DONE: INC HL ; DEC A ; JR NZ,NEXT_PTR EX DE,HL ; POP DE ; POP AF ; AND A ; SBC HL,DE ; LD B,H ; LD C,L ; INC BC ; ADD HL,DE ; EX DE,HL ; RET ;
; THE 'LINE ADDRESS' SUBROUTINE
mark_09D8: LINE_ADDR: PUSH HL ; LD HL,USER_RAM ; LD D,H ; LD E,L ; mark_09DE: NEXT_TEST: POP BC ; CALL CP_LINES RET NC ; PUSH BC ; CALL NEXT_ONE EX DE,HL ; JR NEXT_TEST
; THE 'COMPARE LINE NUMBERS' SUBROUTINE
mark_09EA: CP_LINES: LD A,(HL) ; CP B ; RET NZ ; INC HL ; LD A,(HL) ; DEC HL ; CP C ; RET ;
; THE 'NEXT LINE OR VARIABLE' SUBROUTINE
mark_09F2:
NEXT_ONE: PUSH HL ; LD A,(HL) ; CP $40 ; JR C,LINES BIT 5,A ; JR Z,NEXT_0_4 ; skip forward ADD A,A ; JP M,NEXT_PLUS_FIVE CCF ; Complement Carry Flag mark_0A01: NEXT_PLUS_FIVE: LD BC,$0005 ; JR NC,NEXT_LETT LD C,$11 ; 17 mark_0A08: NEXT_LETT: RLA ; INC HL ; LD A,(HL) ; JR NC,NEXT_LETT ; loop JR NEXT_ADD ; ___ mark_0A0F: LINES: INC HL ; mark_0A10: NEXT_0_4: INC HL ; BC = word at HL++ LD C,(HL) ; INC HL ; LD B,(HL) ; INC HL ; mark_0A15: NEXT_ADD: ADD HL,BC ; POP DE ;
; THE 'DIFFERENCE' SUBROUTINE
mark_0A17:
DIFFER: AND A ; SBC HL,DE ; LD B,H ; BC := (HL-DE) LD C,L ; ADD HL,DE ; EX DE,HL ; DE := old HL ??? RET ;
; THE 'LINE_ENDS' SUBROUTINE
mark_0A1F:
LINE_ENDS: LD B,(IY+DF_SZ-RAMBASE) PUSH BC ; CALL B_LINES POP BC ; DEC B ; JR B_LINES
; THE 'CLS' COMMAND ROUTINE
mark_0A2A: CLS: LD B,CHARS_VERTICAL ; number of lines to clear. $18 = 24 originally. mark_0A2C: B_LINES: RES 1,(IY+FLAGS-RAMBASE) ; Signal printer not in use LD C,CHARS_HORIZONTAL+1 ; $21 ; extra 1 is for HALT opcode ? PUSH BC ; CALL LOC_ADDR POP BC ; LD A,(RAMTOP+1) ; is RAMTOP_hi CP $4D ; JR C,COLLAPSED ; ; If RAMTOP less then 4D00, RAM less than D00 = 3.25 K, ; uses collapsed display. ; SET 7,(IY+S_POSN_y-RAMBASE) mark_0A42: CLEAR_LOC: XOR A ; prepare a space CALL PRINT_SP ; prints a space LD HL,(S_POSN) ; LD A,L ; OR H ; AND $7E ; JR NZ,CLEAR_LOC JP LOC_ADDR ; ___ mark_0A52: COLLAPSED: LD D,H ; DE := HL LD E,L ; DEC HL ; LD C,B ; LD B,0 ; Will loop 256 times LDIR ; Copy Bytes LD HL,(VARS) ;
; THE 'RECLAIMING' SUBROUTINES
mark_0A5D:
RECLAIM_1: CALL DIFFER mark_0A60: RECLAIM_2: PUSH BC ; LD A,B ; CPL ; LD B,A ; LD A,C ; CPL ; LD C,A ; INC BC ; CALL POINTERS EX DE,HL ; POP HL ; ADD HL,DE ; PUSH DE ; LDIR ; Copy Bytes POP HL ; RET ;
; THE 'E_LINE NUMBER' SUBROUTINE
mark_0A73: E_LINE_NUM: LD HL,(E_LINE) ; CALL TEMP_PTR2 RST _GET_CHAR BIT 5,(IY+FLAGX-RAMBASE) RET NZ ; LD HL,MEM_0_1st ; LD (STKEND),HL ; CALL INT_TO_FP CALL FP_TO_BC JR C,NO_NUMBER ; to NO_NUMBER LD HL,-10000 ; $D8F0 ; value '-10000' ADD HL,BC ; mark_0A91: NO_NUMBER: JP C,REPORT_C ; to REPORT_C CP A ; JP SET_MIN
; THE 'REPORT AND LINE NUMBER' PRINTING SUBROUTINES
mark_0A98: OUT_NUM: PUSH DE ; PUSH HL ; XOR A ; BIT 7,B ; JR NZ,UNITS LD H,B ; HL := BC LD L,C ; LD E,$FF ; JR THOUSAND ; ___ mark_0AA5: OUT_NO: PUSH DE ; LD D,(HL) ; INC HL ; LD E,(HL) ; PUSH HL ; EX DE,HL ; LD E,ZX_SPACE ; set E to leading space. mark_0AAD: THOUSAND: LD BC,-1000 ; $FC18 ; CALL OUT_DIGIT LD BC,-100 ; $FF9C ; CALL OUT_DIGIT LD C,-10 ; $F6 ; B is already FF, so saves a byte. CALL OUT_DIGIT LD A,L ; mark_0ABF: UNITS: CALL OUT_CODE POP HL ; POP DE ; RET ;
; THE 'UNSTACK_Z' SUBROUTINE
; This subroutine is used to return early from a routine when checking syntax. ; On the ZX81 the same routines that execute commands also check the syntax ; on line entry. This enables precise placement of the error marker in a line ; that fails syntax. ; The sequence CALL SYNTAX_Z ; RET Z can be replaced by a call to this routine ; although it has not replaced every occurrence of the above two instructions. ; Even on the ZX80 this routine was not fully utilized. mark_0AC5: UNSTACK_Z: CALL SYNTAX_Z ; resets the ZERO flag if ; checking syntax. POP HL ; drop the return address. RET Z ; return to previous calling routine if ; checking syntax. JP (HL) ; else jump to the continuation address in ; the calling routine as RET would have done.
; THE 'LPRINT' COMMAND ROUTINE
; ; mark_0ACB: LPRINT: SET 1,(IY+FLAGS-RAMBASE) ; Signal printer in use
; THE 'PRINT' COMMAND ROUTINE
mark_0ACF:
PRINT: LD A,(HL) ; CP ZX_NEWLINE ; JP Z,PRINT_END ; to PRINT_END mark_0AD5: PRINT_1: SUB ZX_COMMA ; $1A == 26 ADC A,$00 ; JR Z,SPACING ; to SPACING ; ; Compare with AT, ; less comma recently subtracted. ; CP ZX_AT-ZX_COMMA ; $A7 == 167 JR NZ,NOT_AT ; RST _NEXT_CHAR CALL CLASS_6 CP ZX_COMMA ; $1A = 26 JP NZ,REPORT_C ; RST _NEXT_CHAR CALL CLASS_6 CALL SYNTAX_ON RST _FP_CALC ;; DEFB __exchange ;; DEFB __end_calc ;; CALL STK_TO_BC CALL PRINT_AT JR PRINT_ON ; ___ mark_0AFA: NOT_AT: CP ZX_TAB-ZX_COMMA ; $A8 == 168 JR NZ,NOT_TAB RST _NEXT_CHAR CALL CLASS_6 CALL SYNTAX_ON CALL STK_TO_A JP NZ,REPORT_B AND $1F ; truncate to 0 to 31 characters ??? LD C,A ; BIT 1,(IY+FLAGS-RAMBASE) ; Is printer in use JR Z,TAB_TEST SUB (IY+PR_CC-RAMBASE) SET 7,A ; ADD A,$3C ; 60 CALL NC,COPY_BUFF mark_0B1E: TAB_TEST: ADD A,(IY+S_POSN_x-RAMBASE) ; screen position X CP CHARS_HORIZONTAL+1 ; 33 (characters horizontal plus newline ???) LD A,(S_POSN_y) ; screen position Y SBC A,1 ; CALL TEST_VAL SET 0,(IY+FLAGS-RAMBASE) ; sv FLAGS - Suppress leading space JR PRINT_ON ; ___ mark_0B31: NOT_TAB: CALL SCANNING CALL PRINT_STK mark_0B37: PRINT_ON: RST _GET_CHAR SUB ZX_COMMA ; $1A ADC A,0 ; JR Z,SPACING CALL CHECK_END JP PRINT_END ; ___ mark_0B44: SPACING: CALL NC,FIELD RST _NEXT_CHAR CP ZX_NEWLINE ; RET Z ; JP PRINT_1 ; ___ mark_0B4E: SYNTAX_ON: CALL SYNTAX_Z RET NZ ; POP HL ; JR PRINT_ON ; ___ mark_0B55: PRINT_STK: CALL UNSTACK_Z BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result? CALL Z,STK_FETCH JR Z,PR_STR_4 JP PRINT_FP ; jump forward ; ___ mark_0B64: PR_STR_1: LD A,ZX_QUOTE ; $0B mark_0B66: PR_STR_2: RST _PRINT_A mark_0B67: PR_STR_3: LD DE,(X_PTR) ; mark_0B6B: PR_STR_4: LD A,B ; OR C ; DEC BC ; RET Z ; LD A,(DE) ; INC DE ; LD (X_PTR),DE ; BIT 6,A ; JR Z,PR_STR_2 CP $C0 ; JR Z,PR_STR_1 PUSH BC ; CALL TOKENS POP BC ; JR PR_STR_3 ; ___ mark_0B84: PRINT_END: CALL UNSTACK_Z LD A,ZX_NEWLINE ; RST _PRINT_A RET ; ; ___ mark_0B8B: FIELD: CALL UNSTACK_Z SET 0,(IY+FLAGS-RAMBASE) ; Suppress leading space XOR A ; RST _PRINT_A LD BC,(S_POSN) ; LD A,C ; BIT 1,(IY+FLAGS-RAMBASE) ; Is printer in use JR Z,CENTRE LD A,$5D ; SUB (IY+PR_CC-RAMBASE) mark_0BA4: CENTRE: LD C,$11 ; CP C ; JR NC,RIGHT LD C,$01 ; mark_0BAB: RIGHT: CALL SET_FIELD RET ;
; THE 'PLOT AND UNPLOT' COMMAND ROUTINES
mark_0BAF: PLOT_UNPLOT: ; ; Of the 24 lines, only top 22 ar used for plotting. ; CALL STK_TO_BC LD (COORDS_x),BC ; ;; LD A,$2B ; originally $2B == 32+11 = 43 = 2*22-1 LD A,2*(CHARS_VERTICAL-2)-1 ; SUB B ; JP C,REPORT_B LD B,A ; LD A,$01 ; SRA B ; JR NC,COLUMNS LD A,$04 ; mark_0BC5: COLUMNS: SRA C ; JR NC,FIND_ADDR RLCA ; mark_0BCA: FIND_ADDR: PUSH AF ; CALL PRINT_AT LD A,(HL) ; RLCA ; CP ZX_BRACKET_LEFT ; $10 JR NC,TABLE_PTR RRCA ; JR NC,SQ_SAVED XOR $8F ; mark_0BD9: SQ_SAVED: LD B,A ; mark_0BDA: TABLE_PTR: LD DE,P_UNPLOT ; Address: P_UNPLOT LD A,(T_ADDR) ; get T_ADDR_lo SUB E ; JP M,PLOT POP AF ; CPL ; AND B ; JR UNPLOT ; ___ mark_0BE9: PLOT: POP AF ; OR B ; mark_0BEB: UNPLOT: CP 8 ; Only apply to graphic characters (0 to 7) JR C,PLOT_END XOR $8F ; binary 1000 1111 mark_0BF1: PLOT_END: EXX ; RST _PRINT_A EXX ; RET ;
; THE 'STACK_TO_BC' SUBROUTINE
mark_0BF5:
STK_TO_BC: CALL STK_TO_A LD B,A ; PUSH BC ; CALL STK_TO_A LD E,C ; POP BC ; LD D,C ; LD C,A ; RET ;
; THE 'STACK_TO_A' SUBROUTINE
mark_0C02: STK_TO_A: CALL FP_TO_A JP C,REPORT_B LD C,$01 ; RET Z ; LD C,$FF ; RET ;
; THE 'SCROLL' SUBROUTINE
mark_0C0E: SCROLL: LD B,(IY+DF_SZ-RAMBASE) LD C,CHARS_HORIZONTAL+1 ; CALL LOC_ADDR CALL ONE_SPACE LD A,(HL) ; LD (DE),A ; INC (IY+S_POSN_y-RAMBASE) LD HL,(D_FILE) ; INC HL ; LD D,H ; LD E,L ; CPIR ; JP RECLAIM_1
; THE 'SYNTAX' TABLES
; i) The Offset table mark_0C29: offset_t: DEFB P_LPRINT - $ ; 8B offset DEFB P_LLIST - $ ; 8D offset DEFB P_STOP - $ ; 2D offset DEFB P_SLOW - $ ; 7F offset DEFB P_FAST - $ ; 81 offset DEFB P_NEW - $ ; 49 offset DEFB P_SCROLL - $ ; 75 offset DEFB P_CONT - $ ; 5F offset DEFB P_DIM - $ ; 40 offset DEFB P_REM - $ ; 42 offset DEFB P_FOR - $ ; 2B offset DEFB P_GOTO - $ ; 17 offset DEFB P_GOSUB - $ ; 1F offset DEFB P_INPUT - $ ; 37 offset DEFB P_LOAD - $ ; 52 offset DEFB P_LIST - $ ; 45 offset DEFB P_LET - $ ; 0F offset DEFB P_PAUSE - $ ; 6D offset DEFB P_NEXT - $ ; 2B offset DEFB P_POKE - $ ; 44 offset DEFB P_PRINT - $ ; 2D offset DEFB P_PLOT - $ ; 5A offset DEFB P_RUN - $ ; 3B offset DEFB P_SAVE - $ ; 4C offset DEFB P_RAND - $ ; 45 offset DEFB P_IF - $ ; 0D offset DEFB P_CLS - $ ; 52 offset DEFB P_UNPLOT - $ ; 5A offset DEFB P_CLEAR - $ ; 4D offset DEFB P_RETURN - $ ; 15 offset DEFB P_COPY - $ ; 6A offset
; ii) The parameter table. mark_0C48: P_LET: DEFB _CLASS_01 ; A variable is required. DEFB ZX_EQUAL ; Separator: '=' DEFB _CLASS_02 ; An expression, numeric or string, ; must follow. mark_0C4B: P_GOTO: DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_00 ; No further operands. DEFW GOTO mark_0C4F: P_IF: DEFB _CLASS_06 ; A numeric expression must follow. DEFB ZX_THEN ; Separator: 'THEN' DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW IF mark_0C54: P_GOSUB: DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_00 ; No further operands. DEFW GOSUB mark_0C58: P_STOP: DEFB _CLASS_00 ; No further operands. DEFW STOP mark_0C5B: P_RETURN: DEFB _CLASS_00 ; No further operands. DEFW RETURN mark_0C5E: P_FOR: DEFB _CLASS_04 ; A single character variable must ; follow. DEFB ZX_EQUAL ; Separator: '=' DEFB _CLASS_06 ; A numeric expression must follow. DEFB ZX_TO ; Separator: 'TO' DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW FOR mark_0C66: P_NEXT: DEFB _CLASS_04 ; A single character variable must ; follow. DEFB _CLASS_00 ; No further operands. DEFW NEXT mark_0C6A: P_PRINT: DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW PRINT ; not LPRINT ??? mark_0C6D: P_INPUT: DEFB _CLASS_01 ; A variable is required. DEFB _CLASS_00 ; No further operands. DEFW INPUT mark_0C71: P_DIM: DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW DIM mark_0C74: P_REM: DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW REM mark_0C77: P_NEW: DEFB _CLASS_00 ; No further operands. DEFW NEW mark_0C7A: P_RUN: DEFB _CLASS_03 ; A numeric expression may follow ; else default to zero. DEFW RUN mark_0C7D: P_LIST: DEFB _CLASS_03 ; A numeric expression may follow ; else default to zero. DEFW LIST mark_0C80: P_POKE: DEFB _CLASS_06 ; A numeric expression must follow. DEFB ZX_COMMA ; Separator: ',' DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_00 ; No further operands. DEFW POKE mark_0C86: P_RAND: DEFB _CLASS_03 ; A numeric expression may follow ; else default to zero. DEFW RAND mark_0C89: P_LOAD: DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW LOAD mark_0C8C: P_SAVE: DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW SAVE mark_0C8F: P_CONT: DEFB _CLASS_00 ; No further operands. DEFW CONT mark_0C92: P_CLEAR: DEFB _CLASS_00 ; No further operands. DEFW CLEAR mark_0C95: P_CLS: DEFB _CLASS_00 ; No further operands. DEFW CLS mark_0C98: P_PLOT: DEFB _CLASS_06 ; A numeric expression must follow. DEFB ZX_COMMA ; Separator: ',' DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_00 ; No further operands. DEFW PLOT_UNPLOT mark_0C9E: P_UNPLOT: DEFB _CLASS_06 ; A numeric expression must follow. DEFB ZX_COMMA ; Separator: ',' DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_00 ; No further operands. DEFW PLOT_UNPLOT mark_0CA4: P_SCROLL: DEFB _CLASS_00 ; No further operands. DEFW SCROLL mark_0CA7: P_PAUSE: DEFB _CLASS_06 ; A numeric expression must follow. DEFB _CLASS_00 ; No further operands. DEFW PAUSE mark_0CAB: P_SLOW: DEFB _CLASS_00 ; No further operands. DEFW SLOW mark_0CAE: P_FAST: DEFB _CLASS_00 ; No further operands. DEFW FAST mark_0CB1: P_COPY: DEFB _CLASS_00 ; No further operands. DEFW COPY mark_0CB4: P_LPRINT: DEFB _CLASS_05 ; Variable syntax checked entirely ; by routine. DEFW LPRINT mark_0CB7: P_LLIST: DEFB _CLASS_03 ; A numeric expression may follow ; else default to zero. DEFW LLIST
; THE 'LINE SCANNING' ROUTINE
mark_0CBA: LINE_SCAN: LD (IY+FLAGS-RAMBASE),1 CALL E_LINE_NUM mark_0CC1: LINE_RUN: CALL SET_MIN LD HL,ERR_NR ; LD (HL),$FF ; LD HL,FLAGX ; BIT 5,(HL) ; JR Z,LINE_NULL CP $E3 ; 'STOP' ? LD A,(HL) ; JP NZ,INPUT_REP CALL SYNTAX_Z RET Z ; RST _ERROR_1 DEFB $0C ; Error Report: BREAK - CONT repeats
; THE 'STOP' COMMAND ROUTINE
; ; mark_0CDC: STOP: RST _ERROR_1 DEFB $08 ; Error Report: STOP statement ; ___ ; the interpretation of a line continues with a check for just spaces ; followed by a carriage return. ; The IF command also branches here with a true value to execute the ; statement after the THEN but the statement can be null so ; 10 IF 1 = 1 THEN ; passes syntax (on all ZX computers). mark_0CDE: LINE_NULL: RST _GET_CHAR LD B,$00 ; prepare to index - early. CP ZX_NEWLINE ; compare to NEWLINE. RET Z ; return if so. LD C,A ; transfer character to C. RST _NEXT_CHAR ; advances. LD A,C ; character to A SUB $E1 ; subtract 'LPRINT' - lowest command. JR C,REPORT_C2 ; forward if less LD C,A ; reduced token to C LD HL,offset_t ; set HL to address of offset table. ADD HL,BC ; index into offset table. LD C,(HL) ; fetch offset ADD HL,BC ; index into parameter table. JR GET_PARAM ; ___ mark_0CF4: SCAN_LOOP: LD HL,(T_ADDR) ; ; -> Entry Point to Scanning Loop mark_0CF7: GET_PARAM: LD A,(HL) ; INC HL ; LD (T_ADDR),HL ; LD BC,SCAN_LOOP PUSH BC ; is pushed on machine stack. LD C,A ; CP ZX_QUOTE ; $0B JR NC,SEPARATOR LD HL,class_tbl ; class_tbl - the address of the class table. LD B,$00 ; ADD HL,BC ; LD C,(HL) ; ADD HL,BC ; PUSH HL ; RST _GET_CHAR RET ; indirect jump to class routine and ; by subsequent RET to SCAN_LOOP.
; THE 'SEPARATOR' ROUTINE
mark_0D10: SEPARATOR: RST _GET_CHAR CP C ; JR NZ,REPORT_C2 ; 'Nonsense in BASIC' RST _NEXT_CHAR RET ; return
; THE 'COMMAND CLASS' TABLE
; mark_0D16: class_tbl: DEFB CLASS_0 - $ ; 17 offset to; Address: CLASS_0 DEFB CLASS_1 - $ ; 25 offset to; Address: CLASS_1 DEFB CLASS_2 - $ ; 53 offset to; Address: CLASS_2 DEFB CLASS_3 - $ ; 0F offset to; Address: CLASS_3 DEFB CLASS_4 - $ ; 6B offset to; Address: CLASS_4 DEFB CLASS_5 - $ ; 13 offset to; Address: CLASS_5 DEFB CLASS_6 - $ ; 76 offset to; Address: CLASS_6
; THE 'CHECK END' SUBROUTINE
; Check for end of statement and that no spurious characters occur after ; a correctly parsed statement. Since only one statement is allowed on each ; line, the only character that may follow a statement is a NEWLINE. ; mark_0D1D: CHECK_END: CALL SYNTAX_Z RET NZ ; return in runtime. POP BC ; else drop return address. mark_0D22: CHECK_2: LD A,(HL) ; fetch character. CP ZX_NEWLINE ; compare to NEWLINE. RET Z ; return if so. mark_0D26: REPORT_C2: JR REPORT_C ; 'Nonsense in BASIC'
; COMMAND CLASSES 03, 00, 05
mark_0D28: CLASS_3: CP ZX_NEWLINE ; CALL NUMBER_TO_STK mark_0D2D: CLASS_0: CP A ; mark_0D2E: CLASS_5: POP BC ; CALL Z,CHECK_END EX DE,HL ; LD HL,(T_ADDR) ; LD C,(HL) ; INC HL ; LD B,(HL) ; EX DE,HL ; mark_0D3A: CLASS_END: PUSH BC ; RET ;
; COMMAND CLASSES 01, 02, 04, 06
mark_0D3C:
CLASS_1: CALL LOOK_VARS mark_0D3F: CLASS_4_2: LD (IY+FLAGX-RAMBASE),$00 JR NC,SET_STK SET 1,(IY+FLAGX-RAMBASE) JR NZ,SET_STRLN mark_0D4B: REPORT_2: RST _ERROR_1 DEFB $01 ; Error Report: Variable not found ; ___ mark_0D4D: SET_STK: CALL Z,STK_VAR BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result? JR NZ,SET_STRLN XOR A ; CALL SYNTAX_Z CALL NZ,STK_FETCH LD HL,FLAGX ; OR (HL) ; LD (HL),A ; EX DE,HL ; mark_0D63: SET_STRLN: LD (STRLEN),BC ; LD (DEST),HL ; ; THE 'REM' COMMAND ROUTINE mark_0D6A: REM: RET ; ; ___ mark_0D6B: CLASS_2: POP BC ; LD A,(FLAGS) ; sv mark_0D6F: INPUT_REP: PUSH AF ; CALL SCANNING POP AF ; LD BC,LET ; Address: LET LD D,(IY+FLAGS-RAMBASE) XOR D ; AND $40 ; JR NZ,REPORT_C ; to REPORT_C BIT 7,D ; JR NZ,CLASS_END ; to CLASS_END JR CHECK_2 ; to CHECK_2 ; ___ mark_0D85: CLASS_4: CALL LOOK_VARS PUSH AF ; LD A,C ; OR $9F ; INC A ; JR NZ,REPORT_C ; to REPORT_C POP AF ; JR CLASS_4_2 ; to CLASS_4_2 ; ___ mark_0D92: CLASS_6: CALL SCANNING BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result? RET NZ ; mark_0D9A: REPORT_C: RST _ERROR_1 DEFB $0B ; Error Report: Nonsense in BASIC
; THE 'NUMBER TO STACK' SUBROUTINE
; ; mark_0D9C:
NUMBER_TO_STK: JR NZ,CLASS_6 ; back to CLASS_6 with a non-zero number. CALL SYNTAX_Z RET Z ; return if checking syntax. ; in runtime a zero default is placed on the calculator stack. RST _FP_CALC ;; DEFB __stk_zero ;; DEFB __end_calc ;; RET ; return.
; THE 'SYNTAX_Z' SUBROUTINE
; This routine returns with zero flag set if checking syntax. ; Calling this routine uses three instruction bytes compared to four if the ; bit test is implemented inline. mark_0DA6: SYNTAX_Z: BIT 7,(IY+FLAGS-RAMBASE) ; checking syntax only? RET ; return.
; THE 'IF' COMMAND ROUTINE
; In runtime, the class routines have evaluated the test expression and ; the result, true or false, is on the stack. mark_0DAB:
IF: CALL SYNTAX_Z JR Z,IF_END ; forward if checking syntax ; else delete the Boolean value on the calculator stack. RST _FP_CALC ;; DEFB __delete ;; DEFB __end_calc ;; ; register DE points to exponent of floating point value. LD A,(DE) ; fetch exponent. AND A ; test for zero - FALSE. RET Z ; return if so. mark_0DB6: IF_END: JP LINE_NULL ; jump back
; THE 'FOR' COMMAND ROUTINE
; ; mark_0DB9: FOR: CP ZX_STEP ; is current character 'STEP' ? JR NZ,F_USE_ONE ; forward if not RST _NEXT_CHAR CALL CLASS_6 ; stacks the number CALL CHECK_END JR F_REORDER ; forward to F_REORDER ; ___ mark_0DC6: F_USE_ONE: CALL CHECK_END RST _FP_CALC ;; DEFB __stk_one ;; DEFB __end_calc ;; mark_0DCC: F_REORDER: RST _FP_CALC ;; v, l, s. DEFB __st_mem_0 ;; v, l, s. DEFB __delete ;; v, l. DEFB __exchange ;; l, v. DEFB __get_mem_0 ;; l, v, s. DEFB __exchange ;; l, s, v. DEFB __end_calc ;; l, s, v. CALL LET LD (MEM),HL ; set MEM to address variable. DEC HL ; point to letter. LD A,(HL) ; SET 7,(HL) ; LD BC,$0006 ; ADD HL,BC ; RLCA ; JR C,F_LMT_STP SLA C ; CALL MAKE_ROOM INC HL ; mark_0DEA: F_LMT_STP: PUSH HL ; RST _FP_CALC ;; DEFB __delete ;; DEFB __delete ;; DEFB __end_calc ;; POP HL ; EX DE,HL ; LD C,$0A ; ten bytes to be moved. LDIR ; copy bytes LD HL,(PPC) ; set HL to system variable PPC current line. EX DE,HL ; transfer to DE, variable pointer to HL. INC DE ; loop start will be this line + 1 at least. LD (HL),E ; INC HL ; LD (HL),D ; CALL NEXT_LOOP ; considers an initial pass. RET NC ; return if possible. ; else program continues from point following matching NEXT. BIT 7,(IY+PPC_hi-RAMBASE) RET NZ ; return if over 32767 ??? LD B,(IY+STRLEN_lo-RAMBASE) ; fetch variable name from STRLEN_lo RES 6,B ; make a true letter. LD HL,(NXTLIN) ; set HL from NXTLIN ; now enter a loop to look for matching next. mark_0E0E: NXTLIN_NO: LD A,(HL) ; fetch high byte of line number. AND $C0 ; mask off low bits $3F JR NZ,FOR_END ; forward at end of program PUSH BC ; save letter CALL NEXT_ONE ; finds next line. POP BC ; restore letter INC HL ; step past low byte INC HL ; past the INC HL ; line length. CALL TEMP_PTR1 ; sets CH_ADD RST _GET_CHAR CP ZX_NEXT ; EX DE,HL ; next line to HL. JR NZ,NXTLIN_NO ; back with no match ; EX DE,HL ; restore pointer. RST _NEXT_CHAR ; advances and gets letter in A. EX DE,HL ; save pointer CP B ; compare to variable name. JR NZ,NXTLIN_NO ; back with mismatch mark_0E2A: FOR_END: LD (NXTLIN),HL ; update system variable NXTLIN RET ; return.
; THE 'NEXT' COMMAND ROUTINE
; ; mark_0E2E:
NEXT: BIT 1,(IY+FLAGX-RAMBASE) JP NZ,REPORT_2 LD HL,(DEST) BIT 7,(HL) JR Z,REPORT_1 INC HL ; LD (MEM),HL ; RST _FP_CALC ;; DEFB __get_mem_0 ;; DEFB __get_mem_2 ;; DEFB __addition ;; DEFB __st_mem_0 ;; DEFB __delete ;; DEFB __end_calc ;; CALL NEXT_LOOP RET C ; LD HL,(MEM) ; LD DE,$000F ; ADD HL,DE ; LD E,(HL) ; INC HL ; LD D,(HL) ; EX DE,HL ; JR GOTO_2 ; ___ mark_0E58: REPORT_1: RST _ERROR_1 DEFB $00 ; Error Report: NEXT without FOR
; THE 'NEXT_LOOP' SUBROUTINE
; ; mark_0E5A:
NEXT_LOOP: RST _FP_CALC ;; DEFB __get_mem_1 ;; DEFB __get_mem_0 ;; DEFB __get_mem_2 ;; DEFB __less_0 ;; DEFB __jump_true ;; DEFB LMT_V_VAL - $ ;; DEFB __exchange ;; mark_0E62: LMT_V_VAL: DEFB __subtract ;; DEFB __greater_0 ;; DEFB __jump_true ;; DEFB IMPOSS - $ ;; DEFB __end_calc ;; AND A ; clear carry flag RET ; return. ; ___ mark_0E69: IMPOSS: DEFB __end_calc ;; SCF ; set carry flag RET ; return.
; THE 'RAND' COMMAND ROUTINE
; The keyword was 'RANDOMISE' on the ZX80, is 'RAND' here on the ZX81 and ; becomes 'RANDOMIZE' on the ZX Spectrum. ; In all invocations the procedure is the same - to set the SEED system variable ; with a supplied integer value or to use a time-based value if no number, or ; zero, is supplied. mark_0E6C:
RAND: CALL FIND_INT LD A,B ; test value OR C ; for zero JR NZ,SET_SEED ; forward if not zero LD BC,(FRAMES) ; fetch value of FRAMES system variable. mark_0E77: SET_SEED: LD (SEED),BC ; update the SEED system variable. RET ; return.
; THE 'CONT' COMMAND ROUTINE
; Another abbreviated command. ROM space was really tight. ; CONTINUE at the line number that was set when break was pressed. ; Sometimes the current line, sometimes the next line. mark_0E7C:
CONT: LD HL,(OLDPPC) ; set HL from system variable OLDPPC JR GOTO_2 ; forward
; THE 'GOTO' COMMAND ROUTINE
; This token also suffered from the shortage of room and there is no space ; getween GO and TO as there is on the ZX80 and ZX Spectrum. The same also ; applies to the GOSUB keyword. mark_0E81: GOTO: CALL FIND_INT LD H,B ; LD L,C ; mark_0E86: GOTO_2: LD A,H ; CP $F0 ; ZX_LIST ??? JR NC,REPORT_B CALL LINE_ADDR LD (NXTLIN),HL ; sv RET ;
; THE 'POKE' COMMAND ROUTINE
mark_0E92: POKE: CALL FP_TO_A JR C,REPORT_B ; forward, with overflow JR Z,POKE_SAVE ; forward, if positive NEG ; negate mark_0E9B: POKE_SAVE: PUSH AF ; preserve value. CALL FIND_INT ; gets address in BC ; invoking the error routine with overflow ; or a negative number. POP AF ; restore value. ; Note. the next two instructions are legacy code from the ZX80 and ; inappropriate here. BIT 7,(IY+ERR_NR-RAMBASE) ; test ERR_NR - is it still $FF ? RET Z ; return with error. LD (BC),A ; update the address contents. RET ; return.
; THE 'FIND INTEGER' SUBROUTINE
mark_0EA7: FIND_INT: CALL FP_TO_BC JR C,REPORT_B ; forward with overflow RET Z ; return if positive (0-65535). mark_0EAD: REPORT_B: RST _ERROR_1 DEFB $0A ; Error Report: Integer out of range ; ; Seems stupid, $0A is 10 but the ERROR_CODE_INTEGER_OUT_OF_RANGE is 11 ; maybe gets incremented ???
; THE 'RUN' COMMAND ROUTINE
mark_0EAF:
RUN: CALL GOTO JP CLEAR
; THE 'GOSUB' COMMAND ROUTINE
mark_0EB5: GOSUB: LD HL,(PPC) ; INC HL ; EX (SP),HL ; PUSH HL ; LD (ERR_SP),SP ; set the error stack pointer - ERR_SP CALL GOTO LD BC,6 ;
; THE 'TEST ROOM' SUBROUTINE
; ; checks ther is room for 36 bytes on the stack ; mark_0EC5: TEST_ROOM: LD HL,(STKEND) ; ADD HL,BC ; HL = STKEND + BC JR C,REPORT_4 EX DE,HL ; DE = STKEND + BC LD HL,$0024 ; 36 decimal ADD HL,DE ; HL = 36 + STKEND + BC SBC HL,SP ; HL = 36 + STKEND + BC - SP RET C ; mark_0ED3: REPORT_4: LD L,3 ; JP ERROR_3
; THE 'RETURN' COMMAND ROUTINE
mark_0ED8: RETURN: POP HL ; EX (SP),HL ; LD A,H ; CP $3E ; JR Z,REPORT_7 LD (ERR_SP),SP ; JR GOTO_2 ; back ; ___ mark_0EE5: REPORT_7: EX (SP),HL ; PUSH HL ; RST _ERROR_1 DEFB 6 ; Error Report: RETURN without GOSUB ; ; Contradicts BASIC manual: ; 7 is ERROR_CODE_RETURN_WITHOUT_GOSUB ; 6 is ERROR_CODE_ARITHMETIC_OVERFLOW ;
; THE 'INPUT' COMMAND ROUTINE
mark_0EE9:
INPUT: BIT 7,(IY+PPC_hi-RAMBASE) JR NZ,REPORT_8 ; to REPORT_8 CALL X_TEMP LD HL,FLAGX ; SET 5,(HL) ; RES 6,(HL) ; LD A,(FLAGS) ; AND $40 ; 64 LD BC,2 ; JR NZ,PROMPT ; to PROMPT LD C,$04 ; mark_0F05: PROMPT: OR (HL) ; LD (HL),A ; RST _BC_SPACES LD (HL),ZX_NEWLINE LD A,C ; RRCA ; RRCA ; JR C,ENTER_CUR LD A,$0B ; ZX_QUOTE ??? LD (DE),A ; DEC HL ; LD (HL),A ; mark_0F14: ENTER_CUR: DEC HL ; LD (HL),ZX_CURSOR ; LD HL,(S_POSN) ; LD (T_ADDR),HL ; POP HL ; JP LOWER ; ___ mark_0F21: REPORT_8: RST _ERROR_1 DEFB 7 ; Error Report: End of file
; THE 'PAUSE' COMMAND ROUTINE
mark_0F23:
FAST: CALL SET_FAST RES 6,(IY+CDFLAG-RAMBASE) RET ; return.
; THE 'SLOW' COMMAND ROUTINE
mark_0F2B: SLOW: SET 6,(IY+CDFLAG-RAMBASE) JP SLOW_FAST
; THE 'PAUSE' COMMAND ROUTINE
mark_0F32: PAUSE: CALL FIND_INT CALL SET_FAST LD H,B ; LD L,C ; CALL DISPLAY_P LD (IY+FRAMES_hi-RAMBASE),$FF CALL SLOW_FAST JR DEBOUNCE
; THE 'BREAK' SUBROUTINE
mark_0F46: BREAK_1: LD A,$7F ; read port $7FFE - keys B,N,M,.,SPACE. IN A,(IO_PORT_KEYBOARD_RD) ; RRA ; carry will be set if space not pressed.
; THE 'DEBOUNCE' SUBROUTINE
mark_0F4B:
DEBOUNCE: RES 0,(IY+CDFLAG-RAMBASE) ; update LD A,$FF ; LD (DEBOUNCE_VAR),A ; update RET ; return.
; THE 'SCANNING' SUBROUTINE
; This recursive routine is where the ZX81 gets its power. ; Provided there is enough memory it can evaluate ; an expression of unlimited complexity. ; Note. there is no unary plus so, as on the ZX80, PRINT +1 gives a syntax error. ; PRINT +1 works on the Spectrum but so too does PRINT + "STRING". mark_0F55:
SCANNING: RST _GET_CHAR LD B,0 ; set B register to zero. PUSH BC ; stack zero as a priority end-marker. mark_0F59: S_LOOP_1: CP ZX_RND JR NZ,S_TEST_PI ; forward, if not, to S_TEST_PI
; THE 'RND' FUNCTION
RND: CALL SYNTAX_Z JR Z,S_JPI_END ; forward if checking syntax to S_JPI_END LD BC,(SEED) ; sv CALL STACK_BC RST _FP_CALC ;; DEFB __stk_one ;; DEFB __addition ;; DEFB __stk_data ;; DEFB $37 ;;Exponent: $87, Bytes: 1 DEFB $16 ;;(+00,+00,+00) DEFB __multiply ;; DEFB __stk_data ;; DEFB $80 ;;Bytes: 3 DEFB $41 ;;Exponent $91 DEFB $00,$00,$80 ;;(+00) DEFB __n_mod_m ;; DEFB __delete ;; DEFB __stk_one ;; DEFB __subtract ;; DEFB __duplicate ;; DEFB __end_calc ;; CALL FP_TO_BC LD (SEED),BC ; update the SEED system variable. LD A,(HL) ; HL addresses the exponent of the last value. AND A ; test for zero JR Z,S_JPI_END ; forward, if so SUB $10 ; else reduce exponent by sixteen LD (HL),A ; thus dividing by 65536 for last value. mark_0F8A: S_JPI_END: JR S_PI_END ; forward ; ___ mark_0F8C: S_TEST_PI: CP ZX_PI ; the 'PI' character JR NZ,S_TST_INK ; forward, if not
; THE 'PI' EVALUATION
CALL SYNTAX_Z JR Z,S_PI_END ; forward if checking syntax RST _FP_CALC ;; DEFB __stk_half_pi ;; DEFB __end_calc ;; INC (HL) ; double the exponent giving PI on the stack. mark_0F99: S_PI_END: RST _NEXT_CHAR ; advances character pointer. JP S_NUMERIC ; jump forward to set the flag ; to signal numeric result before advancing. ; ___ mark_0F9D: S_TST_INK: CP ZX_INKEY_STR ; JR NZ,S_ALPHANUM ; forward, if not
; THE 'INKEY$' EVALUATION
CALL KEYBOARD LD B,H ; LD C,L ; LD D,C ; INC D ; CALL NZ,DECODE LD A,D ; ADC A,D ; LD B,D ; LD C,A ; EX DE,HL ; JR S_STRING ; forward ; ___ mark_0FB2: S_ALPHANUM: CALL ALPHANUM JR C,S_LTR_DGT ; forward, if alphanumeric CP ZX_PERIOD ; is character a '.' ? JP Z,S_DECIMAL ; jump forward if so LD BC,$09D8 ; prepare priority 09, operation 'subtract' CP ZX_MINUS ; is character unary minus '-' ? JR Z,S_PUSH_PO ; forward, if so CP ZX_BRACKET_LEFT ; is character a '(' ? JR NZ,S_QUOTE ; forward if not CALL CH_ADD_PLUS_1 ; advances character pointer. CALL SCANNING ; recursively call to evaluate the sub_expression. CP ZX_BRACKET_RIGHT; is subsequent character a ')' ? JR NZ,S_RPT_C ; forward if not CALL CH_ADD_PLUS_1 ; advances. JR S_J_CONT_3 ; relative jump to S_JP_CONT3 and then S_CONT3 ; ___ ; consider a quoted string e.g. PRINT "Hooray!" ; Note. quotes are not allowed within a string. mark_0FD6: S_QUOTE: CP ZX_QUOTE ; is character a quote (") ? JR NZ,S_FUNCTION ; forward, if not CALL CH_ADD_PLUS_1 ; advances PUSH HL ; * save start of string. JR S_QUOTE_S ; forward ; ___ mark_0FE0: S_Q_AGAIN: CALL CH_ADD_PLUS_1 mark_0FE3: S_QUOTE_S: CP ZX_QUOTE ; is character a '"' ? JR NZ,S_Q_NL ; forward if not to S_Q_NL POP DE ; * retrieve start of string AND A ; prepare to subtract. SBC HL,DE ; subtract start from current position. LD B,H ; transfer this length LD C,L ; to the BC register pair. mark_0FED: S_STRING: LD HL,FLAGS ; address system variable FLAGS RES 6,(HL) ; signal string result BIT 7,(HL) ; test if checking syntax. CALL NZ,STK_STO_STR ; in run-time stacks the ; string descriptor - start DE, length BC. RST _NEXT_CHAR ; advances pointer. mark_0FF8: S_J_CONT_3: JP S_CONT_3 ; ___ ; A string with no terminating quote has to be considered. mark_0FFB: S_Q_NL: CP ZX_NEWLINE JR NZ,S_Q_AGAIN ; loop back if not mark_0FFF: S_RPT_C: JP REPORT_C ; ___ mark_1002: S_FUNCTION: SUB $C4 ; subtract 'CODE' reducing codes ; CODE thru '<>' to range $00 - $XX JR C,S_RPT_C ; back, if less ; test for NOT the last function in character set. LD BC,$04EC ; prepare priority $04, operation 'not' CP $13 ; compare to 'NOT' ( - CODE) JR Z,S_PUSH_PO ; forward, if so JR NC,S_RPT_C ; back with anything higher ; else is a function 'CODE' thru 'CHR$' LD B,$10 ; priority sixteen binds all functions to ; arguments removing the need for brackets. ADD A,$D9 ; add $D9 to give range $D9 thru $EB ; bit 6 is set to show numeric argument. ; bit 7 is set to show numeric result. ; now adjust these default argument/result indicators. LD C,A ; save code in C CP $DC ; separate 'CODE', 'VAL', 'LEN' JR NC,S_NUMBER_TO_STRING ; skip forward if string operand RES 6,C ; signal string operand. mark_101A: S_NUMBER_TO_STRING: CP $EA ; isolate top of range 'STR$' and 'CHR$' JR C,S_PUSH_PO ; skip forward with others RES 7,C ; signal string result. mark_1020: S_PUSH_PO: PUSH BC ; push the priority/operation RST _NEXT_CHAR JP S_LOOP_1 ; jump back ; ___ mark_1025: S_LTR_DGT: CP ZX_A ; compare to 'A'. JR C,S_DECIMAL ; forward if less to S_DECIMAL CALL LOOK_VARS JP C,REPORT_2 ; back if not found ; a variable is always 'found' when checking ; syntax. CALL Z,STK_VAR ; stacks string parameters or ; returns cell location if numeric. LD A,(FLAGS) ; fetch FLAGS CP $C0 ; compare to numeric result/numeric operand JR C,S_CONT_2 ; forward if not numeric INC HL ; address numeric contents of variable. LD DE,(STKEND) ; set destination to STKEND CALL MOVE_FP ; stacks the five bytes EX DE,HL ; transfer new free location from DE to HL. LD (STKEND),HL ; update STKEND system variable. JR S_CONT_2 ; forward ; ___ ; The Scanning Decimal routine is invoked when a decimal point or digit is ; found in the expression. ; When checking syntax, then the 'hidden floating point' form is placed ; after the number in the BASIC line. ; In run-time, the digits are skipped and the floating point number is picked ; up. mark_1047: S_DECIMAL: CALL SYNTAX_Z JR NZ,S_STK_DEC ; forward in run-time CALL DEC_TO_FP RST _GET_CHAR ; advances HL past digits LD BC,$0006 ; six locations are required. CALL MAKE_ROOM INC HL ; point to first new location LD (HL),$7E ; insert the number marker 126 decimal. INC HL ; increment EX DE,HL ; transfer destination to DE. LD HL,(STKEND) ; set HL from STKEND which points to the ; first location after the 'last value' LD C,$05 ; five bytes to move. AND A ; clear carry. SBC HL,BC ; subtract five pointing to 'last value'. LD (STKEND),HL ; update STKEND thereby 'deleting the value. LDIR ; copy the five value bytes. EX DE,HL ; basic pointer to HL which may be white-space ; following the number. DEC HL ; now points to last of five bytes. CALL TEMP_PTR1 ; advances the character ; address skipping any white-space. JR S_NUMERIC ; forward ; to signal a numeric result. ; ___ ; In run-time the branch is here when a digit or point is encountered. mark_106F: S_STK_DEC: RST _NEXT_CHAR CP $7E ; compare to 'number marker' JR NZ,S_STK_DEC ; loop back until found ; skipping all the digits. INC HL ; point to first of five hidden bytes. LD DE,(STKEND) ; set destination from STKEND system variable CALL MOVE_FP ; stacks the number. LD (STKEND),DE ; update system variable STKEND. LD (CH_ADD),HL ; update system variable CH_ADD. mark_1083: S_NUMERIC: SET 6,(IY+FLAGS-RAMBASE) ; Signal numeric result mark_1087: S_CONT_2: RST _GET_CHAR mark_1088: S_CONT_3: CP ZX_BRACKET_LEFT ; compare to opening bracket '(' JR NZ,S_OPERTR ; forward if not BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result? JR NZ,S_LOOP ; forward if numeric ; else is a string CALL SLICING RST _NEXT_CHAR JR S_CONT_3 ; back ; ___ ; the character is now manipulated to form an equivalent in the table of ; calculator literals. This is quite cumbersome and in the ZX Spectrum a ; simple look-up table was introduced at this point. mark_1098: S_OPERTR: LD BC,$00C3 ; prepare operator 'subtract' as default. ; also set B to zero for later indexing. CP ZX_GREATER_THAN ; is character '>' ? JR C,S_LOOP ; forward if less, as ; we have reached end of meaningful expression SUB ZX_MINUS ; is character '-' ? JR NC,SUBMLTDIV ; forward with - * / and '**' '<>' ADD A,13 ; increase others by thirteen ; $09 '>' thru $0C '+' JR GET_PRIO ; forward ; ___ mark_10A7: SUBMLTDIV: CP $03 ; isolate $00 '-', $01 '*', $02 '/' JR C,GET_PRIO ; forward if so ; else possibly originally $D8 '**' thru $DD '<>' already reduced by $16 SUB $C2 ; giving range $00 to $05 JR C,S_LOOP ; forward if less CP $06 ; test the upper limit for nonsense also JR NC,S_LOOP ; forward if so ADD A,$03 ; increase by 3 to give combined operators of ; $00 '-' ; $01 '*' ; $02 '/' ; $03 '**' ; $04 'OR' ; $05 'AND' ; $06 '<=' ; $07 '>=' ; $08 '<>' ; $09 '>' ; $0A '<' ; $0B '=' ; $0C '+' mark_10B5: GET_PRIO: ADD A,C ; add to default operation 'sub' ($C3) LD C,A ; and place in operator byte - C. LD HL,tbl_pri - $C3 ; theoretical base of the priorities table. ADD HL,BC ; add C ( B is zero) LD B,(HL) ; pick up the priority in B mark_10BC: S_LOOP: POP DE ; restore previous LD A,D ; load A with priority. CP B ; is present priority higher JR C,S_TIGHTER ; forward if so to S_TIGHTER AND A ; are both priorities zero JP Z,GET_CHAR ; exit if zero via GET_CHAR PUSH BC ; stack present values PUSH DE ; stack last values CALL SYNTAX_Z JR Z,S_SYNTEST ; forward is checking syntax LD A,E ; fetch last operation AND $3F ; mask off the indicator bits to give true ; calculator literal. LD B,A ; place in the B register for BERG ; perform the single operation RST _FP_CALC ;; DEFB __fp_calc_2 ;; DEFB __end_calc ;; JR S_RUNTEST ; forward ; ___ mark_10D5: S_SYNTEST: LD A,E ; transfer masked operator to A XOR (IY+FLAGS-RAMBASE) ; XOR with FLAGS like results will reset bit 6 AND $40 ; test bit 6 mark_10DB: S_RPORT_C: JP NZ,REPORT_C ; back if results do not agree. ; ___ ; in run-time impose bit 7 of the operator onto bit 6 of the FLAGS mark_10DE: S_RUNTEST: POP DE ; restore last operation. LD HL,FLAGS ; address system variable FLAGS SET 6,(HL) ; presume a numeric result BIT 7,E ; test expected result in operation JR NZ,S_LOOPEND ; forward if numeric RES 6,(HL) ; reset to signal string result mark_10EA: S_LOOPEND: POP BC ; restore present values JR S_LOOP ; back ; ___ mark_10ED: S_TIGHTER: PUSH DE ; push last values and consider these LD A,C ; get the present operator. BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result? JR NZ,S_NEXT ; forward if numeric to S_NEXT AND $3F ; strip indicator bits to give clear literal. ADD A,$08 ; add eight - augmenting numeric to equivalent ; string literals. LD C,A ; place plain literal back in C. CP $10 ; compare to 'AND' JR NZ,S_NOT_AND ; forward if not SET 6,C ; set the numeric operand required for 'AND' JR S_NEXT ; forward to S_NEXT ; ___ mark_1102: S_NOT_AND: JR C,S_RPORT_C ; back if less than 'AND' ; Nonsense if '-', '*' etc. CP __strs_add ; compare to 'strs_add' literal JR Z,S_NEXT ; forward if so signaling string result SET 7,C ; set bit to numeric (Boolean) for others. mark_110A: S_NEXT: PUSH BC ; stack 'present' values RST _NEXT_CHAR JP S_LOOP_1 ; jump back
; THE 'TABLE OF PRIORITIES'
mark_110F: tbl_pri: DEFB 6 ; '-' DEFB 8 ; '*' DEFB 8 ; '/' DEFB 10 ; '**' DEFB 2 ; 'OR' DEFB 3 ; 'AND' DEFB 5 ; '<=' DEFB 5 ; '>=' DEFB 5 ; '<>' DEFB 5 ; '>' DEFB 5 ; '<' DEFB 5 ; '=' DEFB 6 ; '+'
; THE 'LOOK_VARS' SUBROUTINE
mark_111C:
LOOK_VARS: SET 6,(IY+FLAGS-RAMBASE) ; Signal numeric result RST _GET_CHAR CALL ALPHA JP NC,REPORT_C ; to REPORT_C PUSH HL ; LD C,A ; RST _NEXT_CHAR PUSH HL ; RES 5,C ; CP $10 ; $10 JR Z,V_RUN_SYN SET 6,C ; CP ZX_DOLLAR ; $0D JR Z,V_STR_VAR ; forward SET 5,C ; mark_1139: V_CHAR: CALL ALPHANUM JR NC,V_RUN_SYN ; forward when not RES 6,C ; RST _NEXT_CHAR JR V_CHAR ; loop back ; ___ mark_1143: V_STR_VAR: RST _NEXT_CHAR RES 6,(IY+FLAGS-RAMBASE) ; Signal string result mark_1148: V_RUN_SYN: LD B,C ; CALL SYNTAX_Z JR NZ,V_RUN ; forward LD A,C ; AND $E0 ; SET 7,A ; LD C,A ; JR V_SYNTAX ; forward ; ___ mark_1156: V_RUN: LD HL,(VARS) ; sv mark_1159: V_EACH: LD A,(HL) ; AND $7F ; JR Z,V_80_BYTE ; CP C ; JR NZ,V_NEXT ; RLA ; ADD A,A ; JP P,V_FOUND_2 JR C,V_FOUND_2 POP DE ; PUSH DE ; PUSH HL ; mark_116B: V_MATCHES: INC HL ; mark_116C: V_SPACES: LD A,(DE) ; INC DE ; AND A ; JR Z,V_SPACES ; back CP (HL) ; JR Z,V_MATCHES ; back OR $80 ; CP (HL) ; JR NZ,V_GET_PTR ; forward LD A,(DE) ; CALL ALPHANUM JR NC,V_FOUND_1 ; forward mark_117F: V_GET_PTR: POP HL ; mark_1180: V_NEXT: PUSH BC ; CALL NEXT_ONE EX DE,HL ; POP BC ; JR V_EACH ; back ; ___ mark_1188: V_80_BYTE: SET 7,B ; mark_118A: V_SYNTAX: POP DE ; RST _GET_CHAR CP $10 ; JR Z,V_PASS ; forward SET 5,B ; JR V_END ; forward ; ___ mark_1194: V_FOUND_1: POP DE ; mark_1195: V_FOUND_2: POP DE ; POP DE ; PUSH HL ; RST _GET_CHAR mark_1199: V_PASS: CALL ALPHANUM JR NC,V_END ; forward if not alphanumeric RST _NEXT_CHAR JR V_PASS ; back ; ___ mark_11A1: V_END: POP HL ; RL B ; BIT 6,B ; RET ;
; THE 'STK_VAR' SUBROUTINE
mark_11A7:
STK_VAR: XOR A ; LD B,A ; BIT 7,C ; JR NZ,SV_COUNT ; forward BIT 7,(HL) ; JR NZ,SV_ARRAYS ; forward INC A ; mark_11B2: SV_SIMPLE_STR: INC HL ; LD C,(HL) ; INC HL ; LD B,(HL) ; INC HL ; EX DE,HL ; CALL STK_STO_STR RST _GET_CHAR JP SV_SLICE_QUERY ; jump forward ; ___ mark_11BF: SV_ARRAYS: INC HL ; INC HL ; INC HL ; LD B,(HL) ; BIT 6,C ; JR Z,SV_PTR ; forward DEC B ; JR Z,SV_SIMPLE_STR ; forward EX DE,HL ; RST _GET_CHAR CP $10 ; JR NZ,REPORT_3 ; forward EX DE,HL ; mark_11D1: SV_PTR: EX DE,HL ; JR SV_COUNT ; forward ; ___ mark_11D4: SV_COMMA: PUSH HL ; RST _GET_CHAR POP HL ; CP ZX_COMMA ; $1A == 26 JR Z,SV_LOOP ; forward BIT 7,C ; JR Z,REPORT_3 ; forward BIT 6,C ; JR NZ,SV_CLOSE ; forward CP ZX_BRACKET_RIGHT ; $11 JR NZ,SV_RPT_C ; forward RST _NEXT_CHAR RET ; ; ___ mark_11E9: SV_CLOSE: CP ZX_BRACKET_RIGHT ; $11 JR Z,SV_DIM ; forward CP $DF ; JR NZ,SV_RPT_C ; forward mark_11F1: SV_CH_ADD: RST _GET_CHAR DEC HL ; LD (CH_ADD),HL ; sv JR SV_SLICE ; forward ; ___ mark_11F8: SV_COUNT: LD HL,$0000 ; mark_11FB: SV_LOOP: PUSH HL ; RST _NEXT_CHAR POP HL ; LD A,C ; CP ZX_DOUBLE_QUOTE ; JR NZ,SV_MULT ; forward RST _GET_CHAR CP ZX_BRACKET_RIGHT JR Z,SV_DIM ; forward CP ZX_TO ; JR Z,SV_CH_ADD ; back mark_120C: SV_MULT: PUSH BC ; PUSH HL ; CALL DE_DE_PLUS_ONE EX (SP),HL ; EX DE,HL ; CALL INT_EXP1 JR C,REPORT_3 DEC BC ; CALL GET_HL_TIMES_DE ADD HL,BC ; POP DE ; POP BC ; DJNZ SV_COMMA ; loop back BIT 7,C ; mark_1223: SV_RPT_C: JR NZ,SL_RPT_C PUSH HL ; BIT 6,C ; JR NZ,SV_ELEM_STR LD B,D ; LD C,E ; RST _GET_CHAR CP ZX_BRACKET_RIGHT; is character a ')' ? JR Z,SV_NUMBER ; skip forward mark_1231: REPORT_3: RST _ERROR_1 DEFB $02 ; Error Report: Subscript wrong mark_1233: SV_NUMBER: RST _NEXT_CHAR POP HL ; LD DE,$0005 ; CALL GET_HL_TIMES_DE ADD HL,BC ; RET ; return >> ; ___ mark_123D: SV_ELEM_STR: CALL DE_DE_PLUS_ONE EX (SP),HL ; CALL GET_HL_TIMES_DE POP BC ; ADD HL,BC ; INC HL ; LD B,D ; LD C,E ; EX DE,HL ; CALL STK_ST_0 RST _GET_CHAR CP ZX_BRACKET_RIGHT ; is it ')' ? JR Z,SV_DIM ; forward if so CP ZX_COMMA ; $1A == 26 ; is it ',' ? JR NZ,REPORT_3 ; back if not mark_1256: SV_SLICE: CALL SLICING mark_1259: SV_DIM: RST _NEXT_CHAR mark_125A: SV_SLICE_QUERY: CP $10 ; JR Z,SV_SLICE ; back RES 6,(IY+FLAGS-RAMBASE) ; Signal string result RET ; return.
; THE 'SLICING' SUBROUTINE
; ; mark_1263: SLICING: CALL SYNTAX_Z CALL NZ,STK_FETCH RST _NEXT_CHAR CP ZX_BRACKET_RIGHT; is it ')' ? JR Z,SL_STORE ; forward if so PUSH DE ; XOR A ; PUSH AF ; PUSH BC ; LD DE,$0001 ; RST _GET_CHAR POP HL ; CP ZX_TO ; is it 'TO' ? JR Z,SL_SECOND ; forward if so POP AF ; CALL INT_EXP2 PUSH AF ; LD D,B ; LD E,C ; PUSH HL ; RST _GET_CHAR POP HL ; CP ZX_TO ; is it 'TO' ? JR Z,SL_SECOND ; forward if so CP ZX_BRACKET_RIGHT; $11 mark_128B: SL_RPT_C: JP NZ,REPORT_C LD H,D ; LD L,E ; JR SL_DEFINE ; forward ; ___ mark_1292: SL_SECOND: PUSH HL ; RST _NEXT_CHAR POP HL ; CP ZX_BRACKET_RIGHT; is it ')' ? JR Z,SL_DEFINE ; forward if so POP AF ; CALL INT_EXP2 PUSH AF ; RST _GET_CHAR LD H,B ; LD L,C ; CP ZX_BRACKET_RIGHT; is it ')' ? JR NZ,SL_RPT_C ; back if not mark_12A5: SL_DEFINE: POP AF ; EX (SP),HL ; ADD HL,DE ; DEC HL ; EX (SP),HL ; AND A ; SBC HL,DE ; LD BC,$0000 ; JR C,SL_OVER ; forward INC HL ; AND A ; JP M,REPORT_3 ; jump back LD B,H ; LD C,L ; mark_12B9: SL_OVER: POP DE ; RES 6,(IY+FLAGS-RAMBASE) ; Signal string result mark_12BE: SL_STORE: CALL SYNTAX_Z RET Z ; return if checking syntax.
; THE 'STK_STORE' SUBROUTINE
; ; mark_12C2: STK_ST_0: XOR A ; mark_12C3: STK_STO_STR: PUSH BC ; CALL TEST_5_SP POP BC ; LD HL,(STKEND) ; sv LD (HL),A ; INC HL ; LD (HL),E ; INC HL ; LD (HL),D ; INC HL ; LD (HL),C ; INC HL ; LD (HL),B ; INC HL ; LD (STKEND),HL ; sv RES 6,(IY+FLAGS-RAMBASE) ; Signal string result RET ; return.
; THE 'INT EXP' SUBROUTINES
; ; mark_12DD: INT_EXP1: XOR A ; mark_12DE: INT_EXP2: PUSH DE ; PUSH HL ; PUSH AF ; CALL CLASS_6 POP AF ; CALL SYNTAX_Z JR Z,I_RESTORE ; forward if checking syntax PUSH AF ; CALL FIND_INT POP DE ; LD A,B ; OR C ; SCF ; Set Carry Flag JR Z,I_CARRY ; forward POP HL ; PUSH HL ; AND A ; SBC HL,BC ; mark_12F9: I_CARRY: LD A,D ; SBC A,$00 ; mark_12FC: I_RESTORE: POP HL ; POP DE ; RET ;
; THE 'DE,(DE+1)' SUBROUTINE
; INDEX and LOAD Z80 subroutine. ; This emulates the 6800 processor instruction LDX 1,X which loads a two_byte ; value from memory into the register indexing it. Often these are hardly worth ; the bother of writing as subroutines and this one doesn't save any time or ; memory. The timing and space overheads have to be offset against the ease of ; writing and the greater program readability from using such toolkit routines. mark_12FF:
DE_DE_PLUS_ONE: EX DE,HL ; move index address into HL. INC HL ; increment to address word. LD E,(HL) ; pick up word low_order byte. INC HL ; index high_order byte and LD D,(HL) ; pick it up. RET ; return with DE = word.
; THE 'GET_HL_TIMES_DE' SUBROUTINE
; mark_1305:
GET_HL_TIMES_DE: CALL SYNTAX_Z RET Z ; PUSH BC ; LD B,$10 ; LD A,H ; LD C,L ; LD HL,$0000 ; mark_1311: HL_LOOP: ADD HL,HL ; JR C,HL_END ; forward with carry RL C ; RLA ; JR NC,HL_AGAIN ; forward with no carry ADD HL,DE ; mark_131A: HL_END: JP C,REPORT_4 mark_131D: HL_AGAIN: DJNZ HL_LOOP ; loop back POP BC ; RET ; return.
; THE 'LET' SUBROUTINE
; ; mark_1321: LET: LD HL,(DEST) BIT 1,(IY+FLAGX-RAMBASE) JR Z,L_EXISTS ; forward LD BC,$0005 ; mark_132D: L_EACH_CH: INC BC ; ; check mark_132E: L_NO_SP: INC HL ; LD A,(HL) ; AND A ; JR Z,L_NO_SP ; back CALL ALPHANUM JR C,L_EACH_CH ; back CP ZX_DOLLAR ; is it '$' ? JP Z,L_NEW_STR ; forward if so RST _BC_SPACES ; BC_SPACES PUSH DE ; LD HL,(DEST) ; DEC DE ; LD A,C ; SUB $06 ; LD B,A ; LD A,$40 ; JR Z,L_SINGLE mark_134B: L_CHAR: INC HL ; LD A,(HL) ; AND A ; is it a space ? JR Z,L_CHAR ; back INC DE ; LD (DE),A ; DJNZ L_CHAR ; loop back OR $80 ; LD (DE),A ; LD A,$80 ; mark_1359: L_SINGLE: LD HL,(DEST) ; XOR (HL) ; POP HL ; CALL L_FIRST mark_1361: L_NUMERIC: PUSH HL ; RST _FP_CALC ;; DEFB __delete ;; DEFB __end_calc ;; POP HL ; LD BC,$0005 ; AND A ; SBC HL,BC ; JR L_ENTER ; forward ; ___ mark_136E: L_EXISTS: BIT 6,(IY+FLAGS-RAMBASE) ; Numeric or string result? JR Z,L_DELETE_STR ; forward LD DE,$0006 ; ADD HL,DE ; JR L_NUMERIC ; back ; ___ mark_137A: L_DELETE_STR: LD HL,(DEST) ; LD BC,(STRLEN) ; BIT 0,(IY+FLAGX-RAMBASE) JR NZ,L_ADD_STR ; forward LD A,B ; OR C ; RET Z ; PUSH HL ; RST _BC_SPACES PUSH DE ; PUSH BC ; LD D,H ; LD E,L ; INC HL ; LD (HL),$00 ; LDDR ; Copy Bytes PUSH HL ; CALL STK_FETCH POP HL ; EX (SP),HL ; AND A ; SBC HL,BC ; ADD HL,BC ; JR NC,L_LENGTH ; forward LD B,H ; LD C,L ; mark_13A3: L_LENGTH: EX (SP),HL ; EX DE,HL ; LD A,B ; OR C ; JR Z,L_IN_W_S ; forward if zero LDIR ; Copy Bytes mark_13AB: L_IN_W_S: POP BC ; POP DE ; POP HL ;
; THE 'L_ENTER' SUBROUTINE ; ; Part of the LET command contains a natural subroutine which is a ; conditional LDIR. The copy only occurs of BC is non-zero.
mark_13AE:
L_ENTER: EX DE,HL ; #if ORIGINAL #else COND_MV #endif LD A,B ; OR C ; RET Z ; PUSH DE ; LDIR ; Copy Bytes POP HL ; RET ; return.
mark_13B7:
L_ADD_STR: DEC HL ; DEC HL ; DEC HL ; LD A,(HL) ; PUSH HL ; PUSH BC ; CALL L_STRING POP BC ; POP HL ; INC BC ; INC BC ; INC BC ; JP RECLAIM_2 ; jump back to exit via RECLAIM_2 ; ___ mark_13C8: L_NEW_STR: LD A,$60 ; prepare mask %01100000 LD HL,(DEST) ; XOR (HL) ;
; THE 'L_STRING' SUBROUTINE
; mark_13CE:
L_STRING: PUSH AF ; CALL STK_FETCH EX DE,HL ; ADD HL,BC ; PUSH HL ; INC BC ; INC BC ; INC BC ; RST _BC_SPACES EX DE,HL ; POP HL ; DEC BC ; DEC BC ; PUSH BC ; LDDR ; Copy Bytes EX DE,HL ; POP BC ; DEC BC ; LD (HL),B ; DEC HL ; LD (HL),C ; POP AF ; mark_13E7: L_FIRST: PUSH AF ; CALL REC_V80 POP AF ; DEC HL ; LD (HL),A ; LD HL,(STKBOT) ; sv LD (E_LINE),HL ; sv DEC HL ; LD (HL),$80 ; RET ;
; THE 'STK_FETCH' SUBROUTINE
; This routine fetches a five-byte value from the calculator stack ; reducing the pointer to the end of the stack by five. ; For a floating-point number the exponent is in A and the mantissa ; is the thirty-two bits EDCB. ; For strings, the start of the string is in DE and the length in BC. ; A is unused. mark_13F8: STK_FETCH: LD HL,(STKEND) ; load HL from system variable STKEND DEC HL ; LD B,(HL) ; DEC HL ; LD C,(HL) ; DEC HL ; LD D,(HL) ; DEC HL ; LD E,(HL) ; DEC HL ; LD A,(HL) ; LD (STKEND),HL ; set system variable STKEND to lower value. RET ; return.
; THE 'DIM' COMMAND ROUTINE
; An array is created and initialized to zeros which is also the space ; character on the ZX81. mark_1409:
DIM: CALL LOOK_VARS mark_140C: D_RPORT_C: JP NZ,REPORT_C CALL SYNTAX_Z JR NZ,D_RUN ; forward RES 6,C ; CALL STK_VAR CALL CHECK_END mark_141C: D_RUN: JR C,D_LETTER ; forward PUSH BC ; CALL NEXT_ONE CALL RECLAIM_2 POP BC ; mark_1426: D_LETTER: SET 7,C ; LD B,$00 ; PUSH BC ; LD HL,$0001 ; BIT 6,C ; JR NZ,D_SIZE ; forward LD L,$05 ; mark_1434: D_SIZE: EX DE,HL ; mark_1435: D_NO_LOOP: RST _NEXT_CHAR LD H,$40 ; CALL INT_EXP1 JP C,REPORT_3 POP HL ; PUSH BC ; INC H ; PUSH HL ; LD H,B ; LD L,C ; CALL GET_HL_TIMES_DE EX DE,HL ; RST _GET_CHAR CP ZX_COMMA ; $1A == 26 JR Z,D_NO_LOOP ; back CP ZX_BRACKET_RIGHT; is it ')' ? JR NZ,D_RPORT_C ; back if not RST _NEXT_CHAR POP BC ; LD A,C ; LD L,B ; LD H,$00 ; INC HL ; INC HL ; ADD HL,HL ; ADD HL,DE ; JP C,REPORT_4 PUSH DE ; PUSH BC ; PUSH HL ; LD B,H ; LD C,L ; LD HL,(E_LINE) ; sv DEC HL ; CALL MAKE_ROOM INC HL ; LD (HL),A ; POP BC ; DEC BC ; DEC BC ; DEC BC ; INC HL ; LD (HL),C ; INC HL ; LD (HL),B ; POP AF ; INC HL ; LD (HL),A ; LD H,D ; LD L,E ; DEC DE ; LD (HL),0 ; POP BC ; LDDR ; Copy Bytes mark_147F: DIM_SIZES: POP BC ; LD (HL),B ; DEC HL ; LD (HL),C ; DEC HL ; DEC A ; JR NZ,DIM_SIZES ; back RET ; return.
; THE 'RESERVE' ROUTINE
; ; mark_1488: RESERVE: LD HL,(STKBOT) ; address STKBOT DEC HL ; now last byte of workspace CALL MAKE_ROOM INC HL ; INC HL ; POP BC ; LD (E_LINE),BC ; sv POP BC ; EX DE,HL ; INC HL ; RET ;
; THE 'CLEAR' COMMAND ROUTINE
; ; mark_149A: CLEAR: LD HL,(VARS) ; sv LD (HL),$80 ; INC HL ; LD (E_LINE),HL ; sv
; THE 'X_TEMP' SUBROUTINE
; ; mark_14A3:
X_TEMP: LD HL,(E_LINE) ; sv
; THE 'SET_STK' ROUTINES
; ; mark_14A6:
SET_STK_B: LD (STKBOT),HL ; sv ; mark_14A9: SET_STK_E: LD (STKEND),HL ; sv RET ;
; THE 'CURSOR_IN' ROUTINE
; This routine is called to set the edit line to the minimum cursor/newline ; and to set STKEND, the start of free space, at the next position. mark_14AD:
CURSOR_IN: LD HL,(E_LINE) ; fetch start of edit line LD (HL),ZX_CURSOR ; insert cursor character INC HL ; point to next location. LD (HL),ZX_NEWLINE ; insert NEWLINE character INC HL ; point to next free location. LD (IY+DF_SZ-RAMBASE),2 ; set lower screen display file size JR SET_STK_B ; exit via SET_STK_B above
; THE 'SET_MIN' SUBROUTINE
; ; mark_14BC: SET_MIN: LD HL,$405D ; normal location of calculator's memory area LD (MEM),HL ; update system variable MEM LD HL,(STKBOT) ; JR SET_STK_E ; back
; THE 'RECLAIM THE END_MARKER' ROUTINE
mark_14C7: REC_V80: LD DE,(E_LINE) ; sv JP RECLAIM_1
; THE 'ALPHA' SUBROUTINE
mark_14CE: ALPHA: CP ZX_A ; $26 JR ALPHA_2 ; skip forward
; THE 'ALPHANUM' SUBROUTINE
mark_14D2: ALPHANUM: CP ZX_0 ; mark_14D4: ALPHA_2: CCF ; Complement Carry Flag RET NC ; CP $40 ; RET ;
; THE 'DECIMAL TO FLOATING POINT' SUBROUTINE
; mark_14D9:
DEC_TO_FP: CALL INT_TO_FP ; gets first part CP ZX_PERIOD ; is character a '.' ? JR NZ,E_FORMAT ; forward if not RST _FP_CALC ;; DEFB __stk_one ;; DEFB __st_mem_0 ;; DEFB __delete ;; DEFB __end_calc ;; mark_14E5: NXT_DGT_1: RST _NEXT_CHAR CALL STK_DIGIT JR C,E_FORMAT ; forward RST _FP_CALC ;; DEFB __get_mem_0 ;; DEFB __stk_ten ;; #if ORIGINAL DEFB __division ; DEFB $C0 ;;st-mem-0 DEFB __multiply ;; #else DEFB $04 ;;+multiply DEFB $C0 ;;st-mem-0 DEFB $05 ;;+division #endif DEFB __addition ;; DEFB __end_calc ;; JR NXT_DGT_1 ; loop back till exhausted ; ___ mark_14F5: E_FORMAT: CP ZX_E ; is character 'E' ? RET NZ ; return if not LD (IY+MEM_0_1st-RAMBASE),$FF ; initialize sv MEM_0_1st to $FF TRUE RST _NEXT_CHAR CP ZX_PLUS ; is character a '+' ? JR Z,SIGN_DONE ; forward if so CP ZX_MINUS ; is it a '-' ? JR NZ,ST_E_PART ; forward if not INC (IY+MEM_0_1st-RAMBASE) ; sv MEM_0_1st change to FALSE mark_1508: SIGN_DONE: RST _NEXT_CHAR mark_1509: ST_E_PART: CALL INT_TO_FP RST _FP_CALC ;; m, e. DEFB __get_mem_0 ;; m, e, (1/0) TRUE/FALSE DEFB __jump_true ;; DEFB E_POSTVE - $ ;; DEFB __negate ;; m, _e mark_1511: E_POSTVE: DEFB __e_to_fp ;; x. DEFB __end_calc ;; x. RET ; return.
; THE 'STK_DIGIT' SUBROUTINE
; mark_1514:
STK_DIGIT: CP ZX_0 ; RET C ; CP ZX_A ; $26 CCF ; Complement Carry Flag RET C ; SUB ZX_0 ;
; THE 'STACK_A' SUBROUTINE
; mark_151D:
STACK_A: LD C,A ; LD B,0 ;
; THE 'STACK_BC' SUBROUTINE
; The ZX81 does not have an integer number format so the BC register contents ; must be converted to their full floating-point form. mark_1520:
STACK_BC: LD IY,ERR_NR ; re-initialize the system variables pointer. PUSH BC ; save the integer value. ; now stack zero, five zero bytes as a starting point. RST _FP_CALC ;; DEFB __stk_zero ;; 0. DEFB __end_calc ;; POP BC ; restore integer value. LD (HL),$91 ; place $91 in exponent 65536. ; this is the maximum possible value LD A,B ; fetch hi-byte. AND A ; test for zero. JR NZ,STK_BC_2 ; forward if not zero LD (HL),A ; else make exponent zero again OR C ; test lo-byte RET Z ; return if BC was zero - done. ; else there has to be a set bit if only the value one. LD B,C ; save C in B. LD C,(HL) ; fetch zero to C LD (HL),$89 ; make exponent $89 256. mark_1536: STK_BC_2: DEC (HL) ; decrement exponent - halving number SLA C ; C<-76543210<-0 RL B ; C<-76543210<-C JR NC,STK_BC_2 ; loop back if no carry SRL B ; 0->76543210->C RR C ; C->76543210->C INC HL ; address first byte of mantissa LD (HL),B ; insert B INC HL ; address second byte of mantissa LD (HL),C ; insert C DEC HL ; point to the DEC HL ; exponent again RET ; return.
; THE 'INTEGER TO FLOATING POINT' SUBROUTINE
; ; mark_1548: INT_TO_FP: PUSH AF ; RST _FP_CALC ;; DEFB __stk_zero ;; DEFB __end_calc ;; POP AF ; mark_154D: NXT_DGT_2: CALL STK_DIGIT RET C ; RST _FP_CALC ;; DEFB __exchange ;; DEFB __stk_ten ;; DEFB __multiply ;; DEFB __addition ;; DEFB __end_calc ;; RST _NEXT_CHAR JR NXT_DGT_2
; THE 'E_FORMAT TO FLOATING POINT' SUBROUTINE
; (Offset $38: 'e_to_fp') ; invoked from DEC_TO_FP and PRINT_FP. ; e.g. 2.3E4 is 23000. ; This subroutine evaluates xEm where m is a positive or negative integer. ; At a simple level x is multiplied by ten for every unit of m. ; If the decimal exponent m is negative then x is divided by ten for each unit. ; A short-cut is taken if the exponent is greater than seven and in this ; case the exponent is reduced by seven and the value is multiplied or divided ; by ten million. ; Note. for the ZX Spectrum an even cleverer method was adopted which involved ; shifting the bits out of the exponent so the result was achieved with six ; shifts at most. The routine below had to be completely re-written mostly ; in Z80 machine code. ; Although no longer operable, the calculator literal was retained for old ; times sake, the routine being invoked directly from a machine code CALL. ; ; On entry in the ZX81, m, the exponent, is the 'last value', and the ; floating-point decimal mantissa is beneath it. mark_155A: e_to_fp: RST _FP_CALC ;; x, m. DEFB __duplicate ;; x, m, m. DEFB __less_0 ;; x, m, (1/0). DEFB __st_mem_0 ;; x, m, (1/0). DEFB __delete ;; x, m. DEFB __abs ;; x, +m. mark_1560: E_LOOP: DEFB __stk_one ;; x, m,1. DEFB __subtract ;; x, m-1. DEFB __duplicate ;; x, m-1,m-1. DEFB __less_0 ;; x, m-1, (1/0). DEFB __jump_true ;; x, m-1. DEFB E_END - $ ;; x, m-1. DEFB __duplicate ;; x, m-1, m-1. DEFB __stk_data ;; DEFB $33 ;;Exponent: $83, Bytes: 1 DEFB $40 ;;(+00,+00,+00) x, m-1, m-1, 6. DEFB __subtract ;; x, m-1, m-7. DEFB __duplicate ;; x, m-1, m-7, m-7. DEFB __less_0 ;; x, m-1, m-7, (1/0). DEFB __jump_true ;; x, m-1, m-7. DEFB E_LOW - $ ;; ; but if exponent m is higher than 7 do a bigger chunk. ; multiplying (or dividing if negative) by 10 million - 1e7. DEFB __exchange ;; x, m-7, m-1. DEFB __delete ;; x, m-7. DEFB __exchange ;; m-7, x. DEFB __stk_data ;; DEFB $80 ;;Bytes: 3 DEFB $48 ;;Exponent $98 DEFB $18,$96,$80 ;;(+00) m-7, x, 10,000,000 (=f) DEFB __jump ;; DEFB E_CHUNK - $ ;; ; ___ mark_157A: E_LOW: DEFB __delete ;; x, m-1. DEFB __exchange ;; m-1, x. DEFB __stk_ten ;; m-1, x, 10 (=f). mark_157D: E_CHUNK: DEFB __get_mem_0 ;; m-1, x, f, (1/0) DEFB __jump_true ;; m-1, x, f DEFB E_DIVSN - $ ;; DEFB __multiply ;; m-1, x*f. DEFB __jump ;; DEFB E_SWAP - $ ;; ; ___ mark_1583: E_DIVSN: DEFB __division ;; m-1, x/f (= new x). mark_1584: E_SWAP: DEFB __exchange ;; x, m-1 (= new m). DEFB __jump ;; x, m. DEFB E_LOOP - $ ;; ; ___ mark_1587: E_END: DEFB __delete ;; x. (-1) DEFB __end_calc ;; x. RET ; return.
; THE 'FLOATING-POINT TO BC' SUBROUTINE
; The floating-point form on the calculator stack is compressed directly into ; the BC register rounding up if necessary. ; Valid range is 0 to 65535.4999 mark_158A:
FP_TO_BC: CALL STK_FETCH ; exponent to A ; mantissa to EDCB. AND A ; test for value zero. JR NZ,FPBC_NZRO ; forward if not ; else value is zero LD B,A ; zero to B LD C,A ; also to C PUSH AF ; save the flags on machine stack JR FPBC_END ; forward ; ___ ; EDCB => BCE mark_1595: FPBC_NZRO: LD B,E ; transfer the mantissa from EDCB LD E,C ; to BCE. Bit 7 of E is the 17th bit which LD C,D ; will be significant for rounding if the ; number is already normalized. SUB $91 ; subtract 65536 CCF ; complement carry flag BIT 7,B ; test sign bit PUSH AF ; push the result SET 7,B ; set the implied bit JR C,FPBC_END ; forward with carry from SUB/CCF ; number is too big. INC A ; increment the exponent and NEG ; negate to make range $00 - $0F CP $08 ; test if one or two bytes JR C,BIG_INT ; forward with two LD E,C ; shift mantissa LD C,B ; 8 places right LD B,$00 ; insert a zero in B SUB $08 ; reduce exponent by eight mark_15AF: BIG_INT: AND A ; test the exponent LD D,A ; save exponent in D. LD A,E ; fractional bits to A RLCA ; rotate most significant bit to carry for ; rounding of an already normal number. JR Z,EXP_ZERO ; forward if exponent zero ; the number is normalized mark_15B5: FPBC_NORM: SRL B ; 0->76543210->C RR C ; C->76543210->C DEC D ; decrement exponent JR NZ,FPBC_NORM ; loop back till zero mark_15BC: EXP_ZERO: JR NC,FPBC_END ; forward without carry to NO_ROUND ??? INC BC ; round up. LD A,B ; test result OR C ; for zero JR NZ,FPBC_END ; forward if not to GRE_ZERO ??? POP AF ; restore sign flag SCF ; set carry flag to indicate overflow PUSH AF ; save combined flags again mark_15C6: FPBC_END: PUSH BC ; save BC value ; set HL and DE to calculator stack pointers. RST _FP_CALC ;; DEFB __end_calc ;; POP BC ; restore BC value POP AF ; restore flags LD A,C ; copy low byte to A also. RET ; return
; THE 'FLOATING-POINT TO A' SUBROUTINE
; ; mark_15CD:
FP_TO_A: CALL FP_TO_BC RET C ; PUSH AF ; DEC B ; INC B ; JR Z,FP_A_END ; forward if in range POP AF ; fetch result SCF ; set carry flag signaling overflow RET ; return mark_15D9: FP_A_END: POP AF ; RET ;
; THE 'PRINT A FLOATING-POINT NUMBER' SUBROUTINE
; prints 'last value' x on calculator stack. ; There are a wide variety of formats see Chapter 4. ; e.g. ; PI prints as 3.1415927 ; .123 prints as 0.123 ; .0123 prints as .0123 ; 999999999999 prints as 1000000000000 ; 9876543210123 prints as 9876543200000 ; Begin by isolating zero and just printing the '0' character ; for that case. For negative numbers print a leading '-' and ; then form the absolute value of x. mark_15DB:
PRINT_FP: RST _FP_CALC ;; x. DEFB __duplicate ;; x, x. DEFB __less_0 ;; x, (1/0). DEFB __jump_true ;; DEFB PF_NEGTVE - $ ;; x. DEFB __duplicate ;; x, x DEFB __greater_0 ;; x, (1/0). DEFB __jump_true ;; DEFB PF_POSTVE - $ ;; x. DEFB __delete ;; . DEFB __end_calc ;; . LD A,ZX_0 ; load accumulator with character '0' RST _PRINT_A RET ; return. >> ; ___ mark_15EA: PF_NEGTVE: DEFB __abs ;; +x. DEFB __end_calc ;; x. LD A,ZX_MINUS ; load accumulator with '-' RST _PRINT_A RST _FP_CALC ;; x. mark_15F0: PF_POSTVE: DEFB __end_calc ;; x. ; register HL addresses the exponent of the floating-point value. ; if positive, and point floats to left, then bit 7 is set. LD A,(HL) ; pick up the exponent byte CALL STACK_A ; places on calculator stack. ; now calculate roughly the number of digits, n, before the decimal point by ; subtracting a half from true exponent and multiplying by log to ; the base 10 of 2. ; The true number could be one higher than n, the integer result. RST _FP_CALC ;; x, e. DEFB __stk_data ;; DEFB $78 ;;Exponent: $88, Bytes: 2 DEFB $00,$80 ;;(+00,+00) x, e, 128.5. DEFB __subtract ;; x, e -.5. DEFB __stk_data ;; DEFB $EF ;;Exponent: $7F, Bytes: 4 DEFB $1A,$20,$9A,$85 ;; .30103 (log10 2) DEFB __multiply ;; x, DEFB __int ;; DEFB __st_mem_1 ;; x, n. DEFB __stk_data ;; DEFB $34 ;;Exponent: $84, Bytes: 1 DEFB $00 ;;(+00,+00,+00) x, n, 8. DEFB __subtract ;; x, n-8. DEFB __negate ;; x, 8-n. DEFB __e_to_fp ;; x * (10^n) ; finally the 8 or 9 digit decimal is rounded. ; a ten-digit integer can arise in the case of, say, 999999999.5 ; which gives 1000000000. DEFB __stk_half ;; DEFB __addition ;; DEFB __int ;; i. DEFB __end_calc ;; ; If there were 8 digits then final rounding will take place on the calculator ; stack above and the next two instructions insert a masked zero so that ; no further rounding occurs. If the result is a 9 digit integer then ; rounding takes place within the buffer. LD HL,$406B ; address system variable MEM_2_5th ; which could be the 'ninth' digit. LD (HL),$90 ; insert the value $90 10010000 ; now starting from lowest digit lay down the 8, 9 or 10 digit integer ; which represents the significant portion of the number ; e.g. PI will be the nine-digit integer 314159265 LD B,10 ; count is ten digits. mark_1615: PF_LOOP: INC HL ; increase pointer PUSH HL ; preserve buffer address. PUSH BC ; preserve counter. RST _FP_CALC ;; i. DEFB __stk_ten ;; i, 10. DEFB __n_mod_m ;; i mod 10, i/10 DEFB __exchange ;; i/10, remainder. DEFB __end_calc ;; CALL FP_TO_A ; $00-$09 OR $90 ; make left hand nibble 9 POP BC ; restore counter POP HL ; restore buffer address. LD (HL),A ; insert masked digit in buffer. DJNZ PF_LOOP ; loop back for all ten ; the most significant digit will be last but if the number is exhausted then ; the last one or two positions will contain zero ($90). ; e.g. for 'one' we have zero as estimate of leading digits. ; 1*10^8 100000000 as integer value ; 90 90 90 90 90 90 90 90 91 90 as buffer mem3/mem4 contents. INC HL ; advance pointer to one past buffer LD BC,$0008 ; set C to 8 ( B is already zero ) PUSH HL ; save pointer. mark_162C: PF_NULL: DEC HL ; decrease pointer LD A,(HL) ; fetch masked digit CP $90 ; is it a leading zero ? JR Z,PF_NULL ; loop back if so ; at this point a significant digit has been found. carry is reset. SBC HL,BC ; subtract eight from the address. PUSH HL ; ** save this pointer too LD A,(HL) ; fetch addressed byte ADD A,$6B ; add $6B - forcing a round up ripple ; if $95 or over. PUSH AF ; save the carry result. ; now enter a loop to round the number. After rounding has been considered ; a zero that has arisen from rounding or that was present at that position ; originally is changed from $90 to $80. mark_1639: PF_RND_LP: POP AF ; retrieve carry from machine stack. INC HL ; increment address LD A,(HL) ; fetch new byte ADC A,0 ; add in any carry DAA ; decimal adjust accumulator ; carry will ripple through the '9' PUSH AF ; save carry on machine stack. AND $0F ; isolate character 0 - 9 AND set zero flag ; if zero. LD (HL),A ; place back in location. SET 7,(HL) ; set bit 7 to show printable. ; but not if trailing zero after decimal point. JR Z,PF_RND_LP ; back if a zero ; to consider further rounding and/or trailing ; zero identification. POP AF ; balance stack POP HL ; ** retrieve lower pointer ; now insert 6 trailing zeros which are printed if before the decimal point ; but mark the end of printing if after decimal point. ; e.g. 9876543210123 is printed as 9876543200000 ; 123.456001 is printed as 123.456 LD B,6 ; the count is six. mark_164B: PF_ZERO_6: LD (HL),$80 ; insert a masked zero DEC HL ; decrease pointer. DJNZ PF_ZERO_6 ; loop back for all six ; n-mod-m reduced the number to zero and this is now deleted from the calculator ; stack before fetching the original estimate of leading digits. RST _FP_CALC ;; 0. DEFB __delete ;; . DEFB __get_mem_1 ;; n. DEFB __end_calc ;; n. CALL FP_TO_A JR Z,PF_POS ; skip forward if positive NEG ; negate makes positive mark_165B: PF_POS: LD E,A ; transfer count of digits to E INC E ; increment twice INC E ; POP HL ; * retrieve pointer to one past buffer. mark_165F: GET_FIRST: DEC HL ; decrement address. DEC E ; decrement digit counter. LD A,(HL) ; fetch masked byte. AND $0F ; isolate right-hand nibble. JR Z,GET_FIRST ; back with leading zero ; now determine if E-format printing is needed LD A,E ; transfer now accurate number count to A. SUB 5 ; subtract five CP 8 ; compare with 8 as maximum digits is 13. JP P,PF_E_FMT ; forward if positive to PF_E_FMT CP $F6 ; test for more than four zeros after point. JP M,PF_E_FMT ; forward if so to PF_E_FMT ADD A,6 ; test for zero leading digits, e.g. 0.5 JR Z,PF_ZERO_1 ; forward if so to PF_ZERO_1 JP M,PF_ZEROS ; forward if more than one zero to PF_ZEROS ; else digits before the decimal point are to be printed LD B,A ; count of leading characters to B. mark_167B: PF_NIB_LP: CALL PF_NIBBLE DJNZ PF_NIB_LP ; loop back for counted numbers JR PF_DC_OUT ; forward to consider decimal part to PF_DC_OUT ; ___ mark_1682: PF_E_FMT: LD B,E ; count to B CALL PF_NIBBLE ; prints one digit. CALL PF_DC_OUT ; considers fractional part. LD A,ZX_E ; RST _PRINT_A LD A,B ; transfer exponent to A AND A ; test the sign. JP P,PF_E_POS ; forward if positive to PF_E_POS NEG ; negate the negative exponent. LD B,A ; save positive exponent in B. LD A,ZX_MINUS ; JR PF_E_SIGN ; skip forward to PF_E_SIGN ; ___ mark_1698: PF_E_POS: LD A,ZX_PLUS ; mark_169A: PF_E_SIGN: RST _PRINT_A ; now convert the integer exponent in B to two characters. ; it will be less than 99. LD A,B ; fetch positive exponent. LD B,$FF ; initialize left hand digit to minus one. mark_169E: PF_E_TENS: INC B ; increment ten count SUB 10 ; subtract ten from exponent JR NC,PF_E_TENS ; loop back if greater than ten ADD A,10 ; reverse last subtraction LD C,A ; transfer remainder to C LD A,B ; transfer ten value to A. AND A ; test for zero. JR Z,PF_E_LOW ; skip forward if so to PF_E_LOW CALL OUT_CODE ; prints as digit '1' - '9' mark_16AD: PF_E_LOW: LD A,C ; low byte to A CALL OUT_CODE ; prints final digit of the ; exponent. RET ; return. >>
; THE 'FLOATING POINT PRINT ZEROS' LOOP ; ------------------------------------- ; This branch deals with zeros after decimal point. ; e.g. .01 or .0000999 ; Note. that printing to the ZX Printer destroys A and that A should be ; initialized to '0' at each stage of the loop. ; Originally LPRINT .00001 printed as .0XYZ1
mark_16B2: PF_ZEROS: NEG ; negate makes number positive 1 to 4. LD B,A ; zero count to B. LD A,ZX_PERIOD ; prepare character '.' RST _PRINT_A #if ORIGINAL LD A,ZX_0 ; prepare a '0' PFZROLP RST _PRINT_A DJNZ PFZROLP ; obsolete loop back to PFZROLP #else PF_ZRO_LP LD A,ZX_0 ; prepare a '0' in the accumulator each time. RST _PRINT_A DJNZ PF_ZRO_LP ;+ New loop back to PF-ZRO-LP #endif JR PF_FRAC_LP ; forward ; there is a need to print a leading zero e.g. 0.1 but not with .01 mark_16BF: PF_ZERO_1: LD A,ZX_0 ; prepare character '0'. RST _PRINT_A ; this subroutine considers the decimal point and any trailing digits. ; if the next character is a marked zero, $80, then nothing more to print. mark_16C2: PF_DC_OUT: DEC (HL) ; decrement addressed character INC (HL) ; increment it again RET PE ; return with overflow (was 128) >> ; as no fractional part ; else there is a fractional part so print the decimal point. LD A,ZX_PERIOD ; prepare character '.' RST _PRINT_A ; now enter a loop to print trailing digits mark_16C8: PF_FRAC_LP: DEC (HL) ; test for a marked zero. INC (HL) ; RET PE ; return when digits exhausted >> CALL PF_NIBBLE JR PF_FRAC_LP ; back for all fractional digits ; ___ ; subroutine to print right-hand nibble mark_16D0: PF_NIBBLE: LD A,(HL) ; fetch addressed byte AND $0F ; mask off lower 4 bits CALL OUT_CODE DEC HL ; decrement pointer. RET ; return.
; THE 'PREPARE TO ADD' SUBROUTINE
; This routine is called twice to prepare each floating point number for ; addition, in situ, on the calculator stack. ; The exponent is picked up from the first byte which is then cleared to act ; as a sign byte and accept any overflow. ; If the exponent is zero then the number is zero and an early return is made. ; The now redundant sign bit of the mantissa is set and if the number is ; negative then all five bytes of the number are twos-complemented to prepare ; the number for addition. ; On the second invocation the exponent of the first number is in B. mark_16D8: PREP_ADD: LD A,(HL) ; fetch exponent. LD (HL),0 ; make this byte zero to take any overflow and ; default to positive. AND A ; test stored exponent for zero. RET Z ; return with zero flag set if number is zero. INC HL ; point to first byte of mantissa. BIT 7,(HL) ; test the sign bit. SET 7,(HL) ; set it to its implied state. DEC HL ; set pointer to first byte again. RET Z ; return if bit indicated number is positive.>> ; if negative then all five bytes are twos complemented starting at LSB. PUSH BC ; save B register contents. LD BC,$0005 ; set BC to five. ADD HL,BC ; point to location after 5th byte. LD B,C ; set the B counter to five. LD C,A ; store original exponent in C. SCF ; set carry flag so that one is added. ; now enter a loop to twos_complement the number. ; The first of the five bytes becomes $FF to denote a negative number. mark_16EC: NEG_BYTE: DEC HL ; point to first or more significant byte. LD A,(HL) ; fetch to accumulator. CPL ; complement. ADC A,0 ; add in initial carry or any subsequent carry. LD (HL),A ; place number back. DJNZ NEG_BYTE ; loop back five times LD A,C ; restore the exponent to accumulator. POP BC ; restore B register contents. RET ; return.
; THE 'FETCH TWO NUMBERS' SUBROUTINE
; This routine is used by addition, multiplication and division to fetch ; the two five_byte numbers addressed by HL and DE from the calculator stack ; into the Z80 registers. ; The HL register may no longer point to the first of the two numbers. ; Since the 32-bit addition operation is accomplished using two Z80 16-bit ; instructions, it is important that the lower two bytes of each mantissa are ; in one set of registers and the other bytes all in the alternate set. ; ; In: HL = highest number, DE= lowest number ; ; : alt': ; : ; Out: ; :H,B-C:C,B: num1 ; :L,D-E:D-E: num2 mark_16F7: FETCH_TWO: PUSH HL ; save HL PUSH AF ; save A - result sign when used from division. LD C,(HL) ; INC HL ; LD B,(HL) ; LD (HL),A ; insert sign when used from multiplication. INC HL ; LD A,C ; m1 LD C,(HL) ; PUSH BC ; PUSH m2 m3 INC HL ; LD C,(HL) ; m4 INC HL ; LD B,(HL) ; m5 BC holds m5 m4 EX DE,HL ; make HL point to start of second number. LD D,A ; m1 LD E,(HL) ; PUSH DE ; PUSH m1 n1 INC HL ; LD D,(HL) ; INC HL ; LD E,(HL) ; PUSH DE ; PUSH n2 n3 EXX ; - - - - - - - POP DE ; POP n2 n3 POP HL ; POP m1 n1 POP BC ; POP m2 m3 EXX ; - - - - - - - INC HL ; LD D,(HL) ; INC HL ; LD E,(HL) ; DE holds n4 n5 POP AF ; restore saved POP HL ; registers. RET ; return.
; THE 'SHIFT ADDEND' SUBROUTINE
; The accumulator A contains the difference between the two exponents. ; This is the lowest of the two numbers to be added mark_171A:
SHIFT_FP: AND A ; test difference between exponents. RET Z ; return if zero. both normal. CP 33 ; compare with 33 bits. JR NC,ADDEND_0 ; forward if greater than 32 PUSH BC ; preserve BC - part LD B,A ; shift counter to B. ; Now perform B right shifts on the addend L'D'E'D E ; to bring it into line with the augend H'B'C'C B mark_1722: ONE_SHIFT: EXX ; - - - SRA L ; 76543210->C bit 7 unchanged. RR D ; C->76543210->C RR E ; C->76543210->C EXX ; - - - RR D ; C->76543210->C RR E ; C->76543210->C DJNZ ONE_SHIFT ; loop back B times POP BC ; restore BC RET NC ; return if last shift produced no carry. >> ; if carry flag was set then accuracy is being lost so round up the addend. CALL ADD_BACK RET NZ ; return if not FF 00 00 00 00 ; this branch makes all five bytes of the addend zero and is made during ; addition when the exponents are too far apart for the addend bits to ; affect the result. mark_1736: ADDEND_0: EXX ; select alternate set for more significant ; bytes. XOR A ; clear accumulator. ; this entry point (from multiplication) sets four of the bytes to zero or if ; continuing from above, during addition, then all five bytes are set to zero. mark_1738: ZEROS_4_OR_5: LD L,0 ; set byte 1 to zero. LD D,A ; set byte 2 to A. LD E,L ; set byte 3 to zero. EXX ; select main set LD DE,$0000 ; set lower bytes 4 and 5 to zero. RET ; return.
; THE 'ADD_BACK' SUBROUTINE
; Called from SHIFT_FP above during addition and after normalization from ; multiplication. ; This is really a 32_bit increment routine which sets the zero flag according ; to the 32-bit result. ; During addition, only negative numbers like FF FF FF FF FF, ; the twos-complement version of xx 80 00 00 01 say ; will result in a full ripple FF 00 00 00 00. ; FF FF FF FF FF when shifted right is unchanged by SHIFT_FP but sets the ; carry invoking this routine. mark_1741:
ADD_BACK: INC E ; RET NZ ; INC D ; RET NZ ; EXX ; INC E ; JR NZ,ALL_ADDED ; forward if no overflow INC D ; mark_174A: ALL_ADDED: EXX ; RET ; return with zero flag set for zero mantissa.
; THE 'SUBTRACTION' OPERATION
; just switch the sign of subtrahend and do an add. mark_174C:
SUBTRACT: LD A,(DE) ; fetch exponent byte of second number the ; subtrahend. AND A ; test for zero RET Z ; return if zero - first number is result. INC DE ; address the first mantissa byte. LD A,(DE) ; fetch to accumulator. XOR $80 ; toggle the sign bit. LD (DE),A ; place back on calculator stack. DEC DE ; point to exponent byte. ; continue into addition routine.
; THE 'ADDITION' OPERATION
; The addition operation pulls out all the stops and uses most of the Z80's ; registers to add two floating-point numbers. ; This is a binary operation and on entry, HL points to the first number ; and DE to the second. mark_1755:
ADDITION: EXX ; - - - PUSH HL ; save the pointer to the next literal. EXX ; - - - PUSH DE ; save pointer to second number PUSH HL ; save pointer to first number - will be the ; result pointer on calculator stack. CALL PREP_ADD LD B,A ; save first exponent byte in B. EX DE,HL ; switch number pointers. CALL PREP_ADD LD C,A ; save second exponent byte in C. CP B ; compare the exponent bytes. JR NC,SHIFT_LEN ; forward if second higher LD A,B ; else higher exponent to A LD B,C ; lower exponent to B EX DE,HL ; switch the number pointers. mark_1769: SHIFT_LEN: PUSH AF ; save higher exponent SUB B ; subtract lower exponent CALL FETCH_TWO CALL SHIFT_FP POP AF ; restore higher exponent. POP HL ; restore result pointer. LD (HL),A ; insert exponent byte. PUSH HL ; save result pointer again. ; now perform the 32-bit addition using two 16-bit Z80 add instructions. LD L,B ; transfer low bytes of mantissa individually LD H,C ; to HL register ADD HL,DE ; the actual binary addition of lower bytes ; now the two higher byte pairs that are in the alternate register sets. EXX ; switch in set EX DE,HL ; transfer high mantissa bytes to HL register. ADC HL,BC ; the actual addition of higher bytes with ; any carry from first stage. EX DE,HL ; result in DE, sign bytes ($FF or $00) to HL ; now consider the two sign bytes LD A,H ; fetch sign byte of num1 ADC A,L ; add including any carry from mantissa ; addition. 00 or 01 or FE or FF LD L,A ; result in L. ; possible outcomes of signs and overflow from mantissa are ; ; H + L + carry = L RRA XOR L RRA
; 00 + 00 = 00 00 00 ; 00 + 00 + carry = 01 00 01 carry ; FF + FF = FE C FF 01 carry ; FF + FF + carry = FF C FF 00 ; FF + 00 = FF FF 00 ; FF + 00 + carry = 00 C 80 80 RRA ; C->76543210->C XOR L ; set bit 0 if shifting required. EXX ; switch back to main set EX DE,HL ; full mantissa result now in D'E'D E registers. POP HL ; restore pointer to result exponent on ; the calculator stack. RRA ; has overflow occurred ? JR NC,TEST_NEG ; skip forward if not ; if the addition of two positive mantissas produced overflow or if the ; addition of two negative mantissas did not then the result exponent has to ; be incremented and the mantissa shifted one place to the right. LD A,1 ; one shift required. CALL SHIFT_FP ; performs a single shift ; rounding any lost bit INC (HL) ; increment the exponent. JR Z,ADD_REP_6 ; forward to ADD_REP_6 if the exponent ; wraps round from FF to zero as number is too ; big for the system. ; at this stage the exponent on the calculator stack is correct. mark_1790: TEST_NEG: EXX ; switch in the alternate set. LD A,L ; load result sign to accumulator. AND $80 ; isolate bit 7 from sign byte setting zero ; flag if positive. EXX ; back to main set. INC HL ; point to first byte of mantissa LD (HL),A ; insert $00 positive or $80 negative at ; position on calculator stack. DEC HL ; point to exponent again. JR Z,GO_NC_MLT ; forward if positive to GO_NC_MLT ; a negative number has to be twos-complemented before being placed on stack. LD A,E ; fetch lowest (rightmost) mantissa byte. NEG ; Negate CCF ; Complement Carry Flag LD E,A ; place back in register LD A,D ; ditto CPL ; ADC A,0 ; LD D,A ; EXX ; switch to higher (leftmost) 16 bits. LD A,E ; ditto CPL ; ADC A,0 ; LD E,A ; LD A,D ; ditto CPL ; ADC A,0 ; JR NC,END_COMPL ; forward without overflow to END_COMPL ; else entire mantissa is now zero. 00 00 00 00 RRA ; set mantissa to 80 00 00 00 EXX ; switch. INC (HL) ; increment the exponent. mark_17B3: ADD_REP_6: JP Z,REPORT_6 ; jump forward if exponent now zero to REPORT_6 ; 'Number too big' EXX ; switch back to alternate set. mark_17B7: END_COMPL: LD D,A ; put first byte of mantissa back in DE. EXX ; switch to main set. mark_17B9: GO_NC_MLT: XOR A ; clear carry flag and ; clear accumulator so no extra bits carried ; forward as occurs in multiplication. JR TEST_NORM ; forward to common code at TEST_NORM ; but should go straight to NORMALIZE.
; THE 'PREPARE TO MULTIPLY OR DIVIDE' SUBROUTINE
; this routine is called twice from multiplication and twice from division ; to prepare each of the two numbers for the operation. ; Initially the accumulator holds zero and after the second invocation bit 7 ; of the accumulator will be the sign bit of the result. mark_17BC: PREP_MULTIPLY_OR_DIVIDE: SCF ; set carry flag to signal number is zero. DEC (HL) ; test exponent INC (HL) ; for zero. RET Z ; return if zero with carry flag set. INC HL ; address first mantissa byte. XOR (HL) ; exclusive or the running sign bit. SET 7,(HL) ; set the implied bit. DEC HL ; point to exponent byte. RET ; return.
; THE 'MULTIPLICATION' OPERATION
; ; mark_17C6:
MULTIPLY: XOR A ; reset bit 7 of running sign flag. CALL PREP_MULTIPLY_OR_DIVIDE RET C ; return if number is zero. ; zero * anything = zero. EXX ; - - - PUSH HL ; save pointer to 'next literal' EXX ; - - - PUSH DE ; save pointer to second number EX DE,HL ; make HL address second number. CALL PREP_MULTIPLY_OR_DIVIDE EX DE,HL ; HL first number, DE - second number JR C,ZERO_RESULT ; forward with carry to ZERO_RESULT ; anything * zero = zero. PUSH HL ; save pointer to first number. CALL FETCH_TWO ; fetches two mantissas from ; calc stack to B'C'C,B D'E'D E ; (HL will be overwritten but the result sign ; in A is inserted on the calculator stack) LD A,B ; transfer low mantissa byte of first number AND A ; clear carry. SBC HL,HL ; a short form of LD HL,$0000 to take lower ; two bytes of result. (2 program bytes) EXX ; switch in alternate set PUSH HL ; preserve HL SBC HL,HL ; set HL to zero also to take higher two bytes ; of the result and clear carry. EXX ; switch back. LD B,33 ; register B can now be used to count 33 shifts. JR STRT_MLT ; forward to loop entry point STRT_MLT ; ___ ; The multiplication loop is entered at STRT_LOOP. mark_17E7: MLT_LOOP: JR NC,NO_ADD ; forward if no carry ; else add in the multiplicand. ADD HL,DE ; add the two low bytes to result EXX ; switch to more significant bytes. ADC HL,DE ; add high bytes of multiplicand and any carry. EXX ; switch to main set. ; in either case shift result right into B'C'C A mark_17EE: NO_ADD: EXX ; switch to alternate set RR H ; C > 76543210 > C RR L ; C > 76543210 > C EXX ; RR H ; C > 76543210 > C RR L ; C > 76543210 > C mark_17F8: STRT_MLT: EXX ; switch in alternate set. RR B ; C > 76543210 > C RR C ; C > 76543210 > C EXX ; now main set RR C ; C > 76543210 > C RRA ; C > 76543210 > C DJNZ MLT_LOOP ; loop back 33 timeS ; EX DE,HL ; EXX ; EX DE,HL ; EXX ; POP BC ; POP HL ; LD A,B ; ADD A,C ; JR NZ,MAKE_EXPT ; forward AND A ; mark_180E: MAKE_EXPT: DEC A ; CCF ; Complement Carry Flag mark_1810: DIVN_EXPT: RLA ; CCF ; Complement Carry Flag RRA ; JP P,OFLW1_CLR JR NC,REPORT_6 AND A ; mark_1819: OFLW1_CLR: INC A ; JR NZ,OFLW2_CLR JR C,OFLW2_CLR EXX ; BIT 7,D ; EXX ; JR NZ,REPORT_6 mark_1824: OFLW2_CLR: LD (HL),A ; EXX ; LD A,B ; EXX ; ; addition joins here with carry flag clear. mark_1828: TEST_NORM: JR NC,NORMALIZE ; forward LD A,(HL) ; AND A ; mark_182C: NEAR_ZERO: LD A,$80 ; prepare to rescue the most significant bit ; of the mantissa if it is set. JR Z,SKIP_ZERO ; skip forward mark_1830: ZERO_RESULT: XOR A ; make mask byte zero signaling set five ; bytes to zero. mark_1831: SKIP_ZERO: EXX ; switch in alternate set AND D ; isolate most significant bit (if A is $80). CALL ZEROS_4_OR_5 ; sets mantissa without ; affecting any flags. RLCA ; test if MSB set. bit 7 goes to bit 0. ; either $00 -> $00 or $80 -> $01 LD (HL),A ; make exponent $01 (lowest) or $00 zero JR C,OFLOW_CLR ; forward if first case INC HL ; address first mantissa byte on the ; calculator stack. LD (HL),A ; insert a zero for the sign bit. DEC HL ; point to zero exponent JR OFLOW_CLR ; forward ; ___ ; this branch is common to addition and multiplication with the mantissa ; result still in registers D'E'D E . mark_183F: NORMALIZE: LD B,32 ; a maximum of thirty-two left shifts will be ; needed. mark_1841: SHIFT_ONE: EXX ; address higher 16 bits. BIT 7,D ; test the leftmost bit EXX ; address lower 16 bits. JR NZ,NORML_NOW ; forward if leftmost bit was set RLCA ; this holds zero from addition, 33rd bit ; from multiplication. RL E ; C < 76543210 < C RL D ; C < 76543210 < C EXX ; address higher 16 bits. RL E ; C < 76543210 < C RL D ; C < 76543210 < C EXX ; switch to main set. DEC (HL) ; decrement the exponent byte on the calculator ; stack. JR Z,NEAR_ZERO ; back if exponent becomes zero ; it's just possible that the last rotation ; set bit 7 of D. We shall see. DJNZ SHIFT_ONE ; loop back ; if thirty-two left shifts were performed without setting the most significant ; bit then the result is zero. JR ZERO_RESULT ; back ; ___ mark_1859: NORML_NOW: RLA ; for the addition path, A is always zero. ; for the mult path, ... JR NC,OFLOW_CLR ; forward ; this branch is taken only with multiplication. CALL ADD_BACK JR NZ,OFLOW_CLR ; forward EXX ; LD D,$80 ; EXX ; INC (HL) ; JR Z,REPORT_6 ; forward ; now transfer the mantissa from the register sets to the calculator stack ; incorporating the sign bit already there. mark_1868: OFLOW_CLR: PUSH HL ; save pointer to exponent on stack. INC HL ; address first byte of mantissa which was ; previously loaded with sign bit $00 or $80. EXX ; - - - PUSH DE ; push the most significant two bytes. EXX ; - - - POP BC ; pop - true mantissa is now BCDE. ; now pick up the sign bit. LD A,B ; first mantissa byte to A RLA ; rotate out bit 7 which is set RL (HL) ; rotate sign bit on stack into carry. RRA ; rotate sign bit into bit 7 of mantissa. ; and transfer mantissa from main registers to calculator stack. LD (HL),A ; INC HL ; LD (HL),C ; INC HL ; LD (HL),D ; INC HL ; LD (HL),E ; POP HL ; restore pointer to num1 now result. POP DE ; restore pointer to num2 now STKEND. EXX ; - - - POP HL ; restore pointer to next calculator literal. EXX ; - - - RET ; return. ; ___ mark_1880: REPORT_6: RST _ERROR_1 DEFB 5 ; Error Report: Arithmetic overflow.
; THE 'DIVISION' OPERATION
; "Of all the arithmetic subroutines, division is the most complicated and ; the least understood. It is particularly interesting to note that the ; Sinclair programmer himself has made a mistake in his programming ( or has ; copied over someone else's mistake!) for ; PRINT PEEK 6352 [ $18D0 ] ('unimproved' ROM, 6351 [ $18CF ] ) ; should give 218 not 225." ; - Dr. Ian Logan, Syntax magazine Jul/Aug 1982. ; [ i.e. the jump should be made to div-34th ] ; First check for division by zero. mark_1882:
DIVISION: EX DE,HL ; consider the second number first. XOR A ; set the running sign flag. CALL PREP_MULTIPLY_OR_DIVIDE JR C,REPORT_6 ; back if zero ; 'Arithmetic overflow' EX DE,HL ; now prepare first number and check for zero. CALL PREP_MULTIPLY_OR_DIVIDE RET C ; return if zero, 0/anything is zero. EXX ; - - - PUSH HL ; save pointer to the next calculator literal. EXX ; - - - PUSH DE ; save pointer to divisor - will be STKEND. PUSH HL ; save pointer to dividend - will be result. CALL FETCH_TWO ; fetches the two numbers ; into the registers H'B'C'C B ; L'D'E'D E EXX ; - - - PUSH HL ; save the two exponents. LD H,B ; transfer the dividend to H'L'H L LD L,C ; EXX ; LD H,C ; LD L,B ; XOR A ; clear carry bit and accumulator. LD B,$DF ; count upwards from -33 decimal JR DIVISION_START ; forward to mid-loop entry point ; ___ mark_18A2: DIV_LOOP: RLA ; multiply partial quotient by two RL C ; setting result bit from carry. EXX ; RL C ; RL B ; EXX ; mark_18AB: DIV_34TH: ADD HL,HL ; EXX ; ADC HL,HL ; EXX ; JR C,SUBN_ONLY ; forward mark_18B2: DIVISION_START: SBC HL,DE ; subtract divisor part. EXX ; SBC HL,DE ; EXX ; JR NC,NUM_RESTORE ; forward if subtraction goes ADD HL,DE ; else restore EXX ; ADC HL,DE ; EXX ; AND A ; clear carry JR COUNT_ONE ; forward ; ___ mark_18C2: SUBN_ONLY: AND A ; SBC HL,DE ; EXX ; SBC HL,DE ; EXX ; mark_18C9: NUM_RESTORE: SCF ; set carry flag mark_18CA: COUNT_ONE: INC B ; increment the counter JP M,DIV_LOOP ; back while still minus to DIV_LOOP PUSH AF ; JR Z,DIVISION_START ; back to DIV_START ; "This jump is made to the wrong place. No 34th bit will ever be obtained ; without first shifting the dividend. Hence important results like 1/10 and ; 1/1000 are not rounded up as they should be. Rounding up never occurs when ; it depends on the 34th bit. The jump should be made to div_34th above." ; - Dr. Frank O'Hara, "The Complete Spectrum ROM Disassembly", 1983, ; published by Melbourne House. ; (Note. on the ZX81 this would be JR Z,DIV_34TH) ; ; However if you make this change, then while (1/2=.5) will now evaluate as ; true, (.25=1/4), which did evaluate as true, no longer does. LD E,A ; LD D,C ; EXX ; LD E,C ; LD D,B ; POP AF ; RR B ; POP AF ; RR B ; EXX ; POP BC ; POP HL ; LD A,B ; SUB C ; JP DIVN_EXPT ; jump back
; THE 'INTEGER TRUNCATION TOWARDS ZERO' SUBROUTINE
; mark_18E4: TRUNCATE: LD A,(HL) ; fetch exponent CP $81 ; compare to +1 JR NC,T_GR_ZERO ; forward, if 1 or more ; else the number is smaller than plus or minus 1 and can be made zero. LD (HL),$00 ; make exponent zero. LD A,$20 ; prepare to set 32 bits of mantissa to zero. JR NIL_BYTES ; forward ; ___ mark_18EF: T_GR_ZERO: SUB $A0 ; subtract +32 from exponent RET P ; return if result is positive as all 32 bits ; of the mantissa relate to the integer part. ; The floating point is somewhere to the right ; of the mantissa NEG ; else negate to form number of rightmost bits ; to be blanked. ; for instance, disregarding the sign bit, the number 3.5 is held as ; exponent $82 mantissa .11100000 00000000 00000000 00000000 ; we need to set $82 - $A0 = $E2 NEG = $1E (thirty) bits to zero to form the ; integer. ; The sign of the number is never considered as the first bit of the mantissa ; must be part of the integer. mark_18F4: NIL_BYTES: PUSH DE ; save pointer to STKEND EX DE,HL ; HL points at STKEND DEC HL ; now at last byte of mantissa. LD B,A ; Transfer bit count to B register. SRL B ; divide by SRL B ; eight SRL B ; JR Z,BITS_ZERO ; forward if zero ; else the original count was eight or more and whole bytes can be blanked. mark_1900: BYTE_ZERO: LD (HL),0 ; set eight bits to zero. DEC HL ; point to more significant byte of mantissa. DJNZ BYTE_ZERO ; loop back ; now consider any residual bits. mark_1905: BITS_ZERO: AND $07 ; isolate the remaining bits JR Z,IX_END ; forward if none LD B,A ; transfer bit count to B counter. LD A,$FF ; form a mask 11111111 mark_190C: LESS_MASK: SLA A ; 1 <- 76543210 <- o slide mask leftwards. DJNZ LESS_MASK ; loop back for bit count AND (HL) ; lose the unwanted rightmost bits LD (HL),A ; and place in mantissa byte. mark_1912: IX_END: EX DE,HL ; restore result pointer from DE. POP DE ; restore STKEND from stack. RET ; return.
; Up to this point all routine addresses have been maintained so that the ; modified ROM is compatible with any machine-code software that uses ROM ; routines. ; The final section does not maintain address entry points as the routines ; within are not generally called directly.
;** FLOATING-POINT CALCULATOR ** ;******************************** ; As a general rule the calculator avoids using the IY register. ; Exceptions are val and str$. ; So an assembly language programmer who has disabled interrupts to use IY ; for other purposes can still use the calculator for mathematical ; purposes.
; THE 'TABLE OF CONSTANTS'
; The ZX81 has only floating-point number representation. ; Both the ZX80 and the ZX Spectrum have integer numbers in some form.
TAB_CNST #if ORIGINAL mark_1915: ; 00 00 00 00 00 stk_zero: DEFB $00 ;;Bytes: 1 DEFB $B0 ;;Exponent $00 DEFB $00 ;;(+00,+00,+00) mark_1918: ; 81 00 00 00 00 stk_one: DEFB $31 ;;Exponent $81, Bytes: 1 DEFB $00 ;;(+00,+00,+00) mark_191A: ; 80 00 00 00 00 stk_half: DEFB $30 ;;Exponent: $80, Bytes: 1 DEFB $00 ;;(+00,+00,+00) mark_191C: ; 81 49 0F DA A2 stk_half_pi: DEFB $F1 ;;Exponent: $81, Bytes: 4 DEFB $49,$0F,$DA,$A2 ;; mark_1921: ; 84 20 00 00 00 stk_ten: DEFB $34 ;;Exponent: $84, Bytes: 1 DEFB $20 ;;(+00,+00,+00) #else ; This table has been modified so that the constants are held in their ; uncompressed, ready-to-use, 5-byte form. DEFB $00 ; the value zero. DEFB $00 ; DEFB $00 ; DEFB $00 ; DEFB $00 ; DEFB $81 ; the floating point value 1. DEFB $00 ; DEFB $00 ; DEFB $00 ; DEFB $00 ; DEFB $80 ; the floating point value 1/2. DEFB $00 ; DEFB $00 ; DEFB $00 ; DEFB $00 ; DEFB $81 ; the floating point value pi/2. DEFB $49 ; DEFB $0F ; DEFB $DA ; DEFB $A2 ; DEFB $84 ; the floating point value ten. DEFB $20 ; DEFB $00 ; DEFB $00 ; DEFB $00 ; #endif
; THE 'TABLE OF ADDRESSES'
; ; starts with binary operations which have two operands and one result. ; three pseudo binary operations first. #if ORIGINAL mark_1923: #else #endif
tbl_addrs: DEFW jump_true ; $00 Address: $1C2F - jump_true DEFW exchange ; $01 Address: $1A72 - exchange DEFW delete ; $02 Address: $19E3 - delete ; true binary operations. DEFW SUBTRACT ; $03 Address: $174C - subtract DEFW MULTIPLY ; $04 Address: $176C - multiply DEFW DIVISION ; $05 Address: $1882 - division DEFW to_power ; $06 Address: $1DE2 - to_power DEFW or ; $07 Address: $1AED - or DEFW boolean_num_and_num ; $08 Address: $1AF3 - boolean_num_and_num DEFW num_l_eql ; $09 Address: $1B03 - num_l_eql DEFW num_gr_eql ; $0A Address: $1B03 - num_gr_eql DEFW nums_neql ; $0B Address: $1B03 - nums_neql DEFW num_grtr ; $0C Address: $1B03 - num_grtr DEFW num_less ; $0D Address: $1B03 - num_less DEFW nums_eql ; $0E Address: $1B03 - nums_eql DEFW ADDITION ; $0F Address: $1755 - addition DEFW strs_and_num ; $10 Address: $1AF8 - str_and_num DEFW str_l_eql ; $11 Address: $1B03 - str_l_eql DEFW str_gr_eql ; $12 Address: $1B03 - str_gr_eql DEFW strs_neql ; $13 Address: $1B03 - strs_neql DEFW str_grtr ; $14 Address: $1B03 - str_grtr DEFW str_less ; $15 Address: $1B03 - str_less DEFW strs_eql ; $16 Address: $1B03 - strs_eql DEFW strs_add ; $17 Address: $1B62 - strs_add ; unary follow DEFW neg ; $18 DEFW code ; $19 DEFW val ; $1A DEFW len ; $1B DEFW sin ; $1C DEFW cos ; $1D DEFW tan ; $1E DEFW asn ; $1F DEFW acs ; $20 DEFW atn ; $21 DEFW ln ; $22 DEFW exp ; $23 DEFW int ; $24 DEFW sqr ; $25 DEFW sgn ; $26 DEFW abs ; $27 DEFW PEEK ; $28 Address: $1A1B - peek !!!! DEFW usr_num ; $29 DEFW str_dollar ; $2A DEFW chr_dollar ; $2B DEFW not ; $2C ; end of true unary DEFW duplicate ; $2D DEFW n_mod_m ; $2E DEFW jump ; $2F DEFW stk_data ; $30 DEFW dec_jr_nz ; $31 DEFW less_0 ; $32 DEFW greater_0 ; $33 DEFW end_calc ; $34 DEFW get_argt ; $35 DEFW TRUNCATE ; $36 DEFW FP_CALC_2 ; $37 DEFW e_to_fp ; $38 ; the following are just the next available slots for the 128 compound literals ; which are in range $80 - $FF. DEFW series_xx ; $39 : $80 - $9F. DEFW stk_const_xx ; $3A : $A0 - $BF. DEFW st_mem_xx ; $3B : $C0 - $DF. DEFW get_mem_xx ; $3C : $E0 - $FF. ; Aside: 3D - 7F are therefore unused calculator literals. ; 39 - 7B would be available for expansion.
; THE 'FLOATING POINT CALCULATOR'
; ; mark_199D: CALCULATE: CALL STACK_POINTERS ; is called to set up the ; calculator stack pointers for a default ; unary operation. HL = last value on stack. ; DE = STKEND first location after stack. ; the calculate routine is called at this point by the series generator... mark_19A0: GEN_ENT_1: LD A,B ; fetch the Z80 B register to A LD (BERG),A ; and store value in system variable BERG. ; this will be the counter for dec_jr_nz ; or if used from FP_CALC2 the calculator ; instruction. ; ... and again later at this point mark_19A4: GEN_ENT_2: EXX ; switch sets EX (SP),HL ; and store the address of next instruction, ; the return address, in H'L'. ; If this is a recursive call then the H'L' ; of the previous invocation goes on stack. ; c.f. end_calc. EXX ; switch back to main set. ; this is the re-entry looping point when handling a string of literals. mark_19A7: RE_ENTRY: LD (STKEND),DE ; save end of stack EXX ; switch to alt LD A,(HL) ; get next literal INC HL ; increase pointer' ; single operation jumps back to here mark_19AE: SCAN_ENT: PUSH HL ; save pointer on stack * AND A ; now test the literal JP P,FIRST_3D ; forward if in range $00 - $3D ; anything with bit 7 set will be one of ; 128 compound literals. ; compound literals have the following format. ; bit 7 set indicates compound. ; bits 6-5 the subgroup 0-3. ; bits 4-0 the embedded parameter $00 - $1F. ; The subgroup 0-3 needs to be manipulated to form the next available four ; address places after the simple literals in the address table. LD D,A ; save literal in D AND $60 ; and with 01100000 to isolate subgroup RRCA ; rotate bits RRCA ; 4 places to right RRCA ; not five as we need offset * 2 RRCA ; 00000xx0 ADD A,$72 ; add ($39 * 2) to give correct offset. ; alter above if you add more literals. LD L,A ; store in L for later indexing. LD A,D ; bring back compound literal AND $1F ; use mask to isolate parameter bits JR ENT_TABLE ; forward ; ___ ; the branch was here with simple literals. mark_19C2: FIRST_3D: CP $18 ; compare with first unary operations. JR NC,DOUBLE_A ; with unary operations ; it is binary so adjust pointers. EXX ; LD BC,-5 LD D,H ; transfer HL, the last value, to DE. LD E,L ; ADD HL,BC ; subtract 5 making HL point to second ; value. EXX ; mark_19CE: DOUBLE_A: RLCA ; double the literal LD L,A ; and store in L for indexing mark_19D0: ENT_TABLE: LD DE,tbl_addrs ; Address: tbl_addrs LD H,$00 ; prepare to index ADD HL,DE ; add to get address of routine LD E,(HL) ; low byte to E INC HL ; LD D,(HL) ; high byte to D LD HL,RE_ENTRY EX (SP),HL ; goes on machine stack ; address of next literal goes to HL. * PUSH DE ; now the address of routine is stacked. EXX ; back to main set ; avoid using IY register. LD BC,(STKEND+1) ; STKEND_hi ; nothing much goes to C but BERG to B ; and continue into next ret instruction ; which has a dual identity
; THE 'DELETE' SUBROUTINE
; offset $02: 'delete' ; A simple return but when used as a calculator literal this ; deletes the last value from the calculator stack. ; On entry, as always with binary operations, ; HL=first number, DE=second number ; On exit, HL=result, DE=stkend. ; So nothing to do mark_19E3: delete: RET ; return - indirect jump if from above.
; THE 'SINGLE OPERATION' SUBROUTINE
; offset $37: 'FP_CALC_2' ; this single operation is used, in the first instance, to evaluate most ; of the mathematical and string functions found in BASIC expressions. mark_19E4:
FP_CALC_2: POP AF ; drop return address. LD A,(BERG) ; load accumulator from system variable BERG ; value will be literal eg. 'tan' EXX ; switch to alt JR SCAN_ENT ; back ; next literal will be end_calc in scanning
; THE 'TEST 5 SPACES' SUBROUTINE
; This routine is called from MOVE_FP, STK_CONST and STK_STORE to ; test that there is enough space between the calculator stack and the ; machine stack for another five_byte value. It returns with BC holding ; the value 5 ready for any subsequent LDIR. mark_19EB: TEST_5_SP: PUSH DE ; save PUSH HL ; registers LD BC,5 ; an overhead of five bytes CALL TEST_ROOM ; tests free RAM raising ; an error if not. POP HL ; else restore POP DE ; registers. RET ; return with BC set at 5.
; THE 'MOVE A FLOATING POINT NUMBER' SUBROUTINE
; offset $2D: 'duplicate' ; This simple routine is a 5-byte LDIR instruction ; that incorporates a memory check. ; When used as a calculator literal it duplicates the last value on the ; calculator stack. ; Unary so on entry HL points to last value, DE to stkend mark_19F6: duplicate: MOVE_FP: CALL TEST_5_SP ; test free memory ; and sets BC to 5. LDIR ; copy the five bytes. RET ; return with DE addressing new STKEND ; and HL addressing new last value.
; THE 'STACK LITERALS' SUBROUTINE
; offset $30: 'stk_data' ; When a calculator subroutine needs to put a value on the calculator ; stack that is not a regular constant this routine is called with a ; variable number of following data bytes that convey to the routine ; the floating point form as succinctly as is possible. mark_19FC: stk_data: LD H,D ; transfer STKEND LD L,E ; to HL for result. mark_19FE: STK_CONST: CALL TEST_5_SP ; tests that room exists ; and sets BC to $05. EXX ; switch to alternate set PUSH HL ; save the pointer to next literal on stack EXX ; switch back to main set EX (SP),HL ; pointer to HL, destination to stack. #if ORIGINAL PUSH BC ; save BC - value 5 from test room ??. #else ;; PUSH BC ; save BC - value 5 from test room. No need. #endif LD A,(HL) ; fetch the byte following 'stk_data' AND $C0 ; isolate bits 7 and 6 RLCA ; rotate RLCA ; to bits 1 and 0 range $00 - $03. LD C,A ; transfer to C INC C ; and increment to give number of bytes ; to read. $01 - $04 LD A,(HL) ; reload the first byte AND $3F ; mask off to give possible exponent. JR NZ,FORM_EXP ; forward to FORM_EXP if it was possible to ; include the exponent. ; else byte is just a byte count and exponent comes next. INC HL ; address next byte and LD A,(HL) ; pick up the exponent ( - $50). mark_1A14: FORM_EXP: ADD A,$50 ; now add $50 to form actual exponent LD (DE),A ; and load into first destination byte. LD A,$05 ; load accumulator with $05 and SUB C ; subtract C to give count of trailing ; zeros plus one. INC HL ; increment source INC DE ; increment destination #if ORIGINAL LD B,$00 ; prepare to copy. Note. B is zero. LDIR ; copy C bytes POP BC ; restore 5 counter to BC. #else LDIR ; copy C bytes #endif EX (SP),HL ; put HL on stack as next literal pointer ; and the stack value - result pointer - ; to HL. EXX ; switch to alternate set. POP HL ; restore next literal pointer from stack ; to H'L'. EXX ; switch back to main set. LD B,A ; zero count to B XOR A ; clear accumulator mark_1A27: STK_ZEROS: DEC B ; decrement B counter RET Z ; return if zero. >> ; DE points to new STKEND ; HL to new number. LD (DE),A ; else load zero to destination INC DE ; increase destination JR STK_ZEROS ; loop back until done.
; THE 'SKIP CONSTANTS' SUBROUTINE
; This routine traverses variable-length entries in the table of constants, ; stacking intermediate, unwanted constants onto a dummy calculator stack, ; in the first five bytes of the ZX81 ROM. #if ORIGINAL mark_1A2D: SKIP_CONS: AND A ; test if initially zero. mark_1A2E: SKIP_NEXT: RET Z ; return if zero. >> PUSH AF ; save count. PUSH DE ; and normal STKEND LD DE,$0000 ; dummy value for STKEND at start of ROM ; Note. not a fault but this has to be ; moved elsewhere when running in RAM. ; CALL STK_CONST ; works through variable ; length records. POP DE ; restore real STKEND POP AF ; restore count DEC A ; decrease JR SKIP_NEXT ; loop back #else ; Since the table now uses uncompressed values, some extra ROM space is ; required for the table but much more is released by getting rid of routines ; like this. #endif
; THE 'MEMORY LOCATION' SUBROUTINE
; This routine, when supplied with a base address in HL and an index in A, ; will calculate the address of the A'th entry, where each entry occupies ; five bytes. It is used for addressing floating-point numbers in the ; calculator's memory area. mark_1A3C: LOC_MEM: LD C,A ; store the original number $00-$1F. RLCA ; double. RLCA ; quadruple. ADD A,C ; now add original value to multiply by five. LD C,A ; place the result in C. LD B,$00 ; set B to 0. ADD HL,BC ; add to form address of start of number in HL. RET ; return.
; THE 'GET FROM MEMORY AREA' SUBROUTINE
; offsets $E0 to $FF: 'get_mem_0', 'get_mem_1' etc. ; A holds $00-$1F offset. ; The calculator stack increases by 5 bytes. mark_1A45:
get_mem_xx: #if ORIGINAL PUSH DE ; save STKEND LD HL,(MEM) ; MEM is base address of the memory cells. #else ; Note. first two instructions have been swapped to create a subroutine. LD HL,(MEM) ; MEM is base address of the memory cells. INDEX_5 ; new label PUSH DE ; save STKEND #endif CALL LOC_MEM ; so that HL = first byte CALL MOVE_FP ; moves 5 bytes with memory ; check. ; DE now points to new STKEND. POP HL ; the original STKEND is now RESULT pointer. RET ; return.
; THE 'STACK A CONSTANT' SUBROUTINE
stk_const_xx: #if ORIGINAL ; offset $A0: 'stk_zero' ; offset $A1: 'stk_one' ; offset $A2: 'stk_half' ; offset $A3: 'stk_half_pi' ; offset $A4: 'stk_ten' ; ; This routine allows a one-byte instruction to stack up to 32 constants ; held in short form in a table of constants. In fact only 5 constants are ; required. On entry the A register holds the literal ANDed with $1F. ; It isn't very efficient and it would have been better to hold the ; numbers in full, five byte form and stack them in a similar manner ; to that which would be used later for semi-tone table values. mark_1A51: LD H,D ; save STKEND - required for result LD L,E ; EXX ; swap PUSH HL ; save pointer to next literal LD HL,stk_zero ; Address: stk_zero - start of table of ; constants EXX ; CALL SKIP_CONS CALL STK_CONST EXX ; POP HL ; restore pointer to next literal. EXX ; RET ; return. #else stk_con_x LD HL,TAB_CNST ; Address: Table of constants. JR INDEX_5 ; and join subroutine above. #endif
; THE 'STORE IN A MEMORY AREA' SUBROUTINE
; Offsets $C0 to $DF: 'st_mem_0', 'st_mem_1' etc. ; Although 32 memory storage locations can be addressed, only six ; $C0 to $C5 are required by the ROM and only the thirty bytes (6*5) ; required for these are allocated. ZX81 programmers who wish to ; use the floating point routines from assembly language may wish to ; alter the system variable MEM to point to 160 bytes of RAM to have ; use the full range available. ; A holds derived offset $00-$1F. ; Unary so on entry HL points to last value, DE to STKEND. mark_1A63: st_mem_xx: PUSH HL ; save the result pointer. EX DE,HL ; transfer to DE. LD HL,(MEM) ; fetch MEM the base of memory area. CALL LOC_MEM ; sets HL to the destination. EX DE,HL ; swap - HL is start, DE is destination. #if ORIGINAL CALL MOVE_FP ; note. a short ld bc,5; ldir ; the embedded memory check is not required ; so these instructions would be faster! #else LD C,5 ;+ one extra byte but LDIR ;+ faster and no memory check. #endif EX DE,HL ; DE = STKEND POP HL ; restore original result pointer RET ; return.
; THE 'EXCHANGE' SUBROUTINE
; offset $01: 'exchange' ; This routine exchanges the last two values on the calculator stack ; On entry, as always with binary operations, ; HL=first number, DE=second number ; On exit, HL=result, DE=stkend. mark_1A72: exchange: LD B,$05 ; there are five bytes to be swapped ; start of loop. mark_1A74: SWAP_BYTE: LD A,(DE) ; each byte of second #if ORIGINAL LD C,(HL) ; each byte of first EX DE,HL ; swap pointers #else LD C,A ;+ LD A,(HL) #endif LD (DE),A ; store each byte of first LD (HL),C ; store each byte of second INC HL ; advance both INC DE ; pointers. DJNZ SWAP_BYTE ; loop back until all 5 done. #if ORIGINAL EX DE,HL ; even up the exchanges so that DE addresses STKEND. #else ; omit #endif RET ; return.
; THE 'SERIES GENERATOR' SUBROUTINE
; The ZX81 uses Chebyshev polynomials to generate approximations for ; SIN, ATN, LN and EXP. These are named after the Russian mathematician ; Pafnuty Chebyshev, born in 1821, who did much pioneering work on numerical ; series. As far as calculators are concerned, Chebyshev polynomials have an ; advantage over other series, for example the Taylor series, as they can ; reach an approximation in just six iterations for SIN, eight for EXP and ; twelve for LN and ATN. The mechanics of the routine are interesting but ; for full treatment of how these are generated with demonstrations in ; Sinclair BASIC see "The Complete Spectrum ROM Disassembly" by Dr Ian Logan ; and Dr Frank O'Hara, published 1983 by Melbourne House. mark_1A7F: series_xx: LD B,A ; parameter $00 - $1F to B counter CALL GEN_ENT_1 ; A recursive call to a special entry point ; in the calculator that puts the B register ; in the system variable BERG. The return ; address is the next location and where ; the calculator will expect its first ; instruction - now pointed to by HL'. ; The previous pointer to the series of ; five-byte numbers goes on the machine stack. ; The initialization phase. DEFB __duplicate ;; x,x DEFB __addition ;; x+x DEFB __st_mem_0 ;; x+x DEFB __delete ;; . DEFB __stk_zero ;; 0 DEFB __st_mem_2 ;; 0 ; a loop is now entered to perform the algebraic calculation for each of ; the numbers in the series mark_1A89: G_LOOP: DEFB __duplicate ;; v,v. DEFB __get_mem_0 ;; v,v,x+2 DEFB __multiply ;; v,v*x+2 DEFB __get_mem_2 ;; v,v*x+2,v DEFB __st_mem_1 ;; DEFB __subtract ;; DEFB __end_calc ;; ; the previous pointer is fetched from the machine stack to H'L' where it ; addresses one of the numbers of the series following the series literal. CALL stk_data ; is called directly to ; push a value and advance H'L'. CALL GEN_ENT_2 ; recursively re-enters ; the calculator without disturbing ; system variable BERG ; H'L' value goes on the machine stack and is ; then loaded as usual with the next address. DEFB __addition ;; DEFB __exchange ;; DEFB __st_mem_2 ;; DEFB __delete ;; DEFB __dec_jr_nz ;; DEFB $EE ;;back to G_LOOP, G_LOOP ; when the counted loop is complete the final subtraction yields the result ; for example SIN X. DEFB __get_mem_1 ;; DEFB __subtract ;; DEFB __end_calc ;; RET ; return with H'L' pointing to location ; after last number in series.
; Handle unary minus (18)
; Unary so on entry HL points to last value, DE to STKEND. mark_1AA0: mark_1AA0: neg: LD A, (HL) ; fetch exponent of last value on the ; calculator stack. AND A ; test it. RET Z ; return if zero. INC HL ; address the byte with the sign bit. LD A,(HL) ; fetch to accumulator. XOR $80 ; toggle the sign bit. LD (HL),A ; put it back. DEC HL ; point to last value again. RET ; return.
; Absolute magnitude (27)
; This calculator literal finds the absolute value of the last value, ; floating point, on calculator stack. mark_1AAA:
abs: INC HL ; point to byte with sign bit. RES 7,(HL) ; make the sign positive. DEC HL ; point to last value again. RET ; return.
; Signum (26)
; This routine replaces the last value on the calculator stack, ; which is in floating point form, with one if positive and with -minus one ; if negative. If it is zero then it is left as such. mark_1AAF:
sgn: INC HL ; point to first byte of 4-byte mantissa. LD A,(HL) ; pick up the byte with the sign bit. DEC HL ; point to exponent. DEC (HL) ; test the exponent for INC (HL) ; the value zero. SCF ; set the carry flag. CALL NZ,FP_0_OR_1 ; replaces last value with one ; if exponent indicates the value is non-zero. ; in either case mantissa is now four zeros. INC HL ; point to first byte of 4-byte mantissa. RLCA ; rotate original sign bit to carry. RR (HL) ; rotate the carry into sign. DEC HL ; point to last value. RET ; return.
; Handle PEEK function (28)
; This function returns the contents of a memory address. ; The entire address space can be peeked including the ROM. mark_1ABE: PEEK: CALL FIND_INT ; puts address in BC. LD A,(BC) ; load contents into A register. mark_1AC2: IN_PK_STK: JP STACK_A ; exit via STACK_A to put value on the ; calculator stack.
; USR number (29)
; The USR function followed by a number 0-65535 is the method by which ; the ZX81 invokes machine code programs. This function returns the ; contents of the BC register pair. ; Note. that STACK_BC re-initializes the IY register to ERR_NR if a user-written ; program has altered it. mark_1AC5: usr_num: CALL FIND_INT ; to fetch the ; supplied address into BC. LD HL,STACK_BC ; address: STACK_BC is PUSH HL ; pushed onto the machine stack. PUSH BC ; then the address of the machine code ; routine. RET ; make an indirect jump to the routine ; and, hopefully, to STACK_BC also.
; Greater than zero ($33)
; Test if the last value on the calculator stack is greater than zero. ; This routine is also called directly from the end-tests of the comparison ; routine. mark_1ACE: greater_0: LD A,(HL) ; fetch exponent. AND A ; test it for zero. RET Z ; return if so. LD A,$FF ; prepare XOR mask for sign bit JR SIGN_TO_C ; forward to SIGN_TO_C ; to put sign in carry ; (carry will become set if sign is positive) ; and then overwrite location with 1 or 0 ; as appropriate.
; Handle NOT operator ($2C)
; This overwrites the last value with 1 if it was zero else with zero ; if it was any other value. ; ; e.g. NOT 0 returns 1, NOT 1 returns 0, NOT -3 returns 0. ; ; The subroutine is also called directly from the end-tests of the comparison ; operator. mark_1AD5: not: LD A,(HL) ; get exponent byte. NEG ; negate - sets carry if non-zero. CCF ; complement so carry set if zero, else reset. JR FP_0_OR_1 ; forward to FP_0_OR_1.
; Less than zero (32)
; Destructively test if last value on calculator stack is less than zero. ; Bit 7 of second byte will be set if so. mark_1ADB: less_0: XOR A ; set xor mask to zero ; (carry will become set if sign is negative). ; transfer sign of mantissa to Carry Flag. mark_1ADC: SIGN_TO_C: INC HL ; address 2nd byte. XOR (HL) ; bit 7 of HL will be set if number is negative. DEC HL ; address 1st byte again. RLCA ; rotate bit 7 of A to carry.
; Zero or one
; This routine places an integer value zero or one at the addressed location ; of calculator stack or MEM area. The value one is written if carry is set on ; entry else zero. mark_1AE0:
FP_0_OR_1: PUSH HL ; save pointer to the first byte LD B,$05 ; five bytes to do. mark_1AE3: FP_loop: LD (HL),$00 ; insert a zero. INC HL ; DJNZ FP_loop ; repeat. POP HL ; RET NC ; LD (HL),$81 ; make value 1 RET ; return.
; Handle OR operator (07)
; The Boolean OR operator. eg. X OR Y ; The result is zero if both values are zero else a non-zero value. ; ; e.g. 0 OR 0 returns 0. ; -3 OR 0 returns -3. ; 0 OR -3 returns 1. ; -3 OR 2 returns 1. ; ; A binary operation. ; On entry HL points to first operand (X) and DE to second operand (Y). mark_1AED: or: LD A,(DE) ; fetch exponent of second number AND A ; test it. RET Z ; return if zero. SCF ; set carry flag JR FP_0_OR_1 ; back to FP_0_OR_1 to overwrite the first operand ; with the value 1.
; Handle number AND number (08)
; The Boolean AND operator. ; ; e.g. -3 AND 2 returns -3. ; -3 AND 0 returns 0. ; 0 and -2 returns 0. ; 0 and 0 returns 0. ; ; Compare with OR routine above. boolean_num_and_num: LD A,(DE) ; fetch exponent of second number. AND A ; test it. RET NZ ; return if not zero. JR FP_0_OR_1 ; back to FP_0_OR_1 to overwrite the first operand ; with zero for return value.
; Handle string AND number (10)
; e.g. "YOU WIN" AND SCORE>99 will return the string if condition is true ; or the null string if false. strs_and_num: LD A,(DE) ; fetch exponent of second number. AND A ; test it. RET NZ ; return if number was not zero - the string ; is the result. ; if the number was zero (false) then the null string must be returned by ; altering the length of the string on the calculator stack to zero. PUSH DE ; save pointer to the now obsolete number ; (which will become the new STKEND) DEC DE ; point to the 5th byte of string descriptor. XOR A ; clear the accumulator. LD (DE),A ; place zero in high byte of length. DEC DE ; address low byte of length. LD (DE),A ; place zero there - now the null string. POP DE ; restore pointer - new STKEND. RET ; return.
; Perform comparison ($09-$0E, $11-$16)
; True binary operations. ; ; A single entry point is used to evaluate six numeric and six string ; comparisons. On entry, the calculator literal is in the B register and ; the two numeric values, or the two string parameters, are on the ; calculator stack. ; The individual bits of the literal are manipulated to group similar ; operations although the SUB 8 instruction does nothing useful and merely ; alters the string test bit. ; Numbers are compared by subtracting one from the other, strings are ; compared by comparing every character until a mismatch, or the end of one ; or both, is reached. ; ; Numeric Comparisons.
; The 'x>y' example is the easiest as it employs straight-thru logic. ; Number y is subtracted from x and the result tested for greater_0 yielding ; a final value 1 (true) or 0 (false). ; For 'x<y' the same logic is used but the two values are first swapped on the ; calculator stack. ; For 'x=y' NOT is applied to the subtraction result yielding true if the ; difference was zero and false with anything else. ; The first three numeric comparisons are just the opposite of the last three ; so the same processing steps are used and then a final NOT is applied. ; ; literal Test No sub 8 ExOrNot 1st RRCA exch sub ? End-Tests ; ========= ==== == ======== === ======== ======== ==== === = === === === ; num_l_eql x<=y 09 00000001 dec 00000000 00000000 ---- x-y ? --- >0? NOT ; num_gr_eql x>=y 0A 00000010 dec 00000001 10000000c swap y-x ? --- >0? NOT ; nums_neql x<>y 0B 00000011 dec 00000010 00000001 ---- x-y ? NOT --- NOT ; num_grtr x>y 0C 00000100 - 00000100 00000010 ---- x-y ? --- >0? --- ; num_less x<y 0D 00000101 - 00000101 10000010c swap y-x ? --- >0? --- ; nums_eql x=y 0E 00000110 - 00000110 00000011 ---- x-y ? NOT --- --- ; ; comp -> C/F ; ==== === ; str_l_eql x$<=y$ 11 00001001 dec 00001000 00000100 ---- x$y$ 0 !or >0? NOT ; str_gr_eql x$>=y$ 12 00001010 dec 00001001 10000100c swap y$x$ 0 !or >0? NOT ; strs_neql x$<>y$ 13 00001011 dec 00001010 00000101 ---- x$y$ 0 !or >0? NOT ; str_grtr x$>y$ 14 00001100 - 00001100 00000110 ---- x$y$ 0 !or >0? --- ; str_less x$<y$ 15 00001101 - 00001101 10000110c swap y$x$ 0 !or >0? --- ; strs_eql x$=y$ 16 00001110 - 00001110 00000111 ---- x$y$ 0 !or >0? --- ; ; String comparisons are a little different in that the eql/neql carry flag ; from the 2nd RRCA is, as before, fed into the first of the end tests but ; along the way it gets modified by the comparison process. The result on the ; stack always starts off as zero and the carry fed in determines if NOT is ; applied to it. So the only time the greater-0 test is applied is if the ; stack holds zero which is not very efficient as the test will always yield ; zero. The most likely explanation is that there were once separate end tests ; for numbers and strings. ; $1B03 SAME ADDRESS FOR MULTIPLE ROUTINES ???
num_l_eql: num_gr_eql: nums_neql: num_grtr: num_less: nums_eql: str_l_eql: str_gr_eql: strs_neql: str_grtr: str_less: strs_eql: num_lt_eql: #if ORIGINAL mark_1B03: LD A,B ; transfer literal to accumulator. SUB $08 ; subtract eight - which is not useful. #else LD A,B ; transfer literal to accumulator. ;; SUB $08 ; subtract eight - which is not useful. #endif BIT 2,A ; isolate '>', '<', '='. JR NZ,EX_OR_NOT ; skip to EX_OR_NOT with these. DEC A ; else make $00-$02, $08-$0A to match bits 0-2. EX_OR_NOT: #if ORIGINAL mark_1B0B: #endif RRCA ; the first RRCA sets carry for a swap. JR NC,NUM_OR_STR ; forward to NUM_OR_STR with other 8 cases ; for the other 4 cases the two values on the calculator stack are exchanged. PUSH AF ; save A and carry. PUSH HL ; save HL - pointer to first operand. ; (DE points to second operand). CALL exchange ; routine exchange swaps the two values. ; (HL = second operand, DE = STKEND) POP DE ; DE = first operand EX DE,HL ; as we were. POP AF ; restore A and carry. ; Note. it would be better if the 2nd RRCA preceded the string test. ; It would save two duplicate bytes and if we also got rid of that sub 8 ; at the beginning we wouldn't have to alter which bit we test. NUM_OR_STR: #if ORIGINAL mark_1B16: BIT 2,A ; test if a string comparison. JR NZ,STRINGS ; forward to STRINGS if so. ; continue with numeric comparisons. RRCA ; 2nd RRCA causes eql/neql to set carry. PUSH AF ; save A and carry #else RRCA ;+ causes 'eql/neql' to set carry. PUSH AF ;+ save the carry flag. BIT 2,A ; test if a string comparison. JR NZ,STRINGS ; forward to STRINGS if so. #endif CALL SUBTRACT ; leaves result on stack. JR END_TESTS ; forward to END_TESTS ; ___ STRINGS: #if ORIGINAL mark_1B21: RRCA ; 2nd RRCA causes eql/neql to set carry. PUSH AF ; save A and carry. #else ;; RRCA ; 2nd RRCA causes eql/neql to set carry. ;; PUSH AF ; save A and carry. #endif CALL STK_FETCH ; gets 2nd string params PUSH DE ; save start2 *. PUSH BC ; and the length. CALL STK_FETCH ; gets 1st string ; parameters - start in DE, length in BC. POP HL ; restore length of second to HL. ; A loop is now entered to compare, by subtraction, each corresponding character ; of the strings. For each successful match, the pointers are incremented and ; the lengths decreased and the branch taken back to here. If both string ; remainders become null at the same time, then an exact match exists. #if ORIGINAL mark_1B2C: #endif BYTE_COMP: LD A,H ; test if the second string OR L ; is the null string and hold flags. EX (SP),HL ; put length2 on stack, bring start2 to HL *. LD A,B ; hi byte of length1 to A JR NZ,SEC_PLUS ; forward to SEC_PLUS if second not null. OR C ; test length of first string. #if ORIGINAL mark_1B33: #endif SECOND_LOW: POP BC ; pop the second length off stack. JR Z,BOTH_NULL ; forward if first string is also ; of zero length. ; the true condition - first is longer than second (SECOND_LESS) POP AF ; restore carry (set if eql/neql) CCF ; complement carry flag. ; Note. equality becomes false. ; Inequality is true. By swapping or applying ; a terminal 'not', all comparisons have been ; manipulated so that this is success path. JR STR_TEST ; forward to leave via STR_TEST ; ___ ; the branch was here with a match #if ORIGINAL mark_1B3A: #endif BOTH_NULL: POP AF ; restore carry - set for eql/neql JR STR_TEST ; forward to STR_TEST ; ___ ; the branch was here when 2nd string not null and low byte of first is yet ; to be tested. mark_1B3D: SEC_PLUS: OR C ; test the length of first string. JR Z,FRST_LESS ; forward to FRST_LESS if length is zero. ; both strings have at least one character left. LD A,(DE) ; fetch character of first string. SUB (HL) ; subtract with that of 2nd string. JR C,FRST_LESS ; forward to FRST_LESS if carry set JR NZ,SECOND_LOW ; back to SECOND_LOW and then STR_TEST ; if not exact match. DEC BC ; decrease length of 1st string. INC DE ; increment 1st string pointer. INC HL ; increment 2nd string pointer. EX (SP),HL ; swap with length on stack DEC HL ; decrement 2nd string length JR BYTE_COMP ; back to BYTE_COMP ; ___ ; the false condition. mark_1B4D: FRST_LESS: POP BC ; discard length POP AF ; pop A AND A ; clear the carry for false result. ; ___ ; exact match and x$>y$ rejoin here mark_1B50: STR_TEST: PUSH AF ; save A and carry RST _FP_CALC ;; DEFB __stk_zero ;; an initial false value. DEFB __end_calc ;; ; both numeric and string paths converge here. mark_1B54: END_TESTS: POP AF ; pop carry - will be set if eql/neql PUSH AF ; save it again. CALL C,not ; sets true(1) if equal(0) ; or, for strings, applies true result. CALL greater_0 ; ?????????? POP AF ; pop A RRCA ; the third RRCA - test for '<=', '>=' or '<>'. CALL NC,not ; apply a terminal NOT if so. RET ; return.
; String concatenation ($17)
; This literal combines two strings into one e.g. LET A$ = B$ + C$ ; The two parameters of the two strings to be combined are on the stack. mark_1B62: strs_add: CALL STK_FETCH ; fetches string parameters ; and deletes calculator stack entry. PUSH DE ; save start address. PUSH BC ; and length. CALL STK_FETCH ; for first string POP HL ; re-fetch first length PUSH HL ; and save again PUSH DE ; save start of second string PUSH BC ; and its length. ADD HL,BC ; add the two lengths. LD B,H ; transfer to BC LD C,L ; and create RST _BC_SPACES ; BC_SPACES in workspace. ; DE points to start of space. CALL STK_STO_STR ; stores parameters ; of new string updating STKEND. POP BC ; length of first POP HL ; address of start #if ORIGINAL LD A,B ; test for OR C ; zero length. JR Z,OTHER_STR ; to OTHER_STR if null string LDIR ; copy string to workspace. #else CALL COND_MV ;+ a conditional (NZ) ldir routine. #endif mark_1B7D: OTHER_STR: POP BC ; now second length POP HL ; and start of string #if ORIGINAL LD A,B ; test this one OR C ; for zero length JR Z,STACK_POINTERS ; skip forward to STACK_POINTERS if so as complete. LDIR ; else copy the bytes. ; and continue into next routine which ; sets the calculator stack pointers. #else CALL COND_MV ;+ a conditional (NZ) ldir routine. #endif
; Check stack pointers
; Register DE is set to STKEND and HL, the result pointer, is set to five ; locations below this. ; This routine is used when it is inconvenient to save these values at the ; time the calculator stack is manipulated due to other activity on the ; machine stack. ; This routine is also used to terminate the VAL routine for ; the same reason and to initialize the calculator stack at the start of ; the CALCULATE routine. mark_1B85: STACK_POINTERS: LD HL,(STKEND) ; fetch STKEND value from system variable. LD DE,-5 PUSH HL ; push STKEND value. ADD HL,DE ; subtract 5 from HL. POP DE ; pop STKEND to DE. RET ; return.
; Handle CHR$ (2B)
; This function returns a single character string that is a result of ; converting a number in the range 0-255 to a string e.g. CHR$ 38 = "A". ; Note. the ZX81 does not have an ASCII character set. mark_1B8F:
chr_dollar: CALL FP_TO_A ; puts the number in A. JR C,REPORT_Bd ; forward if overflow JR NZ,REPORT_Bd ; forward if negative #if ORIGINAL PUSH AF ; save the argument. #endif LD BC,1 ; one space required. RST _BC_SPACES ; BC_SPACES makes DE point to start #if ORIGINAL POP AF ; restore the number. #endif LD (DE),A ; and store in workspace #if ORIGINAL CALL STK_STO_STR ; stacks descriptor. EX DE,HL ; make HL point to result and DE to STKEND. RET ; return. #else JR str_STK ;+ relative jump to similar sequence in str$. #endif ; ___ mark_1BA2: REPORT_Bd: RST _ERROR_1 DEFB $0A ; Error Report: Integer out of range
; Handle VAL ($1A)
; VAL treats the characters in a string as a numeric expression. ; e.g. VAL "2.3" = 2.3, VAL "2+4" = 6, VAL ("2" + "4") = 24.
val: #if ORIGINAL mark_1BA4: LD HL,(CH_ADD) ; fetch value of system variable CH_ADD #else RST _GET_CHAR ;+ shorter way to fetch CH_ADD. #endif PUSH HL ; and save on the machine stack. CALL STK_FETCH ; fetches the string operand ; from calculator stack. PUSH DE ; save the address of the start of the string. INC BC ; increment the length for a carriage return. RST _BC_SPACES ; BC_SPACES creates the space in workspace. POP HL ; restore start of string to HL. LD (CH_ADD),DE ; load CH_ADD with start DE in workspace. PUSH DE ; save the start in workspace LDIR ; copy string from program or variables or ; workspace to the workspace area. EX DE,HL ; end of string + 1 to HL DEC HL ; decrement HL to point to end of new area. LD (HL),ZX_NEWLINE ; insert a carriage return at end. ; ZX81 has a non-ASCII character set RES 7,(IY+FLAGS-RAMBASE) ; signal checking syntax. CALL CLASS_6 ; evaluates string ; expression and checks for integer result. CALL CHECK_2 ; checks for carriage return. POP HL ; restore start of string in workspace. LD (CH_ADD),HL ; set CH_ADD to the start of the string again. SET 7,(IY+FLAGS-RAMBASE) ; signal running program. CALL SCANNING ; evaluates the string ; in full leaving result on calculator stack. POP HL ; restore saved character address in program. LD (CH_ADD),HL ; and reset the system variable CH_ADD. JR STACK_POINTERS ; back to exit via STACK_POINTERS. ; resetting the calculator stack pointers ; HL and DE from STKEND as it wasn't possible ; to preserve them during this routine.
; Handle STR$ (2A)
; This function returns a string representation of a numeric argument. ; The method used is to trick the PRINT_FP routine into thinking it ; is writing to a collapsed display file when in fact it is writing to ; string workspace. ; If there is already a newline at the intended print position and the ; column count has not been reduced to zero then the print routine ; assumes that there is only 1K of RAM and the screen memory, like the rest ; of dynamic memory, expands as necessary using calls to the ONE_SPACE ; routine. The screen is character-mapped not bit-mapped. mark_1BD5: str_dollar: LD BC,1 ; create an initial byte in workspace RST _BC_SPACES ; using BC_SPACES restart. LD (HL),ZX_NEWLINE ; place a carriage return there. LD HL,(S_POSN) ; fetch value of S_POSN column/line PUSH HL ; and preserve on stack. LD L,$FF ; make column value high to create a ; contrived buffer of length 254. LD (S_POSN),HL ; and store in system variable S_POSN. LD HL,(DF_CC) ; fetch value of DF_CC PUSH HL ; and preserve on stack also. LD (DF_CC),DE ; now set DF_CC which normally addresses ; somewhere in the display file to the start ; of workspace. PUSH DE ; save the start of new string. CALL PRINT_FP POP DE ; retrieve start of string. LD HL,(DF_CC) ; fetch end of string from DF_CC. AND A ; prepare for true subtraction. SBC HL,DE ; subtract to give length. LD B,H ; and transfer to the BC LD C,L ; register. POP HL ; restore original LD (DF_CC),HL ; DF_CC value POP HL ; restore original LD (S_POSN),HL ; S_POSN values. #if ORIGINAL #else str_STK: ; New entry-point to exploit similarities and save 3 bytes of code. #endif CALL STK_STO_STR ; stores the string ; descriptor on the calculator stack. EX DE,HL ; HL = last value, DE = STKEND. RET ; return.
; THE 'CODE' FUNCTION
; (offset $19: 'code') ; Returns the code of a character or first character of a string ; e.g. CODE "AARDVARK" = 38 (not 65 as the ZX81 does not have an ASCII ; character set). mark_1C06: code: CALL STK_FETCH ; fetch and delete the string parameters. ; DE points to the start, BC holds the length. LD A,B ; test length OR C ; of the string. JR Z,STK_CODE ; skip with zero if the null string. LD A,(DE) ; else fetch the first character. mark_1C0E: STK_CODE: JP STACK_A ; jump back (with memory check)
; THE 'LEN' SUBROUTINE
; (offset $1b: 'len') ; Returns the length of a string. ; In Sinclair BASIC strings can be more than twenty thousand characters long ; so a sixteen-bit register is required to store the length mark_1C11: len: CALL STK_FETCH ; fetch and delete the ; string parameters from the calculator stack. ; register BC now holds the length of string. JP STACK_BC ; jump back to save result on the ; calculator stack (with memory check).
; THE 'DECREASE THE COUNTER' SUBROUTINE
; (offset $31: 'dec_jr_nz') ; The calculator has an instruction that decrements a single-byte ; pseudo-register and makes consequential relative jumps just like ; the Z80's DJNZ instruction. mark_1C17: dec_jr_nz: EXX ; switch in set that addresses code PUSH HL ; save pointer to offset byte LD HL,BERG ; address BERG in system variables DEC (HL) ; decrement it POP HL ; restore pointer JR NZ,JUMP_2 ; to JUMP_2 if not zero INC HL ; step past the jump length. EXX ; switch in the main set. RET ; return. ; Note. as a general rule the calculator avoids using the IY register ; otherwise the cumbersome 4 instructions in the middle could be replaced by ; dec (iy+$xx) - using three instruction bytes instead of six.
; THE 'JUMP' SUBROUTINE
; (Offset $2F; 'jump') ; This enables the calculator to perform relative jumps just like ; the Z80 chip's JR instruction. ; This is one of the few routines to be polished for the ZX Spectrum. ; See, without looking at the ZX Spectrum ROM, if you can get rid of the ; relative jump. mark_1C23: jump: EXX ;switch in pointer set JUMP_2: LD E,(HL) ; the jump byte 0-127 forward, 128-255 back. #if ORIGINAL mark_1C24: XOR A ; clear accumulator. BIT 7,E ; test if negative jump JR Z,JUMP_3 ; skip, if positive CPL ; else change to $FF. #else ; Note. Elegance from the ZX Spectrum. LD A,E ;+ RLA ;+ SBC A,A ;+ #endif mark_1C2B: JUMP_3: LD D,A ; transfer to high byte. ADD HL,DE ; advance calculator pointer forward or back. EXX ; switch out pointer set. RET ; return.
; THE 'JUMP ON TRUE' SUBROUTINE
; (Offset $00; 'jump_true') ; This enables the calculator to perform conditional relative jumps ; dependent on whether the last test gave a true result ; On the ZX81, the exponent will be zero for zero or else $81 for one. mark_1C2F:
jump_true: LD A,(DE) ; collect exponent byte AND A ; is result 0 or 1 ? JR NZ,jump ; back to JUMP if true (1). EXX ; else switch in the pointer set. INC HL ; step past the jump length. EXX ; switch in the main set. RET ; return.
; THE 'MODULUS' SUBROUTINE
; ( Offset $2E: 'n_mod_m' ) ; ( i1, i2 -- i3, i4 ) ; The subroutine calculate N mod M where M is the positive integer, the ; 'last value' on the calculator stack and N is the integer beneath. ; The subroutine returns the integer quotient as the last value and the ; remainder as the value beneath. ; e.g. 17 MOD 3 = 5 remainder 2 ; It is invoked during the calculation of a random number and also by ; the PRINT_FP routine. mark_1C37: n_mod_m: RST _FP_CALC ;; 17, 3. DEFB __st_mem_0 ;; 17, 3. DEFB __delete ;; 17. DEFB __duplicate ;; 17, 17. DEFB __get_mem_0 ;; 17, 17, 3. DEFB __division ;; 17, 17/3. DEFB __int ;; 17, 5. DEFB __get_mem_0 ;; 17, 5, 3. DEFB __exchange ;; 17, 3, 5. DEFB __st_mem_0 ;; 17, 3, 5. DEFB __multiply ;; 17, 15. DEFB __subtract ;; 2. DEFB __get_mem_0 ;; 2, 5. DEFB __end_calc ;; 2, 5. RET ; return.
; THE 'INTEGER' FUNCTION
; (offset $24: 'int') ; This function returns the integer of x, which is just the same as truncate ; for positive numbers. The truncate literal truncates negative numbers ; upwards so that -3.4 gives -3 whereas the BASIC INT function has to ; truncate negative numbers down so that INT -3.4 is 4. ; It is best to work through using, say, plus or minus 3.4 as examples. mark_1C46:
int: RST _FP_CALC ;; x. (= 3.4 or -3.4). DEFB __duplicate ;; x, x. DEFB __less_0 ;; x, (1/0) DEFB __jump_true ;; x, (1/0) DEFB int - $ ;; X_NEG DEFB __truncate ;; trunc 3.4 = 3. DEFB __end_calc ;; 3. RET ; return with + int x on stack. mark_1C4E: X_NEG: DEFB __duplicate ;; -3.4, -3.4. DEFB __truncate ;; -3.4, -3. DEFB __st_mem_0 ;; -3.4, -3. DEFB __subtract ;; -.4 DEFB __get_mem_0 ;; -.4, -3. DEFB __exchange ;; -3, -.4. DEFB __not ;; -3, (0). DEFB __jump_true ;; -3. DEFB EXIT - $ ;; -3. DEFB __stk_one ;; -3, 1. DEFB __subtract ;; -4. mark_1C59: EXIT: DEFB __end_calc ;; -4. RET ; return.
; Exponential (23)
; ; mark_1C5B:
exp: RST _FP_CALC ;; DEFB __stk_data ;; DEFB $F1 ;;Exponent: $81, Bytes: 4 DEFB $38,$AA,$3B,$29 ;; DEFB __multiply ;; DEFB __duplicate ;; DEFB __int ;; DEFB $C3 ;;st_mem_3 DEFB __subtract ;; DEFB __duplicate ;; DEFB __addition ;; DEFB __stk_one ;; DEFB __subtract ;; DEFB __series_08 ;; DEFB $13 ;;Exponent: $63, Bytes: 1 DEFB $36 ;;(+00,+00,+00) DEFB $58 ;;Exponent: $68, Bytes: 2 DEFB $65,$66 ;;(+00,+00) DEFB $9D ;;Exponent: $6D, Bytes: 3 DEFB $78,$65,$40 ;;(+00) DEFB $A2 ;;Exponent: $72, Bytes: 3 DEFB $60,$32,$C9 ;;(+00) DEFB $E7 ;;Exponent: $77, Bytes: 4 DEFB $21,$F7,$AF,$24 ;; DEFB $EB ;;Exponent: $7B, Bytes: 4 DEFB $2F,$B0,$B0,$14 ;; DEFB $EE ;;Exponent: $7E, Bytes: 4 DEFB $7E,$BB,$94,$58 ;; DEFB $F1 ;;Exponent: $81, Bytes: 4 DEFB $3A,$7E,$F8,$CF ;; DEFB $E3 ;;get_mem_3 DEFB __end_calc ;; CALL FP_TO_A JR NZ,N_NEGTV JR C,REPORT_6b ADD A,(HL) ; JR NC,RESULT_OK mark_1C99: REPORT_6b: RST _ERROR_1 DEFB $05 ; Error Report: Number too big mark_1C9B: N_NEGTV: JR C,RESULT_ZERO SUB (HL) ; JR NC,RESULT_ZERO NEG ; Negate mark_1CA2: RESULT_OK: LD (HL),A ; RET ; return. mark_1CA4: RESULT_ZERO: RST _FP_CALC ;; DEFB __delete ;; DEFB __stk_zero ;; DEFB __end_calc ;; RET ; return.
; THE 'NATURAL LOGARITHM' FUNCTION
; (offset $22: 'ln') ; Like the ZX81 itself, 'natural' logarithms came from Scotland. ; They were devised in 1614 by well-traveled Scotsman John Napier who noted ; "Nothing doth more molest and hinder calculators than the multiplications, ; divisions, square and cubical extractions of great numbers". ; ; Napier's logarithms enabled the above operations to be accomplished by ; simple addition and subtraction simplifying the navigational and ; astronomical calculations which beset his age. ; Napier's logarithms were quickly overtaken by logarithms to the base 10 ; devised, in conjunction with Napier, by Henry Briggs a Cambridge-educated ; professor of Geometry at Oxford University. These simplified the layout ; of the tables enabling humans to easily scale calculations. ; ; It is only recently with the introduction of pocket calculators and ; computers like the ZX81 that natural logarithms are once more at the fore, ; although some computers retain logarithms to the base ten. ; 'Natural' logarithms are powers to the base 'e', which like 'pi' is a ; naturally occurring number in branches of mathematics. ; Like 'pi' also, 'e' is an irrational number and starts 2.718281828... ; ; The tabular use of logarithms was that to multiply two numbers one looked ; up their two logarithms in the tables, added them together and then looked ; for the result in a table of antilogarithms to give the desired product. ; ; The EXP function is the BASIC equivalent of a calculator's 'antiln' function ; and by picking any two numbers, 1.72 and 6.89 say, ; 10 PRINT EXP ( LN 1.72 + LN 6.89 ) ; will give just the same result as ; 20 PRINT 1.72 * 6.89. ; Division is accomplished by subtracting the two logs. ; ; Napier also mentioned "square and cubicle extractions". ; To raise a number to the power 3, find its 'ln', multiply by 3 and find the ; 'antiln'. e.g. PRINT EXP( LN 4 * 3 ) gives 64. ; Similarly to find the n'th root divide the logarithm by 'n'. ; The ZX81 ROM used PRINT EXP ( LN 9 / 2 ) to find the square root of the ; number 9. The Napieran square root function is just a special case of ; the 'to_power' function. A cube root or indeed any root/power would be just ; as simple. ; First test that the argument to LN is a positive, non-zero number. mark_1CA9:
ln: RST _FP_CALC ;; DEFB __duplicate ;; DEFB __greater_0 ;; DEFB __jump_true ;; DEFB VALID - $ ;;to VALID DEFB __end_calc ;; mark_1CAF: REPORT_Ab: RST _ERROR_1 DEFB $09 ; Error Report: Invalid argument VALID: #if ORIGINAL mark_1CB1: DEFB __stk_zero ;; Note. not necessary. DEFB __delete ;; #endif DEFB __end_calc ;; LD A,(HL) ; LD (HL),$80 ; CALL STACK_A RST _FP_CALC ;; DEFB __stk_data ;; DEFB $38 ;;Exponent: $88, Bytes: 1 DEFB $00 ;;(+00,+00,+00) DEFB __subtract ;; DEFB __exchange ;; DEFB __duplicate ;; DEFB __stk_data ;; DEFB $F0 ;;Exponent: $80, Bytes: 4 DEFB $4C,$CC,$CC,$CD ;; DEFB __subtract ;; DEFB __greater_0 ;; DEFB __jump_true ;; DEFB GRE_8 - $ ;; DEFB __exchange ;; DEFB __stk_one ;; DEFB __subtract ;; DEFB __exchange ;; DEFB __end_calc ;; INC (HL) ; RST _FP_CALC ;; mark_1CD2: GRE_8: DEFB __exchange ;; DEFB __stk_data ;; DEFB $F0 ;;Exponent: $80, Bytes: 4 DEFB $31,$72,$17,$F8 ;; DEFB __multiply ;; DEFB __exchange ;; DEFB __stk_half ;; DEFB __subtract ;; DEFB __stk_half ;; DEFB __subtract ;; DEFB __duplicate ;; DEFB __stk_data ;; DEFB $32 ;;Exponent: $82, Bytes: 1 DEFB $20 ;;(+00,+00,+00) DEFB __multiply ;; DEFB __stk_half ;; DEFB __subtract ;; DEFB __series_0C ;; DEFB $11 ;;Exponent: $61, Bytes: 1 DEFB $AC ;;(+00,+00,+00) DEFB $14 ;;Exponent: $64, Bytes: 1 DEFB $09 ;;(+00,+00,+00) DEFB $56 ;;Exponent: $66, Bytes: 2 DEFB $DA,$A5 ;;(+00,+00) DEFB $59 ;;Exponent: $69, Bytes: 2 DEFB $30,$C5 ;;(+00,+00) DEFB $5C ;;Exponent: $6C, Bytes: 2 DEFB $90,$AA ;;(+00,+00) DEFB $9E ;;Exponent: $6E, Bytes: 3 DEFB $70,$6F,$61 ;;(+00) DEFB $A1 ;;Exponent: $71, Bytes: 3 DEFB $CB,$DA,$96 ;;(+00) DEFB $A4 ;;Exponent: $74, Bytes: 3 DEFB $31,$9F,$B4 ;;(+00) DEFB $E7 ;;Exponent: $77, Bytes: 4 DEFB $A0,$FE,$5C,$FC ;; DEFB $EA ;;Exponent: $7A, Bytes: 4 DEFB $1B,$43,$CA,$36 ;; DEFB $ED ;;Exponent: $7D, Bytes: 4 DEFB $A7,$9C,$7E,$5E ;; DEFB $F0 ;;Exponent: $80, Bytes: 4 DEFB $6E,$23,$80,$93 ;; DEFB __multiply ;; DEFB __addition ;; DEFB __end_calc ;; RET ; return.
#if ORIGINAL #else ; ------------------------------ ; THE NEW 'SQUARE ROOT' FUNCTION ; ------------------------------ ; (Offset $25: 'sqr') ; "If I have seen further, it is by standing on the shoulders of giants" - ; Sir Isaac Newton, Cambridge 1676. ; The sqr function has been re-written to use the Newton-Raphson method. ; Joseph Raphson was a student of Sir Isaac Newton at Cambridge University ; and helped publicize his work. ; Although Newton's method is centuries old, this routine, appropriately, is ; based on a FORTH word written by Steven Vickers in the Jupiter Ace manual. ; Whereas that method uses an initial guess of one, this one manipulates ; the exponent byte to obtain a better starting guess. ; First test for zero and return zero, if so, as the result. ; If the argument is negative, then produce an error. ;
sqr RST _FP_CALC ;; x DEFB __st_mem_3 ;; x. (seed for guess) DEFB __end_calc ;; x. ; HL now points to exponent of argument on calculator stack. LD A,(HL) ; Test for zero argument AND A ; RET Z ; Return with zero on the calculator stack. ; Test for a positive argument INC HL ; Address byte with sign bit. BIT 7,(HL) ; Test the bit. JR NZ,REPORT_Ab ; back to REPORT_A ; 'Invalid argument' ; This guess is based on a Usenet discussion. ; Halve the exponent to achieve a good guess.(accurate with .25 16 64 etc.) LD HL,$4071 ; Address first byte of mem-3 LD A,(HL) ; fetch exponent of mem-3 XOR $80 ; toggle sign of exponent of mem-3 SRA A ; shift right, bit 7 unchanged. INC A ; JR Z,ASIS ; forward with say .25 -> .5 JP P,ASIS ; leave increment if value > .5 DEC A ; restore to shift only. ASIS XOR $80 ; restore sign. LD (HL),A ; and put back 'halved' exponent. ; Now re-enter the calculator. RST 28H ;; FP-CALC x SLOOP DEFB __duplicate ;; x,x. DEFB __get_mem_3 ;; x,x,guess DEFB __st_mem_4 ;; x,x,guess DEFB __division ;; x,x/guess. DEFB __get_mem_3 ;; x,x/guess,guess DEFB __addition ;; x,x/guess+guess DEFB __stk_half ;; x,x/guess+guess,.5 DEFB __multiply ;; x,(x/guess+guess)*.5 DEFB __st_mem_3 ;; x,newguess DEFB __get_mem_4 ;; x,newguess,oldguess DEFB __subtract ;; x,newguess-oldguess DEFB __abs ;; x,difference. DEFB __greater_0 ;; x,(0/1). DEFB __jump_true ;; x. DEFB SLOOP - $ ;; x. DEFB __delete ;; . DEFB __get_mem_3 ;; retrieve final guess. DEFB __end_calc ;; sqr x. RET ; return with square root on stack ; or in ZX81 BASIC ; ; 5 PRINT "NEWTON RAPHSON SQUARE ROOTS" ; 10 INPUT "NUMBER ";N ; 20 INPUT "GUESS ";G ; 30 PRINT " NUMBER "; N ;" GUESS "; G ; 40 FOR I = 1 TO 10 ; 50 LET B = N/G ; 60 LET C = B+G ; 70 LET G = C/2 ; 80 PRINT I; " VALUE "; G ; 90 NEXT I ; 100 PRINT "NAPIER METHOD"; SQR N #endif
; THE 'TRIGONOMETRIC' FUNCTIONS
; Trigonometry is rocket science. It is also used by carpenters and pyramid ; builders. ; Some uses can be quite abstract but the principles can be seen in simple ; right-angled triangles. Triangles have some special properties - ; ; 1) The sum of the three angles is always PI radians (180 degrees). ; Very helpful if you know two angles and wish to find the third. ; 2) In any right-angled triangle the sum of the squares of the two shorter ; sides is equal to the square of the longest side opposite the right-angle. ; Very useful if you know the length of two sides and wish to know the ; length of the third side. ; 3) Functions sine, cosine and tangent enable one to calculate the length ; of an unknown side when the length of one other side and an angle is ; known. ; 4) Functions arcsin, arccosine and arctan enable one to calculate an unknown ; angle when the length of two of the sides is known.
; THE 'REDUCE ARGUMENT' SUBROUTINE
; (offset $35: 'get_argt') ; ; This routine performs two functions on the angle, in radians, that forms ; the argument to the sine and cosine functions. ; First it ensures that the angle 'wraps round'. That if a ship turns through ; an angle of, say, 3*PI radians (540 degrees) then the net effect is to turn ; through an angle of PI radians (180 degrees). ; Secondly it converts the angle in radians to a fraction of a right angle, ; depending within which quadrant the angle lies, with the periodicity ; resembling that of the desired sine value. ; The result lies in the range -1 to +1. ; ; 90 deg. ; ; (pi/2) ; II +1 I ; | ; sin+ |\ | /| sin+ ; cos- | \ | / | cos+ ; tan- | \ | / | tan+ ; | \|/) | ; 180 deg. (pi) 0 |----+----|-- 0 (0) 0 degrees ; | /|\ | ; sin- | / | \ | sin- ; cos- | / | \ | cos+ ; tan+ |/ | \| tan- ; | ; III -1 IV ; (3pi/2) ; ; 270 deg. mark_1D18: get_argt: RST _FP_CALC ;; X. DEFB __stk_data ;; DEFB $EE ;;Exponent: $7E, ;;Bytes: 4 DEFB $22,$F9,$83,$6E ;; X, 1/(2*PI) DEFB __multiply ;; X/(2*PI) = fraction DEFB __duplicate ;; DEFB __stk_half ;; DEFB __addition ;; DEFB __int ;; DEFB __subtract ;; now range -.5 to .5 DEFB __duplicate ;; DEFB __addition ;; now range -1 to 1. DEFB __duplicate ;; DEFB __addition ;; now range -2 to 2. ; quadrant I (0 to +1) and quadrant IV (-1 to 0) are now correct. ; quadrant II ranges +1 to +2. ; quadrant III ranges -2 to -1. DEFB __duplicate ;; Y, Y. DEFB __abs ;; Y, abs(Y). range 1 to 2 DEFB __stk_one ;; Y, abs(Y), 1. DEFB __subtract ;; Y, abs(Y)-1. range 0 to 1 DEFB __duplicate ;; Y, Z, Z. DEFB __greater_0 ;; Y, Z, (1/0). DEFB __st_mem_0 ;; store as possible sign ;; for cosine function. DEFB __jump_true ;; DEFB Z_PLUS - $ ;; with quadrants II and III ; else the angle lies in quadrant I or IV and value Y is already correct. DEFB __delete ;; Y delete test value. DEFB __end_calc ;; Y. RET ; return. with Q1 and Q4 >>> ; The branch was here with quadrants II (0 to 1) and III (1 to 0). ; Y will hold -2 to -1 if this is quadrant III. mark_1D35: Z_PLUS: DEFB __stk_one ;; Y, Z, 1 DEFB __subtract ;; Y, Z-1. Q3 = 0 to -1 DEFB __exchange ;; Z-1, Y. DEFB __less_0 ;; Z-1, (1/0). DEFB __jump_true ;; Z-1. DEFB YNEG - $ ;; ;;if angle in quadrant III ; else angle is within quadrant II (-1 to 0) DEFB __negate ; range +1 to 0 mark_1D3C: YNEG: DEFB __end_calc ;; quadrants II and III correct. RET ; return.
; THE 'COSINE' FUNCTION
; (offset $1D: 'cos') ; Cosines are calculated as the sine of the opposite angle rectifying the ; sign depending on the quadrant rules. ; ; ; /| ; h /y| ; / |o ; / x | ; /----| ; a ; ; The cosine of angle x is the adjacent side (a) divided by the hypotenuse 1. ; However if we examine angle y then a/h is the sine of that angle. ; Since angle x plus angle y equals a right-angle, we can find angle y by ; subtracting angle x from pi/2. ; However it's just as easy to reduce the argument first and subtract the ; reduced argument from the value 1 (a reduced right-angle). ; It's even easier to subtract 1 from the angle and rectify the sign. ; In fact, after reducing the argument, the absolute value of the argument ; is used and rectified using the test result stored in mem-0 by 'get-argt' ; for that purpose. mark_1D3E:
cos: RST _FP_CALC ;; angle in radians. DEFB __get_argt ;; X reduce -1 to +1 DEFB __abs ;; ABS X 0 to 1 DEFB __stk_one ;; ABS X, 1. DEFB __subtract ;; now opposite angle ;; though negative sign. DEFB __get_mem_0 ;; fetch sign indicator. DEFB __jump_true ;; DEFB C_ENT - $ ;;fwd to C_ENT ;;forward to common code if in QII or QIII DEFB __negate ;; else make positive. DEFB __jump ;; DEFB C_ENT - $ ;;fwd to C_ENT ;;with quadrants QI and QIV
; THE 'SINE' FUNCTION
; (offset $1C: 'sin') ; This is a fundamental transcendental function from which others such as cos ; and tan are directly, or indirectly, derived. ; It uses the series generator to produce Chebyshev polynomials. ; ; ; /| ; 1 / | ; / |x ; /a | ; /----| ; y ; ; The 'get-argt' function is designed to modify the angle and its sign ; in line with the desired sine value and afterwards it can launch straight ; into common code. mark_1D49: sin: RST _FP_CALC ;; angle in radians DEFB __get_argt ;; reduce - sign now correct. mark_1D4B: C_ENT: DEFB __duplicate ;; DEFB __duplicate ;; DEFB __multiply ;; DEFB __duplicate ;; DEFB __addition ;; DEFB __stk_one ;; DEFB __subtract ;; DEFB __series_06 ;; DEFB $14 ;;Exponent: $64, Bytes: 1 DEFB $E6 ;;(+00,+00,+00) DEFB $5C ;;Exponent: $6C, Bytes: 2 DEFB $1F,$0B ;;(+00,+00) DEFB $A3 ;;Exponent: $73, Bytes: 3 DEFB $8F,$38,$EE ;;(+00) DEFB $E9 ;;Exponent: $79, Bytes: 4 DEFB $15,$63,$BB,$23 ;; DEFB $EE ;;Exponent: $7E, Bytes: 4 DEFB $92,$0D,$CD,$ED ;; DEFB $F1 ;;Exponent: $81, Bytes: 4 DEFB $23,$5D,$1B,$EA ;; DEFB __multiply ;; DEFB __end_calc ;; RET ; return.
; THE 'TANGENT' FUNCTION
; (offset $1E: 'tan') ; ; Evaluates tangent x as sin(x) / cos(x). ; ; ; /| ; h / | ; / |o ; /x | ; /----| ; a ; ; The tangent of angle x is the ratio of the length of the opposite side ; divided by the length of the adjacent side. As the opposite length can ; be calculates using sin(x) and the adjacent length using cos(x) then ; the tangent can be defined in terms of the previous two functions. ; Error 6 if the argument, in radians, is too close to one like pi/2 ; which has an infinite tangent. e.g. PRINT TAN (PI/2) evaluates as 1/0. ; Similarly PRINT TAN (3*PI/2), TAN (5*PI/2) etc. mark_1D6E:
tan: RST _FP_CALC ;; x. DEFB __duplicate ;; x, x. DEFB __sin ;; x, sin x. DEFB __exchange ;; sin x, x. DEFB __cos ;; sin x, cos x. DEFB __division ;; sin x/cos x (= tan x). DEFB __end_calc ;; tan x. RET ; return.
; THE 'ARCTAN' FUNCTION
; (Offset $21: 'atn') ; The inverse tangent function with the result in radians. ; This is a fundamental transcendental function from which others such as ; asn and acs are directly, or indirectly, derived. ; It uses the series generator to produce Chebyshev polynomials. mark_1D76:
atn: LD A,(HL) ; fetch exponent CP $81 ; compare to that for 'one' JR C,SMALL ; forward, if less RST _FP_CALC ;; X. DEFB __stk_one ;; DEFB __negate ;; DEFB __exchange ;; DEFB __division ;; DEFB __duplicate ;; DEFB __less_0 ;; DEFB __stk_half_pi ;; DEFB __exchange ;; DEFB __jump_true ;; DEFB CASES - $ ;; DEFB __negate ;; DEFB __jump ;; DEFB CASES - $ ;; ; ___ mark_1D89: SMALL: RST _FP_CALC ;; DEFB __stk_zero ;; mark_1D8B: CASES: DEFB __exchange ;; DEFB __duplicate ;; DEFB __duplicate ;; DEFB __multiply ;; DEFB __duplicate ;; DEFB __addition ;; DEFB __stk_one ;; DEFB __subtract ;; DEFB __series_0C ;; DEFB $10 ;;Exponent: $60, Bytes: 1 DEFB $B2 ;;(+00,+00,+00) DEFB $13 ;;Exponent: $63, Bytes: 1 DEFB $0E ;;(+00,+00,+00) DEFB $55 ;;Exponent: $65, Bytes: 2 DEFB $E4,$8D ;;(+00,+00) DEFB $58 ;;Exponent: $68, Bytes: 2 DEFB $39,$BC ;;(+00,+00) DEFB $5B ;;Exponent: $6B, Bytes: 2 DEFB $98,$FD ;;(+00,+00) DEFB $9E ;;Exponent: $6E, Bytes: 3 DEFB $00,$36,$75 ;;(+00) DEFB $A0 ;;Exponent: $70, Bytes: 3 DEFB $DB,$E8,$B4 ;;(+00) DEFB $63 ;;Exponent: $73, Bytes: 2 DEFB $42,$C4 ;;(+00,+00) DEFB $E6 ;;Exponent: $76, Bytes: 4 DEFB $B5,$09,$36,$BE ;; DEFB $E9 ;;Exponent: $79, Bytes: 4 DEFB $36,$73,$1B,$5D ;; DEFB $EC ;;Exponent: $7C, Bytes: 4 DEFB $D8,$DE,$63,$BE ;; DEFB $F0 ;;Exponent: $80, Bytes: 4 DEFB $61,$A1,$B3,$0C ;; DEFB __multiply ;; DEFB __addition ;; DEFB __end_calc ;; RET ; return.
; THE 'ARCSIN' FUNCTION
; (Offset $1F: 'asn') ; The inverse sine function with result in radians. ; Derived from arctan function above. ; Error A unless the argument is between -1 and +1 inclusive. ; Uses an adaptation of the formula asn(x) = atn(x/sqr(1-x*x)) ; ; ; /| ; / | ; 1/ |x ; /a | ; /----| ; y ; ; e.g. We know the opposite side (x) and hypotenuse (1) ; and we wish to find angle a in radians. ; We can derive length y by Pythagoras and then use ATN instead. ; Since y*y + x*x = 1*1 (Pythagoras Theorem) then ; y=sqr(1-x*x) - no need to multiply 1 by itself. ; So, asn(a) = atn(x/y) ; or more fully, ; asn(a) = atn(x/sqr(1-x*x)) ; Close but no cigar. ; While PRINT ATN (x/SQR (1-x*x)) gives the same results as PRINT ASN x, ; it leads to division by zero when x is 1 or -1. ; To overcome this, 1 is added to y giving half the required angle and the ; result is then doubled. ; That is, PRINT ATN (x/(SQR (1-x*x) +1)) *2 ; ; ; . /| ; . c/ | ; . /1 |x ; . c b /a | ; ---------/----| ; 1 y ; ; By creating an isosceles triangle with two equal sides of 1, angles c and ; c are also equal. If b+c+d = 180 degrees and b+a = 180 degrees then c=a/2. ; ; A value higher than 1 gives the required error as attempting to find the ; square root of a negative number generates an error in Sinclair BASIC. mark_1DC4:
asn: RST _FP_CALC ;; x. DEFB __duplicate ;; x, x. DEFB __duplicate ;; x, x, x. DEFB __multiply ;; x, x*x. DEFB __stk_one ;; x, x*x, 1. DEFB __subtract ;; x, x*x-1. DEFB __negate ;; x, 1-x*x. DEFB __sqr ;; x, sqr(1-x*x) = y. DEFB __stk_one ;; x, y, 1. DEFB __addition ;; x, y+1. DEFB __division ;; x/y+1. DEFB __atn ;; a/2 (half the angle) DEFB __duplicate ;; a/2, a/2. DEFB __addition ;; a. DEFB __end_calc ;; a. RET ; return.
; THE 'ARCCOS' FUNCTION
; (Offset $20: 'acs') ; The inverse cosine function with the result in radians. ; Error A unless the argument is between -1 and +1. ; Result in range 0 to pi. ; Derived from asn above which is in turn derived from the preceding atn. It ; could have been derived directly from atn using acs(x) = atn(sqr(1-x*x)/x). ; However, as sine and cosine are horizontal translations of each other, ; uses acs(x) = pi/2 - asn(x) ; e.g. the arccosine of a known x value will give the required angle b in ; radians. ; We know, from above, how to calculate the angle a using asn(x). ; Since the three angles of any triangle add up to 180 degrees, or pi radians, ; and the largest angle in this case is a right-angle (pi/2 radians), then ; we can calculate angle b as pi/2 (both angles) minus asn(x) (angle a). ; ;; ; /| ; 1 /b| ; / |x ; /a | ; /----| ; y mark_1DD4:
acs: RST _FP_CALC ;; x. DEFB __asn ;; asn(x). DEFB __stk_half_pi ;; asn(x), pi/2. DEFB __subtract ;; asn(x) - pi/2. DEFB __negate ;; pi/2 - asn(x) = acs(x). DEFB __end_calc ;; acs(x) RET ; return. #if ORIGINAL
; THE 'SQUARE ROOT' FUNCTION
; (Offset $25: 'sqr') ; Error A if argument is negative. ; This routine is remarkable for its brevity - 7 bytes. ; ; The ZX81 code was originally 9K and various techniques had to be ; used to shoe-horn it into an 8K Rom chip. ; This routine uses Napier's method for calculating square roots which was ; devised in 1614 and calculates the value as EXP (LN 'x' * 0.5). ; ; This is a little on the slow side as it involves two polynomial series. ; A series of 12 for LN and a series of 8 for EXP. ; This was of no concern to John Napier since his tables were 'compiled forever'. mark_1DDB:
sqr: RST _FP_CALC ;; x. DEFB __duplicate ;; x, x. DEFB __not ;; x, 1/0 DEFB __jump_true ;; x, (1/0). DEFB LAST - $ ;; exit if argument zero ;; with zero result. ; else continue to calculate as x ** .5 DEFB __stk_half ;; x, .5. DEFB __end_calc ;; x, .5. #endif
; THE 'EXPONENTIATION' OPERATION
; (Offset $06: 'to_power') ; This raises the first number X to the power of the second number Y. ; As with the ZX80, ; 0 ** 0 = 1 ; 0 ** +n = 0 ; 0 ** -n = arithmetic overflow. mark_1DE2: to_power: RST _FP_CALC ;; X,Y. DEFB __exchange ;; Y,X. DEFB __duplicate ;; Y,X,X. DEFB __not ;; Y,X,(1/0). DEFB __jump_true ;; DEFB XISO - $ ;;forward to XISO if X is zero. ; else X is non-zero. function 'ln' will catch a negative value of X. DEFB __ln ;; Y, LN X. DEFB __multiply ;; Y * LN X DEFB __end_calc ;; JP exp ; jump back to EXP routine. -> ; ___ ; These routines form the three simple results when the number is zero. ; begin by deleting the known zero to leave Y the power factor. mark_1DEE: XISO: DEFB __delete ;; Y. DEFB __duplicate ;; Y, Y. DEFB __not ;; Y, (1/0). DEFB __jump_true ;; DEFB ONE - $ ;; if Y is zero. ; the power factor is not zero. If negative then an error exists. DEFB __stk_zero ;; Y, 0. DEFB __exchange ;; 0, Y. DEFB __greater_0 ;; 0, (1/0). DEFB __jump_true ;; 0 DEFB LAST - $ ;; if Y was any positive ;; number. ; else force division by zero thereby raising an Arithmetic overflow error. ; There are some one and two-byte alternatives but perhaps the most formal ; might have been to use end_calc; rst 08; defb 05. ; #if ORIGINAL ; the SG ROM seems to want it the old way! #if 1 DEFB __stk_one ;; 0, 1. DEFB __exchange ;; 1, 0. DEFB __division ;; 1/0 >> error #else DEFB $34 ;+ end-calc REPORT_6c RST 08H ;+ ERROR-1 DEFB $05 ;+ Error Report: Number too big #endif ; ___ mark_1DFB: ONE: DEFB __delete ;; . DEFB __stk_one ;; 1. mark_1DFD: LAST: DEFB __end_calc ;; last value 1 or 0. RET ; return.
; THE 'SPARE LOCATIONS'
SPARE: #if ORIGINAL mark_1DFF: DEFB $FF ; That's all folks. #else mark_1DFE: L1DFE: ;; DEFB $FF, $FF ; Two spare bytes. DEFB $00, $00 ; Two spare bytes (as per the Shoulders of Giants ROM) #endif
; THE 'ZX81 CHARACTER SET'
mark_1E00:
char_set ; - begins with space character. ; $00 - Character: ' ' CHR$(0) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $01 - Character: mosaic CHR$(1) DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $02 - Character: mosaic CHR$(2) DEFB %00001111 DEFB %00001111 DEFB %00001111 DEFB %00001111 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $03 - Character: mosaic CHR$(3) DEFB %11111111 DEFB %11111111 DEFB %11111111 DEFB %11111111 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $04 - Character: mosaic CHR$(4) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 ; $05 - Character: mosaic CHR$(5) DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 ; $06 - Character: mosaic CHR$(6) DEFB %00001111 DEFB %00001111 DEFB %00001111 DEFB %00001111 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 ; $07 - Character: mosaic CHR$(7) DEFB %11111111 DEFB %11111111 DEFB %11111111 DEFB %11111111 DEFB %11110000 DEFB %11110000 DEFB %11110000 DEFB %11110000 ; $08 - Character: mosaic CHR$(8) DEFB %10101010 DEFB %01010101 DEFB %10101010 DEFB %01010101 DEFB %10101010 DEFB %01010101 DEFB %10101010 DEFB %01010101 ; $09 - Character: mosaic CHR$(9) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %10101010 DEFB %01010101 DEFB %10101010 DEFB %01010101 ; $0A - Character: mosaic CHR$(10) DEFB %10101010 DEFB %01010101 DEFB %10101010 DEFB %01010101 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $0B - Character: '"' CHR$(11) DEFB %00000000 DEFB %00100100 DEFB %00100100 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $0C - Character: £ CHR$(12) DEFB %00000000 DEFB %00011100 DEFB %00100010 DEFB %01111000 DEFB %00100000 DEFB %00100000 DEFB %01111110 DEFB %00000000 ; $0D - Character: '$' CHR$(13) DEFB %00000000 DEFB %00001000 DEFB %00111110 DEFB %00101000 DEFB %00111110 DEFB %00001010 DEFB %00111110 DEFB %00001000 ; $0E - Character: ':' CHR$(14) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00000000 ; $0F - Character: '?' CHR$(15) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00000100 DEFB %00001000 DEFB %00000000 DEFB %00001000 DEFB %00000000 ; $10 - Character: '(' CHR$(16) DEFB %00000000 DEFB %00000100 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00000100 DEFB %00000000 ; $11 - Character: ')' CHR$(17) DEFB %00000000 DEFB %00100000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00100000 DEFB %00000000 ; $12 - Character: '>' CHR$(18) DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00001000 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00000000 ; $13 - Character: '<' CHR$(19) DEFB %00000000 DEFB %00000000 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00001000 DEFB %00000100 DEFB %00000000 ; $14 - Character: '=' CHR$(20) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00111110 DEFB %00000000 DEFB %00111110 DEFB %00000000 DEFB %00000000 ; $15 - Character: '+' CHR$(21) DEFB %00000000 DEFB %00000000 DEFB %00001000 DEFB %00001000 DEFB %00111110 DEFB %00001000 DEFB %00001000 DEFB %00000000 ; $16 - Character: '-' CHR$(22) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00111110 DEFB %00000000 DEFB %00000000 DEFB %00000000 ; $17 - Character: '*' CHR$(23) DEFB %00000000 DEFB %00000000 DEFB %00010100 DEFB %00001000 DEFB %00111110 DEFB %00001000 DEFB %00010100 DEFB %00000000 ; $18 - Character: '/' CHR$(24) DEFB %00000000 DEFB %00000000 DEFB %00000010 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00100000 DEFB %00000000 ; $19 - Character: ';' CHR$(25) DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00000000 DEFB %00000000 DEFB %00010000 DEFB %00010000 DEFB %00100000 ; $1A - Character: ',' CHR$(26) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00001000 DEFB %00001000 DEFB %00010000 ; $1B - Character: '"' CHR$(27) DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00000000 DEFB %00011000 DEFB %00011000 DEFB %00000000 ; $1C - Character: '0' CHR$(28) DEFB %00000000 DEFB %00111100 DEFB %01000110 DEFB %01001010 DEFB %01010010 DEFB %01100010 DEFB %00111100 DEFB %00000000 ; $1D - Character: '1' CHR$(29) DEFB %00000000 DEFB %00011000 DEFB %00101000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00111110 DEFB %00000000 ; $1E - Character: '2' CHR$(30) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00000010 DEFB %00111100 DEFB %01000000 DEFB %01111110 DEFB %00000000 ; $1F - Character: '3' CHR$(31) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00001100 DEFB %00000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $20 - Character: '4' CHR$(32) DEFB %00000000 DEFB %00001000 DEFB %00011000 DEFB %00101000 DEFB %01001000 DEFB %01111110 DEFB %00001000 DEFB %00000000 ; $21 - Character: '5' CHR$(33) DEFB %00000000 DEFB %01111110 DEFB %01000000 DEFB %01111100 DEFB %00000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $22 - Character: '6' CHR$(34) DEFB %00000000 DEFB %00111100 DEFB %01000000 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $23 - Character: '7' CHR$(35) DEFB %00000000 DEFB %01111110 DEFB %00000010 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $24 - Character: '8' CHR$(36) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $25 - Character: '9' CHR$(37) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %00111110 DEFB %00000010 DEFB %00111100 DEFB %00000000 ; $26 - Character: 'A' CHR$(38) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %01111110 DEFB %01000010 DEFB %01000010 DEFB %00000000 ; $27 - Character: 'B' CHR$(39) DEFB %00000000 DEFB %01111100 DEFB %01000010 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %01111100 DEFB %00000000 ; $28 - Character: 'C' CHR$(40) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000000 DEFB %01000000 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $29 - Character: 'D' CHR$(41) DEFB %00000000 DEFB %01111000 DEFB %01000100 DEFB %01000010 DEFB %01000010 DEFB %01000100 DEFB %01111000 DEFB %00000000 ; $2A - Character: 'E' CHR$(42) DEFB %00000000 DEFB %01111110 DEFB %01000000 DEFB %01111100 DEFB %01000000 DEFB %01000000 DEFB %01111110 DEFB %00000000 ; $2B - Character: 'F' CHR$(43) DEFB %00000000 DEFB %01111110 DEFB %01000000 DEFB %01111100 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %00000000 ; $2C - Character: 'G' CHR$(44) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000000 DEFB %01001110 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $2D - Character: 'H' CHR$(45) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01111110 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00000000 ; $2E - Character: 'I' CHR$(46) DEFB %00000000 DEFB %00111110 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00001000 DEFB %00111110 DEFB %00000000 ; $2F - Character: 'J' CHR$(47) DEFB %00000000 DEFB %00000010 DEFB %00000010 DEFB %00000010 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $30 - Character: 'K' CHR$(48) DEFB %00000000 DEFB %01000100 DEFB %01001000 DEFB %01110000 DEFB %01001000 DEFB %01000100 DEFB %01000010 DEFB %00000000 ; $31 - Character: 'L' CHR$(49) DEFB %00000000 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %01000000 DEFB %01111110 DEFB %00000000 ; $32 - Character: 'M' CHR$(50) DEFB %00000000 DEFB %01000010 DEFB %01100110 DEFB %01011010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00000000 ; $33 - Character: 'N' CHR$(51) DEFB %00000000 DEFB %01000010 DEFB %01100010 DEFB %01010010 DEFB %01001010 DEFB %01000110 DEFB %01000010 DEFB %00000000 ; $34 - Character: 'O' CHR$(52) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $35 - Character: 'P' CHR$(53) DEFB %00000000 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %01111100 DEFB %01000000 DEFB %01000000 DEFB %00000000 ; $36 - Character: 'Q' CHR$(54) DEFB %00000000 DEFB %00111100 DEFB %01000010 DEFB %01000010 DEFB %01010010 DEFB %01001010 DEFB %00111100 DEFB %00000000 ; $37 - Character: 'R' CHR$(55) DEFB %00000000 DEFB %01111100 DEFB %01000010 DEFB %01000010 DEFB %01111100 DEFB %01000100 DEFB %01000010 DEFB %00000000 ; $38 - Character: 'S' CHR$(56) DEFB %00000000 DEFB %00111100 DEFB %01000000 DEFB %00111100 DEFB %00000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $39 - Character: 'T' CHR$(57) DEFB %00000000 DEFB %11111110 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $3A - Character: 'U' CHR$(58) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00111100 DEFB %00000000 ; $3B - Character: 'V' CHR$(59) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %00100100 DEFB %00011000 DEFB %00000000 ; $3C - Character: 'W' CHR$(60) DEFB %00000000 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01000010 DEFB %01011010 DEFB %00100100 DEFB %00000000 ; $3D - Character: 'X' CHR$(61) DEFB %00000000 DEFB %01000010 DEFB %00100100 DEFB %00011000 DEFB %00011000 DEFB %00100100 DEFB %01000010 DEFB %00000000 ; $3E - Character: 'Y' CHR$(62) DEFB %00000000 DEFB %10000010 DEFB %01000100 DEFB %00101000 DEFB %00010000 DEFB %00010000 DEFB %00010000 DEFB %00000000 ; $3F - Character: 'Z' CHR$(63) DEFB %00000000 DEFB %01111110 DEFB %00000100 DEFB %00001000 DEFB %00010000 DEFB %00100000 DEFB %01111110 DEFB %00000000 ; .END ;TASM assembler instruction. ; ; This marks the end of the ZX81 ROM. ; ; As a bonus feature, I will now include the code for ; the G007 graphics board and ; the ZX81 monitor ; ; The 8K space divides into four 2K spaces like so: ; ; 2000 RAM (1K or 2K) remapped ; 2800 G007 ROM ; 3000 ; 3800 ZX81 Monitor ; ; The G007 uses some RAM ; 2300 G007 RAM variables ; ; 2000 #code $2000,$0800 ; RAM_2K ; 2300 ; xxxx data 4 ; reserves 4 bytes from the #data segment for variable "Toto" #code $2800,$0800 #if 0 ; ; just copy bytes. Provides a reference for comparing code output. ; ; G007 Graphics ; Start = 2800H ; End = 2FFFH mark_2800: DEFB $2A, $23, $23, $3A, $25, $23, $67, $EB mark_2808: DEFB $3A, $18, $23, $3D, $92, $57, $3E, $07 mark_2810: DEFB $A3, $6F, $26, $2D, $7E, $2A, $08, $23 mark_2818: DEFB $4A, $06, $00, $09, $09, $CB, $3A, $CB mark_2820: DEFB $1B, $CB, $3A, $CB, $1B, $CB, $3A, $CB mark_2828: DEFB $1B, $19, $47, $C9, $3A, $21, $23, $2A mark_2830: DEFB $27, $23, $ED, $5B, $23, $23, $D9, $2A mark_2838: DEFB $25, $23, $ED, $5B, $29, $23, $CB, $77 mark_2840: DEFB $28, $1C, $CB, $7F, $C0, $3A, $21, $40 mark_2848: DEFB $F5, $06, $08, $0F, $FD, $CB, $21, $16 mark_2850: DEFB $10, $F9, $EB, $D9, $EB, $D9, $CD, $5E mark_2858: DEFB $28, $F1, $32, $21, $40, $C9, $01, $DE mark_2860: DEFB $FF, $3A, $18, $23, $3D, $93, $ED, $52 mark_2868: DEFB $F2, $76, $28, $01, $22, $00, $7B, $D5 mark_2870: DEFB $19, $EB, $B7, $ED, $52, $D1, $E5, $63 mark_2878: DEFB $5F, $D9, $01, $FB, $28, $7D, $D9, $6F mark_2880: DEFB $D9, $B7, $ED, $52, $F2, $90, $28, $01 mark_2888: DEFB $04, $29, $2F, $19, $EB, $B7, $ED, $52 mark_2890: DEFB $ED, $43, $1A, $23, $D1, $B7, $ED, $52 mark_2898: DEFB $19, $30, $09, $EB, $01, $F6, $28, $D9 mark_28A0: DEFB $53, $5F, $7A, $D9, $ED, $43, $1C, $23 mark_28A8: DEFB $D9, $57, $D5, $D9, $C1, $3A, $21, $23 mark_28B0: DEFB $FE, $40, $30, $02, $45, $4B, $04, $0C mark_28B8: DEFB $CB, $3C, $38, $02, $28, $08, $CB, $1D mark_28C0: DEFB $CB, $3A, $CB, $1B, $18, $F2, $55, $CB mark_28C8: DEFB $3D, $D9, $C5, $CD, $07, $28, $D1, $3A mark_28D0: DEFB $1F, $23, $4F, $FD, $CB, $21, $06, $38 mark_28D8: DEFB $08, $3A, $1E, $23, $AE, $B1, $A0, $AE mark_28E0: DEFB $77, $D9, $7D, $05, $C8, $93, $30, $0A mark_28E8: DEFB $0D, $C8, $82, $D9, $19, $D9, $2A, $1A mark_28F0: DEFB $23, $E9, $2A, $1C, $23, $E9, $6F, $D9 mark_28F8: DEFB $19, $18, $D8, $6F, $D9, $CB, $00, $30 mark_2900: DEFB $D2, $2B, $18, $CF, $6F, $D9, $CB, $08 mark_2908: DEFB $30, $C9, $23, $18, $C6, $2A, $A8, $0E mark_2910: DEFB $E9, $CD, $0D, $29, $38, $0E, $21, $00 mark_2918: DEFB $00, $28, $06, $ED, $42, $F8, $C8, $18 mark_2920: DEFB $03, $ED, $4A, $F0, $E1, $C9, $CD, $11 mark_2928: DEFB $29, $22, $25, $23, $CD, $11, $29, $E5 mark_2930: DEFB $CD, $02, $0C, $C1, $ED, $5B, $25, $23 mark_2938: DEFB $28, $0C, $3D, $C0, $21, $9C, $0C, $22 mark_2940: DEFB $30, $40, $43, $C3, $B2, $0B ; The Plot Routine: ; A == the plot number N ; BC == screen X ; DE == screen Y mark_2946: DEFB $B7, $28 mark_2948: DEFB $F9, $3D, $D5, $C5, $F5, $CD, $1F, $2E mark_2950: DEFB $F1, $D1, $C1, $FE, $81, $20, $0F, $ED mark_2958: DEFB $53, $30, $23, $ED, $43, $32, $23, $01 mark_2960: DEFB $00, $00, $50, $58, $3E, $0B, $CB, $57 mark_2968: DEFB $28, $09, $2A, $29, $23, $E5, $2A, $27 mark_2970: DEFB $23, $18, $07, $2A, $32, $23, $E5, $2A mark_2978: DEFB $30, $23, $B7, $ED, $5A, $D1, $E8, $22 mark_2980: DEFB $23, $23, $EB, $B7, $ED, $4A, $E8, $22 mark_2988: DEFB $25, $23, $5F, $3E, $C0, $A4, $E0, $3E mark_2990: DEFB $C0, $A2, $E0, $D9, $E5, $D5, $C5, $CD mark_2998: DEFB $9F, $29, $C1, $D1, $E1, $D9, $C9, $D9 mark_29A0: DEFB $7C, $B2, $37, $20, $05, $3A, $18, $23 mark_29A8: DEFB $3D, $BD, $3A, $21, $23, $1F, $32, $21 mark_29B0: DEFB $23, $7B, $CB, $7F, $28, $04, $2A, $10 mark_29B8: DEFB $23, $E9, $F5, $E6, $03, $1F, $3D, $2F mark_29C0: DEFB $67, $9F, $6F, $22, $1E, $23, $E5, $CB mark_29C8: DEFB $5B, $20, $28, $3A, $20, $23, $AB, $E6 mark_29D0: DEFB $FB, $28, $0F, $21, $34, $23, $7B, $07 mark_29D8: DEFB $07, $07, $E6, $03, $85, $6F, $7E, $32 mark_29E0: DEFB $21, $40, $CD, $2C, $28, $E1, $FD, $CB mark_29E8: DEFB $21, $0E, $38, $26, $7C, $A5, $28, $22 mark_29F0: DEFB $2C, $5D, $E5, $3A, $21, $23, $CB, $6B mark_29F8: DEFB $20, $3D, $CB, $73, $20, $3E, $E1, $CB mark_2A00: DEFB $7F, $20, $0F, $E5, $CD, $00, $28, $A6 mark_2A08: DEFB $32, $17, $23, $D1, $7E, $B2, $AB, $A0 mark_2A10: DEFB $AE, $77, $F1, $32, $20, $23, $21, $2A mark_2A18: DEFB $23, $11, $2E, $23, $01, $08, $00, $CB mark_2A20: DEFB $67, $28, $11, $3A, $21, $23, $E6, $C0 mark_2A28: DEFB $17, $30, $02, $CB, $F7, $32, $21, $23 mark_2A30: DEFB $2E, $26, $0E, $04, $ED, $B8, $C9, $21 mark_2A38: DEFB $FF, $FF, $18, $03, $2A, $0A, $23, $D9 mark_2A40: DEFB $E6, $E0, $C2, $AD, $0E, $21, $21, $23 mark_2A48: DEFB $FD, $36, $21, $55, $06, $03, $23, $23 mark_2A50: DEFB $5E, $23, $23, $56, $D5, $10, $F7, $C1 mark_2A58: DEFB $D1, $E1, $B7, $28, $02, $44, $4D, $78 mark_2A60: DEFB $BC, $30, $03, $C5, $E3, $C1, $7C, $BA mark_2A68: DEFB $30, $01, $EB, $7A, $D9, $E6, $07, $3C mark_2A70: DEFB $47, $7C, $CB, $05, $07, $07, $07, $10 mark_2A78: DEFB $F9, $67, $22, $38, $23, $D9, $7C, $B8 mark_2A80: DEFB $3E, $07, $30, $03, $C5, $E3, $C1, $EB mark_2A88: DEFB $F5, $79, $D9, $67, $6F, $4F, $22, $1A mark_2A90: DEFB $23, $06, $FE, $D9, $93, $D9, $30, $04 mark_2A98: DEFB $06, $00, $ED, $44, $57, $D9, $7A, $90 mark_2AA0: DEFB $D9, $67, $BA, $30, $02, $EB, $04, $6C mark_2AA8: DEFB $2C, $5C, $CB, $3B, $F1, $0F, $30, $09 mark_2AB0: DEFB $E5, $D5, $C5, $0F, $D9, $38, $CD, $18 mark_2AB8: DEFB $CE, $D9, $60, $2E, $01, $C1, $D1, $E3 mark_2AC0: DEFB $2D, $20, $16, $2A, $1A, $23, $79, $BC mark_2AC8: DEFB $38, $01, $67, $BD, $30, $01, $6F, $22 mark_2AD0: DEFB $1A, $23, $E1, $2D, $28, $E7, $E5, $18 mark_2AD8: DEFB $15, $7B, $92, $38, $0A, $5F, $CB, $40 mark_2AE0: DEFB $28, $0C, $79, $80, $4F, $18, $D9, $84 mark_2AE8: DEFB $5F, $3E, $01, $B0, $81, $4F, $79, $D9 mark_2AF0: DEFB $FD, $CB, $21, $0E, $38, $CA, $E3, $D5 mark_2AF8: DEFB $C5, $E5, $ED, $5B, $1A, $23, $47, $B9 mark_2B00: DEFB $30, $02, $41, $4F, $ED, $43, $1A, $23 mark_2B08: DEFB $79, $BB, $3C, $38, $01, $7B, $6F, $7A mark_2B10: DEFB $B8, $30, $02, $78, $3D, $95, $3C, $F5 mark_2B18: DEFB $CD, $07, $28, $F1, $4F, $EB, $21, $39 mark_2B20: DEFB $23, $7E, $07, $07, $07, $77, $2B, $CB mark_2B28: DEFB $06, $B6, $2A, $1E, $23, $AD, $2F, $6F mark_2B30: DEFB $EB, $7B, $AE, $B2, $A0, $AE, $77, $0D mark_2B38: DEFB $20, $14, $E1, $24, $CB, $7D, $28, $94 mark_2B40: DEFB $E1, $E1, $E1, $E1, $7C, $A5, $CA, $12 mark_2B48: DEFB $2A, $2C, $E5, $C3, $45, $2A, $CB, $18 mark_2B50: DEFB $30, $DF, $23, $41, $79, $E6, $07, $4F mark_2B58: DEFB $CB, $38, $CB, $38, $CB, $38, $28, $08 mark_2B60: DEFB $7B, $AE, $B2, $AE, $77, $23, $10, $F8 mark_2B68: DEFB $37, $0C, $18, $CB, $2A, $13, $23, $2D mark_2B70: DEFB $B5, $C2, $09, $08, $25, $C2, $B8, $23 mark_2B78: DEFB $3A, $21, $23, $17, $D8, $00, $00, $2A mark_2B80: DEFB $28, $23, $3A, $27, $23, $CB, $72, $20 mark_2B88: DEFB $05, $6F, $C6, $08, $30, $0D, $7C, $D6 mark_2B90: DEFB $08, $30, $04, $3A, $18, $23, $3D, $32 mark_2B98: DEFB $29, $23, $AF, $32, $27, $23, $CB, $72 mark_2BA0: DEFB $C0, $CD, $EF, $2E, $7D, $FE, $F9, $38 mark_2BA8: DEFB $02, $1E, $02, $2F, $E6, $07, $3C, $57 mark_2BB0: DEFB $7C, $3C, $D9, $57, $D9, $D5, $CD, $07 mark_2BB8: DEFB $28, $D9, $79, $AE, $D9, $EB, $C1, $C5 mark_2BC0: DEFB $6F, $26, $00, $29, $10, $FD, $EB, $06 mark_2BC8: DEFB $02, $3A, $7B, $40, $AE, $FD, $B6, $7C mark_2BD0: DEFB $A2, $AE, $0D, $28, $01, $77, $53, $23 mark_2BD8: DEFB $10, $EF, $0E, $20, $09, $D9, $23, $15 mark_2BE0: DEFB $28, $02, $10, $D6, $E1, $C3, $9A, $29 mark_2BE8: DEFB $ED, $57, $0F, $30, $08, $AF, $32, $22 mark_2BF0: DEFB $40, $3C, $32, $13, $23, $CD, $CF, $0A mark_2BF8: DEFB $AF, $32, $13, $23, $FD, $36, $22, $02 mark_2C00: DEFB $4F, $C9, $CD, $A0, $0C, $DA, $AD, $0E mark_2C08: DEFB $0E, $01, $C8, $0E, $FF, $C9, $FD, $46 mark_2C10: DEFB $22, $0E, $21, $CD, $18, $09, $CD, $9B mark_2C18: DEFB $09, $7E, $12, $FD, $34, $3A, $2A, $0C mark_2C20: DEFB $40, $23, $54, $5D, $ED, $B1, $C3, $5D mark_2C28: DEFB $0A, $8B, $8D, $2D, $7F, $81, $49, $75 mark_2C30: DEFB $5F, $40, $42, $2B, $17, $1F, $37, $52 mark_2C38: DEFB $45, $0F, $6D, $2B, $44, $2D, $5A, $3B mark_2C40: DEFB $4C, $45, $0D, $52, $54, $4D, $15, $6A mark_2C48: DEFB $01, $14, $02, $06, $00, $81, $0E, $06 mark_2C50: DEFB $DE, $05, $AB, $0D, $06, $00, $B5, $0E mark_2C58: DEFB $00, $DC, $0C, $00, $D8, $0E, $04, $14 mark_2C60: DEFB $06, $DF, $06, $05, $B9, $0D, $04, $00 mark_2C68: DEFB $2E, $0E, $05, $E8, $2B, $01, $00, $E9 mark_2C70: DEFB $0E, $05, $A7, $2E, $05, $6A, $0D, $00 mark_2C78: DEFB $C3, $03, $03, $AF, $0E, $03, $30, $07 mark_2C80: DEFB $06, $1A, $06, $00, $92, $0E, $03, $6C mark_2C88: DEFB $0E, $05, $40, $03, $05, $4D, $2F, $00 mark_2C90: DEFB $7C, $0E, $00, $B2, $0E, $03, $4E, $2E mark_2C98: DEFB $06, $1A, $06, $1A, $06, $00, $26, $29 mark_2CA0: DEFB $2A, $A8, $0E, $E9, $00, $0E, $0C, $06 mark_2CA8: DEFB $00, $AE, $2E, $03, $B2, $2E, $03, $B6 mark_2CB0: DEFB $2E, $03, $53, $2F, $05, $CB, $0A, $03 mark_2CB8: DEFB $2C, $07, $FD, $36, $01, $01, $CD, $73 mark_2CC0: DEFB $0A, $CD, $95, $0A, $21, $00, $40, $36 mark_2CC8: DEFB $FF, $21, $2D, $40, $CB, $6E, $28, $0E mark_2CD0: DEFB $FE, $E3, $7E, $C2, $6F, $0D, $CD, $A6 mark_2CD8: DEFB $0D, $C8, $CF, $0C, $CF, $08, $DF, $06 mark_2CE0: DEFB $00, $FE, $76, $C8, $4F, $E7, $79, $D6 mark_2CE8: DEFB $E1, $38, $3B, $4F, $21, $29, $0C, $09 mark_2CF0: DEFB $4E, $09, $18, $03, $2A, $30, $40, $7E mark_2CF8: DEFB $23, $22, $30, $40, $01, $F4, $0C, $C5 ; ; bits shifting right within a byte: ; mark_2D00: DEFB $80, $40, $20, $10, $08, $04, $02, $01 ; mark_2D08: DEFB $CD, $F7, $2B, $C3, $07, $02, $32, $28 mark_2D10: DEFB $40, $EB, $21, $0A, $00, $39, $7E, $3C mark_2D18: DEFB $E6, $F0, $D6, $D0, $4F, $23, $7E, $D6 mark_2D20: DEFB $04, $B1, $4F, $3A, $3B, $40, $07, $9F mark_2D28: DEFB $A1, $20, $10, $2A, $10, $40, $01, $DF mark_2D30: DEFB $FF, $09, $CB, $FC, $22, $04, $23, $3E mark_2D38: DEFB $01, $18, $0E, $2A, $0C, $40, $ED, $4B mark_2D40: DEFB $00, $23, $09, $CB, $FC, $22, $06, $23 mark_2D48: DEFB $AF, $32, $19, $23, $2B, $7E, $EB, $C9 mark_2D50: DEFB $3A, $19, $23, $3D, $C2, $73, $2D, $3E mark_2D58: DEFB $1E, $ED, $47, $ED, $6A, $2A, $04, $23 mark_2D60: DEFB $01, $08, $01, $3E, $FE, $CD, $B5, $02 mark_2D68: DEFB $3E, $1F, $ED, $47, $3A, $28, $40, $D6 mark_2D70: DEFB $08, $18, $04, $2B, $3A, $28, $40, $4F mark_2D78: DEFB $DD, $E1, $FD, $CB, $3B, $7E, $C2, $9D mark_2D80: DEFB $02, $3E, $FE, $06, $01, $21, $9A, $2D mark_2D88: DEFB $CD, $95, $2D, $29, $00, $5F, $2A, $06 mark_2D90: DEFB $23, $CB, $FC, $DD, $E9, $ED, $4F, $3E mark_2D98: DEFB $DD, $FB, $76, $21, $00, $22, $3E, $24 mark_2DA0: DEFB $36, $01, $35, $28, $02, $CF, $1A, $23 mark_2DA8: DEFB $BC, $20, $F5, $65, $11, $00, $20, $01 mark_2DB0: DEFB $00, $01, $ED, $B0, $24, $14, $04, $ED mark_2DB8: DEFB $B0, $21, $F1, $07, $11, $A0, $23, $01 mark_2DC0: DEFB $60, $00, $ED, $B0, $16, $20, $21, $B4 mark_2DC8: DEFB $2F, $46, $18, $04, $5E, $23, $7E, $12 mark_2DD0: DEFB $23, $10, $F9, $14, $CB, $52, $28, $F1 mark_2DD8: DEFB $C9 ; Delete Display File: mark_2DD9: DEFB $CD, $E7, $02, $21, $87, $3E, $CD mark_2DE0: DEFB $D8, $09, $C0, $EB, $2A, $29, $40, $CB mark_2DE8: DEFB $76, $28, $04, $ED, $53, $29, $40, $2A mark_2DF0: DEFB $0C, $40, $C3, $5D, $0A, $CD, $D9, $2D mark_2DF8: DEFB $01, $92, $19, $2A, $0C, $40, $2B, $CD mark_2E00: DEFB $9E, $09, $3E, $76, $12, $13, $12, $23 mark_2E08: DEFB $23, $36, $3E, $23, $36, $87, $23, $36 mark_2E10: DEFB $8D, $23, $36, $19, $23, $77, $23, $77 mark_2E18: DEFB $CD, $07, $02, $3E, $01, $18, $37 ; Check & Set Up Display File: mark_2E1F: DEFB $2A mark_2E20: DEFB $65, $22, $11, $D9, $BF, $19, $3A, $64 mark_2E28: DEFB $22, $D6, $21, $B4, $B5, $C4, $9B, $2D mark_2E30: DEFB $2A, $0C, $40, $3E, $76, $2B, $2B, $BE mark_2E38: DEFB $C4, $F5, $2D, $2A, $0C, $40, $ED, $5B mark_2E40: DEFB $00, $23, $19, $22, $06, $23, $11, $09 mark_2E48: DEFB $00, $19, $22, $08, $23, $C9, $CD, $02 mark_2E50: DEFB $0C, $C0 ; Clear The Screen: mark_2E52: DEFB $3D, $FA, $2A, $0A, $F5, $CD mark_2E58: DEFB $1F, $2E, $F1, $FE, $02, $ED, $4B, $18 mark_2E60: DEFB $23, $2A, $0C, $40, $30, $30, $3D, $23 mark_2E68: DEFB $22, $0E, $40, $2B, $2B, $2B, $1E, $00 mark_2E70: DEFB $06, $10, $2B, $73, $2B, $73, $2B, $77 mark_2E78: DEFB $2B, $77, $10, $FA, $0D, $20, $F1, $06 mark_2E80: DEFB $09, $3E, $01, $2B, $73, $10, $FC, $21 mark_2E88: DEFB $34, $23, $06, $14, $3D, $28, $F4, $21 mark_2E90: DEFB $21, $18, $22, $39, $40, $C9, $C0, $2B mark_2E98: DEFB $2B, $06, $20, $2B, $2B, $2B, $7E, $2F mark_2EA0: DEFB $77, $10, $FA, $0D, $20, $F3, $C9, $2A mark_2EA8: DEFB $96, $0A, $3E, $4D, $18, $35, $3E, $DD mark_2EB0: DEFB $18, $2E, $3E, $D6, $18, $02, $3E, $CE mark_2EB8: DEFB $F5, $CD, $02, $0C, $06, $1E, $3D, $FE mark_2EC0: DEFB $06, $30, $19, $CB, $3F, $67, $28, $02 mark_2EC8: DEFB $3E, $01, $F5, $9F, $6F, $CB, $8C, $25 mark_2ED0: DEFB $22, $7B, $40, $CD, $1F, $2E, $06, $1F mark_2ED8: DEFB $F1, $32, $14, $23, $78, $ED, $47, $F1 mark_2EE0: DEFB $2A, $71, $0D, $85, $6F, $E9, $57, $3A mark_2EE8: DEFB $39, $40, $E6, $80, $C3, $6C, $2B, $7A mark_2EF0: DEFB $D1, $D9, $E5, $D5, $C5, $2A, $0C, $23 mark_2EF8: DEFB $87, $30, $0B, $2A, $0E, $23, $CB, $77 mark_2F00: DEFB $28, $04, $2A, $15, $23, $3F, $EB, $6F mark_2F08: DEFB $26, $00, $9F, $4F, $3A, $7B, $40, $2F mark_2F10: DEFB $FD, $A6, $7C, $A9, $4F, $29, $29, $19 mark_2F18: DEFB $06, $08, $D9, $D5, $C9, $FD, $35, $39 mark_2F20: DEFB $3E, $18, $90, $47, $87, $87, $87, $6F mark_2F28: DEFB $3A, $18, $23, $95, $D8, $3E, $21, $91 mark_2F30: DEFB $4F, $26, $00, $29, $09, $ED, $4B, $08 mark_2F38: DEFB $23, $09, $CD, $EF, $2E, $01, $22, $00 mark_2F40: DEFB $D9, $79, $AE, $D9, $77, $09, $D9, $23 mark_2F48: DEFB $10, $F7, $C3, $9A, $29, $CD, $D9, $2D mark_2F50: DEFB $C3, $F6, $02, $CD, $02, $0C, $C0, $3D mark_2F58: DEFB $FA, $69, $08, $CD, $1F, $2E, $CD, $E7 mark_2F60: DEFB $02, $3A, $18, $23, $47, $2A, $08, $23 mark_2F68: DEFB $AF, $5F, $D3, $FB, $3E, $7F, $DB, $FE mark_2F70: DEFB $0F, $D2, $86, $08, $DB, $FB, $87, $FA mark_2F78: DEFB $AD, $2F, $30, $F0, $0E, $20, $C5, $4E mark_2F80: DEFB $06, $08, $CB, $01, $1F, $B3, $57, $DB mark_2F88: DEFB $FB, $1F, $30, $FB, $7A, $D3, $FB, $10 mark_2F90: DEFB $F1, $23, $C1, $0D, $20, $E8, $23, $23 mark_2F98: DEFB $3E, $03, $B8, $38, $02, $5F, $1D, $DB mark_2FA0: DEFB $FB, $1F, $30, $FB, $7B, $D3, $FB, $10 mark_2FA8: DEFB $C3, $3E, $04, $D3, $FB, $C3, $07, $02 mark_2FB0: DEFB $FB, $10, $C3, $3E, $0A, $12, $A0, $13 mark_2FB8: DEFB $23, $15, $A4, $16, $23, $40, $C1, $60 mark_2FC0: DEFB $08, $61, $2D, $75, $06, $76, $23, $01 mark_2FC8: DEFB $0A, $54, $02, $85, $C1, $7F, $73, $80 mark_2FD0: DEFB $2D, $8D, $50, $8E, $2D, $E3, $C3, $E4 mark_2FD8: DEFB $0E, $E5, $2D, $13, $00, $75, $01, $E6 mark_2FE0: DEFB $0A, $55, $0D, $1E, $0F, $1E, $16, $20 mark_2FE8: DEFB $10, $07, $11, $08, $18, $C0, $35, $EE mark_2FF0: DEFB $36, $55, $37, $C6, $AD, $E6, $AE, $2E mark_2FF8: DEFB $F2, $C3, $F3, $1D, $F4, $2F, $ED, $00 ; #else ; G007 source code, reverse engineered. ; ; The ZX81 labels will have been generated already. ; No need to include ZX definitions: ; ;=============================== ; G007 Hi-Res graphics board for the ZX81 ; ; Source code partially reverse-engineered. ; A work in progress... ; ; 2011-05-15 Assembles to create correct ROM image. ; ; Verifed by comparing the hex files, ; the known-good reference generated by ; 2048 defb statements containing the original bytes. ; ; The source code below is not guaranteed to create the ROM image ; exactly the way the original author had in mind, ; as I'm not him and I don't have the original source code. ; ; This file was created from a disassembly generated by the ; impressive VB81 emulator program. ; ; It was already know that the G007 switched out pages of ; the ZX81 BASIC ROM and patched in replacements from ; the G007 ROM and the ZX81's internal 1K (or 2K) RAM. ; This is a thrifty use of RAM that expansion RAM packs ; usually just disabled. ; ; Memory map: ; ; 0000-0FFF ; 2000-23FF 1K RAM inside ZX81, remapped here. ; 2400-27FF 1K RAM more RAM if ZX81 has a 2K RAM chip ; 2800-2FFF 2K G007 ; 3000-3FFF Empty ; 4000-7FFF External RAM pack ; ; Patching: ; ; 2C00-2CFF (ROM) also appears at 0C00-0CFF ; 2000-20FF (RAM) also appears at 0000-00FF ; 2200-22FF (RAM) also appears at 0200-02FF ; ; The ROM patching is active all the time. ; The RAM patching is active only in hi-res mode. ; ; The patches didn't cover every modification needed, ; so one routine is copied from ROM to RAM and ; then individual bytes are modified there ; by the initialisation routine which also ; initialises some variables. ; ; Graphics routines: ; ; These have not yet been analysed. ; Bresenham's algorithm will be there is some form. ; ; Triangle-filling is a sophisticated feature, ; using 8-bit maths for a practical speed. ; ; Future enhancements ; ; Programmers may like to try writing faster routines, ; or adding more commands now that memory is cheap. ; ; The bit-mask array at $2D00 looks like this: ; 10000000 ; 01000000 ; 00100000 ; 00010000 ; 00001000 ; 00000100 ; 00000010 ; 00000001 ; ; and suggests that pixels ; may be plotted one at a time. ; When I wrote a triangle-filling algorithm for the Atom, ; I made frequent use of horizontal lines. ; I optimised these by working out the partially-filled ; bytes at the left and right sides, and writing ; whole bytes (of 8 pixels) between the partial bytes. ; This used two overlapping tables like so: ; ; 10000000 ; BIT_MASK_R ; 11000000 ; 11100000 ; 11110000 ; 11111000 ; 11111100 ; 11111110 ; 11111111 ; BIT_MASK_L ; 01111111 ; 00111111 ; 00011111 ; 00001111 ; 00000111 ; 00000011 ; 00000001 ; ; This technique might be able to increase the triangle filling speed. ; ; A proper ellipse algorithm would also be welcome, ; not one of those approximations using polygons! ; ;=============================== ; Assembly was done by a very handy online tool: ; http://k1.dyndns.org/cgi-bin/zasm.cgi ; which avoids the chore of installing it ; on one's own machine. ; The online tool is limited to one source file and ; one include file, but that's not a big deal ; for a tiny 2K ROM like this one. ; ;=============================== ; Problems ; The G007 ROM had several instances of opcdoes ; beginning with FD CB ; involving the Y register. ; VB81 disassembled them into statements ; that looked mangled and would not re-assemble. ; So I've just added them with defb statements. ; ; ;=============================== ; Global constants: ;=============================== FALSE equ 0 NOT_G007 equ FALSE ;=============================== ; ZX81 constants: ;=============================== ; ZX characters (not the same as ASCII) ;------------------------------- ZX_EQU equ $14 ZX_COMMA equ $1A ZX_THEN equ $DE ZX_TO equ $DF ZX_INV_K equ $B0 ZX_NEWLINE equ $76 HRG_BYTES_PER_LINE equ 34 ALL_BITS_SET equ -1 ;------------------------------- ; tokens ;------------------------------- _CLASS_00 equ 0 _CLASS_01 equ 1 _CLASS_02 equ 2 _CLASS_03 equ 3 _CLASS_04 equ 4 _CLASS_05 equ 5 _CLASS_06 equ 6 ;=============================== ; ZX81 I/O locations: ;=============================== IO_PORT_KEYBOARD_RD equ $FE ; A0 low ZX_NMI_GEN equ $FD ; A1 low ZX_PRINTER_PORT equ $FB ; A2 low ;=============================== ; ZX81 RAM variables ;=============================== RAMBASE equ $4000 ERR_NR equ $4000 FLAGS equ $4001 ERR_SP equ $4002 RAMTOP equ $4004 MODE equ $4006 PPC equ $4007 VERSN equ $4009 E_PPC equ $400A D_FILE equ $400C DF_CC equ $400E VARS equ $4010 DEST equ $4012 E_LINE equ $4014 CH_ADD equ $4016 X_PTR equ $4018 STKBOT equ $401A STKEND equ $401C BERG equ $401E MEM equ $401F UNUSED_8 equ $4021 G007_FLAG_Y equ UNUSED_8 ; DF_SZ equ $4022 S_TOP equ $4023 LAST_K equ $4025 DEBOUNCE equ $4027 MARGIN equ $4028 NXTLIN equ $4029 NXT_LINE equ $4029 OLDPPC equ $402B FLAGX equ $402D STRLEN equ $402E T_ADDR equ $4030 SEED equ $4032 FRAMES equ $4034 COORDS equ $4036 PR_CC equ $4038 S_POSN equ $4039 S_POSN_hi equ S_POSN+1 CDFLAG equ $403B PRBUFF equ $403C MEMBOT equ $407B PROGRAM equ $407D UNUSED_16 equ $407B UNUSED_16_hi equ UNUSED_16+1 G007_RESTART equ UNUSED_16 ; First byte after system variables: USER_RAM equ $407D MAX_RAM equ $7FFF ;=============================== ; ZX BASIC ROM addresses ;=============================== ; restart constants START equ $0000 ; = 0 ERROR_1 equ $0008 ; = 8 PRINT_A equ $0010 ; = 16 GET_CHAR equ $0018 ; = 24 TEST_SP equ $001C ; = 28 NEXT_CH equ $0020 ; = 32 FP_CALC equ $0028 ; = 40 ;------------------------------- L0108 equ $0108 SLOW_FAST equ $0207 ; DISPLAY_5 equ $02B5 ; L029D equ $029D ; Inside the 'LOC_ADDR' subroutine SET_FAST equ $02E7 LOAD equ $0340 LIST equ $0730 COPY equ $0869 SAVE equ $02F6 NEW equ $03C3 LLIST equ $072C PRINT_CH equ $07F1 ; old, replaced L0809 equ $0809 LOC_ADDR equ $0918 ONE_SPACE equ $099B MAKE_ROOM equ $099E LINE_ADDR equ $09D8 E_LINE_NO equ $0A73 L0A95 equ $0A95 ; []*BIOS ROM*. Part way into 088A COPY_CONT PRINT equ $0ACF CLS equ $0A2A RECLAIM_1 equ $0A5D LPRINT equ $0ACB PRINT equ $0ACF PLOT_UNPLOT equ $0BAF L0BB2 equ $0BB2 ; STK_TO_A equ $0C02 SCROLL equ $0C0E ;------------------------------- ; Parameter table addresses: ; Checked in ROM disassembly book: ;------------------------------- P_LET equ $0C48 P_GOTO equ $0C4B P_IF equ $0C4F P_GOSUB equ $0C54 P_STOP equ $0C58 P_RETURN equ $0C5B P_FOR equ $0C5E P_NEXT equ $0C66 P_PRINT equ $0C6A P_INPUT equ $0C6D P_DIM equ $0C71 P_REM equ $0C74 P_NEW equ $0C77 P_RUN equ $0C7A P_LIST equ $0C7D P_POKE equ $0C80 P_RAND equ $0C86 P_LOAD equ $0C89 P_SAVE equ $0C8C P_CONT equ $0C8F P_CLEAR equ $0C92 P_CLS equ $0C95 P_PLOT equ $0C98 ; redefined in G007 patch P_UNPLOT equ $0C9E ; redefined in G007 patch P_SCROLL equ $0CA4 P_PAUSE equ $0CA7 ;P_SLOW equ $0CAB ;P_FAST equ $0CAE ;P_COPY equ $0CB1 ;P_LPRINT equ $0CB4 ;P_LLIST equ $0CB7 ;------------------------------- STOP equ $0CDC ; defined in this file REM equ $0D6A INPUT_RE equ $0D6F REPORT_C equ $0D9A ; according to the book REPORT_C_007 equ $0D26 ; seems the right one SYNTAX_Z equ $0DA6 IF equ $0DAB FOR equ $0DB9 NEXT equ $0E2E RAND equ $0E6C CONT equ $0E7C GOTO equ $0E81 POKE equ $0E92 L0EA8 equ $0EA8 ; Inside the 'FIND_INT.' subroutine REPORT_B equ $0EAD ; check this!!! RUN equ $0EAF CLEAR_007 equ $0EB2 ; goes to JP CLEAR GOSUB equ $0EB5 RETURN equ $0ED8 INPUT equ $0EE9 FAST equ $0F23 SLOW equ $0F2B PAUSE equ $0F32 DIM equ $1409 CLEAR equ $149A SET_MEM equ $14BC ;=============================== ; G007 Memory patch addresses ;=============================== ; These patches only appear in hi-res mode ; RAM_PATCH_0000 equ $0000 RAM_PATCH_0200 equ $0200 ROM_PATCH_0C00 equ $0C00 ; ; Their aliases are always present: ; RAM_PATCH_2000 equ $2000 RAM_PATCH_2200 equ $2200 ;=============================== ; G007 Plot number notes (a work in prgress) ;=============================== ; N-1 ; ; 76543 210 ; ..... .00 Line in white. ; ..... .01 Line black. ; ..... .10 Line inverting. ; ..... .11 Line inverting, omit last pixel. ; ; ..... 0.. Absolute co-ordinates ; ..... 1.. Relative co-ordinates ; ; ....0 ... Line ; ....1 ... Single pixel ; ; .0100 ... Coarse dotted line ; .0101 ... Add 40: triangle, plain ; .1001.... Add 72: triangle, textured (not available in invert mode) ; .1000 ... Add 64: fine dotted line ; .1100 ... Add 96: chain dotted line ; ; Note that PLOT 12 and PLOT 16 miss out the pixel, so simply move the PLOT position. ; ;=============================== ; G007 Byte constants ;=============================== G007_INT_REG_VALUE_FOR_HI_RES_GRAPHICS equ $1F G007_INT_REG_VALUE_FOR_LO_RES_GRAPHICS equ $1E ; Dec. Hex. Bytes System Variables: G007 ; 8448 2100 Reserved for user defined characters ; 8703 21FF ; ; 8704 2200 Another page, possibly ; 8959 22FF ;=============================== ; G007 Byte constants ;=============================== MEM_PATCH_SIZE equ $0100 ;=============================== ; G007 RAM variables ;=============================== V2265 equ $2265 V2264 equ $2264 ; Dec. Hex. Bytes System Variables: G007 ; 8960 2300 2 Offset of hi-res display file, less 9, from the D-FILE variable ; 8962 2302 2 Not used ; 8964 2304 2 Start address of last line of lo-res display file G007_DISPLAY_OFFSET_FROM_DFILE_LESS_9 equ $2300 ; conflict! G007_UNUSED_2302 equ $2302 G007_DISPLAY_ADDRESS_LO_RES_LAST_LINE equ $2304 G007_DISPLAY_ADDRESS_LESS_9 equ $2306 ; 8966 2306 2 Start address of hi-res display file, less 9 (used for video) G007_DISPLAY_ADDRESS equ $2308 ; 8968 2308 2 Start address of hi-res display file G007_TRIANGLE_TEXTURE equ $230A ; 8970 230A 2 Bytes defining triangle texture G007_CHAR_TABLE_ADDR_0_63 equ $230C ; 8972 230C 2 Character table address for CHR$0-63 G007_CHAR_TABLE_ADDR_128_159 equ $230E ; 8974 230E 2 Character table address for CHR$128-159 G007_PLOT_ROUTINES_VECTOR equ $2310 ; 8976 2310 2 Vector for additional plot routines G007_FLAG_0 equ $2312 ; 8978 2312 3 * Various flags G007_FLAG_1 equ $2313 G007_FLAG_2 equ $2314 G007_USR_DEF_CHR_TAB_LESS_256 equ $2315 ; 8981 2315 2 Address of user-defined character table, less 256 G007_READ_POINT_BYTE equ $2317 ; 8983 2317 1 Read-point byte. Non-zero if pixel is set. G007_DISPLAY_HEIGHT equ $2318 ; 8984 2318 1 * Display height, normally 192 G007_FLAG_3 equ $2319 ; 8985 2319 1 Flags ;G007_TEMP_WORD_0 equ $231C ; G007_TEMP_BYTE_0 equ $231A ; 9886 231A 7 Temporary variables for PLOT routine. G007_TEMP_BYTE_1 equ $231B G007_TEMP_WORD_1 equ $231C ; ;G007_TEMP_BYTE_2 equ $231C ;G007_TEMP_BYTE_3 equ $231D G007_TEMP_WORD_2 equ $231E ; G007_TEMP_BYTE_4 equ $231E G007_TEMP_BYTE_5 equ $231F G007_TEMP_BYTE_6 equ $2320 G007_OUT_OF_RANGE_FLAGS equ $2321 ; 8993 2321 1 Plot out of range flags. Bit 7 = latest statement G007_UNUSED equ $2322 ; 8994 2322 1 Not used G007_PLOT_X equ $2323 ; 8995 2323 2 X co-ordinate for PLOT. Signed 16-bit G007_PLOT_Y equ $2325 ; 8997 2325 2 Y co-ordinate for PLOT. Signed 16-bit G007_PLOT_X_PREVIOUS_N1 equ $2327 ; 8999 2327 8 X and Y co-ordinates for previous two statements G007_PLOT_Y_PREVIOUS_N1 equ $2329 G007_PLOT_X_PREVIOUS_N2 equ $232B G007_PLOT_Y_PREVIOUS_N2 equ $232D G007_FLAG_4 equ $232F ; 9007 232F 1 Flags G007_ORIGIN_Y equ $2330 ; 9008 2330 2 Y co-ordinate of graphics origin G007_ORIGIN_X equ $2332 ; 9010 2332 2 X co-ordinate of graphics origin G007_LINE_TYPE equ $2334 ; 9012 2334 4 Bytes defining four line types G007_TEMP_BYTE_7 equ $2338 ; 9016 2338 2 Temporary variable for PLOT G007_TEMP_BYTE_8 equ $2339 ; G007_V23A0 equ $23A0 ;=============================== ; G007 RAM routines ;=============================== ; Yes, there is such a thing! L23B8 equ $23B8 ;=============================== ; G007 ROM routines ;=============================== L2BF7 equ $2BF7 ; invalid opcode address SLOW_FAST_007 equ $2D08 ; new! ;=============================== ; Needed by zasm: #target rom ; declare target file format #code $2800,$0800 ; declare code segment start and size ; ;=============================== ; G007 ROM assembly code ;=============================== ; Start of ROM contents; ; 10240/12287 : 2800/2FFF ;=============================== G007_GET_PIXEL_ADDRESS_AND_MASK: ; LD HL,(G007_PLOT_X) ; X co-ordinate for PLOT. Signed 16-bit. Fetch 16 bits L2803: LD A,(G007_PLOT_Y) ; Y co-ordinate for PLOT. Signed 16-bit. Fetch 8 bits LD H,A ; and store in H L2807: EX DE,HL ; then swap it into D LD A,(G007_DISPLAY_HEIGHT) DEC A; SUB D ; LD D,A LD A,7 ; A must be 0 to 7 AND A,E ; E is the LS byte of the X co-ordinate LD L,A ; L = A = selects bit-mask LD H,$2D ; bit-mask array is at $2D00 LD A,(HL) ; get byte with bit set at appropriate position LD HL,(G007_DISPLAY_ADDRESS) ; Start address of hi-res display file ;------------------------------- ; Add two bytes for every line ; LD C,D ; BC = Y coordinate LD B,0 ADD HL,BC ; HL += BC*2 ADD HL,BC ;------------------------------- ; DE /= 8 gets byte offset from left of screen ; SRL D ; shift DE right RR E SRL D ; shift DE right RR E SRL D ; shift DE right RR E ADD HL,DE ; HL = G007_DISPLAY_ADDRESS + byte offset of XY-co-ordinates LD B,A ; A and B hold the bitmask RET ; return ;=============================== L282C: LD A,(G007_OUT_OF_RANGE_FLAGS) ; Plot out of range flags. Bit 7 = latest statement LD HL,(G007_PLOT_X_PREVIOUS_N1) LD DE,(G007_PLOT_X) ; X co-ordinate for PLOT. Signed 16-bit EXX LD HL,(G007_PLOT_Y) LD DE,(G007_PLOT_Y_PREVIOUS_N1) ; test two most recent out-of-range flag bits: BIT 6,A JR Z,L285E BIT 7,A RET NZ LD A,(G007_FLAG_Y); PUSH AF LD B,8 ;------------------------------- loop_284B: RRCA ;284C FD;CB;21;16 LD C,SLA (IY+CH_ADD-RAMBASE) output from VB81 disassembler #if 1 RL (IY+G007_FLAG_Y-RAMBASE) ; makes FD CB 21 16 - correct! #else ; force bytes defb $FD defb $CB defb $21 defb $16 #endif DJNZ loop_284B ;------------------------------- EX DE,HL EXX EX DE,HL EXX CALL L285E POP AF LD (G007_FLAG_Y),A; RET ;=============================== L285E: LD BC,-HRG_BYTES_PER_LINE ; NB negative number = $FFDE LD A,(G007_DISPLAY_HEIGHT) DEC A SUB E SBC HL,DE JP P,L2876 ;------------------------------- LD BC,HRG_BYTES_PER_LINE LD A,E PUSH DE ADD HL,DE EX DE,HL OR A,A SBC HL,DE POP DE ;------------------------------- L2876: PUSH HL LD H,E LD E,A EXX LD BC,L28FB LD A,L EXX LD L,A EXX OR A,A SBC HL,DE JP P,L2890 ; [10384] ;------------------------------- LD BC,L2904 CPL ADD HL,DE EX DE,HL OR A,A SBC HL,DE L2890: LD (G007_TEMP_BYTE_0),BC ; Temporary variables for PLOT routine. POP DE OR A,A SBC HL,DE ADD HL,DE JR NC,L28A4 ;------------------------------- EX DE,HL LD BC,L28F6 EXX LD D,E LD E,A LD A,D EXX L28A4: LD (G007_TEMP_WORD_1),BC EXX LD D,A ;------------------------------- PUSH DE EXX POP BC ;------------------------------- LD A,(G007_OUT_OF_RANGE_FLAGS) ; Plot out of range flags. Bit 7 = latest statement CP $40 JR NC,L28B6 ;------------------------------- LD B,L ; BC = LE LD C,E ;------------------------------- L28B6: INC B INC C ;------------------------------- L28B8: SRL H JR C,L28BE JR Z,L28C6 ;------------------------------- L28BE: RR L SRL D RR E JR L28B8 ;------------------------------- L28C6: LD D,L SRL L EXX PUSH BC CALL L2807 POP DE LD A,(G007_TEMP_BYTE_5) LD C,A ;------------------------------- L28D3: ; Bytes from disassembler: ;28D3 FD;CB;21;06 LD C,SLA (IY+6) #if 1 RLC (IY+G007_FLAG_Y-RAMBASE) ; makes FD CB 21 06 #else ; force bytes defb $FD defb $CB defb $21 defb $06 #endif JR C,L28E1 ;------------------------------- LD A,(G007_TEMP_BYTE_4) XOR A,(HL) OR A,C AND A,B XOR A,(HL) LD (HL),A ;------------------------------- L28E1: EXX LD A,L DEC B RET Z ;------------------------------- SUB E JR NC,L28F2 ;------------------------------- DEC C RET Z ;------------------------------- ADD A,D EXX ADD HL,DE EXX LD HL,(G007_TEMP_BYTE_0) ; Temporary variables for PLOT routine. JP (HL) ;------------------------------- L28F2: LD HL,(G007_TEMP_WORD_1) JP (HL) ;------------------------------- L28F6: LD L,A EXX ADD HL,DE JR L28D3 ;------------------------------- L28FB: LD L,A EXX RLC B JR NC,L28D3 ;------------------------------- DEC HL JR L28D3 ;------------------------------- L2904: LD L,A EXX RRC B JR NC,L28D3 ;------------------------------- INC HL JR L28D3 ;------------------------------- G007_INTEGER_FROM_STACK_TO_BC: LD HL,(L0EA8) ; Inside the 'FIND_INT.' subroutine ; 0EA7 FIND_INT CALL 158A,FP_TO_BC ; so 0EA8 should contain $158A, pointing to the FP_TO_BC routine JP (HL) ; exit ;=============================== G007_INTEGER_FROM_STACK_TO_HL: CALL G007_INTEGER_FROM_STACK_TO_BC ; get an integer JR C,L2924 ;------------------------------- LD HL,0 JR Z,L2921 ;------------------------------- SBC HL,BC ; HL = 0 - BC RET M RET Z ;------------------------------- JR L2924 ;------------------------------- L2921: ADC HL,BC RET P ;------------------------------- L2924: POP HL RET ;=============================== ; G007_PLOT_UNPLOT_N_X_Y: ; takes parameters from the stack ; CALL G007_INTEGER_FROM_STACK_TO_HL ; get Y LD (G007_PLOT_Y),HL CALL G007_INTEGER_FROM_STACK_TO_HL ; get X PUSH HL CALL STK_TO_A ; get N ; POP BC ; BC = X LD DE,(G007_PLOT_Y) ; DE = Y JR Z,G007_PLOT ; DEC A ; if (N-1) is zero RET NZ ;------------------------------- LD HL,L0C9C ; then T_ADDR = HL = L0C9C LD (T_ADDR),HL ; ;------------------------------- L2942: LD B,E JP L0BB2 ; []*BIOS ROM* ;=============================== ; The Plot Routine: ; A == the plot number N ; BC == screen X ; DE == screen Y ;------------------------------- G007_PLOT: OR A,A JR Z,L2942 ;------------------------------- DEC A ; when PLOT N value is decremented, bits make more sense ;------------------------------- PUSH DE ; push screen Y PUSH BC ; push screen X PUSH AF ; push plot number-1 CALL G007_CHECK_AND_SET_UP_DISPLAY_FILE POP AF POP DE POP BC ; NB BC and DE are swapped ;------------------------------- CP $81 ; Was A=129 (PLOT 130 sets graphics origin) JR NZ,G007_PLOT_XY_TO_HISTORY ;------------------------------- G007_ORIGIN_SET: ; LD (G007_ORIGIN_Y),DE ; Y co-ordinate of graphics origin LD (G007_ORIGIN_X),BC ; X co-ordinate of graphics origin LD BC,$0000 ; BC = 0 LD D,B ; DE = BC = 0 LD E,B LD A,$0B ;------------------------------- G007_PLOT_XY_TO_HISTORY: ; BIT 2,A; ; the (PLOT_N -1) JR Z,G007_PLOT_ABSOLUTE ;------------------------------- G007_PLOT_RELATIVE: ; LD HL,(G007_PLOT_Y_PREVIOUS_N1) PUSH HL LD HL,(G007_PLOT_X_PREVIOUS_N1) JR G007_PLOT_ABSOLUTE_OR_RELATIVE ;=============================== G007_PLOT_ABSOLUTE: LD HL,(G007_ORIGIN_X) ; X co-ordinate of graphics origin PUSH HL LD HL,(G007_ORIGIN_Y) ; Y co-ordinate of graphics origin ;------------------------------- ; ; G007_PLOT_ABSOLUTE_OR_RELATIVE: ; OR A,A ; update flags ADC HL,DE ; HL = G007_PLOT_Y + G007_ORIGIN_Y POP DE ; DE = G007_PLOT_X co-ordinate of graphics origin RET PE ;------------------------------- LD (G007_PLOT_X),HL ; record plot X EX DE,HL OR A,A ; restore Flags ADC HL,BC ; HL = G007_PLOT_X + G007_ORIGIN_X RET PE ;------------------------------- LD (G007_PLOT_Y),HL ; record plot Y LD E,A LD A,%11000000 ; $C0 AND A,H ; RET PO; ;------------------------------- LD A,%11000000 ; $C0 AND A,D RET PO ;------------------------------- ; push alternate HL,DE,BC EXX PUSH HL PUSH DE PUSH BC CALL G007_RANGE_CHECK ;------------------------------- ; pop alternate BC,DE,HL L299A: POP BC POP DE POP HL EXX RET ;=============================== G007_RANGE_CHECK: EXX LD A,H ; A = H OR D OR A,D SCF JR NZ,G007_RANGE_CHECK_UPDATE ; if not zero, out of range already so ignore display height ;------------------------------- LD A,(G007_DISPLAY_HEIGHT) ; check Y coordinate DEC A ; if (G007_DISPLAY_HEIGHT - Y ) is less than zero, CP L ; set the Carry flag ;------------------------------- G007_RANGE_CHECK_UPDATE: ; latest flag in Carry is shifted into history flags LD A,(G007_OUT_OF_RANGE_FLAGS) ; RRA ; shift bits right. Bit 7 = latest statement LD (G007_OUT_OF_RANGE_FLAGS),A ; LD A,E ; E bit 7 BIT 7,A JR Z,L29BA ;------------------------------- ; gets here if PLOT_N is over 130 LD HL,(G007_PLOT_ROUTINES_VECTOR) ; Vector for additional plot routines JP (HL) ; This looks very promising, ; makes it easy to bolt-on extra graphics routines ;=============================== L29BA: PUSH AF AND 3 RRA DEC A CPL LD H,A SBC A,A LD L,A LD (G007_TEMP_WORD_2),HL PUSH HL BIT 3,E JR NZ,L29F3 ;------------------------------- LD A,(G007_TEMP_BYTE_6) XOR A,E AND %11111011 ; $FB. Bit 2 ignored JR Z,L29E2 ;------------------------------- LD HL,G007_LINE_TYPE LD A,E ;------------------------------- ; A *= 8 RLCA RLCA RLCA ;------------------------------- AND $03 ADD A,L LD L,A LD A,(HL) LD (G007_FLAG_Y),A; ;------------------------------- L29E2: CALL L282C; POP HL ;------------------------------- ; Bytes from disassembler: 29E6 FD;CB;21;0E LD C,SLA (IY+14) RRC (IY+G007_FLAG_Y-RAMBASE) ; makes FD CB 21 0E JR C,L2A12 ;=============================== LD A,H ; A = H and L AND A,L JR Z,L2A12 ;------------------------------- INC L LD E,L PUSH HL ;------------------------------- L29F3: LD A,(G007_OUT_OF_RANGE_FLAGS) ; Bit 7 = latest statement BIT 5,E JR NZ,G007_TRIANGLE_PLAIN ;------------------------------- BIT 6,E JR NZ,G007_TRIANGLE_TEXTURED ;------------------------------- POP HL BIT 7,A JR NZ,L2A12 ;------------------------------- PUSH HL CALL G007_GET_PIXEL_ADDRESS_AND_MASK AND A,(HL) LD (G007_READ_POINT_BYTE),A ; "Read-point" byte. Non-zero if pixel is set. POP DE LD A,(HL) OR A,D XOR A,E AND A,B XOR A,(HL) LD (HL),A ;------------------------------- L2A12: POP AF LD (G007_TEMP_BYTE_6),A LD HL,G007_PLOT_Y_PREVIOUS_N1+1 LD DE,G007_PLOT_Y_PREVIOUS_N2+1 LD BC,8 BIT 4,A JR Z,L2A34 ;------------------------------- LD A,(G007_OUT_OF_RANGE_FLAGS) ; Plot out of range flags. Bit 7 = latest statement AND $C0 ; only want most recent two bits RLA JR NC,L2A2D SET 6,A ; carry flag into bit 6 of A ;------------------------------- L2A2D: LD (G007_OUT_OF_RANGE_FLAGS),A LD L,$26 LD C,4 ;------------------------------- L2A34: LDDR ; (DE--)=(HL--); BC-- UNTIL BC IS ZERO RET ;=============================== G007_TRIANGLE_PLAIN: LD HL,ALL_BITS_SET JR G007_TRIANGLE_TEXTURED_BY_HL ;=============================== G007_TRIANGLE_TEXTURED: LD HL,(G007_TRIANGLE_TEXTURE) ; Bytes defining triangle texture ;------------------------------- G007_TRIANGLE_TEXTURED_BY_HL: EXX AND $E0 ; = 224 JP NZ,REPORT_B; ;------------------------------- L2A45: LD HL,G007_OUT_OF_RANGE_FLAGS LD (IY+G007_FLAG_Y-RAMBASE),$55 LD B,3 ;------------------------------- loop_2A4E: INC HL ; HL+=2 INC HL LD E,(HL) INC HL ; HL+=2 INC HL LD D,(HL) PUSH DE DJNZ loop_2A4E ; BC-- ;------------------------------- POP BC ; restore register pairs POP DE POP HL ;------------------------------- OR A,A JR Z,L2A5F ;------------------------------- LD B,H ; BC=HL LD C,L ;------------------------------- L2A5F: LD A,B CP H JR NC,L2A66 ;------------------------------- PUSH BC EX (SP),HL POP BC ;------------------------------- L2A66: LD A,H CP D JR NC,L2A6B ;------------------------------- EX DE,HL ;------------------------------- L2A6B: LD A,D EXX AND 7 INC A LD B,A LD A,H ;------------------------------- loop_2A72: RLC L ; L *= 2 ;------------------------------- RLCA ; A *= 8 RLCA RLCA ;------------------------------- DJNZ loop_2A72 ; loop while BC-- is not zero ;------------------------------- LD H,A LD (G007_TEMP_BYTE_7),HL ; Temporary variable for PLOT EXX ;------------------------------- LD A,H CP B LD A,7 JR NC,L2A87 ;------------------------------- loop_2A84: PUSH BC EX (SP),HL POP BC ;------------------------------- L2A87: EX DE,HL PUSH AF LD A,C EXX LD H,A LD L,A LD C,A LD (G007_TEMP_BYTE_0),HL ; Temporary variables for PLOT routine. LD B,$FE EXX SUB E EXX ;------------------------------- JR NC,skip_2A9C LD B,$00 NEG skip_2A9C: ;------------------------------- LD D,A EXX LD A,D SUB B EXX LD H,A ;------------------------------- CP D JR NC,skip_2AA7 EX DE,HL INC B skip_2AA7: ;------------------------------- LD L,H INC L LD E,H SRL E POP AF RRCA JR NC,L2AB9 ;------------------------------- PUSH HL ;------------------------------- PUSH DE PUSH BC ;------------------------------- RRCA EXX ;------------------------------- JR C,loop_2A84 JR L2A87 ;=============================== L2AB9: EXX LD H,B LD L,1 ;------------------------------- loop_2ABD: POP BC POP DE ;------------------------------- EX (SP),HL ;------------------------------- L2AC0: DEC L JR NZ,L2AD9 ;------------------------------- LD HL,(G007_TEMP_BYTE_0) ; Temporary variables for PLOT routine. LD A,C ;------------------------------- CP H JR C,skip_2ACB LD H,A skip_2ACB: ;------------------------------- CP L JR NC,skip_2ACF LD L,A skip_2ACF: ;------------------------------- LD (G007_TEMP_BYTE_0),HL ; Temporary variables for PLOT routine. POP HL DEC L ;------------------------------- L2AD4: JR Z,loop_2ABD PUSH HL JR L2AEE ;------------------------------- L2AD9: LD A,E SUB D JR C,L2AE7 ;------------------------------- LD E,A BIT 0,B JR Z,L2AEE ;------------------------------- LD A,C ADD A,B LD C,A JR L2AC0 ;------------------------------- L2AE7: ADD A,H LD E,A LD A,1 OR A,B ADD A,C LD C,A ;------------------------------- L2AEE: LD A,C EXX ;------------------------------- RRC (IY+G007_FLAG_Y-RAMBASE) ; makes FD CB 21 0E JR C,L2AC0 ; if ;------------------------------- EX (SP),HL ;------------------------------- PUSH DE PUSH BC PUSH HL ;------------------------------- LD DE,(G007_TEMP_BYTE_0) LD B,A ;------------------------------- CP C JR NC,skip_2B04 LD B,C LD C,A skip_2B04: ;------------------------------- LD (G007_TEMP_BYTE_0),BC LD A,C ;------------------------------- CP E INC A JR C,skip_2B0E LD A,E skip_2B0E: ;------------------------------- LD L,A LD A,D ;------------------------------- CP B JR NC,skip_2B15 LD A,B DEC A skip_2B15: ;------------------------------- SUB L INC A PUSH AF CALL L2807 POP AF LD C,A EX DE,HL LD HL,G007_TEMP_BYTE_8 LD A,(HL) ;------------------------------- RLCA RLCA RLCA ;------------------------------- LD (HL),A DEC HL RLC (HL) OR A,(HL) LD HL,(G007_TEMP_WORD_2) XOR A,L CPL LD L,A EX DE,HL ;------------------------------- loop_2B31: LD A,E XOR A,(HL) OR A,D AND A,B XOR A,(HL) LD (HL),A ;------------------------------- loop_2B37: DEC C JR NZ,L2B4E ;------------------------------- POP HL INC H BIT 7,L JR Z,L2AD4 ;------------------------------- ; discard 3 words from stack POP HL POP HL POP HL ;------------------------------- POP HL LD A,H AND A,L JP Z,L2A12 ;------------------------------- INC L PUSH HL JP L2A45 ;------------------------------- L2B4E: RR B JR NC,loop_2B31 ;------------------------------- INC HL LD B,C LD A,C AND $07 LD C,A ;------------------------------- SRL B ; B *= 8 SRL B SRL B ;------------------------------- JR Z,L2B68 ;------------------------------- loop_2B60: LD A,E XOR A,(HL) OR A,D XOR A,(HL) LD (HL),A INC HL DJNZ loop_2B60 ;------------------------------- L2B68: SCF INC C JR loop_2B37 ;------------------------------- L2B6C: LD HL,(G007_FLAG_1) DEC L OR A,L JP NZ,L0809 ; iii) Testing S-POSN: 0808 ENTER-CH LD D,A ; G007 skips that first instruction though. ;------------------------------- DEC H ; if --H, JP NZ,L23B8 ; then jump to modified copy of a ZX81 routine, in RAM. ;------------------------------- ; LD A,(G007_OUT_OF_RANGE_FLAGS) ; Plot out of range flags. Bit 7 = latest statement RLA RET C ;------------------------------- NOP NOP LD HL,(G007_PLOT_X_PREVIOUS_N1+1) LD A, (G007_PLOT_X_PREVIOUS_N1) BIT 6,D ;------------------------------- mark_2B87: JR NZ,L2B8E ; 20;05 LD L,A ADD A,8 JR NC,L2B9B ;------------------------------- L2B8E: LD A,H ;------------------------------- SUB 8 JR NC,skip_2B97 LD A,(G007_DISPLAY_HEIGHT) DEC A skip_2B97: ;------------------------------- LD (G007_PLOT_Y_PREVIOUS_N1),A; XOR A,A ; A = 0 ;------------------------------- L2B9B: LD (G007_PLOT_X_PREVIOUS_N1),A BIT 6,D RET NZ ;------------------------------- CALL L2EEF LD A,L ;------------------------------- CP $F9 JR C,skip_2BAB LD E,2 skip_2BAB: ;------------------------------- CPL AND 7 INC A LD D,A LD A,H INC A EXX LD D,A EXX PUSH DE CALL L2807 EXX ;------------------------------- L2BBA: LD A,C XOR A,(HL) EXX EX DE,HL LXXX: POP BC PUSH BC LD L,A LD H,$00 ;------------------------------- loop_2BC3: ADD HL,HL DJNZ loop_2BC3 ;------------------------------- EX DE,HL LD B,2 ;------------------------------- L2BC9: LD A,(G007_RESTART); XOR A,(HL) OR A,(IY+G007_RESTART+1-RAMBASE) AND A,D XOR A,(HL) ;------------------------------- DEC C JR Z,skip_2BD6 LD (HL),A skip_2BD6: ;------------------------------- LD D,E INC HL DJNZ L2BC9 ;------------------------------- LD C,$20 ADD HL,BC EXX INC HL ;------------------------------- DEC D JR Z,skip_2BE4 ;------------------------------- DJNZ L2BBA ;------------------------------- skip_2BE4: ;------------------------------- POP HL JP L299A ;=============================== PRINT_007: LD A,I ; read graphics mode RRCA ;------------------------------- JR NC,skip_2BF5 XOR A,A ; A = 0 LD (DF_SZ),A ; SET DF_SZ INC A LD (G007_FLAG_1),A skip_2BF5: ;------------------------------- CALL PRINT ; [PRINT] XOR A,A ; A = 0 LD (G007_FLAG_1),A LD (IY+DF_SZ-RAMBASE),2 ; The lines above have no return or jump statements ; They will fall into copy of the end of the STK_TO_BC routine ; Perhaps a cunning trick to save two bytes? :-) ;=============================== ;=============================== ;=============================== org ROM_PATCH_0C00 ; ; 256-byte block patched in to ZX81 BASIC at 0C00 hex ; This cleverly alters the language syntax, ; adding parameters and/or pointing to new routines. ; LD C,A ; from the end of the STK_TO_BC routine RET ;=============================== ; THE 'STK_TO_A' SUBROUTINE (duplicated for the paged ROM) ; This subroutine 'loads' the A register with the floating point number held at the top of the calculator stack. The number must be in the range 00-FF. ;------------------------------- STK_TO_A: #if NOT_G007 ; CALL 15CD,FP_TO_A ; This is what the disassembly book says #else CALL L0CA0 ; new in G007 #endif JP C,REPORT_B ;------------------------------- LD C,$01 RET Z LD C,$FF RET ;=============================== ; THE 'SCROLL' COMMAND ROUTINE (duplicated for the paged ROM) ; The first part of the routine sets the correct values of DF_CC and S_POSN to allow for the next printing to occur at the start of the bottom line + 1. ; Next the end address of the first line in the display file is identified and the whole of the display file moved to overwrite this line. ;------------------------------- SCROLL: mark_0C0E: LD B,(IY+DF_SZ-RAMBASE) ; ??? Disassembly book says LD B,(DF_SZ) LD C,CHARS_HORIZONTAL + 1 ;change here, $21 originally CALL LOC_ADDR CALL ONE_SPACE LD A,(HL) LD (DE),A INC (IY+S_POSN_hi-RAMBASE) ; LD HL,(D_FILE); INC HL LD D,H LD E,L CPIR JP RECLAIM_1 ;=============================== ; ; THE SYNTAX TABLES ; ; i) The offset table ; ; There is an offset value for each of the BASIC commands and by ; adding this offset to the value of the address where it is found, ; the correct address for the command in the parameter table is ; obtained. ; 2C29: 8B 8D 2D 7F 81 49 75 mark_0C29: offset_t: ; expression address byte offset expected defb P_LPRINT-$ ; 0CB4 $8B defb P_LLIST-$ ; 0CB1 ; $8D ; defb P_STOP-$ ; 0C58 ; $2D ; defb P_SLOW-$ ; 0CAB ; $7F ; defb P_FAST-$ ; 0CAE ; $81 ; defb P_NEW-$ ; 0C77 ; $49 ; defb P_SCROLL_007-$ ; 0CA4 ; $75 ; ; 2C30: 5F 40 42 2B 17 1F 37 52 defb P_CONT-$ ; 0CBF ; $5F ; defb P_DIM-$ ; 0C71 ; $40 ; defb P_REM-$ ; 0C74 ; $42 ; defb P_FOR-$ ; 0C5E ; $2B ; defb P_GOTO-$ ; 0C4B ; $17 ; defb P_GOSUB-$ ; 0C54 ; $1F ; defb P_INPUT-$ ; 0C6D ; $37 ; defb P_LOAD-$ ; 0C89 ; $52 ; ; 2C38: 45 0F 6D 2B 44 2D 5A 3B defb P_LIST-$ ; 0C7D ; $45 ; defb P_LET-$ ; 0C48 ; $0F ; defb P_PAUSE-$ ; 0CA7 ; $6D ; defb P_NEXT-$ ; 0C66 ; $2B ; defb P_POKE-$ ; 0C80 ; $44 ; defb P_PRINT-$ ; 0C6A ; $2D ; Same as normal ZX81 defb P_PLOT_007-$ ; 0C98 ; $5A ; defb P_RUN-$ ; 0C7A ; $3B ; ; 2C40: 4C 45 0D 52 54 4D 15 6A defb P_SAVE-$ ; 0C8C ; $4C ; defb P_RAND-$ ; 0C86 ; $45 ; defb P_IF-$ ; 0C4F ; $0D ; defb P_CLS-$ ; 0C95 ; $52 ; defb P_UNPLOT_007-$ ; 0C98 ; $54 ; This byte is 5A, an offset to 0C9E, in normal ZX81 ; NB same address as PLOT defb P_CLEAR-$ ; 0C92 ; $4D ; defb P_RETURN-$ ; 0C5B ; $15 ; defb P_COPY-$ ; 0CB1 ; $6A ; ; ; ii) The parameter table. ; ; For each of the BASIC commands there are between 3 and 8 ; entries in the parameter table. The command classes for each of ; the commands are given, together with the required separators and ; these are followed by the address of the appropriate routine. ; 2C48: 01 14 02 mark_0C48: P_LET: defb _CLASS_01 ; A variable is required. defb ZX_EQU ; Separator: '=' defb _CLASS_02 ; An expression, numeric or string, ; must follow. ; 244B: 06 00 81 0E 06 P_GOTO: mark_0C4B: defb _CLASS_06 ; A numeric expression must follow. defb _CLASS_00 ; No further operands. defw GOTO P_IF: mark_0C4F: defb _CLASS_06 ; A numeric expression must follow. mark_0C50 ; 2C50: DE 05 AB 0D defb ZX_THEN ; Separator: 'THEN' defb _CLASS_05 ; Variable syntax checked entirely ; by routine. defw IF P_GOSUB: ; mark_0C54: defb _CLASS_06 ; A numeric expression must follow. defb _CLASS_00 ; No further operands. defw GOSUB ; 0EB5 ; 2C58: 00 DC 0C P_STOP: ; mark_0C58: ; 2C54: 06 00 B5 0E defb _CLASS_00 ; No further operands. defw STOP ; 2c5B: 00 D8 0E P_RETURN defb _CLASS_00 ; No further operands. defw RETURN ; 2c5e: 04 14 P_FOR: #if 0 mark_0C5E: #else #endif defb _CLASS_04 ; A single character variable must ; follow. defb ZX_EQU ; Separator: '=' defb _CLASS_06 ; A numeric expression must follow. defb ZX_TO ; Separator: 'TO' defb _CLASS_06 ; A numeric expression must follow. defb _CLASS_05 ; Variable syntax checked entirely ; by routine. defw FOR ; 0C66: 04 00 2E 0E P_NEXT: ; at $0C66: defb _CLASS_04 ; A single character variable must follow. defb _CLASS_00 ; No further operands. defw NEXT ; 2C6A: 05 E8 2B P_PRINT: mark_0C6A: defb _CLASS_05 ; Variable syntax checked entirely ; by routine. #if NOT_G007 defw PRINT ; This is the original routine #else defw PRINT_007 ; 2BE8 This is the new routine! #endif ; ; 2C6D: 01 00 E9 0E mark_0C6D: P_INPUT: defb _CLASS_01 ; A variable is required. defb _CLASS_00 ; No further operands. defw INPUT P_DIM: mark_0C71: defb _CLASS_05 ; Variable syntax checked entirely ; by routine. #if NOT_G007 defw DIM ; 1409 Original ZX81 replaced by #else defw DIM_007 ; 2EA7 new routine #endif ; ; ; 2c74: 05 6A 0D ; mark_0C74: P_REM: defb _CLASS_05 ; Variable syntax checked entirely by routine. defw REM mark_0C77: P_NEW: defb _CLASS_00 ; No further operands. defw NEW P_RUN: mark_0C7A: P_RUN: defb _CLASS_03 ; A numeric expression may follow ; else default to zero. defw RUN P_LIST: #if 0 mark_0C7D: #else #endif defb _CLASS_03 ; A numeric expression may follow ; else default to zero. defw LIST P_POKE: ; mark_0C80: defb _CLASS_06 ; A numeric expression must follow. defb ZX_COMMA ; Separator: ',' defb _CLASS_06 ; A numeric expression must follow. defb _CLASS_00 ; No further operands. defw POKE P_RAND: ; mark_0C86: defb _CLASS_03 ; A numeric expression may follow ; else default to zero. defw RAND P_LOAD: ; mark_0C89: defb _CLASS_05 ; Variable syntax checked entirely ; by routine. defw LOAD P_SAVE: ; mark_0C8C: defb _CLASS_05 ; Variable syntax checked entirely ; by routine. #if NOT_G007 defw SAVE ; 02F6 original #else defw SAVE_007 ; 2F4D new! #endif ; P_CONT: #if 0 #else #endif mark_0C8F: defb _CLASS_00 ; No further operands. defw CONT ; 0E7C ; P_CLEAR: ; mark_0C92: defb _CLASS_00 #if NOT_G007 defw CLEAR ; 149A original #else defw CLEAR_007 ; 0EB2 new. this points outside the G007 ROM #endif P_CLS: ; mark_0C95: #if NOT_G007 defb _CLASS_00 ; No further operands. ;; defw CLS ; original #else defb _CLASS_03 ; A numeric expression may follow ; else default to zero. defw G007_CLS_N ; 2E4E new! #endif ; ; ; mark_0C98: #if NOT_G007 ; originally the ZX81 does this: P_PLOT: ; original: plot X,Y defb _CLASS_06 ; A numeric expression must follow. X defb ZX_COMMA ; Separator: ',' defb _CLASS_06 ; A numeric expression must follow. Y defb _CLASS_00 ; No further operands. defw PLOT_UNPLOT ; 0BAF P_UNPLOT: ; original: plot X,Y defb _CLASS_06 ; A numeric expression must follow. X defb ZX_COMMA ; Separator: ',' defb _CLASS_06 ; A numeric expression must follow. Y defb _CLASS_00 ; No further operands. defw PLOT_UNPLOT ; 0BAF ; These are identical, so they could overlap #else ; The G007 saves bytes by having these ; identical parameter table entries overlapping: ; 0C98 / 2C98: 06 1A 06 1A 06 00 26 29 P_PLOT_007: P_UNPLOT_007: ; new: plot N,X,Y defb _CLASS_06 ; A numeric expression must follow. N defb ZX_COMMA ; Separator: ',' defb _CLASS_06 ; A numeric expression must follow. X defb ZX_COMMA ; Separator: ',' L0C9C: defb _CLASS_06 ; A numeric expression must follow. Y defb _CLASS_00 ; No further operands. defw G007_PLOT_UNPLOT_N_X_Y ; 2926 new! ; ; by saving those bytes, can put a tiny routine here: L0CA0: LD HL,(L0EA8) ; Inside the 'FIND_INT.' subroutine JP (HL) #endif ; ; Now back to the table! ; P_SCROLL_007 mark_0CA4: defb _CLASS_00 defw SCROLL ; 0C0E P_PAUSE: defb _CLASS_06 defb _CLASS_00 #if NOT_G007 mark_0CA9: ; 2CA9: 0F 32 defw PAUSE ; 0F32 original #else mark_0CA9: ; 2CA9: AE 2E ; 0CA7 defw PAUSE_007 ; 2EAE new! #endif ; ; 2CAB: 03 B2 2E ; 0CAB P_SLOW: mark_0CAB: #if NOT_G007 defb _CLASS_00 ; No further operands. defw SLOW ; 2B 0F SLOW,0F2B original #else defb _CLASS_03 ; new! Extra parameter sets graphic mode defw SLOW_007 ; 2EB2 new! #endif P_FAST: mark_0CAE: #if NOT_G007 defb _CLASS_00 ; No further operands. defw FAST ; 23 0F FAST,0F23 original #else defb _CLASS_03 ; new! Extra parameter sets graphic mode defw FAST_007 ; 2EB6 new! #endif ; ; 2CB1: 03 53 2F ; ; 0CB1 P_COPY: #if NOT_G007 defb _CLASS_00 ; original defw COPY ; 0869 original #else defb _CLASS_03 ; new! Extra parameter sets graphic mode ;;; defw COPY_007 ; 2EB6 new but wrong ???! defw L2F53 ; what the disassembler said #endif mark_0CB4: P_LPRINT: defb _CLASS_05 defw LPRINT ; 0ACB ; ; ; 2CB7: 03 2C 07 ; 0CB7 P_LLIST: defb _CLASS_03 defw LLIST ; 072C ; ; ; the rest of this page is just a copy of the usual ZX81 code LINE_SCAN: L0CBA: ; 2CBA: FD 36 01 01 CD 73 ; 2CC0: 0A CD 95 0A 21 00 40 36 ; 2CC8: FF 21 2D 40 CB 6E 28 0E ; 2CD0: FE E3 7E C2 6F 0D CD A6 ; 2CD8: 0D C8 CF 0C CF 08 DF 06 ; 2CE0: 00 FE 76 C8 4F E7 79 D6 ; 2CE8: E1 38 3B 4F 21 29 0C 09 ; 2CF0: 4E 09 18 03 2A 30 40 7E ; 2CF8: 23 22 30 40 01 F4 0C C5 ; ; which is described as follows in the ZX81 disassembly book: ; ; THE 'LINE SCANNING' ROUTINE ; ; The BASIC interpreter scans each line for BASIC commands and as each one is found ; the appropriate command routine is followed. ; The different parts of the routine are: ;------------------------------- ; i) The LINE_SCAN entry point leads to the line number being checked for validity. LD (IY+FLAGS-RAMBASE),$01 ; FLAGS = 1 CALL E_LINE_NO ; ;------------------------------- ; ii) The LINE_RUN entry point is used when replying to an INPUT prompt ; and this fact has be identified. ;------------------------------- LINE_RUN #if NOT_G007 CALL SET_MEM ; at $14BC #else CALL L0A95 ; JP 14BC,SET_MEM then return ; why it does this I do not know - KH #endif LD HL,ERR_NR LD (HL),$FF LD HL,FLAGX BIT 5,(HL) JR Z,LINE_NULL ;------------------------------- ; iii) The INPUT reply is tested to see if STOP was entered. CP $E3 LD A,(HL) JP NZ,INPUT_RE CALL SYNTAX_Z RET Z ; iv) If appropriate, report D is given. RST ERROR_1 defb $0C ;------------------------------- ; THE 'STOP' COMMAND ROUTINE ; The only action is to give report 9. ;------------------------------- STOP RST ERROR_1 defb 8 ;------------------------------- ; v) A return is made if the line is 'null'. ;------------------------------- LINE_NULL: mark_0CDE: ; old software from disassembly book: RST GET_CHAR LD B,0 CP ZX_NEWLINE RET Z ; vi) The first character is tested so as to check that it is a command. LD C,A RST NEXT_CH LD A,C SUB $E1 #if NOT_G007 JR C,REPORT_C ; $0D9A as per the book #else JR C,REPORT_C_007 ; $0D26 in G007 systems #endif ;------------------------------- ; vii) The offset for the command is found from the offset table. LD C,A LD HL,offset_t ; $0C29 ADD HL,BC LD C,(HL) ADD HL,BC JR GET_PARAM ; viii) The parameters are fetched in turn by a loc that returns to 0CF4. ; The separators are identitied by the test against +0B. ;------------------------------- mark_0CF4: SCAN_LOOP: LD HL,(T_ADDR) GET_PARAM: LD A,(HL) INC HL LD (T_ADDR),HL LD BC,SCAN_LOOP PUSH BC ; ; there is more to this routine but the patch ends here ; ; end of patch ;------------------------------- org $2D00 ; bits shifting right within a byte: ; defb %10000000 ; $80 defb %01000000 ; $40 defb %00100000 ; $20 defb %00010000 ; $10 defb %00001000 ; $08 defb %00000100 ; $04 defb %00000010 ; $02 defb %00000001 ; $01 ;------------------------------- L2D08: SLOW_FAST_007: ; at $2D08 CALL L2BF7 ; ??? not a valid opcode address? JP SLOW_FAST ; old routine ;------------------------------- L2D0E: LD (MARGIN),A ; EX DE,HL LD HL,10 ADD HL,SP LD A,(HL) INC A AND $F0 SUB $D0 LD C,A INC HL LD A,(HL) SUB 4 OR A,C LD C,A LD A,(CDFLAG) ; RLCA SBC A,A AND A,C JR NZ,L2D3B ;------------------------------- LD HL,(VARS) ; LD BC,-(CHARS_HORIZONTAL+1) ; $FFDF ADD HL,BC SET 7,H LD (G007_DISPLAY_ADDRESS_LO_RES_LAST_LINE),HL LD A,1 JR L2D49 ;------------------------------- L2D3B: LD HL,(D_FILE) ; GET D_FILE LD BC,(G007_DISPLAY_OFFSET_FROM_DFILE_LESS_9) ADD HL,BC SET 7,H LD (G007_DISPLAY_ADDRESS_LESS_9),HL XOR A,A ; A = 0 ;------------------------------- L2D49: LD (G007_FLAG_3),A DEC HL LD A,(HL) EX DE,HL RET ;------------------------------- LD A,(G007_FLAG_3) DEC A JP NZ,L2D73 ; [11635] ;------------------------------- LD A,G007_INT_REG_VALUE_FOR_LO_RES_GRAPHICS LD I,A ; Interrupt register = 0x1E sets low res mode ADC HL,HL LD HL,(G007_DISPLAY_ADDRESS_LO_RES_LAST_LINE) LD BC,$0108 LD A,$FE CALL DISPLAY_5; LD A,G007_INT_REG_VALUE_FOR_HI_RES_GRAPHICS LD I,A ; Interrupt register = 0x1F sets high res mode LD A,(MARGIN) ; GET MARGIN SUB 8 JR L2D77 ;------------------------------- L2D73: DEC HL LD A,(MARGIN) ; GET MARGIN ;------------------------------- L2D77: LD C,A POP IX ;------------------------------- BIT 7,(IY+CDFLAG-RAMBASE) ; makes FD CB 3B 7E JP NZ,L029D ; []*BIOS ROM* ;------------------------------- ; This jumps into the middle of the DISPLAY_3, where it meets this code: ;029D 79 LD A,C ;029E ED;44 NEG ;02A0 3C INC A ;02A1 08 EX AF,AF' ;02A2 D3;FE OUT (IO_PORT_KEYBOARD_RD),A ; ZX81 NMI GENERATOR on, for SLOW mode ;02A4 E1 POP HL ; restore registers ;02A5 D1 POP DE ;02A6 C1 POP BC ;02A7 F1 POP AF ;02A8 C9 RET ;------------------------------- LD A,$FE LD B,$01 LD HL,HALT_AT_2D9A CALL LOAD_R_AND_WAIT_FOR_INT ADD HL,HL ; double HL NOP LD E,A LD HL,(G007_DISPLAY_ADDRESS_LESS_9) SET 7,H JP (IX) ;=============================== LOAD_R_AND_WAIT_FOR_INT ; mark_2D95 ; Sets up the refresh register and waits for an interrupt: LD R,A ; Refresh row counter := accumulator LD A,221 ; 221 = $DD EI ; Enable interrupts, then drop into HALT ;------------------------------- HALT_AT_2D9A: HALT ;=============================== ; Copy two pages of ZX81 BASIC ROM to RAM patch areas, ; then modify bytes as controlled by a table of offsets and values ; This uses less memory than two whole pages of ROM ; If the RAM patches are not changed thereafter, ; one might be able to use a 16K ROM to hold two different versions of the ZX81 BASIC space, ; one with the 'RAM' patch and one without. ; The RAM testing would have to be disabled, because the ROM would cause it to fail. ;------------------------------- #if NOT_G007 ZX81_COPY: #else G007_COPY: #endif ;------------------------------- ; First check RAM by loading 512 bytes RAM from $2200 to $23FF with value 1, ; and decrement them to zero to test they are working RAM ; LD HL,RAM_PATCH_2200 ; destination RAM patch 2 LD A,$24 ;------------------------------- mark_2DA0: G007_COPY_LOOP: LD (HL),1 ; try writing the value 1 to a byte in RAM patch 2 DEC (HL) ; try decrementing it to zero JR Z,G007_COPY_SKIP ; if result is zero, skip error code report ;------------------------------- RST $08 defb $1A ; RST8 Arg ; Error Code:'R' (ZX81 on-board 1K/2K RAM not found) ;------------------------------- G007_COPY_SKIP mark_2DA7: INC HL ; next address CP H ; has HL reached $2400 (yet? JR NZ,G007_COPY_LOOP ; no, repeat. ;------------------------------- ; HL is now $2400 ; Copy 256 bytes from BASIC ROM to RAM patch 1: LD H,L ; HL is now $0000 (data source is start of ZX81 BASIC ROM) LD DE,RAM_PATCH_2000 ; data destination LD BC,$0100 ; 256 bytes LDIR ; (HL) -> (DE) 256 times ;------------------------------- ; HL is now $0100 ; DE is now $2100 ; BC is now $0000 ;------------------------------- INC H ; HL is now $0200 INC D ; DE is now $2200 INC B ; BC is now $0100 LDIR ; copy another 256 bytes to RAM patch 2 ;------------------------------- LD HL,PRINT_CH ; src = the original ZX81 print character routine LD DE,G007_V23A0 ; dst = LD BC,$0060 ; 60hex = 96 bytes, so stops just before 0851 (the 'LPRINT_CH routine) LDIR ; move ; BC is now $0000 LD D,$20 ; DE is now $20?? LD HL,TABLE_ONE ; copy data from this table: LOOP_2DC9: LD B,(HL) ; BC is now $0A00 (seems way too many bytes) JR G007_TABLE_END_TEST ; branch to end-of-table test ;------------------------------- G007_TABLE_ONE_LOOP: LD E,(HL) ; Get the offset INC HL ; next byte is data LD A,(HL) ; Get the data byte LD (DE),A ; ($2000+offset) = data byte ;------------------------------- G007_TABLE_END_TEST: mark_2DD0: INC HL ; next offset DJNZ G007_TABLE_ONE_LOOP ; repeat until BC is zero ;------------------------------- INC D ; D becomes $21 BIT 2,D ; test bit 2 (has D incremented to $23 ?) JR Z,LOOP_2DC9 RET ;=============================== ; Delete Display File: ; G007_DELETE_DISPLAY_FILE: CALL SET_FAST; LD HL,$3E87 CALL LINE_ADDR; RET NZ ;------------------------------- EX DE,HL LD HL,(NXT_LINE) ; BIT 6,(HL) JR Z,L2DEF ;------------------------------- LD (NXT_LINE),DE ; ;------------------------------- L2DEF: LD HL,(D_FILE) ; JP RECLAIM_1 ; exit ;------------------------------- L2DF5: CALL G007_DELETE_DISPLAY_FILE LD BC,$1992 ; 6546 decimal = LD HL,(D_FILE) ; DEC HL CALL MAKE_ROOM LD A,ZX_NEWLINE LD (DE),A ; to DE INC DE LD (DE),A ; and DE+1 INC HL ; HL += 2 INC HL LD (HL),$3E ; (HL++) = $3E 'Y' INC HL LD (HL),$87 ; (HL++) = $87 INC HL LD (HL),$8D ; (HL++) = $8D INC HL LD (HL),$19 ; (HL++) = $19 ';' INC HL LD (HL),A ; (HL++) = ZX_NEWLINE INC HL LD (HL),A ; (HL++) = ZX_NEWLINE ;------------------------------- L2E18: CALL SLOW_FAST ; LD A,1 JR G007_CLS_HI_RES ;------------------------------- G007_CHECK_AND_SET_UP_DISPLAY_FILE: ; LD HL,(V2265) ; in the RAM page 2 area ! Usually $4027 LD DE,$BFD9 ; 3FD9 + 32K, or -16423 = 16K - 39 bytes ADD HL,DE LD A,(V2264) SUB CHARS_HORIZONTAL+1 ; bytes per screen line OR A,H OR A,L CALL NZ,G007_COPY LD HL,(D_FILE) ; GET D_FILE LD A,ZX_NEWLINE DEC HL DEC HL CP (HL) ; pointing to a HALT? CALL NZ,L2DF5 ; no, ; else yes LD HL,(D_FILE) ; GET D_FILE LD DE,(G007_DISPLAY_OFFSET_FROM_DFILE_LESS_9) ADD HL,DE LD (G007_DISPLAY_ADDRESS_LESS_9),HL LD DE,9 ; HL += 9 ADD HL,DE LD (G007_DISPLAY_ADDRESS),HL RET ;------------------------------- G007_CLS_N: ; CALL STK_TO_A RET NZ ; drop through if zero flag set ;------------------------------- ; Clear The Screen: ; G007_CLS_A: DEC A JP M,CLS ; if A was zero ;------------------------------- G007_CLS_HI_RES: PUSH AF CALL G007_CHECK_AND_SET_UP_DISPLAY_FILE POP AF ;------------------------------- CP 2 LD BC,(G007_DISPLAY_HEIGHT) LD HL,(D_FILE) JR NC,L2E96 ;------------------------------- DEC A INC HL LD (DF_CC),HL ; Addr. for PRINT AT position DEC HL DEC HL DEC HL LD E,0 ; E = 0 is byte to fill screen ;------------------------------- loop_2E70: LD B,16 ; 16 DEC HL LD (HL),E ; but writes 2 bytes for each loop DEC HL LD (HL),E ; perhaps to reduce loop overhead? ;------------------------------- loop_2E76: DEC HL LD (HL),A ; writes A twice over DEC HL LD (HL),A DJNZ loop_2E76 ;------------------------------- DEC C JR NZ,loop_2E70 ;------------------------------- ; C is now zero LD B,9 ; BC is now $0900 LD A,1 ;------------------------------- loop_2E83: DEC HL LD (HL),E ; (--HL) = E while --BC DJNZ loop_2E83 ;------------------------------- ; BC is now zero LD HL,G007_LINE_TYPE LD B,20 ; decimal 20 DEC A JR Z,loop_2E83 ;------------------------------- LD HL,256*CHARS_VERTICAL + CHARS_HORIZONTAL + 1 LD (S_POSN),HL ; SET S_POSN RET ;=============================== L2E96: RET NZ DEC HL ; HL -= 2 DEC HL ;------------------------------- outer loop loop_2E99: LD B,32 ; 32 bytes to invert DEC HL ; HL -= 2 DEC HL ; skips past 2 HALT characters ;------------------------------- inner loop loop_2E9D: DEC HL LD A,(HL) ; read CPL ; invert (one's complement) LD (HL),A ; write DJNZ loop_2E9D ;------------------------------- DEC C ; C is obviously the number of horizontal lines to invert JR NZ,loop_2E99 ;------------------------------- RET ;------------------------------- mark_2EA7: DIM_007: ; 2EA7 LD HL,($0A96) LD A,$4D JR L2EE3 ;------------------------------- PAUSE_007: LD A,$DD JR skip_2EE0 ;------------------------------- SLOW_007: ; 2EB2 LD A,$D6 JR skip_2EB8 ;------------------------------- FAST_007: ; 2EB6 LD A,$CE ;------------------------------- skip_2EB8: PUSH AF CALL STK_TO_A LD B,$1E DEC A CP 6 JR NC,skip_2EDC ;------------------------------- SRL A LD H,A ;------------------------------- JR Z,skip_2ECA LD A,1 skip_2ECA: ;------------------------------- PUSH AF SBC A,A LD L,A RES 1,H DEC H LD (G007_RESTART),HL CALL G007_CHECK_AND_SET_UP_DISPLAY_FILE LD B,G007_INT_REG_VALUE_FOR_HI_RES_GRAPHICS POP AF LD (G007_FLAG_2),A ;------------------------------- skip_2EDC: LD A,B ; LD I,A ; I = B = G007_INT_REG_VALUE_FOR_HI_RES_GRAPHICS = set video mode POP AF ;------------------------------- skip_2EE0: LD HL,($0D71) ; inside the 'COMMAND CLASS 2' routine of the ZX BASIC ROM ;------------------------------- L2EE3: ADD A,L LD L,A JP (HL) LD D,A LD A,(S_POSN) ; AND $80 JP L2B6C ;=============================== L2EEF: ; print a character on high-res screen perhaps? LD A,D POP DE EXX ;------------------------------- PUSH HL PUSH DE PUSH BC ;------------------------------- LD HL,(G007_CHAR_TABLE_ADDR_0_63) ADD A,A ; if (A < 128) goto JR NC,skip_2F06 ;------------------------------- ; LD HL,(G007_CHAR_TABLE_ADDR_128_159) BIT 6,A JR Z,skip_2F06 ;------------------------------- LD HL,(G007_USR_DEF_CHR_TAB_LESS_256) CCF ;------------------------------- skip_2F06: EX DE,HL ; DE now points to the selected charater table LD L,A ; HL = A LD H,0 SBC A,A ; A = carry LD C,A ; ;------------------------------- LD A,(G007_RESTART) ; invert this variable CPL AND A,(IY+G007_RESTART+1-RAMBASE) ;------------------------------- XOR A,C LD C,A ADD HL,HL ; HL *= 4 ADD HL,HL ADD HL,DE ; HD += DE LD B,8 EXX PUSH DE RET ;=============================== L2F1D: ; The copied and modified PRINT_CH / WRITE_CH jumps here from $23FF DEC (IY+S_POSN-RAMBASE) ; Line and Column for PRINT AT LD A,24 ; 24 character rows per screen SUB B LD B,A ; ADD A,A ADD A,A ADD A,A LD L,A ; L = A * 8 ; LD A,(G007_DISPLAY_HEIGHT) SUB L ; A = G007_DISPLAY_HEIGHT - something RET C ; return if beyond limit ;------------------------------- LD A,CHARS_HORIZONTAL+1 ; 32 video bytes plus HALT ? SUB C LD C,A ;------------------------------- LD H,0 ADD HL,HL ADD HL,BC LD BC,(G007_DISPLAY_ADDRESS) ADD HL,BC ; HL = L*2 + BC + G007_DISPLAY_ADDRESS: ;------------------------------- CALL L2EEF LD BC,$0022 ; B = 0, C = 34 EXX ;------------------------------- COPY_007: loop_2F41: ; LD A,C XOR A,(HL) EXX LD (HL),A ADD HL,BC EXX INC HL DJNZ loop_2F41 ;------------------------------- JP L299A ;------------------------------- SAVE_007: ; 2F4D CALL G007_DELETE_DISPLAY_FILE JP SAVE ;------------------------------- L2F53: CALL STK_TO_A RET NZ DEC A JP M,COPY ; A was zero, jump to low-res copy-to-printer ;------------------------------- CALL G007_CHECK_AND_SET_UP_DISPLAY_FILE ; check we have a display CALL SET_FAST ; print in FAST mode! LD A,(G007_DISPLAY_HEIGHT) LD B,A ; B = (G007_DISPLAY_HEIGHT) LD HL,(G007_DISPLAY_ADDRESS) XOR A,A ; A = 0 LD E,A ; E = 0 OUT (ZX_PRINTER_PORT),A ;------------------------------- loop_2F6C: LD A,$7F ; looks pointless IN A,(IO_PORT_KEYBOARD_RD) RRCA JP NC,$0886 ; []*BIOS ROM* between 0880 COPY-BRK and 0888 REPORT-D2 IN A,(ZX_PRINTER_PORT) ;------------------------------- loop_2F76: ADD A,A JP M,L2FAD ;------------------------------- JR NC,loop_2F6C ;------------------------------- LD C,$20 ; 32 ;------------------------------- loop_2F7E: PUSH BC LD C,(HL) LD B,8 ;------------------------------- ; outer loop loop_2F82: RLC C RRA OR A,E LD D,A ;------------------------------- ; inner loop loop_2F87: IN A,(ZX_PRINTER_PORT) ; read printer port bit 0 into carry flag RRA JR NC,loop_2F87 ; repeat while bit is zero ;------------------------------- LD A,D OUT (ZX_PRINTER_PORT),A ; D register to printer DJNZ loop_2F82 ;------------------------------- INC HL POP BC DEC C JR NZ,loop_2F7E ;------------------------------- INC HL INC HL LD A,3 ;------------------------------- CP B JR C,skip_2F9F ;------------------------------- LD E,A DEC E skip_2F9F: ;------------------------------- loop_2F9F: IN A,(ZX_PRINTER_PORT) ; read printer port bit 0 into carry flag RRA JR NC,loop_2F9F ; repeat while bit is zero ;------------------------------- LD A,E OUT (ZX_PRINTER_PORT),A ; E register to printer DJNZ loop_2F6C ;------------------------------- LD A,4 OUT (ZX_PRINTER_PORT),A ;------------------------------- L2FAD: JP SLOW_FAST ; normal EI DJNZ loop_2F76 ;------------------------------- defb $3E ; something to do with the table below? ;------------------------------- TABLE_ONE: ; ; this table is copied to offsets from $2000, $2200, and $2300 ;------------------------------- ; Modify 9 bytes in page 20xx / 00xx: ;------------------------------- L2FB4 : defb (9+1) ; Modify 9 bytes in page 20xx / 00xx: ;------------------------------- ; Original routine Modifications made in page 00xx ; L2FB5: ; 0011 C2;F1;07 JP NZ,PRINT_CH ; old ; 0011 C2;A0;23 JP NZ,L23A0 ; new, jump somewhere in RAM ??? defb $12 defb $A0 defb $13 defb $23 ;------------------------------- L2FB9 ; 0014 F5 07 JP PRINT_SP ; $07F5 ; 2014 A4 23 JP L23A4 ; new, jumps to somewhere in RAM defb $15 defb $A4 defb $16 defb $23 ;------------------------------- ; 003F CB;D9 SET 3,C ; old ; 003F CB;C1 SET 0,C ; new L2FBD: defb $40 L2FBE: defb $C1 ;------------------------------- ; 005F CD;07;02 CALL SLOW_FAST ; 0207 is old ; 005F CD;08;2D CALL SLOW_FAST_007 ; 2D08 is new L2FBF: defb $60 L2FC0: defb $08 L2FC1: defb $61 L2FC2: defb $2D ;------------------------------- ; 0074 2A;0C;40 LD HL,(D_FILE) ; 0074 2A;06:23 LD HL,(G007_DISPLAY_ADDRESS_LESS_9) ; new L2FC3: defb $75 L2FC4: defb $06 defb $76 L2FC6: defb $23 ;------------------------------- ; Modifications made in page 02xx ;------------------------------- L2FC7: defb $01 defb $0A ; 0253 06;0B LD B,$0B ; 2253 06;02 LD B,$02 L2FC9 defb $54 defb $02 ;------------------------------- ; 0283 01;01;19 LD BC,$1901 ; 2283 01;01;C1 LD BC,$C101 L2FCB: defb $85 defb $C1 ;------------------------------- ; 027E CD;92;02 CALL DISPLAY_3 ; 227E CD;73;2D CALL L2D73 ; L2FCD: defb $7F defb $73 L2FCF: defb $80 defb $2D ;------------------------------- ; 028C CD;92;02 CALL $0292 ; [DISPLAY-3] ; 228C CD;50;2D CALL $2D50 L2FD1: defb $8D L2FD2: defb $50 L2FD3: defb $8E L2FD4: defb $2D ;------------------------------- ; 02E3 32;28;40 LD ($4028),A ; SET MARGIN ; 22E3 C3;0E;2D JP L2D0E L2FD5: defb $E3 L2FD6: defb $C3 defb $E4 defb $0E L2FD9: defb $E5 L2FDA: defb $2D ;------------------------------- L2FDB: defb $13 ; some kind of marker? ;------------------------------- ; ; Original routine Modifications made to the copy of the PRINT-CH routines: ; ; ; Variables in the sparsely populated G007 RAM areas ; ; Initialise 18 (decimal, = 12 in hex)RAM variables at $23xx: ; ; 2FE0 E6 0A ; 23E6 0A ; 2FE2 55 0D ; 2355 0D ; 2FE4 1E 0F ; 231E 0F ; 2FE6 16 20 ; 2316 20 ; 2FE8 10 07 ; 2310 07 ; 2FEA 11 08 ; 2311 08 ; 2FEC 18 C0 ; 2318 C0 ; ;------------------ ; G007_DISPLAY_OFFSET_FROM_DFILE_LESS_9 = E675 (6675 echoed in top 32K ???) L2FDC: defb $00 ; 2300 75 defb $75 ;------------------ L2FDE: defb $01 ; 2301 E6 defb $E6 ;------------------ L2FE0: defb $0A ;------------------ L2FE1: defb $55 ; 2355 0D L2FE2: defb $0D ;------------------ L2FE3: defb $1E ; 231E 0F L2FE4: defb $0F ;------------------ defb $1E ; some kind of marker? ;------------------ L2FE6: defb $16 ; 2316 20 defb $20 ;------------------ ; G007_PLOT_ROUTINES_VECTOR = G007_PLOT_ROUTINES_VECTOR_DEFAULT = $0807 L2FE8: defb $10 ; 2310 07 defb $07 ;------------------ L2FEA: defb $11 ; 2311 08 defb $08 ;------------------ L2FEC: defb $18 ; 2318 C0 defb $C0 ;------------------ L2FEE: defb $35 ; 2335 EE defb $EE ;------------------ L2FF0: defb $36 ; 2336 55 defb $55 ;------------------ L2FF2: defb $37 ; 2337 C6 defb $C6 ;------------------ ; The values below were seen in RAM but are not initialised from this table: ; ; 2306 84 4084 G007_DISPLAY_ADDRESS_LESS_9 ; 2307 40 ; 2308 8D 408D G007_DISPLAY_ADDRESS ; 2309 40 ; 230A 55 00 55,00 G007_TRIANGLE_TEXTURE ; 230C 00 1E00 Character table address for CHR$0-63 ; 230D 1E ; 230E 00 1E00 Character table address for CHR$128-159 ; 230F 1E ;------------------ ; Modding offsets in copied code: ;------------------ ; 07FD CD 08 08 CALL $0808 ; [ENTER-CH] ; 23AC CD E6 2E CALL $2EE6 ; L2FF4: defb $AD defb $E6 defb $AE defb $2E ;------------------ ; 0843 FD 35 39 DEC (IY+SPOSN-RAMBASE) ; 23F2 C3 1D 2F JP $2F1D ; L2FF8: defb $F2 defb $C3 defb $F3 defb $1D defb $F4 defb $2F ;------------------ ; ; 083E 77 LD (HL),A ; WRITE-CH ; 23ED 00 NOP ; WRITE-CH modified! ; L2FFE: defb $ED defb $00 ;------------------ ; end of g007 stuff ; #endif #code $3800,$0800 #if 1 mark_3800: DEFB $21, $00, $38 ; ld hl,Source mark_3803: DEFB $11, $00, $78 ; ld de,Destination mark_3806: DEFB $01, $00, $08 ; ld bc,MonitorSize mark_3809: DEFB $ED, $B0 ; ldir mark_380B: DEFB $3E, $74 ; ld a,74 mark_380D: DEFB $32, $05, $40 ; ld (RamtopHi),a mark_3810: DEFB $CD, $C3, $03 ; call New mark_3813: DEFB $3E, $01 ; ld a,01 mark_3815: DEFB $01, $93, $7B ; ld bc,Input_Prompt_Data mark_3818: DEFB $CD, $02, $79 ; call Get_A_addresses mark_381B: DEFB $CD, $2A, $0A ; call Cls mark_381E: DEFB $ED, $6B, $F8, $7F ; ld hl,Next_Address mark_3822: DEFB $E9 ; jp (hl) ; Routine 0 the disassembler mark_3823: DEFB $CD, $FD, $78, $CD, $09 mark_3828: DEFB $79, $18, $05, $16, $13, $CD, $53, $7B mark_3830: DEFB $CD, $81, $7B, $30, $05, $AF, $32, $21 mark_3838: DEFB $40, $C9 mark_383A: DEFB $21, $7B, $40, $36, $30, $23 mark_3840: DEFB $36, $78, $CD, $2E, $7A, $CD, $72, $7A mark_3848: DEFB $78, $FE, $76, $20, $08, $21, $F5, $7C mark_3850: DEFB $CD, $A4, $7B, $18, $D6, $FE, $CB, $28 mark_3858: DEFB $32, $FE, $ED, $28, $38, $FE, $DD, $28 mark_3860: DEFB $44, $FE, $FD, $28, $44, $CD, $4F, $7A mark_3868: DEFB $2E, $24, $FE, $00, $28, $11, $2E, $2C mark_3870: DEFB $FE, $01, $28, $0E, $2C, $FE, $02, $28 mark_3878: DEFB $09, $3E, $5E, $32, $42, $7D, $2C, $7D mark_3880: DEFB $81, $6F, $26, $7D, $6E, $EB, $CD, $D4 mark_3888: DEFB $7A, $18, $A0, $CD, $72, $7A, $CD, $4F mark_3890: DEFB $7A, $C6, $36, $18, $EC, $CD, $72, $7A mark_3898: DEFB $CD, $4F, $7A, $2E, $18, $FE, $01, $28 mark_38A0: DEFB $DE, $2E, $20, $18, $DA, $3E, $0B, $18 mark_38A8: DEFB $02, $3E, $0D, $32, $41, $7D, $3E, $12 mark_38B0: DEFB $32, $40, $7D, $2A, $F8, $7F, $23, $7E mark_38B8: DEFB $21, $16, $7D, $CD, $89, $7A, $23, $CB mark_38C0: DEFB $BE, $CD, $72, $7A, $78, $FE, $CB, $20 mark_38C8: DEFB $05, $CD, $72, $7A, $18, $BD, $CD, $4F mark_38D0: DEFB $7A, $FE, $03, $28, $93, $F5, $C5, $FE mark_38D8: DEFB $00, $20, $05, $3E, $06, $B8, $20, $03 mark_38E0: DEFB $CD, $72, $7A, $C1, $F1, $18, $81 ; Spare bytes mark_38E7: DEFB $00 mark_38E8: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_38F0: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_38F8: DEFB $00, $00, $00, $00, $00 ; Start/Finish Addresses ; prints request for input then calls input address routine mark_38FD: DEFB $01, $93, $7B mark_3900: DEFB $3E, $02 ; Get_A_addresses mark_3902: DEFB $11, $F8, $7F, $CD, $14, $7A, $C9 ; Check printer mark_3909: DEFB $11, $E1, $10, $CD, $3B, $7B, $2B mark_3910: DEFB $7E, $21, $21, $40, $36, $83, $FD, $CB mark_3918: DEFB $01, $CE, $FE, $1D, $C4, $2A, $0A, $C9 ; routine 1: DEFB hex dump mark_3920: DEFB $CD, $FD, $78, $CD, $09, $79, $21, $F9 mark_3928: DEFB $7C, $CD, $9F, $7B, $21, $7B, $40, $36 mark_3930: DEFB $33, $23, $36, $79, $CD, $81, $7B, $F8 mark_3938: DEFB $CD, $3D, $7A, $0E, $08, $CD, $72, $7A mark_3940: DEFB $0D, $28, $0B, $CD, $81, $7B, $30, $F5 mark_3948: DEFB $16, $0B, $CD, $53, $7B, $C9 mark_394E: DEFB $CD, $48, $79, $18, $E1 ; routine 2: DEFB write mark_3953: DEFB $3E, $01, $01, $93, $7B mark_3958: DEFB $CD, $02, $79, $CD, $2A, $0A, $CD, $0E mark_3960: DEFB $0C, $CD, $3D, $7A, $11, $E8, $1C, $CD mark_3968: DEFB $A5, $7F, $CB, $45, $28, $05, $CD, $AD mark_3970: DEFB $7F, $18, $F7, $ED, $5B, $F8, $7F, $CD mark_3978: DEFB $0B, $7B, $ED, $53, $F8, $7F, $3E, $76 mark_3980: DEFB $D7, $18, $DB ; prints data associated with RST 08 or RST 28 mark_3983: DEFB $0D, $20, $24, $0E, $04 mark_3988: DEFB $16, $13, $CD, $53, $7B, $18, $18, $78 mark_3990: DEFB $FE, $01, $20, $1A, $21, $7B, $40, $36 mark_3998: DEFB $62, $23, $36, $7A, $16, $13, $CD, $53 mark_39A0: DEFB $7B, $21, $F9, $7C, $CD, $9F, $7B, $CD mark_39A8: DEFB $3D, $7A, $CD, $72, $7A, $C9 mark_39AE: DEFB $FE, $05 mark_39B0: DEFB $C0, $21, $7B, $40, $36, $68, $23, $36 mark_39B8: DEFB $7A, $0E, $04, $CD, $9C, $79, $78, $FE mark_39C0: DEFB $34, $C8, $CD, $83, $79, $18, $F7 ; Data Calculates absolute address for JR instructions ; and adds number and addresses to mnemonic mark_39C7: DEFB $CD mark_39C8: DEFB $72, $7A, $AF, $CB, $78, $28, $01, $2F mark_39D0: DEFB $48, $47, $2A, $F8, $7F, $09, $EB, $21 mark_39D8: DEFB $00, $7D, $7B, $CD, $89, $7A, $2B, $7A mark_39E0: DEFB $CD, $89, $7A, $23, $CB, $BE, $2B, $18 mark_39E8: DEFB $1B, $CD, $72, $7A, $2B, $18, $15, $CD mark_39F0: DEFB $07, $7A, $18, $10, $CD, $72, $7A, $2B mark_39F8: DEFB $18, $03, $CD, $07, $7A, $2B, $CD, $A4 mark_3A00: DEFB $7B, $21, $0D, $7C, $C3, $A4, $7B, $21 mark_3A08: DEFB $00, $7D, $CD, $75, $7A, $CD, $72, $7A mark_3A10: DEFB $CB, $BE, $2B, $C9 ; Input_Address mark_3A14: DEFB $F5, $D5, $11, $E4 mark_3A18: DEFB $10, $CD, $3B, $7B, $7D, $BB, $28, $05 mark_3A20: DEFB $CD, $AD, $7F, $18, $F7, $D1, $CD, $21 mark_3A28: DEFB $7B, $F1, $3D, $20, $E7, $C9 ; Initial mark_3A2E: DEFB $21, $42 mark_3A30: DEFB $7D, $36, $5C, $2B, $36, $09, $2B, $36 mark_3A38: DEFB $0F, $2B, $2B, $36, $E0 ; Next_Address mark_3A3D: DEFB $11, $F9, $7F mark_3A40: DEFB $21, $FE, $7C, $1A, $CD, $7F, $7A, $1B mark_3A48: DEFB $1A, $CD, $7F, $7A, $AF, $D7, $C9 ; Octal mark_3A4F: DEFB $78 mark_3A50: DEFB $E6, $07, $4F, $78, $F5, $E6, $38, $0F mark_3A58: DEFB $0F, $0F, $47, $F1, $E6, $C0, $07, $CB mark_3A60: DEFB $07, $C9 ; Cont, $RST mark_3A62: DEFB $CD, $A1, $79, $C3, $2B, $78 mark_3A68: DEFB $0E, $04, $CD, $A1, $79, $CD, $BE, $79 mark_3A70: DEFB $18, $F3 ; Next_Byte mark_3A72: DEFB $21, $FE, $7C, $ED, $5B, $F8 mark_3A78: DEFB $7F, $1A, $13, $ED, $53, $F8, $7F, $CD mark_3A80: DEFB $89, $7A, $D7, $23, $7E, $CB, $BF, $D7 mark_3A88: DEFB $C9 mark_3A89: DEFB $47, $E6, $0F, $C6, $1C, $CB, $FF mark_3A90: DEFB $77, $2B, $78, $E6, $F0, $1F, $1F, $1F mark_3A98: DEFB $1F, $C6, $1C, $77, $C9 ; Offsets mark_3A9D: DEFB $CB, $40, $20 mark_3AA0: DEFB $19, $CD, $B3, $7A, $13, $C9 mark_3AA6: DEFB $3E, $01 mark_3AA8: DEFB $CB, $40, $20, $1E, $18, $05, $CB, $40 mark_3AB0: DEFB $28, $0F, $13, $AF, $18, $14, $CB, $40 mark_3AB8: DEFB $28, $07, $13, $78, $CB, $87, $0F, $18 mark_3AC0: DEFB $09, $CD, $BB, $7A, $13, $C9 mark_3AC7: DEFB $78, $18 mark_3AC8: DEFB $01, $79, $13, $62, $6B, $6E, $26, $7C mark_3AD0: DEFB $CD, $A5, $7B, $C9 ; Control mark_3AD4: DEFB $1A, $F5, $E6, $07 mark_3AD8: DEFB $21, $DD, $7A, $18, $1E, $F1, $F5, $CB mark_3AE0: DEFB $77, $28, $0D, $2A, $3E, $7D, $CB, $55 mark_3AE8: DEFB $36, $00, $23, $28, $F9, $22, $3E, $7D mark_3AF0: DEFB $E6, $38, $0F, $0F, $CB, $0F, $28, $0C mark_3AF8: DEFB $21, $04, $7B, $E5, $3C, $26, $7D, $6F mark_3B00: DEFB $6E, $26, $7A, $E9, $F1, $CB, $7F, $C0 mark_3B08: DEFB $13, $18, $C9 ; Transfer mark_3B0B: DEFB $C5, $7D, $2E, $E0, $95 mark_3B10: DEFB $0F, $47, $7E, $CD, $8C, $7B, $23, $86 mark_3B18: DEFB $D6, $1C, $12, $13, $23, $10, $F3, $C1 mark_3B20: DEFB $C9 mark_3B21: DEFB $C5, $06, $02, $2B, $4E, $2B, $7E mark_3B28: DEFB $CD, $8C, $7B, $81, $D6, $1C, $12, $13 mark_3B30: DEFB $10, $F2, $C1, $3E, $76, $D7, $D7, $C9 mark_3B38: DEFB $11, $EF, $10, $CD, $33, $78, $26, $7E mark_3B40: DEFB $0A, $6F, $CD, $9F, $7B, $03, $0A, $6F mark_3B48: DEFB $03, $CD, $A4, $7B, $CD, $53, $7B, $CD mark_3B50: DEFB $A5, $7F, $C9 ; Print_String mark_3B53: DEFB $C5, $D5, $E5, $FD, $CB mark_3B58: DEFB $21, $46, $28, $08, $ED, $4B, $39, $40 mark_3B60: DEFB $4A, $CD, $0B, $09, $11, $E0, $7F, $ED mark_3B68: DEFB $4B, $3E, $7D, $06, $00, $79, $D6, $E0 mark_3B70: DEFB $4F, $CD, $6B, $0B, $FD, $CB, $21, $4E mark_3B78: DEFB $28, $03, $3E, $76, $D7, $E1, $D1, $C1 mark_3B80: DEFB $C9 ; Check_Finish mark_3B81: DEFB $2A, $FA, $7F, $ED, $5B, $F8, $7F mark_3B88: DEFB $A7, $ED, $52, $C9 mark_3B8C: DEFB $D6, $1C, $07, $07 mark_3B90: DEFB $07, $07, $C9 ; data for input prompt messages mark_3B93: DEFB $DD, $E3, $EB, $E3, $F2 mark_3B98: DEFB $F8, $D5, $DC, $30, $E3, $D0, $E3 ; Add_String, for building mnemonic mark_3B9F: DEFB $3E mark_3BA0: DEFB $E0, $32, $3E, $7D, $AF, $C5, $D5, $A7 mark_3BA8: DEFB $28, $0B, $CB, $7E, $20, $03, $23, $18 mark_3BB0: DEFB $F9, $3D, $23, $18, $F2, $CD, $BB, $7B mark_3BB8: DEFB $D1, $C1, $C9 mark_3BBB: DEFB $ED, $5B, $3E, $7D, $7E mark_3BC0: DEFB $CB, $7F, $20, $05, $CD, $CB, $7B, $18 mark_3BC8: DEFB $F2, $CB, $BF, $FE, $40, $30, $08, $12 mark_3BD0: DEFB $13, $ED, $53, $3E, $7D, $23, $C9 mark_3BD7: DEFB $23 mark_3BD8: DEFB $ED, $53, $3E, $7D, $E5, $FE, $43, $30 mark_3BE0: DEFB $06, $26, $7D, $6F, $6E, $18, $14, $FE mark_3BE8: DEFB $64, $30, $05, $26, $7D, $6F, $18, $0B mark_3BF0: DEFB $21, $FE, $7B, $E5, $26, $7D, $6F, $6E mark_3BF8: DEFB $26, $79, $E9, $CD, $BB, $7B, $E1, $C9 ; ; data for mnemonics ; lower case are ZX inverse characters ; mark_3C00: DEFB $A7, $A8, $A9, $AA, $AD, $B1, $C0, $A6 mark_3C08: DEFB $A6, $C1, $A6, $10, $43, $91, $10, $45 mark_3C10: DEFB $91, $E7, $E7, $C3, $C5, $C1, $C2, $62 mark_3C18: DEFB $A8, $45, $A8, $47, $D6, $49, $D6, $47 mark_3C20: DEFB $A6, $49, $A6, $29, $26, $A6, $28, $35 mark_3C28: DEFB $B1, $38, $28, $AB, $28, $28, $AB, $9C mark_3C30: DEFB $9D, $9E, $9F, $A0, $A1, $A2, $A3, $35 mark_3C38: DEFB $3A, $38, $AD, $56, $31, $B1, $37, $2A mark_3C40: DEFB $B9, $4B, $BD, $2F, $B5, $CD, $58, $A9 mark_3C48: DEFB $58, $A8, $38, $3A, $A7, $38, $C3, $26 mark_3C50: DEFB $33, $A9, $3D, $34, $B7, $34, $B7, $28 mark_3C58: DEFB $B5, $35, $34, $B5, $37, $38, $B9, $2F mark_3C60: DEFB $B5, $80, $D3, $E2, $CB, $CB, $29, $AE mark_3C68: DEFB $2A, $AE, $E5, $E8, $66, $1A, $A6, $4F mark_3C70: DEFB $E6, $10, $5C, $11, $DA, $45, $DA, $80 mark_3C78: DEFB $80, $10, $41, $91, $5C, $DA, $CF, $CF mark_3C80: DEFB $80, $CF, $80, $80, $80, $80, $33, $BF mark_3C88: DEFB $BF, $33, $A8, $A8, $35, $B4, $35, $AA mark_3C90: DEFB $B5, $B2, $47, $A8, $49, $A8, $C7, $C9 mark_3C98: DEFB $38, $31, $A6, $38, $37, $A6, $E4, $38 mark_3CA0: DEFB $C7, $27, $2E, $B9, $37, $2A, $B8, $38 mark_3CA8: DEFB $2A, $B9, $10, $28, $91, $33, $2A, $AC mark_3CB0: DEFB $B3, $AE, $38, $C3, $58, $A8, $2E, $B2 mark_3CB8: DEFB $9C, $A4, $9D, $9E, $CD, $CD, $CD, $CD mark_3CC0: DEFB $49, $A9, $47, $A9, $2E, $9A, $37, $9A mark_3CC8: DEFB $CF, $CF, $80, $80, $5E, $1A, $DE, $E9 mark_3CD0: DEFB $E9, $E0, $E0, $E0, $E0, $A6, $A6, $AE mark_3CD8: DEFB $B7, $80, $80, $AE, $A9, $2E, $B7, $29 mark_3CE0: DEFB $B7, $CD, $28, $B5, $E2, $D3, $34, $B9 mark_3CE8: DEFB $33, $34, $B5, $CB, $29, $2F, $33, $BF mark_3CF0: DEFB $D1, $D1, $D1, $D1, $D1, $2D, $26, $31 mark_3CF8: DEFB $B9, $45, $2B, $A7, $10, $1D, $A9, $27 ; ; data and data pointers for disassembler ; mark_3D00: DEFB $9F, $A6, $C9, $B3, $AE, $BB, $9D, $B6 mark_3D08: DEFB $C6, $2D, $B1, $2E, $BD, $2E, $BE, $10 mark_3D10: DEFB $41, $91, $10, $41, $15, $2A, $27, $91 mark_3D18: DEFB $CD, $D3, $D9, $DF, $3C, $E8, $EB, $EE mark_3D20: DEFB $F3, $F3, $F3, $F5, $91, $81, $89, $6D mark_3D28: DEFB $6A, $F9, $7B, $3A, $70, $9C, $A1, $76 mark_3D30: DEFB $A4, $AA, $AD, $FC, $B3, $96, $B8, $BB mark_3D38: DEFB $C1, $C7, $87, $1B, $81, $A1, $E4, $7F mark_3D40: DEFB $0F, $09, $5C, $27, $A8, $29, $AA, $37 mark_3D48: DEFB $B1, $37, $B7, $2A, $BD, $31, $A9, $26 mark_3D50: DEFB $9A, $2F, $B7, $34, $3A, $B9, $28, $A6 mark_3D58: DEFB $26, $A9, $1A, $C1, $38, $B5, $26, $AB mark_3D60: DEFB $1A, $E9, $2E, $B3, $E9, $EF, $F4, $FA mark_3D68: DEFB $8F, $C7, $FA, $17, $00, $E0, $17, $13 mark_3D70: DEFB $7A, $45, $00, $8A, $C5, $00, $F5, $59 mark_3D78: DEFB $3E, $13, $77, $7A, $45, $00, $92, $C5 mark_3D80: DEFB $9E, $58, $45, $13, $15, $AA, $C5, $6A mark_3D88: DEFB $13, $72, $45, $0B, $07, $B2, $C5, $07 mark_3D90: DEFB $0B, $7F, $E8, $82, $87, $CB, $62, $5C mark_3D98: DEFB $2F, $90, $B8, $6B, $7F, $46, $7E, $81 mark_3DA0: DEFB $00, $FA, $3E, $86, $7A, $43, $86, $92 mark_3DA8: DEFB $C5, $6A, $FF, $5F, $6A, $7A, $3B, $86 mark_3DB0: DEFB $92, $C5, $6A, $7F, $46, $7E, $82, $9E mark_3DB8: DEFB $CF, $92, $00, $7A, $A1, $2F, $8A, $C5 mark_3DC0: DEFB $00, $7A, $A4, $2F, $8A, $C5, $00, $7A mark_3DC8: DEFB $A7, $2F, $8A, $C5, $00, $7A, $63, $00 mark_3DD0: DEFB $92, $C5, $AA, $52, $62, $AA, $BA, $C5 mark_3DD8: DEFB $00, $50, $B2, $15, $A2, $C5, $13, $6A mark_3DE0: DEFB $45, $11, $13, $9A, $C5, $13, $11, $00 mark_3DE8: DEFB $BA, $3E, $B0, $FA, $B6, $B8, $7F, $BC mark_3DF0: DEFB $C4, $87, $D5, $B9, $E1, $D7, $BC, $E2 mark_3DF8: DEFB $D7, $FA, $19, $00, $D8, $37, $13, $6A ; ; print data for menu & routines ; mark_3E00: DEFB $00, $00, $00, $00, $32, $2A, $33, $BA mark_3E08: DEFB $00, $00, $00, $00, $14, $14, $14, $94 mark_3E10: DEFB $1C, $00, $35, $37, $2E, $33, $39, $00 mark_3E18: DEFB $28, $34, $29, $AA, $1D, $00, $2D, $2A mark_3E20: DEFB $3D, $00, $29, $3A, $32, $B5, $1E, $00 mark_3E28: DEFB $3C, $37, $2E, $39, $AA, $1F, $00, $2E mark_3E30: DEFB $33, $38, $2A, $37, $B9, $20, $00, $29 mark_3E38: DEFB $2A, $31, $2A, $39, $AA, $21, $00, $39 mark_3E40: DEFB $37, $26, $33, $38, $2B, $2A, $B7, $22 mark_3E48: DEFB $00, $38, $2A, $26, $37, $28, $AD, $23 mark_3E50: DEFB $00, $37, $2A, $35, $31, $26, $28, $AA mark_3E58: DEFB $24, $00, $26, $38, $38, $2A, $32, $27 mark_3E60: DEFB $31, $2A, $B7, $25, $00, $37, $3A, $33 mark_3E68: DEFB $00, $28, $34, $29, $AA, $26, $00, $28 mark_3E70: DEFB $26, $31, $28, $3A, $31, $26, $39, $34 mark_3E78: DEFB $B7, $27, $00, $28, $2D, $37, $0D, $00 mark_3E80: DEFB $29, $3A, $32, $B5, $28, $00, $26, $38 mark_3E88: DEFB $28, $2E, $2E, $00, $29, $3A, $32, $B5 mark_3E90: DEFB $29, $00, $37, $2A, $33, $3A, $32, $27 mark_3E98: DEFB $2A, $B7, $2A, $00, $2E, $32, $26, $2C mark_3EA0: DEFB $2A, $B7, $2B, $00, $32, $2A, $33, $3A mark_3EA8: DEFB $00, $9E, $80, $80, $00, $00, $00, $00 mark_3EB0: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_3EB8: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_3EC0: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_3EC8: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_3ED0: DEFB $31, $2E, $32, $2E, $B9, $37, $34, $3A mark_3ED8: DEFB $39, $2E, $33, $2A, $80, $38, $39, $26 mark_3EE0: DEFB $37, $39, $80, $26, $29, $29, $37, $2A mark_3EE8: DEFB $38, $38, $80, $2B, $2E, $33, $2E, $38 mark_3EF0: DEFB $2D, $80, $1D, $00, $2B, $34, $37, $80 mark_3EF8: DEFB $35, $37, $2E, $33, $39, $2A, $37, $80 ; addresses of routines mark_3F00: DEFB $23, $78 ;, $.dw, $7823 mark_3F02: DEFB $20, $79 ; , $.dw, $7902 mark_3F04: DEFB $53, $79 ;, $.dw, $7953 mark_3F06: DEFB $FF, $FF mark_3F08: DEFB $FF, $FF mark_3F0A: DEFB $FF, $FF mark_3F0C: DEFB $FF, $FF mark_3F0E: DEFB $FF, $FF mark_3F10: DEFB $FF, $FF mark_3F12: DEFB $13, $38 ;, $.dw, $3813 mark_3F14: DEFB $FF, $FF mark_3F16: DEFB $FF, $FF mark_3F18: DEFB $FF, $FF mark_3F1A: DEFB $FF, $FF mark_3F1C: DEFB $FF, $FF mark_3F1E: DEFB $FF, $FF ; Read_Keyboard mark_3F20: DEFB $D5, $C5, $E5, $2A, $25, $40, $E5, $ED mark_3F28: DEFB $4B, $25, $40, $E1, $C5, $A7, $ED, $42 mark_3F30: DEFB $28, $F5, $79, $3C, $28, $F1, $E1, $CD mark_3F38: DEFB $BD, $07, $7E, $E1, $C1, $D1, $FE, $76 mark_3F40: DEFB $C8, $FE, $77, $C8, $FE, $00, $20, $05 mark_3F48: DEFB $CD, $2A, $0A, $CF, $0C, $FE, $1C, $38 mark_3F50: DEFB $CF, $FE, $2C, $30, $CB, $C9 ; ; Menu ; mark_3F56: DEFB $21, $21 mark_3F58: DEFB $40, $CB, $7E, $28, $04, $2A, $7B, $40 mark_3F60: DEFB $E9, $21, $00, $7E, $06, $14, $3E, $03 mark_3F68: DEFB $32, $21, $40, $11, $E1, $18, $CD, $9F mark_3F70: DEFB $7B, $CD, $53, $7B, $10, $F8, $01, $99 mark_3F78: DEFB $7B, $CD, $3B, $7B, $2B, $7E, $D6, $1C mark_3F80: DEFB $47, $07, $6F, $26, $7F, $7E, $23, $66 mark_3F88: DEFB $6F, $E5, $C5, $CD, $2A, $0A, $C1, $3E mark_3F90: DEFB $E0, $32, $3E, $7D, $78, $21, $10, $7E mark_3F98: DEFB $CD, $A5, $7B, $16, $1A, $CD, $53, $7B mark_3FA0: DEFB $3E, $76, $D7, $D7, $C9 ; ; InputString, the heart of all routines ; mark_3FA5: DEFB $3E, $01, $32 mark_3FA8: DEFB $21, $40, $21, $E0, $7F, $36, $17, $CD mark_3FB0: DEFB $C0, $7F, $CD, $20, $7F, $FE, $76, $20 mark_3FB8: DEFB $10, $3E, $E0, $BD, $28, $F4, $36, $00 mark_3FC0: DEFB $23, $22, $3E, $7D, $CD, $53, $7B, $2B mark_3FC8: DEFB $C9 mark_3FC9: DEFB $FE, $77, $20, $0B, $3E, $E0, $BD mark_3FD0: DEFB $28, $DB, $CD, $BE, $7F, $2B, $18, $D5 mark_3FD8: DEFB $77, $7B, $BD, $28, $D0, $23, $18, $CD ; ; mnemonic string, ram area for mnemonic to be built up ; mark_3FE0: DEFB $00, $00, $00, $00, $00, $00, $00, $00 mark_3FE8: DEFB $00, $00, $00, $00, $00, $00, $00, $00 ; ; spare bytes ; mark_3FF0: DEFB $00, $00, $00, $00, $00, $00, $00, $00 ; ; next address for routine ; mark_3FF8: DEFB $00, $00 ; ; finish address for routine ; mark_3FFA: DEFB $00, $00 ; ; spare bytes ; mark_3FFC: DEFB $00, $00, $00 mark_3FFF: DEFB $C9 ; ret #else ; ; ; Input_Prompt_Data equ $7B93 Get_A_addresses equ $7902 Next_Address equ $7FFB #endif #end ; required by zasm