Dan's 6502 build, aka, The WOPR Jr.

Building your first 6502-based project? We'll help you get started here.
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

Chip is 65C02. No NMOS here.

I'm not sure what interrupt question you want answered. If I'm understanding though, even if I RTS to a garbage location, my ISR should still work. Yet it never goes back high.

I'm not knowingly calling any WAI or BRKs. Don't know what a JAM instruction is.

I don't understand how my calling routine could be effected by my saving the regs on the stack. The subroutine is not reliant on previous register values, and why would that matter anyway? Doesn't push leave the register unchanged?
User avatar
Dr Jefyll
Posts: 3526
Joined: 11 Dec 2009
Location: Ontario, Canada
Contact:

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dr Jefyll »

Dan Moos wrote:
Chip is 65C02. No NMOS here.
Okay, cool -- thanks.
Quote:
If I'm understanding though, even if I RTS to a garbage location, my ISR should still work. Yet it never goes back high.
Rather than RTS, don't you mean RTI? Not confusing interrupts and subroutine calls, I hope. Just checking!
Quote:
I'm not sure what interrupt question you want answered.
I just want an overview of all the relevant pieces of this problem so I understand what is supposed to happen. I haven't read the entire thread; sorry it's too long. So, I'm relying on the summary you gave earlier tonight:
  • "There is a terminal read routine that takes data from the ACIA, and puts it in the COM1_RXBUF string. Every time there is a keypress, that routine checks to see if it was the "enter" key. If it was, it puts a null at the end of the COM1_RXBUF string, and calls the naughty subroutine that's having the problem. That routine's job is to take the string that looks like, for random example, "read 8000 80FF" and turn it into "read<null>8000<null>80FF <null>" It also stores indices in the ARG_INDEX array so that the parser can extract them. The parser would be the next thing called if nothing crashes."

So, does that include the part which involves interrupts? It almost sounds as if the whole thing (except the parser) might be included in the ISR, but you don't say. The summary is helpful, but it seems to omit some key details.
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

I see the confusion. This is not my ISR. Not is it running during my ISR. My ISR just sets a flag that tells my main loop a key has been pressed. Thus, I do indeed mean RTS, not RTI.
User avatar
Dr Jefyll
Posts: 3526
Joined: 11 Dec 2009
Location: Ontario, Canada
Contact:

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dr Jefyll »

Quote:
even if I RTS to a garbage location, my ISR should still work. [...] I do indeed mean RTS, not RTI.
Looking at this with fresh eyes this morning I see I somehow mis-read -- sorry. Indeed you could RTS to a garbage location. And "garbage" to me means PC is eating something it shouldn't, either data or irrelevant code. Or I/O :shock: or uninitialized/unpopulated memory. Often the errant PC will end up repeating a loop of some kind. But it is still fetching instructions and at least trying. :)

A JAM instruction is an undefined opcode which, on an NMOS chip, causes it to cease fetching instructions. It's considered a bug. Only a reset will get you out of the lockup. Modern WDC chips have an instruction whose effect is similar or identical, but they gave it a new opcode and an official name: STP ( SToP ). The only trigger to exit the lockup is if you do a reset (give the CPU's RESB input a low pulse). Another new opcode is WAI (WAIt), which is similar but -- more conveniently -- it accepts the exit trigger on the CPU's IRQB input instead. This works even after an SEI, so you can forgo the actual interrupt if you want to. The datasheet has the details.

OK, so: the ISR sets a flag that tells the main loop a key has been pressed. Is it also the ISR's job to read the the peripheral's status register/whatever (and thus allow the IRQ line to return high)? Y' might wanna show us the ISR too, please -- also any other code that may be pertinent.
Last edited by Dr Jefyll on Sun Feb 11, 2018 5:36 pm, edited 1 time in total.
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

Here I post more code. I tried to fix the horrible formatting a simple cut and paste winds up with. Hopefully I didn't create typos in that process.

EDIT: my formatting turned out no better. sorry :P

Here is the ISR. As you can see, it does as little as possible. I don't think it is related to the problem, as it will long have finished, and multiple JSR's called by the time the problem happens.

Code: Select all

