Jump to content





Part 4 of 11 -- Simple Assembly for Atari BASIC - Implement DPEEK()

Posted by kenjennings, 28 July 2016 · 450 views

6502 Assembly Atari 8-bit Atari BASIC Mac/65

DPEEK
  
==============================================================  
 
Part 1 - Introduction
http://atariage.com/...or-atari-basic/
 
Part 2 - Learn 82.7% of Assembly Language in About Three Pages
http://atariage.com/...or-atari-basic/
 
Part 3 - The World Inside a USR() Routine 
http://atariage.com/...or-atari-basic/
 
Part 4 - Implement DPEEK() 
http://atariage.com/...or-atari-basic/
 
Part 5 - Implement DPOKE  
http://atariage.com/...or-atari-basic/
 
Part 6 - Various Bit Manipulations
http://atariage.com/...or-atari-basic/
 
Part 7 - Convert Integer to Hex String 
http://atariage.com/...or-atari-basic/
 
Part 8 - Convert Integer to Bit String
http://atariage.com/...or-atari-basic/
 
Part 9 - Memory Copy 
http://atariage.com/...or-atari-basic/
 
Part 10 - Binary File I/O  Part 1 (XIO is Broken) 
http://atariage.com/...or-atari-basic/
 
Part 11 - Binary File I/O  Part 2 (XIO is Broken)
http://atariage.com/...-basic-the-end/
 
==============================================================
 
  
Since one byte can hold the value 0 to 255 a value larger than 255 requires two bytes.  The second byte takes the place value 256.   Just as Base10 has “ones” values 0 to 9, and then “tens” value for the next position, so the two bytes provide a Base256 value – “ones” value 0 to 255 in the first position and then 256 as the “tens” in the second position.  
 
The two-digit value limit for Base10:  multiply the maximum base value by 10 for the “tens” value and add the maximum base value of the “ones”, or 9 * 10 + 9 = 99.   
 
The same applies for Base256: multiply the maximum base value by 256 for the “tens” and then add the maximum base value as the “ones”, or 255 * 256 + 255 == 65,535.   (Or, in hex this is $FF * $100 + $FF == $FFFF.)  So, the real value limit of 16-bits starts at 0 and ends at 65,535 or $FFFF.
 
The 64K address space of an 8-bit computer is described by a 16-bit value.  So, the Atari environment is liberally sprinkled with 16-bit values as addresses, pointers, and larger integer values.  Manipulating 16-bit values is complementary to working in the Atari computing environment, but Atari BASIC does not provide a direct and easy method for this.
 
OSS BASIC XL provides the Dpeek() function to perform a 16-bit, two-byte PEEK of the value at a specified memory location.  This can also be duplicated in regular Atari BASIC, although slower.  In BASIC XL the action:


Value = Dpeek( Address )

 
is frequently seen in Atari BASIC programs expressed as: 


VALUE = PEEK( ADDRESS ) + 256 * PEEK( ADDRESS + 1 )

 

That’s not very complicated.  A little programming grease mixed with Atari BASIC’s ability to GOSUB to a variable produces this reusable subroutine:    
 
10 DPEEK=28000
20 ADDRESS=560:GOSUB DPEEK
30 ? "DPEEK(560)= ";VALUE
40 END
. . .
27997 REM IMPLEMENT DPEEK
27998 REM INPUT = ADDRESS
27999 REM OUTPUT = VALUE
28000 VALUE=PEEK(ADDRESS)+256*PEEK(ADDRESS+1)
28001 RETURN
  
The routine is simple; just one real line of BASIC code.  But, execution in Atari BASIC is fairly slow, since it includes a floating point multiplication.  Infrequent slothfulness is forgivable, but repeated use causes obvious performance drag.  What this problem needs is a little assembly language propulsion...
 
 
 
DPEEK Mac/65 Assembler Code
 
