Shop OBEX P1 Docs P2 Docs Learn Events
ISR strangeness — Parallax Forums

ISR strangeness

Chip,

This one is for you. Trying to track down an issue that i described elsewhere, I ended up with the following code:
con
	sys_clk = 50_000_000
	baud_rate = 115_200
	
	rx_pin = 63	
	edg_int = 5
	
dat
		orgh
		org 0

		or	dirb, #$FF

		mov	ijmp1, #rx_isr
		
		setedg	#(%10_000000 | rx_pin)
		setint1	#edg_int
		
rx_loop		waitint

		notb	outb, #6			' COMMENT THIS INSTRUCTION TO SEE DIFFERENT INTERRUPT BEHAVIOR

		tjz	rx_buff, #rx_loop

		mov	rx_buff, #0
		notb	outb, #7

		jmp	#rx_loop

'--------------------------------------------------------------------------------------------------
rx_isr		setb	outb, c
		add	c, #1
		
		tjnz	rx_buff, #.ret
		
		mov	rx_buff, #1

		rep	@.endrep, #10			' make sure to run for much longer than we should
		waitx	full_bit_time			' in the ISR.  At least one more edg event will
.endrep							' have occurred before the ISR completes.

.ret		reti1

'--------------------------------------------------------------------------------------------------
full_bit_time	long	sys_clk / baud_rate
half_bit_time	long	(sys_clk / baud_rate) >> 1

c		long	0
rx_buff		res	1
rx_cnt		res	1

At this point, it's meaningless code, except that is seems to expose an issue.

