506 lines
17 KiB
Plaintext
506 lines
17 KiB
Plaintext
====================================================================
|
||
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
|
||
|
||
|