| |
mankeli
Registered: Oct 2010 Posts: 146 |
How to flags?
I want to set a flag in NMI, basically
lda flags
ora #setbits
sta flags
then I want to check & clear the flag in main routine, and branch if the flag was not set. But since it's not possible to suppress nmi by SEI, I can't make a critical section around the handler. There will be always several instructions.
It's preferrable that the checking code would be only in one place. Currently i have
lda flags
and #testbits
php
eor flags
sta flags
plp
beq skip
..
skip:
I suspect there's a way to do this without the critical section but just wondering if someone knows already. |
|
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
If you can live with just having a single flag per byte, then use a read-modify-write instruction in main to check and clear the flag, eg if the flag is stored in bit zero of sb then
lsr sb
will atomically move the flag to carry and set sb to zero. |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
cool idea CJ. another way would be to have a flag handler flag, so in main code:
inc dontmesswiththeflagnmiplease
; handle flag here
dec dontmesswiththeflagnmiplease
nmi: lda dontmesswiththeflagnmiplease
beq skip
lda flags
ora #setbits
sta flags
skip
another way would be changing the nmi handler low byte before / after the critical code, to an nmi handler which doesnt touches the flag.
ps, well the second one, better inc / dec the high byte of the nmi vector |
| |
oziphantom
Registered: Oct 2014 Posts: 490 |
if you can afford it, use inc/dec to modify $01/$00 and hence you bank in the KERNAL and use its vector to get another NMI |
| |
mankeli
Registered: Oct 2010 Posts: 146 |
ChristopherJam's idea is neat, but uses byte per flag. Oswalds would work if nmi sets the flag constantly but I only set it once and that's why it can't be missed if nmi happens in wrong spot.
There really isn't a way how to do this with multiple flags packed into a byte? |
| |
alwyz
Registered: Dec 2011 Posts: 32 |
If you can't suppress NMI by turning the timer on and off, can you reset the timer repeatedly during instructions to buy yourself enough cycles to do the routines you need to, then when they finish, the timer can count down fully and the NMI can continue?
Not quite what you're aiming for probably, but it's a hack that might work in your situation. |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
ok maybe this is the XYZ problem? you got X problem but you want to solve it Z way, so what is the original problem ? maybe we can find a better solution than nmi/flags/supressing nmi
https://en.wikipedia.org/wiki/XY_problem |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Quoting mankeliThere really isn't a way how to do this with multiple flags packed into a byte?
Well it's a bit fiddly, but if main uses a single "fetch bit x from byte y" routine, then the NMI could check the call stack to see if that routine is in progress, and adjust the return address &/or register contents accordingly...
But yes, what Oswald said. |
| |
mankeli
Registered: Oct 2010 Posts: 146 |
original problem was to set flags in nmi (only once), check them in main prog and clear them after checking and pack several of them to a byte to conserve memory.
But yeah, in this particular case it can be worked around by having byte per flag. But this was a bit of "code golf" type question if this be possible to do at all without disabling the nmi. ("lockless" way) |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
thats the same problem again, what is the problem that you want to solve with the flagging ? |
| |
Zirias
Registered: Jan 2014 Posts: 48 |
Often enough in such situations, you can get away with a "don't care" approach, because your code will always pick up the flag way before the next NMI that would potentially set it is triggered. Did you check that, is there an *actual* (not only potential) race condition? |
| |
mankeli
Registered: Oct 2010 Posts: 146 |
yeah,
lda flags
# if nmi sets the tested flag here, it will be
# overwritten by zero, others get eor'red in
eor flags
# if nmi sets any flag here it's forgotten
sta flags
|
| |
Hoogo
Registered: Jun 2002 Posts: 105 |
The NMI should access and set a flag anytime, and the main loop will process a flag when it has time. It does not matter if the NMI has set the flag already twice before it is processed?
Maybe you can duplicate all flags and make it an array of 2 elements? One element is read by the mainloop, the other is written by NMI, and the main loop switches the index when done.
But using single bits of a byte as flags creates quite some overhead in code. If you don't have a "real" array of flags... maybe it is not worth it at all?
I would prefer CJs first solution.
--------------------
Here's a real lock, but it's of no use here. Here it does nothing more than a flag that denies NMI access to the flags.
lda#$4d ; eor abs
lock sta *
beq locked
....
lda#8d ;sta abs
sta lock |
| |
oziphantom
Registered: Oct 2014 Posts: 490 |
your main code never touches the flags, only the NMI does..
so you have
MainLastState .byte ?
NMIFlags .byte ?
the NMI toggles the flags rather than set and forget.
and then you do
lda MainLastState
-
cmp NMIFlags
beq -
lda NMIFlags
sta MainLastState
and thus your main code looks for a state change, rather than a value. |
| |
mankeli
Registered: Oct 2010 Posts: 146 |
I like your idea oziphantom! Downside of this is that if nmi can't "set" the flag twice, but it shouldn't be a problem.
And hoogo, I already settled on cj's idea. (it's true it creates overhead in code too) But just wanted to see if it's possible. |
| |
oziphantom
Registered: Oct 2014 Posts: 490 |
then get the NMI it set it to the opposite of last flags, thus if it "sets it again" it will still be read as "set" by the main routine. |
| |
mankeli
Registered: Oct 2010 Posts: 146 |
Also one suggestion from TNT/BF (his account is deleted):
This method has a byte per flag, but allows it to queue max 255 sets.
NMI:
INC flag
test:
LDA oldstate
CMP flag
BEQ noflagset
INC oldstate
JSR do_something
noflagset: |
| |
Hoogo
Registered: Jun 2002 Posts: 105 |
For that, a DEC should do, too.
NMI:
INC flag
test:
LDA flag
BEQ noflagset
DEC flag
JSR do_something
noflagset: |
| |
JeeK
Registered: Nov 2019 Posts: 4 |
I would suggest to use a semaphore which protects a bit field of flags. The semaphore protects in the main program the manipulation of the bit field and prevents a race condition on it.
The semaphore could be realized by means of a byte location where a value of 0 represents a set semaphore. It's some limited style of concurrent programming because the "NMI" task is always atomic, not interruptible by the "main program" task.
Therefore the main program does not need to busy wait on the semaphore or to skip. If it manipulates the bit field it sets the semaphore to enter the critical section with mutual exclusive access to the bit field. If the NMI appears while in the critical section, the bit manipulation from NMI is "masked". The main program task is the dominant part.
init:
lda #$ff ; initialize semaphore
sta sema
rts
NMI:
...
inc sema ; atomic set & test
bne skip ; if sema = 1, semaphore was set (sema=0)
; and main program is in critical section!
; otherwise:
lda #1 ; bit 0 flag of a flag set
ora flags
sta flags
skip dec sema ; restore (regarded as atomic with inc sema)
...
rti
prog:
inc sema ; atomic set & test (sema = 0)
; semaphore set
critical:
lda flags ; mutual exclusive
and #1
php ; get flag's state
lda flags ; clear bit 0 flag
and #255-1
sta flags
dec sema ; (sema = $ff)
; semaphore cleared as soon as possible
end_critical:
plp ; restore bit 0 state
beq not_set
jsr do_something ; if bit 0 was set
not_set:
|