;***************************
;Interrupt service routine *
;***************************   
  
isr:      	PHA
		
		LDA   	COM1_STA   	;get ACIA status register
		
      		BPL   	end_isr     	        ;if no IRQ, return from ISR
      		LDA	       #$FF 
      		STA	       READ_FLAG	 ;main loop pols this to see if a key has been presses
	  	
end_isr:    	PLA
		RTI
Also, here is the snippet in my main loop that deals with the flag the ISR sets.

Code: Select all

inf_loop:	LDA	   READ_FLAG	;see if key was pressed. ISR sets this flag
		BNE	   read_irq	        ;not 0? Flag was set
		JMP      inf_loop 	        ;no flag. Loop as usual
read_irq:	STZ	   READ_FLAG	;flag was set. Clear it
		JSR      terminal_r	;process the keypress
		JMP      inf_loop	        ;loop as usual
Finally, here is the terminal_r subroutine. This routine eventually calls process_cmdln, which is the troublesome routine. I edited my comments to reflect our conversation in a few spots, so look for that.

Code: Select all

;**************************
;Terminal read subroutine *
;**************************    
 
terminal_r:   
               
		PHA
		PHX
		PHY
	
		LDX   	STRING_INDEX   	;get string index. This is index is the current position in       the input buffer
		LDY   	COM1_DAT   	        ;get data  from ACIA Rx   
      		CPY   	#$0D      	                ;check if enter was pressed
      		BEQ   	process_cmd   	        ;if enter pressed, process the input string
	      	JSR	        com1_TX_chk	        ;wait til TX buffer clears
	      	TYA		;I can't remember why I initially use Y, and then transfer it. Clue maybe?
      		STA   	COM1_DAT
      		STA   	COM1_RXBUF,X   	;place recieved data into input buffer
      		INX         		                        ;increment it
      		STX   	STRING_INDEX   	;put incremented version back in memory
      	
      		PLY
      		PLX
      		PLA
      		RTS
process_cmd:  
                STZ   	COM1_RXBUF,X   	;put NULL on end of string
      		JSR   	process_cmdln	        ;index arguments, if any. This is the subroutine that crashes	
      		STZ   	STRING_INDEX   	;reset index. When it crashes, this line is never reached
      		JSR   	parse_cmd   	        ;parse the command
      		JMP   	(SELECTED_CMD)      ;run routine for selected command
rtn_to_tr: 	LDA   	#<prompt   	        ;get lower byte of prompt message
      		STA   	STRING_PTR   	        ;store it in first byte of the string pointer
      		LDA   	#>prompt   	        ;get upper byte of prompt message
      		STA   	STRING_PTR+1   	;store it in second byte of string pointer     
      		JSR   	terminal_w   	        ;display prompt
      		PLY
      		PLX
      		PLA
      		RTS
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

I fixed another error in my post. I had incorrectly commented that the stack calls in terminal_r cause the problem. That is incorrect. Those calls work. It is the calls in process_cmdln, the routine I posted earlier. Sorry. I edited that comment out just now
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by barrym95838 »

Code: Select all

     ...
     JMP  (SELECTED_CMD)      ;run routine for selected command
     ...
I'm not sure (because AFAIK you haven't posted a source for any SELECTED_CMDs here), but it seems to me that you could be unbalancing your stack and setting yourself up for failure here. Since this is a JMP and not a JSR, it is the duty of each of your SELECTED_CMDs to JMP rtn_to_tr: when they're done doing their thing, instead of RTS, which if I'm not mistaken would cause a sudden swerve into the weeds like the one you describe. There is no JSR (abs) on the 65c02, but it can be easily simulated with a little "springboard", if you decide to pursue that path.

Mike B.
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

They do indeed jump back. I do it this way because I don't know what routine I'm going to run until the last minute. There is a jump, not a RTS at returning end. There are no JSRs unmatched with RTSs. Each
Jumped o to routine returns to this spot via a another jump
Never the less, this all works.
User avatar
Dr Jefyll
Posts: 3526
Joined: 11 Dec 2009
Location: Ontario, Canada
Contact:

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dr Jefyll »

