It is currently Sat Oct 21, 2017 9:52 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 13 posts ] 
Author Message
PostPosted: Thu Aug 27, 2015 6:04 pm 
Offline

Joined: Thu Jul 23, 2015 7:54 pm
Posts: 145
Location: USA
I've been looking at the source code of a couple games and noticed that a few of them push and pull the processor status onto and from the stack during NMI. Why exactly is this done? What's the reason for some games doing this while others not? And moreso, what is the processor status used for?


Top
 Profile  
 
PostPosted: Thu Aug 27, 2015 6:15 pm 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2963
Location: Tampere, Finland
It's never required to manually push/pull it in interrupt handlers, because the processor automatically pushes (and pulls) it from the stack on interrupt entry/exit. Presumably some developers didn't know this and wanted to be safe.

Processor status contains various flags indicating the state of the processor, which can get mangled within the interrupt, so it's important to save and restore it.

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Thu Aug 27, 2015 7:04 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10066
Location: Rio de Janeiro - Brazil
Sogona wrote:
I've been looking at the source code of a couple games and noticed that a few of them push and pull the processor status onto and from the stack during NMI. Why exactly is this done? What's the reason for some games doing this while others not?

Was that at the beginning and the end of the NMI or in the middle of some calculation performed within the NMI? It's perfectly normal to temporarily save the status flags during calculations if you want to delay a decision for whatever reason, but like thefox said, backing up and restoring the status register manually on interrupts is not necessary because the CPU does that automatically.

Quote:
And moreso, what is the processor status used for?

Mostly for storing flags related to the result of operations performed by the CPU. Whenever the result of an operation is 0, there's one flag that indicates that. When the result is negative, another flag indicates that. There also the carry flag (used for adding, subtracting and shifting), the overflow flag (indicates signed oveflows/underflows after additions/subtractions), and the decimal mode flag, that tells a normal 6502 to do BCD math instead of binary, but has no effect on the NES CPU. Unrelated to mathematical operations, there's the interrupt flag, which is used to enable/disable interrupts, and the B flag, which indicates when the BRK instruction is used.

All decisions that affect the program flow (branches) are based on these status flags. The CPU has instructions that check these flags and branch program execution or not based on their values.


Top
 Profile  
 
PostPosted: Thu Aug 27, 2015 7:26 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5730
Location: Canada
I find php and plp useful when I want to do some common things before taking a branch, rather than duplicating that code in each branch. Like, do the test (lda or cmp or whatever), php to save the result, then do some other stuff, then plp when I want to take the branch later.

As for doing it at the start/end of the NMI or IRQ, there's really no point. That's probably just lack of understanding (it doesn't help, but doesn't really hurt either). On the SNES, though, there's a lot more need to push/pull status because of the 8 vs 16 bit modes.


Top
 Profile  
 
PostPosted: Thu Aug 27, 2015 7:29 pm 
Offline

Joined: Thu Jul 23, 2015 7:54 pm
Posts: 145
Location: USA
tokumaru wrote:
Was that at the beginning and the end of the NMI or in the middle of some calculation performed within the NMI? It's perfectly normal to temporarily save the status flags during calculations if you want to delay a decision for whatever reason, but like thefox said, backing up and restoring the status register manually on interrupts is not necessary because the CPU does that automatically.

At the beginning.
i.e in Metroid:
Code:
NMI:
  php
  pha
  txa
  pha
  tya
  pha

  ;NMI stuff

  pla
  tay
  pla
  tax
  pla
  plp
  rti

Seems interesting they were able to make a game that complex without knowing that was redundant.

Quote:
Mostly for storing flags related to the result of operations performed by the CPU. Whenever the result of an operation is 0, there's one flag that indicates that. When the result is negative, another flag indicates that. There also the carry flag (used for adding, subtracting and shifting), the overflow flag (indicates signed oveflows/underflows after additions/subtractions), and the decimal mode flag, that tells a normal 6502 to do BCD math instead of binary, but has no effect on the NES CPU. Unrelated to mathematical operations, there's the interrupt flag, which is used to enable/disable interrupts, and the B flag, which indicates when the BRK instruction is used.

All decisions that affect the program flow (branches) are based on these status flags. The CPU has instructions that check these flags and branch program execution or not based on their values.

