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