Quote:
They do indeed jump back.
OK, understood.
Quote:
I do it this way because I don't know what routine I'm going to run until the last minute.
Alright. (Ideally what you want is a JSR using indirect mode. And that can be faked with a springboard as Mike said -- by manually pushing the return address then doing an indirect JMP, but never mind that now.)

Code: Select all

            JSR      process_cmdln           ;index arguments, if any. This is the subroutine that crashes   
            STZ      STRING_INDEX      ;reset index. When it crashes, this line is never reached
And "when it crashes" is also when the interrupt line gets stuck low? I think it's more accurate to say it goes and stays low at the time of the following keypress -- right?

This is quite a head-scratcher! Just in case, can you also show us the labels that define all the pertinent memory addresses please.
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

Yes, it is the key press immediately following the crash that sticks the IRQ line low.

Here is my complete label space.

Code: Select all

      
;*************      
;UART Labels *
;*************

;COM1. Main UART		      
COM1_DAT = $5000
COM1_STA = COM1_DAT+1
COM1_CON = COM1_DAT+3
COM1_COM = COM1_DAT+2

;************************
;Display port VIA Labels*
;************************

VIA_1PORTB = $6000
VIA_1PORTA = VIA_1PORTB+1
VIA_1DDRB  = VIA_1PORTB+2
VIA_1DDRA  = VIA_1PORTB+3



;******************
;0 page variables *
;******************

STRING_PTR    	.DS    	$02     ;pointer to current output message string
STRING_INDEX    .DS    	$01     ;index into input buffer
LINE_CNT    	.DS    	$01     ;clear screen line count
ARG_INDEX	.DS	$03	;indices into command line string to arguments
ARG_CNT		.DS    	$01	;number of command line arguments
CMD_INDEX   	.DS    	$02     ;index into command string list	
SELECTED_CMD    .DS    	$02     ;pointer to selected command routine
CMDLN_ERROR	.DS	$01	;command line error condition
ARG_TEMP	.DS 	$04	;holds the 4 bit decoded character values of input arguments
ARG_VALUE	.DS 	$02	;the final 16 bit numeric value of the arguments
ADR_START	.DS	$02	;holds lower limit of commandline memory ops. 
ADR_END		.DS	$02	;holds upper limit of commandline memory ops.
HEX_INPUT	.DS	$02
HEX_HIGH	.DS	$01	;holds ascii representation of high nibble of displayed hex byte
HEX_LOW		.DS	$01	;holds ascii representation of low nibble of displayed hex byte
HEX_DSPLY	.DS	$28	;holds strings of hex/ascii data to be displayed
SCRN_BF_TOP	.DS	$02	;top of ring buffer that contains screen contents. Physical top of screen.
SCRN_BF_CUR	.DS	$02	;pointer to current cursor position in display buffer
ROW		.DS	$01	;current cursor row position
COLUMN		.DS	$01	;current cursor column position
CHARACTER	.DS	$01	;current character to be displayed
BUG_LITE	.DS	$01	;debug status light on VIA_1 PA0
READ_FLAG	.DS	$01

;***************************
;memory starting at page 2 *
;***************************

	     	.ORG   	$0200
	     	
	     	
SCRN_BUF	.DS	$C80	;Screen buffer. 3,200 bytes for an 80*40 character display.   
                           	;Placed at start of page for ease of inderect indexing


SCRN_BUF_ENDL = $80		;C80 is 3,200 for a 3,200 byte 80*40 character screen buffer
SCRN_BUF_ENDH = $0E
                          	
                           	
                           	
COM1_RXBUF   	.DS   	$100    ;UART input buffer
			


;**********************
;ROM data starts here *
;**********************

      		.ORG 	$8000
      
      
;*****************
;command strings *
;*****************
     
commands:   
cmd_empty: 	.BYTE   $00   		;empty:user pressed enter with no characters
cmd_write:  	.BYTE   "write", $00   	;command to write to memory
cmd_read:   	.BYTE   "read", $00  	;command to read from memory
cmd_test:   	.BYTE   "test", $00   	;command for system self-test
cmd_clr:	.BYTE	"clr", $00	;command to clear screen
cmd_list_end:   .BYTE   $FF      	;marker for end of list

