Confused about VRAM Address Increment

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
urbanspr1nter
Posts: 39
Joined: Thu Aug 16, 2012 7:55 pm

Confused about VRAM Address Increment

Post by urbanspr1nter »

Hi, I am reading about how the PPU registers work from the Wiki. Going through the pages, I am having a bit of confusion with understanding the access to the $2007 PPUDATA register.

It says here:
VRAM read/write data register. After access, the video memory address will increment by an amount determined by bit 2 of $2000.
IIRC, the VRAM address is obtained through $2006, where it has been written twice to memory by the CPU. Is my understanding on how the full 16-bit VRAM address is obtained, correct?

1. CPU writes to $2006.
-> The address latch gets filled with this byte.
2. CPU writes to $2006
-> The VRAM is constructed from (pseudo-code)

Code: Select all

vramAddr = (latch.value << 8) | mem.get(0x2006)
3. CPU writes to $2007
4. PPU uses the

Code: Select all

vramAddr
and writes to PPU memory with data found in $2007.

Now, here is where I am stuck... Is the increment happening on vramAddr? Or, is the increment happening in $2006. I am confused about this because These values are only 8-bits and addresses are be 16-bits, right? Where do I write the low/high bytes in this case?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Confused about VRAM Address Increment

Post by tokumaru »

$2006 is just a port for communicating with the CPU, it doesn't actually hold any values. Internally, the PPU has 2 address registers: a temporary one, and an effective one. Writing to $2006 changes the value of the temporary register, first the high byte, then the low byte. After the low byte is written, the value of the temporary register is automatically copied to the effective register, which is the one the PPU for actual VRAM access.

The auto-increment is just something the PPU does to speed up access to VRAM. After each access to VRAM (writing or reading), the PPU automatically increments the value of the effective address register, by either 1 or 32. Incrementing the address by 32 is useful for accessing columns of tiles in the name tables. Since name tables are 32 tiles wide, adding 32 to the address causes it to point to the tile immediately below.
urbanspr1nter
Posts: 39
Joined: Thu Aug 16, 2012 7:55 pm

Re: Confused about VRAM Address Increment

Post by urbanspr1nter »

I see, so if my understanding is correct, then my PPU would have something like this for variables:

Code: Select all

// initially:
vramAddressToggle = false
loVramAddressByte: Byte = 0
hiVramAddressByte: Byte
effectiveVramAddress: DoubleByte = 0

// then:
if(!vramAddressToggle) { 
     loVramAddressByte = mem.get($2006); 
     vramAddressToggle = true; 
}
else { 
     hiVramAddressByte = mem.get($2006); 
     vramAddressToggle = false; 

     effectiveVramAddress = merge(loVramAddressByte, hiVramAddressByte);
} 


// Then on accesses:
ppuMem.get(effectiveVramAddress)

// or..
ppuMem.set(effectiveVramAddress, mem.get($2007));
incrementAddress(effectiveVramAddress); // either 8, or 32?
Also, if the above is true, does that mean we don't reset the vramAddressToggle until we read PPUSTATUS? When does that get reset?
Last edited by urbanspr1nter on Sun Mar 31, 2019 10:26 am, edited 3 times in total.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Confused about VRAM Address Increment

Post by lidnariq »

vramAddressToggle toggles after every write to $2005 or $2006.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Confused about VRAM Address Increment

Post by tokumaru »

Let me try to answer your questions more directly:
urbanspr1nter wrote:IIRC, the VRAM address is obtained through $2006, where it has been written twice to memory by the CPU. Is my understanding on how the full 16-bit VRAM address is obtained, correct?
Yes
1. CPU writes to $2006.
-> The address latch gets filled with this byte.
2. CPU writes to $2006
-> The VRAM is constructed from (pseudo-code)

Code: Select all

