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
|
|||
|
|
|||
|
|