Need help understanding how the Color Data Register works

Discussion of hardware and software development for Super NES and Super Famicom.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
Post Reply
rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Need help understanding how the Color Data Register works

Post by rchoudhary » Wed Apr 10, 2019 4:34 pm

I'm just starting out with SNES development. I found some code to set the background color. The color format is as such:
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: Select all

    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: Select all

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! :)

nocash
Posts: 1116
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Need help understanding how the Color Data Register work

Post by nocash » Wed Apr 10, 2019 5:11 pm

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.

lidnariq
Posts: 8936
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: Need help understanding how the Color Data Register work

Post by lidnariq » Wed Apr 10, 2019 5:14 pm

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.

Nicole
Posts: 218
Joined: Sun Mar 27, 2016 7:56 pm

Re: Need help understanding how the Color Data Register work

Post by Nicole » Wed Apr 10, 2019 5:28 pm

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: Select all

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.

rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: Need help understanding how the Color Data Register work

Post by rchoudhary » Wed Apr 10, 2019 10:01 pm

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: Select all

    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: Select all

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

creaothceann
Posts: 214
Joined: Mon Jan 23, 2006 7:47 am
Location: Germany
Contact:

Re: Need help understanding how the Color Data Register work

Post by creaothceann » Wed Apr 10, 2019 10:54 pm

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...
My current setup:
Super Famicom ("2/1/3" SNS-CPU-GPM-02) → SCART → OSSC → StarTech USB3HDCAP → AmaRecTV 3.10

Nicole
Posts: 218
Joined: Sun Mar 27, 2016 7:56 pm

Re: Need help understanding how the Color Data Register work

Post by Nicole » Wed Apr 10, 2019 11:18 pm

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.

User avatar
koitsu
Posts: 4216
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Need help understanding how the Color Data Register work

Post by koitsu » Wed Apr 10, 2019 11:27 pm

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: Select all

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: Select all

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: Select all

 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: Select all

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

rchoudhary
Posts: 24
Joined: Wed Apr 10, 2019 4:24 pm

Re: Need help understanding how the Color Data Register work

Post by rchoudhary » Thu Apr 11, 2019 9:46 am

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:

User avatar
koitsu
Posts: 4216
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Need help understanding how the Color Data Register work

Post by koitsu » Thu Apr 11, 2019 2:27 pm

*thumbs up* :D

Post Reply