vramAddr = (latch.value << 8) | mem.get(0x2006)
Not really. What you call a "latch" is actually the temporary VRAM address register, commonly referred simply as "t" around these parts. It's a 16-bit register (actually 15), and it's value comes from the values written to PPU port $2006. $2006 really is just a port: the PPU "listens" to the address and data buses waiting for this address (and it's mirrors) to be written to, and when that happens, it takes the value in the data bus and updates t accordingly.

Which bits of t are updated depends on the state of the toggle that selects between the first and second writes to $2006. The first write updates the high byte of t (except for the top 2 bits, which get cleared!), and the second write updates the low byte, and copies the full value to the effective VRAM address register, commonly called "v".

The toggle that selects between the first and second writes is shared between ports $2006 and $2005, where the PPU used it to tell whether to update the X scroll or the Y scroll.
3. CPU writes to $2007
4. PPU uses the

Code: Select all

vramAddr
and writes to PPU memory with data found in $2007.
Yes, but again, there's no "data found in $2007", $2007 is just a port, when the PPU detects a write to this address, it immediately intercepts the write and captures the value being written.
Now, here is where I am stuck... Is the increment happening on vramAddr?
Yeah.
Or, is the increment happening in $2006.
Assuming you mean the temporary VRAM address register (t), then no, t remains unchanged until $2005, $2006 or $2000 are written to. These 3 ports affect t in some way.
I am confused about this because These values are only 8-bits and addresses are be 16-bits, right?
This is why the PPU needs a toggle to select between first and second writes, so it can get 16 bits of data through a single 8-bit port.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Confused about VRAM Address Increment

Post by tokumaru »

urbanspr1nter wrote:

Code: Select all

// initially:
vramAddressHiToggle = false
loVramAddressByte: Byte = 0
hiVramAddressByte: Byte
effectiveVramAddress: DoubleByte = 0
That's one way to do it.

Code: Select all

if(!vramAddressToggle) { 
     loVramAddressByte = mem.get($2006); 
     vramAddressToggle = true; 
}
else { 
     hiVramAddressByte = mem.get($2006); 
     vramAddressAccessCount++; 

     effectiveVramAddress = merge(loVramAddressByte, hiVramAddressByte);
}
Like I said before, $2006 isn't memory, so you probably shouldn't be using mem.get($2006) to get the value. The correct way to do it would be to intercept the write operation at the exact moment it happens and copy the value then.

Other than that, I don't know what you're using vramAddressAccessCount for, and you forgot to set the toggle back to false on the second write. Oh, and the high/low order is inverted here... The high byte is updated on the first write, the low byte on the second. And don't forget to clear the top 2 bits when updating the high byte.

Code: Select all

/ Then on accesses:
ppuMem.get(effectiveVramAddress)
You need to do the auto-increment after reads too. And don't forget that VRAM reads are buffered: on each read, the value in the buffer is returned to the CPU, and the value from VRAM is copied to the buffer. Except when reading the palette, palette data is returned immediately.

Code: Select all

ppuMem.set(effectiveVramAddress, mem.get($2007));
incrementAddress(effectiveVramAddress); // either 8, or 32?
Again, $2007 is a port, not memory. While treating it as memory might seem to work at first, I'm sure something will go wrong really soon.
Also, if the above is true, does that mean we don't reset the vramAddressToggle until we read PPUSTATUS? When does that get reset?
It toggles on every write to $2006 or $2005, and resets when PPUSTATUS is read.
Last edited by tokumaru on Sun Mar 31, 2019 10:24 am, edited 1 time in total.
urbanspr1nter
Posts: 39
Joined: Thu Aug 16, 2012 7:55 pm

Re: Confused about VRAM Address Increment

Post by urbanspr1nter »

Yes, but again, there's no "data found in $2007", $2007 is just a port, when the PPU detects a write to this address, it immediately intercepts the write and captures the value being written.
Oh, then in my implementation, it is fundamentally wrong. I would then need to call a handler for the PPU when my CPU accesses these addresses. Thanks for the clarification on that piece.
Like I said before, $2006 isn't memory, so you probably shouldn't be using mem.get($2006) to get the value. The correct way to do it would be to intercept the write operation at the exact moment it happens and copy the value then.

Other than that, I don't know what you're using vramAddressAccessCount for, and you forgot to set the toggle back to false on the second write. Oh, and the high/low order is inverted here... The high byte is updated on the first write, the low byte on the second. And don't forget to clear the top 2 bits when updating the high byte.
Oops, that was a typo. But yes definitely understand about the $2006 access now.
Assuming you mean the temporary VRAM address register (t), then no, t remains unchanged until $2005, $2006 or $2000 are written to. These 3 ports affect t in some way.
In what way of $2000? The only thing I came across is $2002 in the wiki :-\
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Confused about VRAM Address Increment

Post by lidnariq »

User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Confused about VRAM Address Increment

Post by tokumaru »

$2000 updates the name table bits in the VRAM address. When accessing name tables, the address register has the following format: 0010NNYY YYYXXXXX

$2006 writes affect bits 0 through 13 (and clear bits 14 and 15), $2005 writes change the X and Y bits, and $2000 writes change the NN bits.

One weird aspect of the PPU is that it uses its addresses registers when rendering the image, which is why the scroll settings (set via $2000 and $2005) affect the address registers. Fir rendering purposes, the PPU address registers (t and v have the following format:

0yyyNNYY YYYXXXXX

XXXXX: coarse X scroll;
YYYYY: coarse Y scroll;
NN: name table;
yyy: fine Y scroll;
(the fine X scroll is kept elsewhere)

When the frame starts, the PPU will automatically copy the value from t to v and use that to determine what part of the background to render, as specified by the bits above.
Post Reply