If you run this code as it is, you will get one LED sequence. If you comment line mentioned in the code above, you will get a different sequence (ignoring the LED that's being toggled by the commented-out line). You will notice that the ISR has a long-running REP, which is only there to force the issue/bug. However, this should not impact the observed change in behavior of commenting out the annotated line.

This has been very elusive to pin down, so this test code is probably not as concise as it could be. But hopefully it's enough for you to spot the problem.

Comments

  • SeairthSeairth Posts: 2,474
    edited 2015-10-30 02:37
    by the way, what happens when an interrupt occurs during a REP?

    Edit: that question is just an aside. When I replaced the rep in the above code with a djnz, it made no difference to the behavior I describe above. At least, none that I noticed...
  • Seairth wrote: »
    by the way, what happens when an interrupt occurs during a REP?

    Edit: that question is just an aside. When I replaced the rep in the above code with a djnz, it made no difference to the behavior I describe above. At least, none that I noticed...

    Chip's documentation says:
    When an interrupt event occurs, certain conditions must be met before the interrupt branch can happen:

    ALTDS must not be executing
    AUGS must not be executing or waiting for a S/# instruction
    AUGD must not be executing or waiting for a D/# instruction
    SETQ must not be executing
    SETQ2 must not be executing
    REP must not be executing or active
    STALLI must not be executing or active

    Once these conditions are all met, any pending interrupt is allowed to branch, with priority given to INT1, then INT2, and then INT3.

    I took that to mean there was an implied STALLI/ALLOWI wrapped around these.
  • cgraceycgracey Posts: 14,152
    I'll look at this code in a little bit.

    There is no automatic STALLI/ALLOWI going on. Those only happen if you execute them in your code.
  • cgraceycgracey Posts: 14,152
    edited 2015-10-30 04:35
    I haven't run your code, but I think I know what is happening.

    WAITINT is waiting for an interrupt. The next instruction is already in the pipeline. WAITINT stops waiting when an interrupt occurs. The next instruction executes, while the interrupt CALLD is being injected into the pipeline. The next instruction that executes is CALLD.

    So, the instruction after WAITINT always executes before the interrupt branch occurs.
  • cgracey wrote: »
    I haven't run your code, but I think I know what is happening.

    WAITINT is waiting for an interrupt. The next instruction is already in the pipeline. WAITINT stops waiting when an interrupt occurs. The next instruction executes, while the interrupt CALLD is being injected into the pipeline. The next instruction that executes is CALLD.

    So, the instruction after WAITINT always executes before the interrupt branch occurs.

    Doh! Of course!
  • Chip,

    That was exactly my problem. Both in this code and in the code in FDS demo with interrupts.

    Thanks for spotting that!
  • mindrobots wrote: »
    Chip's documentation says:

    :blush: Thanks for reminding me, by the way. I really should go read that document before I ask any more questions.
  • cgraceycgracey Posts: 14,152
    Seairth wrote: »
    mindrobots wrote: »
    Chip's documentation says:

    :blush: Thanks for reminding me, by the way. I really should go read that document before I ask any more questions.

    Well, the doc's never said anything about WAITCNT executing the next instruction before the interrupt branch. I'll add that today.
  • Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?
  • Sapphire wrote: »
    Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?

    No, that's not true. The only reason I needed a NOP was because the next instruction (TJZ, in this case) that followed was dependent on a state change in an ISR. Except for this case (so far), a spacer instruction is not necessary.
  • cgraceycgracey Posts: 14,152
    Sapphire wrote: »
    Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?

    Let me think about this.

    I'd need to cancel the instruction in the pipe and inhibit the program counter from advancing, so that the return address is correct. It may be just a few gates. I'll look at it today.
  • cgracey wrote: »
    Sapphire wrote: »
    Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?

    Let me think about this.

    I'd need to cancel the instruction in the pipe and inhibit the program counter from advancing, so that the return address is correct. It may be just a few gates. I'll look at it today.

    I don't think this is necessary. Documentation of this particular case should be good enough.
  • cgraceycgracey Posts: 14,152
    edited 2015-10-30 16:51
    Seairth wrote: »
    cgracey wrote: »
    Sapphire wrote: »
    Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?

    Let me think about this.

    I'd need to cancel the instruction in the pipe and inhibit the program counter from advancing, so that the return address is correct. It may be just a few gates. I'll look at it today.

    I don't think this is necessary. Documentation of this particular case should be good enough.

    If it turns out to be simple enough to do, it would save the need to explain. As instructions go, it is maybe the oddest.
  • cgracey wrote: »
    Sapphire wrote: »
    Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?

    Let me think about this.

    I'd need to cancel the instruction in the pipe and inhibit the program counter from advancing, so that the return address is correct. It may be just a few gates. I'll look at it today.

    Please don't do it. Instead, could you make the assembler automatically add a NOP unless the user explicitly specifies otherwise?

  • cgraceycgracey Posts: 14,152
    edited 2015-10-30 21:49
    cgracey wrote: »
    Sapphire wrote: »
    Well that pretty much means you are going to need a NOP after every WAITINT. Why not flush the pipeline on WAITINT instead?

    Let me think about this.

    I'd need to cancel the instruction in the pipe and inhibit the program counter from advancing, so that the return address is correct. It may be just a few gates. I'll look at it today.

    Please don't do it. Instead, could you make the assembler automatically add a NOP unless the user explicitly specifies otherwise?

    Are you saying that you WANT that behavior? Right now, you could follow WAITCNT with a STALLI, some code, then ALLOWI. That would make it possible to always execute some bit of code before the interrupt branch occurs.

    If you could magically snap your fingers and have it either way, which would you prefer? I'm thinking that to make it work as people would expect it to is pretty simple - don't increment the PC on WAITINT and cancel the next instruction already in the pipe.
  • jmgjmg Posts: 15,173
    cgracey wrote: »
    If you could magically snap your fingers and have it either way, which would you prefer? I'm thinking that to make it work as people would expect it to is pretty simple - don't increment the PC on WAITINT and cancel the next instruction already in the pipe.

    'No surprises' is always best, but there does seem to be an Assembler level work around possible here ?

    A safe WAITINT would insert a NOP and there could be a Queued (WAIQINT ?) that trims the NOP, for experienced users who are very tight on code space, or know that the next opcode does not expect any INT updated information.
  • SeairthSeairth Posts: 2,474
    edited 2015-10-30 22:11
    cgracey wrote: »
    Are you saying that you WANT that behavior? Right now, you could follow WAITCNT with a STALLI, some code, then ALLOWI. That would make it possible to always execute some bit of code before the interrupt branch occurs.

    If you could magically snap your fingers and have it either way, which would you prefer? I'm thinking that to make it work as people would expect it to is pretty simple - don't increment the PC on WAITINT and cancel the next instruction already in the pipe.

    That STALLI trick is clever! I'm not sure how it could be used, but that is a unique and interesting feature!

    Leave things as they are.

    Idea: If you were to add a GETINTS instruction that non-destructively returned the 16 event flags and 3 interrupt flags, that STALLI trick might have all sorts of uses!

    Edit: there would also be room in GETINTS to return the three 4-bit interrupt configuration values.
  • cgraceycgracey Posts: 14,152
    edited 2015-10-30 22:13
    Seairth wrote: »
    cgracey wrote: »
    Are you saying that you WANT that behavior? Right now, you could follow WAITCNT with a STALLI, some code, then ALLOWI. That would make it possible to always execute some bit of code before the interrupt branch occurs.

    If you could magically snap your fingers and have it either way, which would you prefer? I'm thinking that to make it work as people would expect it to is pretty simple - don't increment the PC on WAITINT and cancel the next instruction already in the pipe.

    That STALLI trick is clever! I'm not sure how it could be used, but that is a unique and interesting feature!

    Leave things as they are.

    Idea: If you were to add a GETINTS instruction that non-destructively returned the 16 event flags and 3 interrupt flags, that STALLI trick might have all sorts of uses!

    Wow! We actually already have that in the form of SETBRK during a debug interrupt. I could easily make it always available through another instruction. It would just be a few logic gates, no mux's.

    Also, we should determine three more interrupt events to fill slots 13..16. Those would be freebies, too.
  • cgracey wrote: »
    Seairth wrote: »
    cgracey wrote: »
    Are you saying that you WANT that behavior? Right now, you could follow WAITCNT with a STALLI, some code, then ALLOWI. That would make it possible to always execute some bit of code before the interrupt branch occurs.

    If you could magically snap your fingers and have it either way, which would you prefer? I'm thinking that to make it work as people would expect it to is pretty simple - don't increment the PC on WAITINT and cancel the next instruction already in the pipe.

    That STALLI trick is clever! I'm not sure how it could be used, but that is a unique and interesting feature!

    Leave things as they are.

    Idea: If you were to add a GETINTS instruction that non-destructively returned the 16 event flags and 3 interrupt flags, that STALLI trick might have all sorts of uses!

    Wow! We actually already have that in the form of SETBRK during a debug interrupt. I could easily make it always available through another instruction. It would just be a few logic gates, no mux's.

    Also, we should determine three more interrupt events to fill slots 13..16. Those would be freebies, too.

    Chip, I edited my last post with an extension to the idea. Did you see it?
  • cgraceycgracey Posts: 14,152
    edited 2015-10-30 22:24
    Seairth wrote: »
    cgracey wrote: »
    Seairth wrote: »
    cgracey wrote: »
    Are you saying that you WANT that behavior? Right now, you could follow WAITCNT with a STALLI, some code, then ALLOWI. That would make it possible to always execute some bit of code before the interrupt branch occurs.

    If you could magically snap your fingers and have it either way, which would you prefer? I'm thinking that to make it work as people would expect it to is pretty simple - don't increment the PC on WAITINT and cancel the next instruction already in the pipe.

    That STALLI trick is clever! I'm not sure how it could be used, but that is a unique and interesting feature!

    Leave things as they are.

    Idea: If you were to add a GETINTS instruction that non-destructively returned the 16 event flags and 3 interrupt flags, that STALLI trick might have all sorts of uses!

    Wow! We actually already have that in the form of SETBRK during a debug interrupt. I could easily make it always available through another instruction. It would just be a few logic gates, no mux's.

    Also, we should determine three more interrupt events to fill slots 13..16. Those would be freebies, too.

    Chip, I edited my last post with an extension to the idea. Did you see it?

    Yes, it's already in there!
    SETBRK D/#   - during debug ISR’s
    
      D/# = %xxxx_PPPPPPPPPPPPPPPPPPPP_x_ABCDEFG
    
        %PPPPPPPPPPPPPPPPPPPP: 20-bit breakpoint address
                           %A: 1 = make INA/INB read pin states, not RAM
                           %B: 1 = interrupt on asynchronous ‘break’
                           %C: 1 = interrupt on breakpoint address match
                           %D: 1 = interrupt on INT3 ISR code (single step)
                           %E: 1 = interrupt on INT2 ISR code (single step)
                           %F: 1 = interrupt on INT1 ISR code (single step)
                           %G: 1 = interrupt on non-ISR code  (single step)
    
      If D is a register, %AA_BB_CC_DDDD_EEEE_FFFF_G_HHHHHHHHHHHHH is written back to D
    
                          %AA: INT3 state, 0x=inactive, 10=waiting, 11=executing
                          %BB: INT2 state, 0x=inactive, 10=waiting, 11=executing
                          %CC: INT1 state, 0x=inactive, 10=waiting, 11=executing
                        %DDDD: INT3 selector
                        %EEEE: INT2 selector
                        %FFFF: INT1 selector
                           %G: STALLI active
               %HHHHHHHHHHHHH: event traps
    

    We'd need to reduce the INTx state reporting to 1 bit each (inactive vs. waiting/executing), in order to fit 3 more event trap bits.

    Would this be useful, or just an invitation to madness? My friend Walter Banks said that some features are akin to a cannon pointed at the users' feet.
  • jmgjmg Posts: 15,173
    Seairth wrote: »
    That STALLI trick is clever! I'm not sure how it could be used, but that is a unique and interesting feature!
    IT does sound flexible, maybe some Debug could use that, and there might be some apps where a 'late param' that was context variant could be passed to the interrupt.
    Possibly also a poll for a rare, but more important case ?

  • ElectrodudeElectrodude Posts: 1,657
    edited 2015-10-30 22:27
    cgracey wrote: »
    Also, we should determine three more interrupt events to fill slots 13..16. Those would be freebies, too.

    Some ideas:
    - hardware stack full (i.e. pushing again would overflow)
    - hardware stack empty (i.e. popping again would underflow)
    - PTRx out of range (again, for stack)
    - cog X reached debug breakpoint (can already be done using trigger longs and debug ISR)
    - more timers
  • cgraceycgracey Posts: 14,152
    jmg wrote: »
    Seairth wrote: »
    That STALLI trick is clever! I'm not sure how it could be used, but that is a unique and interesting feature!
    IT does sound flexible, maybe some Debug could use that, and there might be some apps where a 'late param' that was context variant could be passed to the interrupt.
    Possibly also a poll for a rare, but more important case ?

    Yes! That's what I was kind of thinking.
  • jmgjmg Posts: 15,173
    cgracey wrote: »
    Also, we should determine three more interrupt events to fill slots 13..16. Those would be freebies, too.

    I still like the idea of an out-of-bound HUB fetch interrupt, which would allow SW DLL type memory management, and make the 512k less of a hard ceiling.

    Large Serial Flash is very cheap, and it is getting faster to access.

    eg P1 type speed running from external flash may be possible.

  • jmgjmg Posts: 15,173
    Some ideas:
    - hardware stack empty (i.e. popping again would underflow)
    Wouldn't that trigger all the time ?
    It is common to frequently run a stack with no-entries.

    A trap on actual underflow/overflow would be more useful

  • +1 for Hub out of bound interrupt.

    opens the door to some external memory management.

    Enjoy!

    Mike
  • jmg wrote: »
    Some ideas:
    - hardware stack empty (i.e. popping again would underflow)
    Wouldn't that trigger all the time ?
    It is common to frequently run a stack with no-entries.

    A trap on actual underflow/overflow would be more useful

    No. It would only trigger immediately after the pop that removed the last entry from the stack. Likewise, the stack-full interrupt would only trigger immediately after the push that filled the last entry.

    A trap on actual underflow/overflow would probably require radically changing how interrupts work. None of the other interrupts retry the instruction that caused them.
  • jmgjmg Posts: 15,173
    No. It would only trigger immediately after the pop that removed the last entry from the stack. Likewise, the stack-full interrupt would only trigger immediately after the push that filled the last entry.
    That's what I mean, that would trigger during normal code operation.
    A trap on actual underflow/overflow would probably require radically changing how interrupts work. None of the other interrupts retry the instruction that caused them.
    I'm not sure where retry comes from ?
    To me, limit checks are just against some number, so do not vary greatly in logic.
    A trap that fires on an actual fault, is more useful than one that fires on normally running code.
    I guess a workaround is to prefill the stack just to avoid the Trap in normal code, but that consumes 1 stack level.

Sign In or Register to comment.