BGnHOFS Register Assignment

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
jwdonal
Posts: 719
Joined: Sat Jun 27, 2009 11:05 pm
Location: New Mexico, USA
Contact:

BGnHOFS Register Assignment

Post by jwdonal »

Regarding the following from Anomie's regs.txt (same info exists in fullsnes.txt):

Code: Select all

210f  ww+++- BG2HOFS - BG2 Horizontal Scroll
2110  ww+++- BG2VOFS - BG2 Vertical Scroll
2111  ww+++- BG3HOFS - BG3 Horizontal Scroll
2112  ww+++- BG3VOFS - BG3 Vertical Scroll
2113  ww+++- BG4HOFS - BG4 Horizontal Scroll
2114  ww+++- BG4VOFS - BG4 Vertical Scroll
        ------xx xxxxxxxx

        Note that these are "write twice" registers, first the low byte is
        written then the high. Current theory is that writes to the register
        work like this:
          BGnHOFS = (Current<<8) | (Prev&~7) | ((Reg>>8)&7);
          Prev = Current;
            or
          BGnVOFS = (Current<<8) | Prev;
          Prev = Current;
1) Can anyone explain to me that insane horizontal offset assignment? Why does it do this?? The vertical offset assignment makes perfect sense to me but what's up with the horizontal?

