BGnHOFS Register Assignment

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
BGnHOFS Register Assignment
by on (#184432)
Regarding the following from Anomie's regs.txt (same info exists in fullsnes.txt):
Code:
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!
Re: BGnHOFS Register Assignment
by on (#184435)
nocash's documentation explains this a little better: http://problemkaputt.de/fullsnes.htm

Code:
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.
Re: BGnHOFS Register Assignment
by on (#184437)
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.
Re: BGnHOFS Register Assignment
by on (#184443)
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?
Re: BGnHOFS Register Assignment
by on (#184455)
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.
Re: BGnHOFS Register Assignment
by on (#184462)
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.
Re: BGnHOFS Register Assignment
by on (#184465)
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.
Re: BGnHOFS Register Assignment
by on (#184468)
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:
//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.
Re: BGnHOFS Register Assignment
by on (#184472)
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.
Re: BGnHOFS Register Assignment
by on (#184479)
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.
Re: BGnHOFS Register Assignment
by on (#184489)
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.
Re: BGnHOFS Register Assignment
by on (#184490)
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...
Re: BGnHOFS Register Assignment
by on (#184491)
Best guesses:
Quote:
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.

Quote:
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.

Quote:
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 ... viewtopic.php?p=183215#p183215
Re: BGnHOFS Register Assignment
by on (#184506)
> 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.
Re: BGnHOFS Register Assignment
by on (#184518)
lidnariq wrote:
Best guesses:
Quote:
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.
Re: BGnHOFS Register Assignment
by on (#184522)
AWJ wrote:
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.
Oh, that would make the weird bit math much clearer.

It would mean the situation is something more nearly like:
new_coarse_nHOFS = (cur_write<<5) | (PPU1_HVOFS_LATCH>>3)
new_fine_nHOFS = PPU2_nHOFS_LATCH
Re: BGnHOFS Register Assignment
by on (#184523)
Excellent insight as always, AWJ.

We're certainly not doing ourselves a lot of favors by trying to implement the logic of both the PPU1 and PPU2 into the same class, as if they're one chip :/
Re: BGnHOFS Register Assignment
by on (#184556)
byuu wrote:
one chip

Might lack of register splitting be one cause of the "Super Famiclone" behavior of the post-2/1/3 revision?
Re: BGnHOFS Register Assignment
by on (#184620)
Thanks everyone. This was very helpful. After AWJ mentioned about the 3 LSbits being only in PPU2 things made a lot more sense. I actually already knew that those bits were only in PPU2 but I wasn't connecting that with its effect on the register assignment. Cool stuff!
lidnariq wrote:
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.
I just wanted to clarify on this...So you believe that all the scroll registers are 11-bit even though all the docs that I can find say they're 10-bit? It's not clear to me what purpose an 11-bit register would server. Only 10 bits are required for the maximum permissible scroll in any background configuration (I'm excluding $210D/E since those are also used for Mode 7).

From Anomie's regs.txt:
Code:
        x = The BG offset, at most 10 bits (some modes effectively use as few
            as 8).

        Note that all BGs wrap if you try to go past their edges. Thus, the
        maximum offset value in BG Modes 0-6 is 1023, since you have at most 64
        tiles (if x/y of BGnSC is set) of 16 pixels each (if the appropriate
        bit of BGMODE is set).
I think it would be very highly unlikely for Nintendo to insert a redundant flip-flop in the circuit design. A transistor saved is a penny earned. :D
Re: BGnHOFS Register Assignment
by on (#184621)
I mean, it's "11" bits because there's a "redundant" 3-bit latch in PPU2 for each horizontal scroll register. Keeping track of D2 on the first write so that it can be transfered later. It's there to make the API not suck.

Total number of latches is apparently
13 bits per horizontal scroll: seven coarse, three fine, and three for a 1-deep FIFO for fine scroll
10 bits per vertical scroll
eight bits shared across all eight registers in PPU1 as a 1-deep FIFO