It is currently Thu Nov 23, 2017 2:40 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Sat Aug 26, 2017 7:44 pm 
Offline

Joined: Thu Aug 16, 2012 7:55 pm
Posts: 34
I am aware that the PPU exposes various registers to the CPU (PPUCTRL/2000, PPUSTATUS/2002, PPUADDR/2006, PPUDATA/2007, etc), from a CPU perspective, is this a unidirectional way of communication to the PPU? For example something like this:

Code:
       PPU Request
[CPU] -------------> [PPU]


In emulator development, if a CPU makes a memory-mapped read/write on a specific PPU register, does that make direct access to the PPU RAM right away? Or is it held in the PPU registers and then written to PPU Memory by the PPU. Stupid question, I know... but getting a bit confused when reading the wiki docs. :oops:


Top
 Profile  
 
PostPosted: Sat Aug 26, 2017 7:57 pm 
Offline
Site Admin
User avatar

Joined: Mon Sep 20, 2004 6:04 am
Posts: 3484
Location: Indianapolis
Conceptually, I think of it as being immediate. But I would assume it's in fact quantized to the next PPU cycle (though I don't really know exactly). Since the PPU/CPU are running at different rates and not in sync. Someone else would know or explain better than me. But it might help if you can think of the particular situation you have in mind.

The $2007 register can be read by the CPU, but it's actually buffered. So you read $2007 once it fills the buffer, then the next $2007 read actually returns the buffer (and fills the buffer with the next byte etc.). However, this doesn't happen when you read the palette memory (which is RAM internal to the PPU). Maybe This kind of implies that palette memory access is immediate? But the rest would go through the PPU's normal process for accessing external memory.

I'm not an emu dev, so hopefully this doesn't hurt more than it helps, haha.


Top
 Profile  
 
PostPosted: Sat Aug 26, 2017 8:18 pm 
Offline

Joined: Thu Aug 16, 2012 7:55 pm
Posts: 34
Hmm yeah the Wiki page here (http://wiki.nesdev.com/w/index.php/PPU_ ... rs#PPUADDR) says:

Quote:
Because the CPU and the PPU are on separate buses, neither has direct access to the other's memory. The CPU writes to VRAM through a pair of registers on the PPU. First it loads an address into PPUADDR, and then it writes repeatedly to PPUDATA to fill VRAM.


So, I am wondering if when we run our PPU cycles after running the current CPU instructions in our execution loop, do we read the registers and perform the memory operations?

Code:
executionLoop() {
      executeCpuInstruction(); // potentially a PPU register access here.
      executePpuCycles(); // do we handle the writes to VRAM here based off the registers?
}


OR does the documentation mean that these "registers" can be seen as directly mapped to PPU RAM as the only "path" to the PPU memory since both CPU and PPU buses are separate (which I assume it means they don't share memory, which I know.)


Top
 Profile  
 
PostPosted: Sat Aug 26, 2017 8:30 pm 
Offline

Joined: Thu Jun 04, 2009 9:07 am
Posts: 30
That would not work for some instructions as it doesn't represent the two happening at the same time. For example, read-modify-write instructions (from nesdev.com/6502_cpu.txt):
Quote:

Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
SLO, SRE, RLA, RRA, ISB, DCP)

# address R/W description
--- ------- --- ------------------------------------------
1 PC R fetch opcode, increment PC
2 PC R fetch address, increment PC
3 address R read from effective address
4 address W write the value back to effective address,
and do the operation on it
5 address W write the new value to effective address


Between CPU cycles 3 and 4, atleast 3 PPU cycles (3.2 on PAL, I think) have happened, so even if reads/writes to $2007 (for example) only happened on PPU clocks, it wouldn't matter to the cpu at all. What will matter is executing 3 PPU cycles for each of those cpu cycles, as the $2007 pointer (loopy_v? is there a better name?) would get incremented 3 times as the instruction does 3 accesses to the address.


Top
 Profile  
 
PostPosted: Sat Aug 26, 2017 8:39 pm 
Offline

Joined: Thu Aug 16, 2012 7:55 pm
Posts: 34
If then for this case -- for emulator development purposes, would it then make sense to create some sort of buffer for these specific registers? Something like a queue that holds a struct of memory reads and writes to the PPU? Then we can read the queue and process these memory operations to "catch up" to the CPU?


Top
 Profile  
 
PostPosted: Sat Aug 26, 2017 9:22 pm 
Offline

Joined: Thu Jun 04, 2009 9:07 am
Posts: 30
That's one option. It takes being careful about when various things trigger catch up, like sprite-0-hit. In my case, I just chose to write my cpu as NES-specific as it has calls to ppu_cycles(3) throughout instructions after emulating each cycle, although sometimes that's not necessary as not every cycle of every instruction has a memory access that's important to the ppu like reading the absolute address operand (eg. could jump to PC=$2000, but the chances of there being useful code from that (bus conflict?) are slim to none) or none at all (eg. nop, branches, etc) just end that switch-case with ppu_cycles(3*whatever cycles). (my emu was NTSC only too :beer:


Top
 Profile  
 
PostPosted: Sun Aug 27, 2017 6:54 am 
Offline

Joined: Thu Aug 16, 2012 7:55 pm
Posts: 34
Haha, ok.. Let me try that. I will plan for some of the cases you had mentioned.... Right now my goal is to just get the Donkey Kong title screen to appear. >.<

I think my plan is that since I have counted all CPU for each instruction, after the execution for the CPU instruction, I will take the CPU cycles * 3 to get the PPU cycles to be executed. The plan is to use that to know how many cycles the PPU should executed. When I run my PPU , I will decrement that value for the specific operations involved such as memory access (2 cycles?) until it is <= 0.

I have been looking at this document to get an idea of how I should execute my PPU cycles: https://wiki.nesdev.com/w/index.php/PPU_rendering.

I would assume that when my PPU state is at scanline 241, I would then just trigger the NMI and Vblank and after the PPU has finished execution, my loop will just check for that NMI flag raised and execute the NMI routine from there...

Also, I just had a realization on how should a CPU read from PPU Memory work? Say an LDA from $2007... That would be buffered into the queue too, right?

Not looking for exact accuracy, but I am not sure of any considerations I should take with this approach... What are your thoughts?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group