Need help understanding how the Color Data Register works

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Need help understanding how the Color Data Register works
by on (#237359)
I'm just starting out with SNES development. I found some code to set the background color. The color format is as such:

Code:


It said that the $2122 refers to the Color Data Register and that the format is 0bbbbbgggggrrrrr. I also know that the SNES is byte-addressable, thus every address refers to an 8-bit location.

Here is a snippet of the code the guide provided that changes the background color to blue:

Code:
    SEP         #$20        ; Set the A register to 8-bit.
    LDA         #%00000000  ; Load the low byte of the blue color.
    STA         $2122
    LDA         #%01111100  ; Load the high byte of the blue color.
    STA         $2122


What I don't understand is why we set the A register to 8-bit mode if color is a 16-bit value? And then how do 2 STA's with 8-bit operands to $2122 result in a 16-bit write to $2122?

Also according to this wiki article, $2122 is the "Data for CG-RAM Write" and $2121 is the "Address for CG-RAM Write". I'm assuming then that the write to $2122 can only happen successfully after first writing to $2121.

In my Snes_Init.asm (that I also got online) there is a line

Code:
STZ    $2121    ; Color number register ($0-ff)


Is this is how the CG-RAM Write Address is set? Is it supposed to be set to 0? In fact, a bunch of addresses are set to zero in Snes_Init.asm. How can all the addresses be 0?

Any help clarifying would be appreciated! :)
Re: Need help understanding how the Color Data Register work
by on (#237361)
That wiki article is merely a summary of the snes registers. For more detailed specs see anomie's txt files (or, if I may advertise my own specs, see fullsnes.htm).

To answer some some of your questions:
There are several "write-twice" registers, that require two 8bit writes to the same register to form a 16bit value (as for why Nintendo was doing it that way: I assume they had originally planned to use a 8bit cpu in the console).
There is an auto-increment feature for things like vram address, so programs may set the start address to zero, and then write the whole vram starting at that address.
Re: Need help understanding how the Color Data Register work
by on (#237362)
The 65816 CPU, when it writes a 16 bit value, writes it to two sequential addresses. (Likewise with reads)

However, a number of functions in the SNES's PPUs require two 8-bit writes to the same address in order to put 16 bits somewhere useful.
Re: Need help understanding how the Color Data Register work
by on (#237363)
rchoudhary wrote:
I don't understand is why we set the A register to 8-bit mode if color is a 16-bit value? And then how do 2 STA's with 8-bit operands to $2122 result in a 16-bit write to $2122?
The reason for this is that $2122 is not actually memory! When you read/write an address from $2100-$21ff, you're actually communicating with the PPU, the SNES's graphics chip. When you write to $2122 the first time, the PPU stores that byte somewhere inside itself, and once you write the second byte, it takes both bytes and stores them in CGRAM, where the palette is stored, then increments the current CGRAM address.

It's important to understand that PPU registers aren't actually memory. When you write to a PPU register like $2100, that doesn't mean you'll be able to read that value from $2100 again. Many PPU registers are write-only, so trying to read them will just give you random garbage. Others are read-only, so trying to write to them won't do anything. Some do two completely different things for reading and writing. And some even cause side-effects when you read them!

rchoudhary wrote:
Code:
STZ    $2121    ; Color number register ($0-ff)


Is this is how the CG-RAM Write Address is set? Is it supposed to be set to 0? In fact, a bunch of addresses are set to zero in Snes_Init.asm. How can all the addresses be 0?
That's right! The reason for this is that the SNES has separate kinds of memory, with their own address spaces.

The CPU has an address space from $000000-$ffffff, which is the one you normally work with when writing assembly. However, this is completely separate from CGRAM, VRAM, or OAM. All of these address spaces are different from each other, so address 0 in CGRAM is different from address 0 in VRAM, which is different from address 0 for the CPU, etc.
Re: Need help understanding how the Color Data Register work
by on (#237374)
Nicole wrote:
The reason for this is that $2122 is not actually memory! When you read/write an address from $2100-$21ff, you're actually communicating with the PPU, the SNES's graphics chip. When you write to $2122 the first time, the PPU stores that byte somewhere inside itself, and once you write the second byte, it takes both bytes and stores them in CGRAM, where the palette is stored, then increments the current CGRAM address.

It's important to understand that PPU registers aren't actually memory. When you write to a PPU register like $2100, that doesn't mean you'll be able to read that value from $2100 again. Many PPU registers are write-only, so trying to read them will just give you random garbage. Others are read-only, so trying to write to them won't do anything. Some do two completely different things for reading and writing. And some even cause side-effects when you read them!


Oh ok. Yeah we dealt with memory mapping a lot in my embedded systems courses. We'd read addresses in memory to like perform I/O with the GPIO pins, send data out with SSI and UART, etc. The part that confused me here was that we were writing 16 bits to an address that points to 8 bits.

Why not have say $2122 and $2123 write to CGRAM, put the accumulator in 16-bit mode, and then only do a single write operation to send the color data? Is there an advantage to splitting it up into two writes?

Nicole wrote:
The CPU has an address space from $000000-$ffffff, which is the one you normally work with when writing assembly. However, this is completely separate from CGRAM, VRAM, or OAM. All of these address spaces are different from each other, so address 0 in CGRAM is different from address 0 in VRAM, which is different from address 0 for the CPU, etc.


So what you're saying is the CGRAM address starts at $0 (because that's how it's initialized. Then I write 8 bits to $2122 in CPU address space which actually writes those 8 bits to $0 in the CGRAM address space. Then the CGRAM address is incremented to $1. After that, I again write 8 bits to $2122 in the CPU address space which this time writes 8 bits to address $1 in CGRAM address space. Now the CGRAM address is incremented to $2.

So if I attempt to again write 16 bits to the CGRAM to change the background color, will it fail since now I would be writing to addresses $2 and $3 in CGRAM? Do I need to call STZ $2121 first to successfully change the background color?

EDIT:

I just tried it out, and while this fails:

Code:
    sep         #$20        ; Set the A register to 8-bit.
    lda         #%00000000  ; Load the low byte of the green color.
    sta         $2122
    lda         #%01111100  ; Load the high byte of the green color.
    sta         $2122

    sep         #$20        ; Set the A register to 8-bit.
    lda         #%00011111  ; Load the low byte of the red color.
    sta         $2122
    lda         #%00000000  ; Load the high byte of the red color.
    sta         $2122


this succeeds:

Code:
    sep         #$20        ; Set the A register to 8-bit.
    lda         #%00000000  ; Load the low byte of the blue color.
    sta         $2122
    lda         #%01111100  ; Load the high byte of the blue color.
    sta         $2122

    stz         $2121

    sep         #$20        ; Set the A register to 8-bit.
    lda         #%00011111  ; Load the low byte of the red color.
    sta         $2122
    lda         #%00000000  ; Load the high byte of the red color.
    sta         $2122


So yeah, I guess you do have to make sure you write to $0 and $1 in the CGRAM address space...
Re: Need help understanding how the Color Data Register work
by on (#237375)
rchoudhary wrote:
Why not have say $2122 and $2123 write to CGRAM, put the accumulator in 16-bit mode, and then only do a single write operation to send the color data? Is there an advantage to splitting it up into two writes?

It might have simplified the chip design.

Early consoles were all about reducing cost as much as possible. (They didn't even have a framebuffer until the PSX.) See the Atari 2600 for an extreme example...
Re: Need help understanding how the Color Data Register work
by on (#237376)
rchoudhary wrote:
Why not have say $2122 and $2123 write to CGRAM, put the accumulator in 16-bit mode, and then only do a single write operation to send the color data? Is there an advantage to splitting it up into two writes?
Maybe there's some practical reason for it, but considering that actually is what they do with VRAM, honestly, I think it's just that the design of the SNES is kind of messy in general.

rchoudhary wrote:
So what you're saying is the CGRAM address starts at $0 (because that's how it's initialized. Then I write 8 bits to $2122 in CPU address space which actually writes those 8 bits to $0 in the CGRAM address space. Then the CGRAM address is incremented to $1. After that, I again write 8 bits to $2122 in the CPU address space which this time writes 8 bits to address $1 in CGRAM address space. Now the CGRAM address is incremented to $2.

So if I attempt to again write 16 bits to the CGRAM to change the background color, will it fail since now I would be writing to addresses $2 and $3 in CGRAM? Do I need to call STZ $2121 first to successfully change the background color?
That's almost right. The only thing you've got wrong here is that CGRAM (and VRAM for that matter) are word-addressed, not byte-addressed. In other words, $00 is the first 16-bit color, $01 is the second 16-bit color, and so on.

So, you write the first 8 bits to $2122, and the PPU stores that in an 8-bit latch. You write 8 bits again to $2122, and only then does it write the full 16 bits to CGRAM $00, and the address is incremented to $01.

You're right about having to set the address back to $00 each time you want to set the background color. The reason it auto-increments is because that's more convenient when you need to upload several colors in one go.
Re: Need help understanding how the Color Data Register work
by on (#237377)
rchoudhary wrote:
Oh ok. Yeah we dealt with memory mapping a lot in my embedded systems courses. We'd read addresses in memory to like perform I/O with the GPIO pins, send data out with SSI and UART, etc. The part that confused me here was that we were writing 16 bits to an address that points to 8 bits.

Why not have say $2122 and $2123 write to CGRAM, put the accumulator in 16-bit mode, and then only do a single write operation to send the color data? Is there an advantage to splitting it up into two writes?

While I understand exactly what previous individuals have said to you (and they're right), I don't think the explanations are clear given your 2nd paragraph above. So let me try to phrase it differently. You have "some" of it right, but then you end up confusing yourself (which means you don't have it completely right).

$2122 is a single 8-bit MMIO register. It acts as a "gateway" to CGRAM, allowing you to write data to CGRAM one byte at a time. Every time you write to it, internally, the PPU auto-increments the CGRAM address (this way you don't have to keep setting $2121 when writing data linearly). The CGRAM data format itself is 16 bits (thus represented by 2 bytes), but I'll explain that format with some examples at the end of my post.

So, if you wanted to make the first 4 bytes of CGRAM values $ff, $7f, $e0, $03 respectively (I'll explain the colour, just hold on!) -- to affect palette entries #0 and #1 -- you'd do this:

Code:
sep #$20
stz $2121  ; Start at palette entry #0 (CGRAM address $0000)
lda #$ff
sta $2122  ; 1st byte of CGRAM = $ff (%11111111) (low byte of CGRAM data palette entry #0)
lda #$7f
sta $2122  ; 2nd byte of CGRAM = $7f (%011111111) (high byte of CGRAM data palette entry #0)
lda #$e0
sta $2122  ; 3rd byte of CGRAM = $e0 (%11100000) (low byte of CGRAM data palette entry #1)
lda #$03
sta $2122  ; 4th byte of CGRAM = $03 (%00000011) (high byte of CGRAM data palette entry #1)

The reason the accumulator is set to 8-bit using sep #$20 is because you're wanting to write a single byte of data to single MMIO register/address ($2122).

Now let's try it the way you described in your paragraph, using a 16-bit accumulator:

Code:
rep #$20
stz $2121   ; Mistake #1
lda #$7fff
sta $2122   ; Mistake #2
lda #$03e0
sta $2122   ; Mistake #3

First mistake: because the accumulator is 16 bits, the stz $2121 writes $00 to $2121, and $00 to $2122. That means you'd be starting at palette entry #0 in CGRAM (good), and the 1st byte of CGRAM data would be $00 (bad).

Second mistake: because the accumulator is 16 bits, the sta $2122 writes $ff to $2122, and $7f to $2123. That means the 2nd byte of CGRAM data would be $ff (bad), and you'd be tweaking $2123 which is the MMIO register for both window masks on BG1 and BG2. CGRAM palette entry #0 would then the CGRAM data/colour $ff00 (%1111111100000000) -- not what you wanted!

Third mistake: because the accumulator is 16 bits, the sta $2122 writes $e0 to $2122, and $03 to $2123. That means the 3rd byte of CGRAM data would be $e0 (bad), and you'd be again tweaking $2123 which is the MMIO register for both window masks on BG1 and BG2. CGRAM palette entry #1 would be "half written" at this point, thus value %????????11100000 -- not what you wanted!

Now going back to my first code section/example (that one that works), explaining the colour itself:

When you write to $2122, you write it in the order of low byte of CGRAM data first, followed by the high byte. The colour data format is as follows, where bbbbb, ggggg, and rrrrr are the intensities of blue/green/red (5 bits each, thus ranging from 0-31 each).

So let's take those values I wrote earlier and see what you get:
Code:
 Highbyte  Lowbyte
---------  --------
%0bbbbbgg  gggrrrrr
%01111111  11111111 -- $7fff: blue=31, green=31, red=31: white
%00000011  11100000 -- $03e0: blue=0, green=31, red=0: green

All colours at full intensity (i.e. red=31, blue=31, green=31)? That'd be white!

Here's a "simple" chart of colours from my old SNES documentation to help:
Code:
 ----------------------------------------------------------------------------
|A quick colour chart could be the following:                                |
|  $7FFF [0111 1111 1111 1111]: White.                                       |
|  $001F [0000 0000 0001 1111]: Red.                                         |
|  $03E0 [0000 0011 1110 0000]: Green.                                       |
|  $7C00 [0111 1100 0000 0000]: Blue.                                        |
|  $7C1F [0111 1100 0001 1111]: Purple.                                      |
|  $7FE0 [0111 1111 1110 0000]: Aqua.                                        |
|  $03FF [0000 0011 1111 1111]: Yellow.                                      |
 ----------------------------------------------------------------------------

As for you asking, essentially, why didn't they just make the CGRAM MMIO register span two bytes in CPU address space (ex. $2122 and $2123), much like they did with $2116 and $2118? They probably had reasons. It usually has to do with busses, chip design, cost savings, etc.. I ask you seriously: does "why" matter? This is how the system is designed, it cannot be changed. If you think this is terrible and awful and confusing, wait until you get to OAM data, particularly where the 9th bit of the X (horizontal) position is, and how all that is arranged.

Finally, just in case you aren't aware: you don't need to keep doing sep #$20 over and over. You WILL, however, find yourself changing register sizes fairly often given the design of all the MMIO registers and other whatnots; get used to seeing sep/rep often. That's just how the 65816 is.

HTH.
Re: Need help understanding how the Color Data Register work
by on (#237387)
koitsu wrote:
I ask you seriously: does "why" matter?


Well in terms of meeting the goals of my project, not at all. But computer/system architecture is really fascinating to me. It's hard for me to shake the itch to know what the heck is going on under the surface, and having taken Computer Architecture and Digital Logic Design at Uni, I feel like I (probably) have the prerequisite knowledge to really get into the weeds of SNES architecture and scratch that itch.

Basically, the "why" is just for fun :wink:
Re: Need help understanding how the Color Data Register work
by on (#237401)
*thumbs up* :D