2) Assuming that 'Reg' refers to the current BGnHOFS register value that is being written to (although this isn't stated anywhere), the '((Reg>>8)&7)' part doesn't make any sense. The registers are only 10-bit. So if you right-shift by 8 then you only have 2 bits left. So doing a mask of the lower 3 bits (i.e. '&7') doesn't make any sense. Are you just supposed to set the upper bit to 0?

Some notes:
- 'Current' is the 8-bit value that's being written *right now* to the PPU by the CPU.
- 'Prev' is an 8-bit latch value storing the last 8-bit value written to any of these registers.
- Assuming that 'Reg' is the current value stored in the 10-bit BGnHOFS register. Not sure why he doesn't just use BGnHOFS instead of creating a new undefined term of 'Reg'.....??

Thanks!
Last edited by jwdonal on Sat Dec 10, 2016 11:36 pm, edited 1 time in total.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: BGnHOFS Register Assignment

Post by koitsu »

nocash's documentation explains this a little better: http://problemkaputt.de/fullsnes.htm

Code: Select all

210Dh - BG1HOFS - BG1 Horizontal Scroll (X) (W) and M7HOFS
210Eh - BG1VOFS - BG1 Vertical Scroll (Y) (W) and M7VOFS
210Fh - BG2HOFS - BG2 Horizontal Scroll (X) (W)
2110h - BG2VOFS - BG2 Vertical Scroll (Y) (W)
2111h - BG3HOFS - BG3 Horizontal Scroll (X) (W)
2112h - BG3VOFS - BG3 Vertical Scroll (Y) (W)
2113h - BG4HOFS - BG4 Horizontal Scroll (X) (W)
2114h - BG4VOFS - BG4 Vertical Scroll (Y) (W)
  1st Write: Lower 8bit  ;\1st/2nd write mechanism uses "BG_old"
  2nd Write: Upper 2bit  ;/
Note: Port 210Dh/210Eh are also used as M7HOFS/M7VOFS, these registers have a similar purpose, but internally they are separate registers: Writing to 210Dh does BOTH update M7HOFS (via M7_old mechanism), and also updates BG1HOFS (via BG_old mechanism). In the same fashion, 210Eh updates both M7VOFS and BG1VOFS.
          BGnHOFS = (Current<<8) | (Prev&~7) | ((Reg>>8)&7);
          Prev = Current;
            or
          BGnVOFS = (Current<<8) | Prev;
          Prev = Current;
In other words: the behaviour of $210d and $210e is multi-purpose depending on if you're in modes 0-6 or mode 7. For modes 0-6, they're 10-bit (range 0-1023) as described (I'm avoiding modes 5 and 6 :-) ). For mode 7, it's more complicated (see "SNES PPU Rotating/Scaling" right beneath that).

Do you need me to post pages from the official documentation that depict these registers' use on the actual screen? They're not particularly useful, but it is covered.
User avatar
jwdonal
Posts: 719
Joined: Sat Jun 27, 2009 11:05 pm
Location: New Mexico, USA
Contact:

Re: BGnHOFS Register Assignment

Post by jwdonal »

Yep, got it. So just keeping the discussion specific to registers $210F-$2114 I believe both of my original questions are still valid. Those registers are physically only 10-bit according to both docs. And I still don't understand why that assignment is so weird. There must be a reason behind it...well, we are talking SNES, so I suppose that's not necessarily true. But I don't even see how that assignment pattern is even supposed to work properly.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: BGnHOFS Register Assignment

Post by koitsu »

Okay, so this is more about 1) a complaint about the syntax of the documentation (the fields in question aren't described anywhere), and 2) why does the PPU internally operate that way (re: all the bitshifts and AND'd with a bitwise NOT of 7 (so %1000)). Yeah?
User avatar
jwdonal
Posts: 719
Joined: Sat Jun 27, 2009 11:05 pm
Location: New Mexico, USA
Contact:

Re: BGnHOFS Register Assignment

Post by jwdonal »

Yes that's it.

Although I wouldn't really say "complaint" about the syntax. It could be that the syntax is actually better than how I would describe it but I'm just not understanding something properly...?

To put question 1 more simply, why isn't the horizontal offset assignment just the exact same as the vertical offset assignment? Why do they even need to be different at all? It must be something to do with the graphics rendering that I either don't understand or haven't learned/seen yet.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: BGnHOFS Register Assignment

Post by koitsu »

I'm under the impression the "crazy formulas" shown only pertain to mode 7. But I can't be sure because of how the documentation is formatted.

If they apply universally to all modes, then my theory would be that the whole current vs. prev vs. written value (presumably Reg) thing is probably an effect of the PPU having some kind of latch buffering going on. Warning: I'm talking out my ass here a bit -- and as such I hope byuu or Revenant or lidnariq appear and correct me, honestly! -- but the NES PPU is very much like this. In fact, it's one of the things (once understood) that "revolutionised" NES emulation. There's a reason there's an enormous doc about it maintained by many of us.

I will say it's strange that the formula is (current*256) | (previous & %11111000) | ((regvalue/256) & %00000111). I'm kinda curious what those "magic 4 bits" are about.
User avatar
jwdonal
Posts: 719
Joined: Sat Jun 27, 2009 11:05 pm
Location: New Mexico, USA
Contact:

Re: BGnHOFS Register Assignment

Post by jwdonal »

koitsu wrote:"magic 4 bits"
Did you mean magic *3* bits? My total guess is that they have something to do with the fine horizontal scroll.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: BGnHOFS Register Assignment

Post by lidnariq »

I'm definitely out of my depth, much as I appreciate koitsu's flattery ;)
All I can really say is that bsnes-plus HEAD includes the following

Code: Select all

//BG2HOFS
void PPU::mmio_w210f(uint8 data) {
  bg2.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg2.regs.hoffset >> 8) & 7);
  regs.bgofs_latchdata = data;
}
This looks like it means you'll get odd (and unhelpful) behavior if you interleave writes to different registers.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: BGnHOFS Register Assignment

Post by Near »

The reason is due to internal latching. The low three bits of the horizontal offset are special and used for internal tile rendering/fetching. You can think of them as actually separate registers internally. Or not, I'm not your father :P

This matters. Try running Theme Park, it'll screw up if you don't do the horizontal scroll latching correctly. Stuff like this, it's best to just grit your teeth and implement it as we've described it :/

