Jump to content





Part 5 of 11 -- Simple Assembly for Atari BASIC - Implement DPOKE

Posted by kenjennings, 29 July 2016 · 464 views

6502 Assembly Atari BASIC Mac/65 Atari 8-bit

DPOKE
 
==============================================================  
 
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/
 
==============================================================
 
 
Writing a two-byte, 16-bit value (DPOKE) goes hand-in-hand with reading (DPEEK).  An 8-bit computer environment has many opportunities for managing 16-bit address and the Atari environment is no exception.   Address of display lists, Address of screen memory.  Numerous buffers and pointers.  Address pointers abound.
 
OSS BASIC XL provides the DPOKE command to perform a 16-bit, two-byte write to a specified memory location.  Like DPEEK, this can be duplicated in regular Atari BASIC, although much slower.  So, in BASIC XL the action:
 

Dpoke Address, Value

 
is frequently seen in Atari BASIC programs expressed as:  
 

POKE ADDRESS, VALUE - INT( VALUE / 256 ) * 256
POKE ADDRESS + 1, INT( VALUE / 256 )

 
That’s a bit more complicated than DPEEK.  Here the value must be broken down into the high byte (integer result of the value divided by 256) and the low byte (the remainder after division), and then the two values assigned to the two consecutive bytes in memory in low byte, high byte order.  This can also be worked into a reusable subroutine in Atari BASIC:
 
10 DPOKE=28500
20 ADDRESS=560:VALUE=39968:GOSUB DPOKE
30 END

. . .

28497 REM IMPLEMENT DPOKE
28498 REM INPUT = ADDRESS
28499 REM INPUT = VALUE
28500 HB=INT(VALUE/256)
28501 POKE ADDRESS,VALUE-HB*256:POKE ADDRESS+1,HB
28502 RETURN
 
This is still fairly simple code.  A small difference here is the high byte is calculated and retained to avoid multiple occurrences of the same floating point division.  But, again, expressed in Atari BASIC this is fairly slow using one floating point division and one multiplication.  As before, applying assembly language rocketry yields:
 
DPOKE in Mac/65 Assembler Code
 
0100 ; DPOKE
0105 ;
0110 ; Write a 16-bit word into
0115 ; the specified address.
0120 ;
0125 ; USR 2 arguments:
0130 ; Addr == address to write.
0135 ; Val  == Value to write.
0140 ;
0145 ; USR return value is 0.
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 ZVAL =  $D6     ; FR0 $D6/$D7 Value
0205 ZADDR = $D8     ; FR0 $D8/$D9 Address
0210 ;
0215     .OPT OBJ
0220 ;
0225 ; Arbitrary. This is relocatable.
0230 ;
0235     *=  $9600
0240 ;
0245 INIT
0250     PLA         ; argument count
0255     TAY
0260     BEQ EXIT    ; shortcut for no args
0265     ASL A       ; now number of bytes
0270     TAY
0275     CMP #$04    ; Value, Address
0280     BEQ PULLDOWN
0285 ;
0290 ; Bad args. Clean up for exit.
0295 ;
0300 DISPOSE ;       any number of args
0305     PLA
0310     DEY
0315     BNE DISPOSE
0320     BEQ EXIT
0325 ;
0330 ; This code works the same
0335 ; for 1, 4, 8 ... arguments.
0340 ;
0345 PULLDOWN
0350     PLA
0355     STA ZARGS,Y
0360     DEY
0365     BNE PULLDOWN
0370 ;
0375 ; Y is already zero here.
0380 ;
0385     LDA ZVAL
0390     STA (ZADDR),Y
0395     INY         ; now Y is 1
0400     LDA ZVAL+1
0405     STA (ZADDR),Y
0410 ;
0415 ; On failure Y = 0  
0420 ; On Success Y = 1
0425 ;
0430 EXIT ;  
0435     STY ZRET
0440     STY ZRET+1
0445     RTS         ; bye.
0450 ;
0455     .END
 
The error detection and handling have a few differences from DPEEK.  The DPOKE routine does not inherently require a response value where DPEEK does, so the DPOKE code does not have separate, early-exit code and uses only one place to set the response value merely to indicate general success or failure.
 
This code uses an additional Page Zero value to accept the value to write to the address:
 
0190 ZRET =  $D4     ; FR0 $D4/$D5 Return value
0195 ZARGS = $D5     ; $D6-1 for arg Pulldown loop
0200 ZVAL =  $D6     ; FR0 $D6/$D7 Value
0205 ZADDR = $D8     ; FR0 $D8/$D9 Address
 
The real work part of the code is obviously different since writing bytes into memory (DPOKE) is not the same as reading bytes from memory (DPEEK):
 
0375 ; Y is already zero here.
0380 ;
0385     LDA ZVAL
0390     STA (ZADDR),Y
0395     INY         ; now Y is 1
0400     LDA ZVAL+1
0405     STA (ZADDR),Y
  
The DPOKE version above conforms to my standard plan (more or less) for stack management and error handling.  Most of the remaining utilities will stick with this plan.
 
However, for the sake of (averting) argument there are admittedly more optimal ways to accomplish this depending on the programmer's definition of optimal.  For instance, the value to write to the address need not be temporarily stored in Page Zero.  After the code pulls the target address from the stack and establishes it in Page Zero it could pull the value and directly store it to the target address, like this:
 
0375 ; Y must be 1 here.
0380 ;
0385     PLA         ; high byte
0390     STA (ZADDR),Y
0395     DEY         ; now Y is 0
0400     PLA         ; low byte
0405     STA (ZADDR),Y
 