Ah, so that's where those are stored


Top
 Profile  
 
PostPosted: Mon Dec 19, 2016 8:52 pm 
Offline

Joined: Wed Nov 30, 2016 4:45 pm
Posts: 88
Location: Southern California
Since the interrupt sequence pushes the processor status register, and that will normally include the I interrupt-disable bit clear, do not bracket the ISR with PHP and PLP (nor SEI and CLI). Why? Because these will then have the effect of re-enabling interrupts before the RTI; and if another interrupt happens to already be pending, you'll go into the ISR again without exiting the first instance of it. In an extreme case of this happening over and over, you'll overflow the stack and crash, whereas otherwise, there will be no crash (even if it's a long time before the processor gets back to the background program).

Edit: rainwarrior correctly pointed out below that PHP and PLP won't cause the problem. I apologize for wasting anyone's time. The problem is only with SEI and CLI. I need to slow down and think through the details more before posting. Thinking has been difficult recently with the problem with the neighbor's dog. I can't sleep, can't work (I work in my office at home), can't concentrate, am always tired.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources


Last edited by Garth on Tue Dec 20, 2016 2:38 pm, edited 3 times in total.

Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 1:02 pm 
Offline

Joined: Thu Aug 28, 2008 1:17 am
Posts: 591
Sogona wrote:
Seems interesting they were able to make a game that complex without knowing that was redundant.

Yeah? You'd be surprised as what some devs do. I would probably chalk this up to a macro that was miss-used in the code. Or maybe they knew about it, bit didn't bother to change it (not write a new macro) because it didn't have any negative effect.

Quote:
Since the interrupt sequence pushes the processor status register, and that will normally include the I interrupt-disable bit clear, do not bracket the ISR with PHP and PLP (nor SEI and CLI). Why? Because these will then have the effect of re-enabling interrupts before the RTI; and if another interrupt happens to already be pending, you'll go into the ISR again without exiting the first instance of it. In an extreme case of this happening over and over, you'll overflow the stack and crash, whereas otherwise, there will be no crash (even if it's a long time before the processor gets back to the background program).

I re-able interrupts in the HuC6280 inside of interrupt routines. I just set flags to indicate that what level of interrupt is already processing (in which case if another of the same level call happens, it just bails out each time until the flag is clear). It's the only way I can juggle three interrupt services at a time, with some interrupt routines having more critical timing concerns than others. I also use this for time-slicing or running background "processing threads".

_________________
__________________________
http://pcedev.wordpress.com


Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 2:13 pm 
Offline

Joined: Wed Nov 30, 2016 4:45 pm
Posts: 88
Location: Southern California
tomaitheous wrote:
I re-able interrupts in the HuC6280 inside of interrupt routines. I just set flags to indicate that what level of interrupt is already processing (in which case if another of the same level call happens, it just bails out each time until the flag is clear). It's the only way I can juggle three interrupt services at a time, with some interrupt routines having more critical timing concerns than others. I also use this for time-slicing or running background "processing threads".

Agreed. It is indeed appropriate sometimes to re-enable interrupts during an ISR, so something of high priority that's quick to service can cut in on the longer servicing of a lower-priority interrupt. Still, if the interrupt-disable flag is set, it should not be cleared right before the RTI. That's not the place to do it.

I have even gone the opposite direction in my system of servicing interrupts in high-level Forth with zero overhead, having the 7-instruction ISR set the I flag in the stacked status so even RTI won't re-enable interrupts. The next time NEXT runs, it re-routes to the Forth ISR, faster than the normal operation of NEXT. The Forth ISR ends with SYSRTI which is like unnest but with a CLI instruction.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources


Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 2:23 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5730
Location: Canada
Garth wrote:
Still, if the interrupt-disable flag is set, it should not be cleared right before the RTI. That's not the place to do it.

A PHP / PLP bookending an IRQ routine won't clear the flag, though. The IRQ itself will set I, so such a PLP can only set the flag, not clear it.


Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 2:31 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
I think a more likely explanation (vs. theories about macro usage) is that the programmers weren't fully familiar with the intricacies, or it was effectively "learned behaviour" (think: habit). Occam's razor applies. I'll try to explain my view of it more verbosely:

I've several 65xx books and most of them do not go over clearly/concisely, in a programmer-friendly fashion, the "behaviour" of interrupts as far as P (status register) goes. The Lichty/Eyes book has an entire chapter dedicated to interrupts, but doesn't explain this unless you really dig (it's deep within a long paragraph, and not clearly demonstrated, thus very easily overlooked), nor do any of the other 65xx books I own (many of which were published during the CPUs heyday) denote this aspect in a way. The only one I have which does is "6502 Assembly Language Programming" from Lance A. Leventhal, circa 1979, and is clear about it -- but it's smack dab mid-chapter (the interrupts chapter is 40 pages long), covered in a couple lines. In contrast, the book "Programming the 6502" from Rodnay Zaks (circa 1983) doesn't really go over this either, at least not clearly/concisely. I can provide quotes from all these books on the subject if asked, but that borders on obsessive-compulsive. (And yes, I DID actually pull said books from my library and review them)

In other words, bare minimum it's something easily overlooked/forgotten, bordering almost on "tribal knowledge" in a sense (cut me some slack).

Now add the following into the mix: Nintendo up to that point, to my knowledge, had little-to-no experience with the 6502 CPU. Here's a reference for that. They went looking at arcade systems when they chose to go with the 6502.

...then consider what the state of 6502 documentation in Japanese may have been at that time.

...and let's also not forget Metroid was a first-generation NES game.

There's a lot of things in NES games in general that induce "what the heck?" reactions when code reviewed. I'm sure if you were to review some of my code from my youth you'd find similar mistakes or oddities.


Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 2:35 pm 
Offline

Joined: Wed Nov 30, 2016 4:45 pm
Posts: 88
Location: Southern California
rainwarrior wrote:
A PHP / PLP bookending an IRQ routine won't clear the flag, though. The IRQ itself will set I, so such a PLP can only set the flag, not clear it.

Ah, you're right. Limit it only to SEI and CLI. I edited my first post above.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources


Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 2:51 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19115
Location: NE Indiana, USA (NTSC)
koitsu wrote:
I think a more likely explanation (vs. theories about macro usage) is that the programmers weren't fully familiar with the intricacies, or it was effectively "learned behaviour" (think: habit).

I agree with most of this. Case in point:

Over a decade and a half ago, in nestech.txt 2.00, koitsu exhibited such an 'oddity' when he wrote:
Be sure to clear the internal VRAM address via $2006 semi-often. You will often encounter a situation where a palette fade or a VRAM update will cause the screen "to be trashed" (squares on the screen, or what seem to be graphical "glitches"). The reason for this is that your code took longer than a VBlank. When the VBlank goes to refresh the screen with the data in the PPU, it takes whatever value is in the internal VRAM address and uses that as the starting base for Name Table #0. The solution is to fix your code by re-assigning the VRAM address to $0000 (or $2000), so that the refresh may occur successfully. Such code would be:
Code:
      LDA #$00
      STA $2006
      STA $2006

You will find code like this in commercial games quite often.

And we did indeed find misguided code like this in commercial games such as Super Mario Bros. But as knowledge of the machine's behavior increased, first among the commercial dev community and then among homebrewers, so did efficiency of the software. It was realized that two writes to $2005 (scroll position) and one to $2000 (NMI, pattern table address, sprite size, and scroll position high bits) before the pre-render line's hblank were necessary and sufficient to fully "clear" the VRAM address in games not using raster effects.

But one thing in the rest of your comment stuck at me:

koitsu wrote:
...and let's also not forget Metroid was a first-generation NES game.

I thought the progression was NROM-128 and equivalents, NROM-256 (led by Xevious and Super Mario Bros.), and then FDS and CNROM-class mappers at roughly the same time. So it'd be from the third generation.


Top
 Profile  
 
PostPosted: Tue Dec 20, 2016 3:04 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
tepples wrote:
I thought the progression was NROM-128 and equivalents, NROM-256 (led by Xevious and Super Mario Bros.), and then FDS and CNROM-class mappers at roughly the same time. So it'd be from the third generation.

You're probably right in this case (re: it not being a first-gen title), but ultimately it amounts to defining what "generation" means, as it's an eye-of-the-beholder thing. This subject is off-topic so I won't reply here, but I have an explanation saved to a .txt file anyway.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group