6502 RTI behavior / debugging ideas...
6502 RTI behavior / debugging ideas...
What happens if the /IRQ input is still low when the RTI instruction is executed? It seems that not a single instruction of the interrupted program is executed. Instead the IRQ handler is called again just after the RTI instruction and the PC keeps stuck on the same position. This behavior ruined my plans for a step by step debugger program, so, I need other ideas...
The most obvious is to place a BRK op-code just after the current instruction (assuming the code under debug is placed in RAM). The original op-code will be restored by the BRK routine before continuation. This would implement the "execute until next" function of the debugger, but not the desired "single step" one. In order to do this the original op-code must be decoded, the Bcc, JMP, JSR, RTS and RTI op-codes have to be identified, the next execution address computed and, then, the BRK op-code placed on that address. In short, we need half a 6502 emulator running on a 6502 system. I wonder if there is another simpler way of doing this.
The most obvious is to place a BRK op-code just after the current instruction (assuming the code under debug is placed in RAM). The original op-code will be restored by the BRK routine before continuation. This would implement the "execute until next" function of the debugger, but not the desired "single step" one. In order to do this the original op-code must be decoded, the Bcc, JMP, JSR, RTS and RTI op-codes have to be identified, the next execution address computed and, then, the BRK op-code placed on that address. In short, we need half a 6502 emulator running on a 6502 system. I wonder if there is another simpler way of doing this.
It looks like you're right - I checked on visual6502, and IRQ doesn't run even one opcode. This surprises me, because I'm sure I've heard of situations where code proceeds very slowly and the cause is found to be a stuck interrupt pin. Maybe different 6502 versions act differently?
I think your sketch of placing a BRK, and having to be careful about the instructions which cause a change of PC, is absolutely normal. It's not as bad as half a 6502 emulator, but it is worse than you'd hope. In some systems there might be a 6522-type timer where you can fire an IRQ very soon after the last one.
I think your sketch of placing a BRK, and having to be careful about the instructions which cause a change of PC, is absolutely normal. It's not as bad as half a 6502 emulator, but it is worse than you'd hope. In some systems there might be a 6522-type timer where you can fire an IRQ very soon after the last one.
jesari, the 6502 outputs a signal called SYNC, which indicates when the next opcode fetch is in progress. If you assert IRQ right after SYNC is negated, the CPU will finish the execution of the current instruction before branching to the interrupt handler.
Put another way, the CPU samples IRQ only when SYNC is asserted.
(Or, in the case of the 65816, only when VPA/VDA are both asserted.)
Put another way, the CPU samples IRQ only when SYNC is asserted.
(Or, in the case of the 65816, only when VPA/VDA are both asserted.)
BigEd wrote:
In some systems there might be a 6522-type timer where you can fire an IRQ very soon after the last one.
Code: Select all
___
/ __|__
/ / |_/ Groetjes, Ruud
\ \__|_\
\___| URL: www.baltissen.org
The timer solution looks easier to code and, BTW, I got a VIA with unused timers. This approach has the disadvantage of being system-dependent. On the other hand I can remember a nice debugger for the Apple II (but I can't remember its name) and that computer had no interrupts at all. It must have resorted to the BRK approach...
Thanks for the tips.
Just to be accurate... I tested the RTI instruction on an real NMOS 6502 made by Rockwell during 1981.
I have worked with a single stepper for the C64 using this method so it should work.
Thanks for the tips.
Just to be accurate... I tested the RTI instruction on an real NMOS 6502 made by Rockwell during 1981.
Ruud wrote:
BigEd wrote:
In some systems there might be a 6522-type timer where you can fire an IRQ very soon after the last one.
jesari wrote:
It must have resorted to the BRK approach...
- GARTHWILSON
- Forum Moderator
- Posts: 8775
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
See my tips #36 and 37 at viewtopic.php?t=342&start=36 .
I also used to use a debugger routine that I would call with a JSR where I wanted to check and see what was in various registers, check I/O status, particular memory locations, etc.. Lacking a logic analyzer, I have also single-cycled the clock so I could probe the buses at each cycle, something that could not be done on the NMOS 6502 since it could not be stopped without losing its registers' data like the 65c02 can. But obviously these approaches are worthless in a real-time situation where the hardware that the computer is supposed to control won't work at all if the computer isn't going at least close to the normal speed. I have had a few projects like that.
I believe the AIM-65 computer used the Sync output to single-step.
I also used to use a debugger routine that I would call with a JSR where I wanted to check and see what was in various registers, check I/O status, particular memory locations, etc.. Lacking a logic analyzer, I have also single-cycled the clock so I could probe the buses at each cycle, something that could not be done on the NMOS 6502 since it could not be stopped without losing its registers' data like the 65c02 can. But obviously these approaches are worthless in a real-time situation where the hardware that the computer is supposed to control won't work at all if the computer isn't going at least close to the normal speed. I have had a few projects like that.
I believe the AIM-65 computer used the Sync output to single-step.
kc5tja wrote:
If you assert IRQ right after SYNC is negated, the CPU will finish the execution of the current instruction before branching to the interrupt handler.
Garth Wilson wrote:
I believe the AIM-65 computer used the Sync output to single-step.
Edit: A key point is that the Debugger and the Code Being Debugged reside in separate memory chips, and the latter's chip-select, combined with SYNC, is what triggers the NMI. Please refer to my subsequent post.
Unlike IRQ, NMI is an edge-sensitive input, triggered by a negative-going (logic high to logic low) transition. Oddly enough that means the KIM-1 arrangement actually triggers NMI at the beginning -- not the end -- of the cycle during which SYNC is asserted. But NMI is not immediately recognized. It needs to remain low for at least 2 clock cycles, so recognition occurs 1 cycle after SYNC is negated. And that's evidently still in time to preempt the following opcode fetch.
Note that gate U26 conveniently ensures that single-stepping will NOT occur in the ROM that fields the NMI interrupt. And, a mechanical switch may perhaps not be your preference, so another gate (driven off a peripheral IO line) may be required. But no timer is needed. With or without a mechanical switch, probably this is what was used in the "nice debugger for the Apple II" that you mentioned.
Note that U26 is an open-collector type gate. You can use a regular NAND gate here, but not if NMI is also driven by another device.
Big Ed wrote:
I'm sure I've heard of situations where code proceeds very slowly and the cause is found to be a stuck interrupt pin. Maybe different 6502 versions act differently?
cheers,
Jeff
Last edited by Dr Jefyll on Wed Nov 17, 2010 8:05 am, edited 2 times in total.
The timed IRQ seems to work. I tested it with the following code where I reduced the delay until just before the PC went stuck (looks like I'm missing 1 cycle somewhere...).
Here is a log. Forget about ***BRK*** and (-2). I'm using the old BRK handler for IRQ (lot of things need to be changed). The code being traced was:
E0DF CLV
E0E0 LDA #8
E0E2 AND $201
E0E5 BNE $E0ED
Thanks to this trick I think I can go ahead with the debugger.
Thanks a lot for all the ideas.
Code: Select all
; new IRQ just after returning and fetching 1 op-code
lda #$A0
sta IER ; enable TIMER2 IRQ
lda #20 ; 21.5 cycles until irq
sta T2CL ; write latch
lda #0
sta T2CH ; Write counter, clear IRQ
; 22 cycles until return
pla ; restore registers
tay
pla
tax
pla
defISR: rti
E0DF CLV
E0E0 LDA #8
E0E2 AND $201
E0E5 BNE $E0ED
Code: Select all
*** BRK ***
PC=$E0DF(-2) P=.VrB..ZC A=$0A X=$00 Y=$00 S=$FF
qmdg>q
*** BRK ***
PC=$E0E0(-2) P=..r...ZC A=$0A X=$00 Y=$00 S=$FF
qmdg>q
*** BRK ***
PC=$E0E2(-2) P=..r....C A=$08 X=$00 Y=$00 S=$FF
Thanks a lot for all the ideas.
kc5tja wrote:
jesari, the 6502 outputs a signal called SYNC, which indicates when the next opcode fetch is in progress. If you assert IRQ right after SYNC is negated, the CPU will finish the execution of the current instruction before branching to the interrupt handler.
Put another way, the CPU samples IRQ only when SYNC is asserted.
Put another way, the CPU samples IRQ only when SYNC is asserted.
André
kc5tja wrote:
Most debuggers for the 6502 rely on the BRK approach, precisely because it ought to work on most 6502 platforms.
Code: Select all
___
/ __|__
/ / |_/ Groetjes, Ruud
\ \__|_\
\___| URL: www.baltissen.org
Ruud wrote:
BigEd wrote:
In some systems there might be a 6522-type timer where you can fire an IRQ very soon after the last one.
André
jesari wrote:
The timed IRQ seems to work....
Thanks to this trick I think I can go ahead with the debugger.
Thanks to this trick I think I can go ahead with the debugger.
Not wishing to redirect your efforts or change the approach, but for interest's sake I had a look at the appleII monitor ROM, around address FA43.
The approach taken there in single-stepping is to copy the opcode and operands into RAM and execute there. Again, special handling for instructions which change flow (branches are adjusted to branch to a branch-taken handler). The instructions are operating on register contents which are restored before and saved after execution. To do all the above the code makes use of the nearby disassembler to compute instruction length. It's worth a read (although I'm not suggesting it's any better than what you have)(*)
Cheers
Ed
(*) Just realised: the test for 'is this a branch' does some masking and then compares with #04. If equal, it's a branch. So the branch offset is adjusted to #04. Which is already in the accumulator! See FA74.