textfiles/programming/tricks65.txt

506 lines
17 KiB
Plaintext
Raw Normal View History

2021-04-15 11:31:59 -07:00
====================================================================
DR 6502 AER 201S Engineering Design 6502 Execution Simulator
====================================================================
Supplementary Notes By: M.J.Malone
6502 Tricks
===========
Did you know...
---------------
... that in the Atlanta Airport, facilities like the automatic
doors are all controlled by a 6502 based VIC 20 computer system?
The 6502 (or any 'older' CPU) is capable of executing about 300,000
instructions per second. If it requires only two instructions to
check the door sensor and another two to open a door then 30,000
doors could be either opened or closed in one second by such a
system. More realistically, the decision and command to open or
shut 1000 doors could made 30 times per second or a maximum of .033
seconds delay between a person activating a sensor and the door
opening. Systems that interact with people seldom have to react
that fast.
... that the 6502 instruction set, though limited, is not
unlike the instruction sets of the RISC systems used in
workstations? The simple design of the 6502 restricts it to a
simple assembly language. Each instruction however, is executed in
only a few machine cycles. If reproduced today in HCMOS, the
current microprocessor logic technology, the 6502 architecture would
be capable of 20-30 MIPS in a non-parallel system architecture.
Special versions of the 6502 have were used in a parallel-network
machine that approached the speed of the supercomputers of the day.
... that the terminator (CSM - 101) used 6502 assembly
language? In the scene at the motel, when the terminator is just
arriving, shortly after seeing the barking dog through the
terminator's eyes, a subroutine is displayed on the screen in 6502
assembly language.
0) Read the Program Counter: JSR and PulL(A) the Address
--------------------------------------------------------
When the 6502 executes a JSR to a subroutine the return address
is pushed onto the stack. When the RTS is executed, the address is
pulled off of the stack and put back into the program counter and
the program continues after the JSR instruction. Sometimes in an
assembled code it is convenient for a program to know where in
memory it resides. It is possible by executing a JSR instruction to
the next instruction to discover the current program counter
address.
page 2
Example:
;
; What is my address?
jsr Next_Inst
Next_Inst pla
clc
adc #$01
sta PCL
pla
adc #$00
sta PCH
;
; The address of Next_Inst is in PCH, PCL
;
1) Set the Program Counter: PusH(A) the Address and RTS
-------------------------------------------------------
When the 6502 executes a JSR to a subroutine the return address
is pushed onto the stack. When the RTS is executed, the address is
pulled off of the stack and put back into the program counter and
the program continues after the JSR instruction. If you know the
address of a point you wish to JMP to in the program then just push
the address into the stack and execute an RTS command. Two things
must be remembered. The address pushed into the stack must be one
less than the address you actually want to go to. The high byte of
the address must be pushed into the stack then the low byte. This
trick is very useful in JMPing to a CALCULATED address simulating an
ON-GOTO statement or a SWITCH/CASE statement.
Example:
--------
;
; We wish to jump to $E080
;
LDA #$E0
PHA
LDA #$7F
PHA
RTS
2) The Easy String Input
------------------------
When a JSR is called, the return address or the address of the
last byte of the JSR operand is pushed into the stack. This return
address as in 0) is available to the subroutine that was called.
This is a convenient method of passing the address of a string to a
routine for printing strings. The following is a coding example for
printing a string.
;
; Print the Hello Message
;
jsr Print_Str
.TEXT "Good morning. Ready...*"
;
page 3
; The Print String Subroutine
;
Print_Str pla ; Pull the RTS address off of the stack
clc
adc #$01 ; Add one to the low byte of the address
sta ptr
pla
adc #$00 ; Add zero to the high byte (add the carry)
sta ptr+1
; This is programmed for the 6502
; to simulate an indirect read
ldy #$00
Next_Char lda (ptr),Y
; For the 65C02
;Next_Char lda (ptr)
cmp #'*' ; '*' is the end of string character
beq End_String
;
; A character of the string is in the .ACC. I assume you have a
; routine that drives your display that accepts ASC II codes
; called 'Print_Char'.
jsr Print_Char
;
; Increment the 'ptr' into the string ==> Do the next character
inc ptr
bne Next_Char
inc ptr+1
jmp Next_Char
;
; Put the address of the '*' back into the stack
; pretending it is the last byte of a JSR statement
; causing the processor to return to execution the
; the first byte after the '*'.
;
End_String lda ptr+1
pha
lda ptr
pha
rts
;
;
3) The Reburnable EPROM
-----------------------
Generalized induction states: if the first can be proven and a
(n+1) can be proven then the proposition is true. In the case
proposed, that of a reburnable EPROM, the first is the first version
of the software programmed into the EPROM, there is no problem with
this process. To have later versions of the program in the same
EPROM we must have an (n+1) or a way to link from one version to the
next.
Erased EPROMs or unprogrammed areas of an EPROM are filled
with $FF. $FF is a valid data value that can be read, compared,
acted upon. As an opcode, $FF is the very rare Rockwell 65C02
page 4
command BBS7 zpage,rel, a conditional branch that tests the if bit 7
of a zero page address is set. If we put a restriction on all
versions of the software that none may begin with this instruction
then we may proceed.
If one version of a program were executing in the EPROM, it
could look into an area of memory reserved for a future version. If
the value of the memory location was $FF, then the superseding
version is not there yet and the program would continue executing
since it is the most current version. If superseding version were
there, the program currently executing would JMP to the new version
since the version currently executing is obsolete. The next version
of the software would first test if yet another version of the
software was in the EPROM before it went on to execute. The (n+1)
link between one version and the next has been established provided
each version first check to see if the (n+1) version is there before
executing.
The limit of the number of versions is the size of the EPROM
divided by the size of a version of the software. If the software
(or test element) is small then many versions are possible in one
EPROM. Hence one EPROM can be used for several revisions of the
code. Each time the EPROM is reused it saves 20-25 minutes of
erasing time. This erasing delay is VERY irritating when
experimenting with time delays and device dependent subroutines
where many revisions may be necessary to achieve a satisfactory
final result.
There are several approaches to reusing EPROMS. The method
hinted above is a chain link method where only the most recent
version will run. The advantages are that no limited is preimposed
on the number of versions and each version can be of a different
length. An alternative would be a preprogrammed vector/jump table
(in the first version of the code) that jumps to only a specific
number of preset addresses depending on whether code is present at
those addresses or on some auxiliary condition. By this method only
a preset number of versions as determined by the number of vectors
put in the table and a preset (quantum) size for each version is
allowed. The advantage is of course, the user can decide just
before resetting the processor which version of the code to run. By
setting switches on a VIA port for instance, these user determined
values could be read internally to select the jump point. This may
be very useful in comparing the operation of two algorithms or in
binary searches where 'which is better' decisions are made and
constants modified in smaller increments.
There are some mechanical details that must be worked out.
Your EPROM must contain the reset vector on the first version and
every version after. Usually the first version of the code is put
at the beginning of the EPROM and the reset vectors go at the end.
To avoid programming the spaces in between on the first version (to
leave them blank), there are two alternatives. The first version
and the reset vector could be programmed in two separate writings
where the programming zone is carefully selected by the user. The
second alternative is to fill unused spaces in EPROM space with $FF
in the binary file. When the $FF are programmed into the EPROM,
those locations will be left blank. This is possible because there
is no difference between a programmed $FF and a blank EPROM location
that reads $FF. On later versions of the code the user must
carefully select the target zone of the EPROM to program only the
page 5
locations required for the version being entered.
There is no limit to the permutations on this idea. If you try
one of the methods suggested and think of a better one yourself then
great, use it. The chain and the jump table methods will be coded
in example code fragments. Note that throughout the examples {UPV}
stands for the user program version to be tested.
Chain Method:
-------------
Assemble the Code with the following command line:
tasm -65 -b -fff reuse.asm reuse.bin reuse.lst
-fff stands for 'f' for fill the rest of memory with 'ff' $FF.
The will result in areas not specifically programmed to default to
$FF which will not program the memory location.
2) Determine the length of the code. Determine the value of an
address that will be beyond the current version of the program. We
will call this address 'Beyond0'. For the first version code the
following:
.ORG $E000
SEI ; Initialize the Stack
LDX #$FF ; You have to do it every time
TXS
;
LDX #$00 ; Wait for extra RST bounces
LDY #$00
InitDelay DEX
BNE InitDelay
DEY
BNE InitDelay
;
LDA Beyond0
CMP #$FF
BEQ ThisVersion
JMP Beyond0
ThisVersion {UPV}
.
.
.
Beyond0 = $E200 ; An address after the current version
.ORG $FFFC
.WORD $E000
.END
page 6
On subsequent versions code that start at address 'Beyond{n-1}'
(referred to as beyond{n} in the previous version) code this:
.ORG $E000
.BYTE $FF
;
.ORG Beyond{n-1}
LDA Beyond{n}
CMP #$FF
BEQ ThisVersion
JMP Beyond{n}
ThisVersion {UPV}
.
.
.
Beyond{n} = $???? ; Something Beyond version n
.ORG $FFFC
.WORD $E000
.END
This may look complex but look at that last fragment. All that has
to be changed to do a new version is Beyond{n-1}=Beyond{n} and
assign the new value for Beyond{n}. Once you get the hang of this
it will take five seconds in a text editor as opposed to 25 minutes
in the EPROM eraser.
The mechanical details of this are important. The .BYTE $FF
and the reset vector .ORG $FFFC \ .WORD $E000 are put in later
versions of the code even though they will not be put in the EPROM.
The .BYTE $FF is just a place holder to let TASM know that we are
producing an object file starting at $E000. The RST vector does not
have to be burned into the EPROM on subsequent runs because the it
is already in the EPROM from the first burn. The .BYTE $FF and the
RST vector mark the beginning and end of the EPROM space and forced
TASM to produce an binary file that is 8K long. This binary file
can then be loaded into the EPROM burner at buffer address 0000 and
except for the $E000 offset all the addresses will match.
After the correct Beyond address is calculated the program
should be reassembled with the -fff option.
On the first version the whole EPROM is programmed. The only
slightly tricky part comes later. ONLY the part of the program that
deals with new version of the code should be burned in after the
first version is already in. This requires the user to either use a
selective copy option in the EPROM burner menu or requires the
SOURCE and TARGET ZONES to be redefined (different EPROM burners are
different). This whole process is sure to take the average ENG SCI
student less than 25 minutes to figure out so it should pay for
itself the first time that erasing is not necessary.
Vector Jump Method
------------------
Assemble the code as before. Determine how long a version will
be at its maximum and allow a margin of safety. Allocate a spare 3
or 4 bits on a VIA port to communicate to the CPU which version to
page 7
run. Code this for the beginning of the EPROM:
.ORG $E000
;
PORT = $A001
;
SEI ; Initialize the Stack
LDX #$FF ; You have to do it every time
TXS
;
LDX #$00 ; Wait for extra RST bounces
LDY #$00
InitDelay DEX
BNE InitDelay
DEY
BNE InitDelay
;
LDA PORT
AND #$07
TAX
BNE G0
JMP Version0
G0 DEX
BNE G1
JMP $E400
G1 DEX
BNE G2
JMP $E800
G2 DEX
BNE G3
JMP $EC00
G3 DEX
BNE G4
JMP $F000
G4 DEX
BNE G5
JMP $F400
G5 DEX
BNE G6
JMP $F800
G6 JMP $FC00
;
Version0 {UPV}
;
.ORG $FFFC
.WORD $E000
.END
On subsequent versions:
.ORG $E000
.BYTE $FF
;
.ORG $F000 ; Version 4
;
{UPV4}
;
.ORG $FFFC
.WORD $E000
.END
page 8
The jump vector table can be implemented more easily with a PUSH
ADDRESS and RTS TRICK. (see trick #1 in this file)
.ORG $E000
;
PORT = $A001
;
SEI ; Initialize the Stack
LDX #$FF ; You have to do it every time
TXS
;
LDX #$00 ; Wait for extra RST bounces
LDY #$00
InitDelay DEX
BNE InitDelay
DEY
BNE InitDelay
;
LDA PORT
AND #$07
BEQ Version0
ASL A
ASL A
ASL A
ASL A
ASL A
ASL A
CLC
ADC #$DF
PHA
LDA #$FF
PHA
RTS
;
Version0 {UPV0}
;
.ORG $FFFC
.WORD $E000
.END