0100 ; DPEEK
0105 ;
0110 ; Return the 16-bit contents
0115 ; at the specified address.
0120 ;
0125 ; USR 1 arguments:
0130 ; Addr == address of value.
0135 ;
0140 ; USR return value is 16-bit
0145 ; contents of address.  
0150 ;
0155 ; Use the FR0 FP register.
0160 ; The return value for BASIC
0165 ; goes in FR0.
0170 ; No FP is used so all of FR0
0175 ; (and more FP registers) can
0180 ; be considered available.
0185 ;
0190 ZRET =  $D4     ; FR0 $D4/$D5 Return value
0195 ZARGS = $D5     ; $D6-1 for arg Pulldown loop
0200 ZADDR = $D6     ; FR0 $D6/$D7 Address
0205 ;
0210     .OPT OBJ
0215 ;
0220 ; Arbitrary. This is relocatable.
0225 ;
0230     *=  $9700
0235 ;
0240 INIT
0245     PLA         ; argument count
0250     TAY
0255     BEQ EXIT_ERR ; shortcut for no args
0260     ASL A       ; now number of bytes
0265     TAY
0270     CMP #$02    ; Address
0275     BEQ PULLDOWN
0280 ;
0285 ; Bad args. Clean up for exit.
0290 ;
0295 DISPOSE ;       any number of args
0300     PLA
0305     DEY
0310     BNE DISPOSE
0315 ;
0320 EXIT_ERR ;      return "error"
0325     STY ZRET    ; Y is Zero
0330     STY ZRET+1  ;
0335     RTS         ; bye.
0340 ;
0345 ; This code works the same
0350 ; for 1, 4, 8 ... arguments.
0355 ;
0360 PULLDOWN
0365     PLA
0370     STA ZARGS,Y
0375     DEY
0380     BNE PULLDOWN
0385 ;
0390 ; Y is already zero here.
0395 ;
0400     LDA (ZADDR),Y
0405     STA ZRET
0410     INY
0415     LDA (ZADDR),Y
0420     STA ZRET+1
0425 ;
0430     RTS         ; bye.
0435 ;
0440     .END
 
In this first example I’ll walk through all the setup and safety checks.  This will be similar for most of the other utilities.  Some programmers would consider that there is much more code here than required.  The safety checks look like overkill, because the working code for this particular utility is so short.  Protecting the BASIC programmer from torpedoing the system is important enough to justify protection.  From the point of view of execution timing this overhead is inconsequential compared to the speed of BASIC.
 
First of all, the routine is relocatable which means the code makes no absolute references to locations within itself.  All branches are relative to the current location, so the code could execute from almost anywhere in memory.  However, it does need a couple of fixed locations for working values in the program:
 
0155 ; Use the FR0 FP register.
0160 ; The return value for BASIC
0165 ; goes in FR0.
0170 ; No FP is used so all of FR0
0175 ; (and more FP registers) can
0180 ; be considered available.
0185 ;
0190 ZRET =  $D4     ; FR0 $D4/$D5 Return value
0195 ZARGS = $D5     ; $D6-1 for arg Pulldown loop
0200 ZADDR = $D6     ; FR0 $D6/$D7 Address
 
The program needs two values – the address from which to PEEK the 16-bit value, and a place to put the 16-bit value for BASIC to reference.  Specifically, the code is using the Page Zero locations.  “Page Zero” means all the address locations where the high byte of the address is $00.  (The 256 locations from address $0000 to $00FF).    Page Zero locations are chosen for several reasons:
 
  • 6502 instructions referencing Page Zero are one byte shorter and usually execute faster than instructions referencing other pages.
 
  • The 6502 has useful addressing modes that only work for Page Zero references.
 
  • BASIC already defines a place in Page Zero for the value the machine language routine returns to BASIC.
 
Since Page Zero locations are so useful they are also highly contested.  The Atari OS defines and uses just about every byte in the first half of Page Zero.  BASIC and the Floating Point routines use almost all of the second half of Page Zero.  As it turns out the locations used by this machine language routine are “claimed” by the Floating Point routines.  However, as long as the machine language routine does not need to use the Floating Point routines then these locations are free for use.
 
Now to the working code.  The routine begins by pulling the argument count from the stack and checking for zero arguments.   If it finds no arguments to process then the routine branches to another location for an early exit.  Yes, the TAY instruction is not needed to correctly branch for no arguments.  The PLA instruction sets the zero flag when the argument count popped from the stack is zero. 
 
However, the exit code will use the Y register to return an error value (which is zero) to BASIC:
 
0240 INIT
0245     PLA         ; argument count
0250     TAY
0255     BEQ EXIT_ERR ; shortcut for no args
 
Next, the routine converts the number of arguments into the number of bytes to pull from the stack.  It uses ASL which is the same as multiplying the value in the Accumulator times two.  This is stored in the Y register which is used as an index for later loops.  The routine verifies the argument count is correct (only 1 argument – the address to PEEK) which is 2 bytes.  If this is correct the routine branches to the code that will pull the stack values and place them in Zero page memory for use later:
 
0260     ASL A       ; now number of bytes
0265     TAY
0270     CMP #$02    ; Address
0275     BEQ PULLDOWN
 
If the argument count is incorrect then the routine discards the argument values on the stack, so it can safely exit.  Remember the Y register already contains the number of argument bytes on the stack:
 
0295 DISPOSE ;       any number of args
0300     PLA
0305     DEY
0310     BNE DISPOSE
 
Next, the routine falls into the exit section.  Earlier, if there were no arguments the routine branched here directly.  Note that if there are no arguments the Y register contained 0 when it branched here directly, and at the conclusion of cleaning up the stack in the DISPOSE loop the Y register will also be 0.  So, in either case of bad arguments, too many or too few, the return value to BASIC is cleared to 0:
 