It's screwy, yes, but it's not nearly as bad as the NES' loopy_t/v stuff.
Revenant
Posts: 462
Joined: Sat Apr 25, 2015 1:47 pm
Location: FL

Re: BGnHOFS Register Assignment

Post by Revenant »

lidnariq wrote:I'm definitely out of my depth, much as I appreciate koitsu's flattery ;)
Me too, to be honest :P I tend to just defer to the bsnes/higan source code for stuff like this, since I don't have a huge amount of hands-on experience with the weird quirks of the SNES PPU.
User avatar
jwdonal
Posts: 719
Joined: Sat Jun 27, 2009 11:05 pm
Location: New Mexico, USA
Contact:

Re: BGnHOFS Register Assignment

Post by jwdonal »

I appreciate all the replies but I don't think either of my questions has actually been answered. Well, maybe a little...
byuu wrote:The reason is due to internal latching. The low three bits of the horizontal offset are special and used for internal tile rendering/fetching.
I suppose this is an answer to question 1 but it's very vague. Any more details? The low 3 bits are definitely used for fine horizontal scrolling but I don't see how that relates to "internal latching". I'm trying to understand the purpose of the strange assignment pattern.

I wrote out a few examples by hand and it seems that the weird assignment behavior only makes a difference if you write to a BGnHOFS register only once instead of the required 2 times. If you always write to the register twice for both low and high bytes I don't believe there is any way that the final register value could contain anything other than what the user wrote. Am I wrong about that? Or is the weird assignment pattern only important for when/if a game writes to the BGnHOFS register once instead of twice?

For my second question I still don't see how this part of the assignment '((Reg>>8)&7)' makes any sense for a 10-bit register. It would make a lot more sense if it was '((Reg>>8)&3)'. Are you just always supposed to set the upper of the three bits to 0 since it's only a 10-bit register? I don't see higan source doing that though...

Additionally, Anomie's regs.txt states that the offset registers $210D-$2114 can only be written during F/V/H-blank (as denoted by the "ww+++-"). Is that actually true? Looking at higan source I don't see anything preventing them from being written whenever you want. Also, unless I made a silly goof, I just tried to set the registers to a non-zero value outside of F/H/V-blank and it worked just fine. I can see why you _shouldn't_ write to those registers outside of F/H/V-blank, but it does seem to be physically possible.
93143
Posts: 1717
Joined: Fri Jul 04, 2014 9:31 pm

Re: BGnHOFS Register Assignment

Post by 93143 »

jwdonal wrote:Additionally, Anomie's regs.txt states that the offset registers $210D-$2114 can only be written during F/V/H-blank (as denoted by the "ww+++-"). Is that actually true? Looking at higan source I don't see anything preventing them from being written whenever you want. Also, unless I made a silly goof, I just tried to set the registers to a non-zero value outside of F/H/V-blank and it worked just fine. I can see why you _shouldn't_ write to those registers outside of F/H/V-blank, but it does seem to be physically possible.
Anomie is conservative that way; as I heard it he doesn't declare something writable if he isn't sure.

You can indeed write to the scroll registers in the middle of active display. The PPU won't notice right away, but after a couple of slivers (in my experience) the new values will take effect. Air Strike Patrol does this, as does my shmup port.

You can change a number of things mid-scanline. Changing the VRAM locations of OBJ data via OBSEL works cleanly during active display, and won't produce any visible changes until the next line (HBlank seems to be a better time to change the size bits, if you must mess with them mid-frame). Changing BGMODE mid-line produces garbage in the BG layers for a short period, but ultimately works in all cases tried so far (that I'm aware of); just don't change to Mode 7 from something else mid-line as the init won't have been done and the results will not be as expected...
Last edited by 93143 on Sun Dec 11, 2016 3:18 am, edited 3 times in total.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: BGnHOFS Register Assignment

Post by lidnariq »