;******************************
; pointers to command strings *
;******************************

cmd_indices:
empty_index:    .WORD   cmd_empty
write_index:    .WORD   cmd_write
read_index:     .WORD   cmd_read
test_index:     .WORD   cmd_test
clr_index	.WORD 	cmd_clr

;******************************
;pointers to command routines *
;******************************
	
cmd_routines:
empty_rtn_ptr:  .WORD   empty_rtn
write_rtn_ptr:  .WORD   write_rtn
read_rtn_ptr:   .WORD   read_rtn
test_rtn_ptr:   .WORD   test_rtn
clr_rtn_ptr	.WORD	clr_rtn
no_cmd_rtn_ptr: .WORD   no_cmd_rtn 

;************
;UI strings *
;************  
      
strings:
prompt:      	.BYTE   $0D, $0A, $0A,  "WOPRjr:" ,$00    	;commandline prompt
empty_msg   	.BYTE   $0D, $0A, $00         			;empty string
message:   	.BYTE   $0D, $0A, "You entered: ", $00 	 	;input response message
write_msg:   	.BYTE   $0D, $0A, "writing", $00   		;temp write message
read_msg:   	.BYTE   $0D, $0A, $0A, "No arguments entered." 	;line 1 of default read message
read_msg2:	.BYTE	$0D, $0A,"Enter a single HEX address, or 2 addresses as a range", $00 ;line 2
test_msg:   	.BYTE   $0D, $0A, "testing", $00
clr_msg:	.BYTE	$0D, $0A, "clearing", $00		;temp clear screen message
nl_cr:		.BYTE	$0D, $0A, $00				;newline, carriage return
no_cmd_msg:   	.BYTE   $0D, $0A, $0A, "Unknown command", $00 	;default message for unknown string
bad_args_msg: 	.BYTE	$0D, $0A, $0A, "Bad, or too many arguments", $00	;wrong or too many arguments
      
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

BTW. , About my indirect jumps: Is the reason to push the return address and JMP() so that I can have an RTS in the routine, making it a little more universal, rather than only being able to return to a hardcoded location? Makes sense if so, although right now I can't imagine needing that flexibility in these particular routines. Maybe a good practice for my future indirect subroutine jumps?
User avatar
Dr Jefyll
Posts: 3526
Joined: 11 Dec 2009
Location: Ontario, Canada
Contact:

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dr Jefyll »

Dan Moos wrote:
BTW. , About my indirect jumps: Is the reason to push the return address and JMP() so that I can have an RTS in the routine, making it a little more universal, rather than only being able to return to a hardcoded location? Makes sense if so, although right now I can't imagine needing that flexibility in these particular routines. Maybe a good practice for my future indirect subroutine jumps?
Right. There's still a hardcoded return address, but it appears only once in the caller routine, not n times (once in each of the callee routines). Just remember we're in 6500-land, so it's Return_Address minus one the caller needs to manually push.
Last edited by Dr Jefyll on Sun Feb 11, 2018 9:04 pm, edited 1 time in total.
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

BTW. I love these kind of bugs. Forces me to gain a much deeper understanding of how the system works.

When reading my code, I offer this disclaimer: this is the first and only thing I've ever written in 6502 assembly, and its been pecked away slowly over many months. You might notice that my skills were at different places in different sections! Also, I'm sure there is old code that never gets used form earlier iterations that I haven't cleaned up yet. A thorough re-write is in the near future. Other than this issue, it all works perfectly though.
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by barrym95838 »

The "springboard" method is easier than manually pushing any hard-coded addresses. All you need to do is find a little out-of-the-way spot to stick

Code: Select all

springboard:
     JMP  (SELECTED_CMD)
then

Code: Select all

     GET  some_stuff_done
     JSR  springboard
     GET  more_stuff_done
and let the assembler figure the addresses and the 65c02 do the pushing for you.

Mike B.
Dan Moos
Posts: 277
Joined: 11 Mar 2017
Location: Lynden, WA

Re: Dan's 6502 build, aka, The WOPR Jr.

Post by Dan Moos »

Damn, that's clever
Post Reply