0320 EXIT_ERR ;      return "error"
0325     STY ZRET    ; Y is Zero
0330     STY ZRET+1  ;
0335     RTS         ; bye.
 
Clearing the return value really isn’t necessary and isn’t exceedingly useful beyond insuring random values are not returned to BASIC in the case of an error.  Returning a real error would require the USR() pass another argument from BASIC that the routine would use to indicate success or failure.  However, the DPEEK action is so simple that this level of error detection begins to enter the arena of silly.  The error detection shown here already leans toward overkill and is done for design consistency with the other utilities covered later.   
 
At this point the arguments are correct.  The routine pulls the values from the stack and places them in Page Zero locations $D6/$D7 referred to as ZADDR.  On entry to this point in the code the Y register contains the number of bytes to pull from the stack (always 2 for this routine).  The loop pulls them off the stack and places them into memory descending as it goes, because the high byte is pulled first, then the low byte.
 
The base address is not ZADDR, but ZARGS, a value defined one byte less than ZADDR.  This is because the Y value will be used as an index from the number of argument bytes (2, 4, 6, etc.) counting down to 1, not to 0.  Counting backwards results in the stack values placed in memory in the correct low byte, high byte order used by the 6502.   When Y reaches 0 it falls out of the loop:
 
0360 PULLDOWN
0365     PLA
0370     STA ZARGS,Y
0375     DEY
0380     BNE PULLDOWN
 
This code works for any number of arguments as long as the destination can be sequential bytes in memory.  However, this is admittedly overkill for only one argument.  More explicit code that directly pulls one 16-bit argument from the stack requires the same number of instructions, but is one byte less (and executes faster).  Just for reference:
 
0360 PULLDOWN
0365     PLA
0370     STA ZADDR+1 ; high byte first.
0375     PLA
0380     STA ZADDR   ; low byte next.
 
The actual work to perform the double byte peek is just the five instructions before the final RTS.  The routine reads two bytes through the address contained in ZADDR ($D6/$D7) and copies the bytes to the return value, ZRET ($D4/$D5):
 
0400     LDA (ZADDR),Y
0405     STA ZRET
0410     INY
0415     LDA (ZADDR),Y
0420     STA ZRET+1
 
After the routine exits Atari BASIC converts the value stored in locations $D4/$D5 (ZRET in the code) into a floating point value.  This becomes the return value from USR().  So, in the following BASIC statement the variable X is assigned the value taken from $D4/$D5:
 
 
 
 
 
 

 


250 X=USR(DPEEK,560): REM DISPLAY LIST ADDRESS

 
 
 
Below are source files and examples of how to load the machine language routine into BASIC included in the disk image and archive:  
 
DPEEK File List:
DPEEK.M65    Saved Mac/65 source
DPEEK.L65    Mac/65 source listing
DPEEK.T65    Mac/65 source listed to H6: (linux)
DPEEK.ASM    Mac/65 assembly listing
DPEEK.TSM    Mac/65 assembly listing to H6: (linux)
DPEEK.OBJ    Mac/65 assembled machine language program (with load segments)
DPEEK.BIN    Assembled machine language program without load segments

 
MKDPEEK.BAS    BASIC program to create the DPEEK.BIN file.  This also contains the DPEEK routine in DATA statements.
MKDPEEK.LIS    LISTed version of MKDPEEK.BAS
MKDPEEK.TLS    LISTed version of MKDPEEK.BAS to H6: (linux)
 
DPEEK.BAS    BASIC program that loads DATA statements into a string.
DPEEK.LIS    LISTed version of DPEEK.BAS
DPEEK.TLS    LISTed version of DPEEK.BAS to H6: (linux)
 
 
The next task should be a test program to demonstrate using DPEEK in BASIC.  But, since DPEEK isn’t much use without DPOKE, we will visit DPOKE first before using the machine language routines together in a BASIC program.
 
 
 
ZIP archive of files:
 
 Attached File  Dpeek_Disk.zip (7.14KB)
downloads: 33
 
 
Tar archive of files (remove the .zip after download)
 
 Attached File  Dpeek_Disk.tgz.zip (3.95KB)
downloads: 36
 
 
 
Therefore I tell you, do not be anxious about your life, what you will eat or what you will drink, nor about your body, what you will put on. Is not life more than food, and the body more than clothing? Look at the birds of the air: they neither sow nor reap nor gather into barns, and yet your heavenly Father feeds them. Are you not of more value than they? And which of you by being anxious can add a single hour to his span of life? And why are you anxious about clothing? Consider the lilies of the field, how they grow: they neither toil nor spin, yet I tell you, even Solomon in all his glory was not arrayed like one of these.    
Matthew 6:25-34