Best guesses:
Can anyone explain to me that insane horizontal offset assignment? Why does it do this??
The fine X scroll probably does something similar to how the NES PPU's fine X scroll is implemented using one output of a multiplexer. It may well be in an entirely different section of the IC, and it might have been easier to route the existing upper byte of the X scroll value instead of the FIFO.
The registers are only 10-bit. So if you right-shift by 8 then you only have 2 bits left. So doing a mask of the lower 3 bits (i.e. '&7') doesn't make any sense. Are you just supposed to set the upper bit to 0?
By elimination, the X scroll registers have to be at least 11-bit, because when it's moved over into the fine X scroll the &4s bit isn't 0.
only makes a difference if you write to a BGnHOFS register only once instead of the required 2 times. If you always write to the register twice for both low and high bytes I don't believe there is any way that the final register value could contain anything other than what the user wrote. Am I wrong about that? Or is the weird assignment pattern only important for when/if a game writes to the BGnHOFS register once instead of twice?
They're only the same if your write pattern is BGxHOFS BGxHOFS BGxVOFS BGxVOFS. If you were tempted to do something like BGxHOFS BGxVOFS BGxHOFS BGxVOFS you'd get something uphelpful.

OTOH, if I'm reading this right, you could also do something daft like BG4VOFS BG1VOFS BG4VOFS BG2VOFS BG4VOFS BG3VOFS and it would be usable... if obfuscatory. But you couldn't with the BGxHOFS registers.

But ... yeah ... https://forums.nesdev.com/viewtopic.php ... 15#p183215
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: BGnHOFS Register Assignment

Post by Near »

> I'm trying to understand the purpose of the strange assignment pattern.

I want to avoid speculation, but my opinion is that they're trying to reduce transistors infinitesimally through latching less bits of the BGnHOFS registers.

If you really want to know for sure why it's doing it, I'm afraid you'd need to analyze die scans.

Again, I'm sorry to say it, but opinions don't really matter. This is what it does, and it's what you have to emulate =(

By all means though, try to verify it, try to break the assumptions, maybe you'll find new edge cases we didn't know about, which can lead to making better sense of things.

> Additionally, Anomie's regs.txt states that the offset registers $210D-$2114 can only be written during F/V/H-blank (as denoted by the "ww+++-"). Is that actually true?

That's not true. Air Strike Patrol writes to the BG3dOFS registers during active display. It does it to show a hud on the left side of the screen, while rotationg the "player start" text on the same BG in the middle of the screen.

(obviously, use higan/accuracy if you want to see the effect. Other emulators don't animate the player start rotation effect.)

Don't forget: anomie quit the SNES scene without saying goodbye so he could go edit Wikipedia instead. He left a very long time ago, and his docs haven't been updated. I've learned a lot since then.

> Changing BGMODE mid-line produces garbage in the BG layers for a short period, but ultimately works in all cases tried so far

And I really need to stress that this is the most psychotic one of all. It completely breaks the idea of trying to have separate state machine paths for each BGMODE. You can't do that if you care about perfection.
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: BGnHOFS Register Assignment

Post by AWJ »

lidnariq wrote:Best guesses:
Can anyone explain to me that insane horizontal offset assignment? Why does it do this??
The fine X scroll probably does something similar to how the NES PPU's fine X scroll is implemented using one output of a multiplexer. It may well be in an entirely different section of the IC, and it might have been easier to route the existing upper byte of the X scroll value instead of the FIFO.
I'm almost 100% certain that the bits of the BGnHOFS registers are actually split between the two chips that comprise the S-PPU: the low 3 bits are on PPU2, and the rest of the bits are on PPU1. VRAM addressing is controlled by PPU1, but the BG shift registers pretty much have to be on PPU2, because there's no data path to send rendered BG pixels between the two chips (there's only the 9 pins for sprite pixels). The low 3 bits of hscroll don't affect VRAM addressing, they affect which bit of the shift registers gets selected as output.
Post Reply