That does look clever as it subtracts two bytes from the code, and removes ZVAL from Page Zero.  However, to make this work the argument handling must be different to pull two, not four bytes worth of arguments, and the Y value must be reset to 1 before arriving at this code, and the error return value needs to be reworked.  In other words, the code savings in one place may be consumed by code needed in another place. 
 
The standardized approach may not be the most optimal, but it is conceptually consistent, flexible, and doesn't need to be re-factored from scratch for each different routine.  Also, regardless of the degree of inefficiency in this less-than-optimal method the execution time is negligible compared to slower Atari BASIC.  More time is spent by Atari BASIC converting the floating point values into 16-bit integers to pass as arguments to the machine language routine than the amount of time for execution of the the machine language routine. 
 
 
Testing DPEEK and DPOKE
 
The Atari BASIC program below, TESTDPK.BAS, demonstrates how to load the machine language routines into strings and it tests the two routines:
 
0 REM H1:TESTDPK.BAS
100 REM TEST DPEEK AND DPOKE USR() TOOLS
110 GRAPHICS 0:POKE 710,0:POKE 82,0
120 GOSUB 10000
130 REM
140 REM TEST DPEEK
150 REM
155 ? "*** DPEEK ***"
160 DL=PEEK(560)+256*PEEK(561)
170 ? "BASIC Dpeek Display List = ";DL
180 DL=0
190 DL=USR(DPEEK,560)
200 ? "USR() Dpeek Display List = ";DL
210 REM
220 REM TEST DPOKE
230 REM
240 CL=10+256*68:REM 711=10,712=68
250 X=USR(DPOKE,711,CL)
260 REM
270 REM VERIFY WITH DPEEK
280 REM
290 ? :? "*** DPOKE ***"
300 ? "10 + 256 * 68 = ";10+256*68
310 CL=PEEK(711)+256*PEEK(712)
320 ? "BASIC After Dpoke = ";CL
330 CL=0
340 CL=USR(DPEEK,711)
350 ? "USR() After Dpoke = ";CL
360 END
970 REM
980 REM DPEEK AND DPOKE
990 REM
10000 DIM DPE$(36),DPO$(38)
10001 DPEEK=ADR(DPE$):DPOKE=ADR(DPO$)
10010 RESTORE 27000
10011 FOR I=0 TO 35
10100 READ D:POKE DPEEK+I,D
10101 NEXT I
10110 FOR I=0 TO 36
10111 READ D:POKE DPOKE+I,D
11000 NEXT I
11001 RETURN
28000 REM dpeek.obj
27000 DATA 104,168,240,10,10,168,201,2
27010 DATA 240,9,104,136,208,252,132,212
27020 DATA 132,213,96,104,153,213,0,136
27030 DATA 208,249,177,214,133,212,200,177
27040 DATA 214,133,213,96
29000 REM dpoke.obj
29000 DATA 104,168,240,28,10,168,201,4
29001 DATA 240,6,104,136,208,252,240,16
29002 DATA 104,153,213,0,136,208,249,165
29003 DATA 214,145,216,200,165,215,145,216
29004 DATA 132,212,132,213,96
 
First the program performs DPEEK the slow way in BASIC by calculating the 16-bit address of the display list (at locations 560 and 561) with a multiply in BASIC (line 160).  Then the program performs the same action using the DPEEK USR() routine (line 190).  The value determined by each method is displayed for comparison:
 
Attached Image
 
Next the program tests the DPOKE USR() routine by assigning a 16-bit value to locations 711 and 712.  First it calculates the 16-bit value and reports it.  Then it performs DPOKE via the USR() routine (Line 250).  Then it retrieves the contents of the two memory locations by the slower BASIC method for DPEEK and by the DPEEK USR() routine.  Finally, it reports the value determined by each method to verify the value was written to memory correctly:
 
Attached Image
 
 
Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive:
 
 
DPOKE File List:

 
DPOKE.M65        Saved Mac/65 source
DPOKE.L65        Mac/65 source listing
DPOKE.T65        Mac/65 source listed to H6: (linux)
DPOKE.ASM        Mac/65 assembly listing
DPOKE.TSM        Mac/65 assembly listing to H6: (linux)
DPOKE.OBJ        Mac/65 assembled machine language program (with load segments)
DPOKE.BIN        Assembled machine language program without load segments
DPOKE.LIS        LISTed DATA statements for DPOKE.BIN routine.
DPOKE.TLS        LISTed DATA statements for DPOKE.BIN routine to H6: (linux)

 
MAKEDPOK.BAS    BASIC program to create the DPOKE.BIN file.  This also contains the DPOKE routine in DATA statements.
MAKEDPOK.LIS    LISTed version of MKDPOKE.BAS
MAKEDPOK.TLS    LISTed version of MKDPOKE.BAS to H6: (linux)
 
TESTDPK.BAS        BASIC program that tests the DPEEK and DPOKE USR() routines.
TESTDPK.LIS        LISTed version of TESTDPK.BAS.
TESTDPK.TLS        LISTed version of TESTDPK.BAS to H6: (linux)
 
 
 
ZIP archive of files:
 
Attached File  Dpoke_Disk.zip (10.46KB)
downloads: 47
 
 
Tar archive of files (remove the .zip after download)
 
Attached File  Dpoke_Disk.tgz.zip (5.83KB)
downloads: 45
 
 
 
For we are his workmanship, created in Christ Jesus for good works, which God prepared beforehand, that we should walk in them.   
Ephesians 2:10