It is currently Sun Oct 22, 2017 4:02 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sun Oct 11, 2015 10:51 pm 
Offline
User avatar

Joined: Wed Aug 26, 2015 8:24 am
Posts: 21
Location: Ontario, Canada
So I'm about ready to write and implement a PPU into my emulator project. Currently my CPU passes nestest tests up until unsupported opcodes tend to appear around halfway through. It is also cycle correct, taking into account page boundaries. The code does all its thing and then tells the CPU to "sleep" for the number of cycles that instruction and addresmode consumes.

Before I start though, I'm wondering how I would time the PPU's 3 ticks per CPU tick. Would it be as simple as just

Code:
void loop(){
   while(running){
      tickCPU();

      tickPPU();
      tickPPU();
      tickPPU();
   }
}


or is it more involved than this?

Thanks!

_________________
Aliasmk- GitHub :: Twitter :: Website
Current ALIAneS Emulator Progress: CPU complete, PPU indev - we have graphics and sprites!


Last edited by Aliasmk on Thu Oct 15, 2015 2:37 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Sun Oct 11, 2015 11:21 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10066
Location: Rio de Janeiro - Brazil
If you plan to support PAL eventually, you can't do it like this, since the PAL consoles have 3.2 PPU cycles per CPU cycle.

I've never written an emulator, but I believe that one way to do it would be to count master clocks instead, since the PPU and CPU clocks are always divisions of that. On NTSC, you run the PPU for 12 master cycles and the CPU for 4, on PAL you run the PPU for 16 master clocks and the CPU for 5.


Top
 Profile  
 
PostPosted: Mon Oct 12, 2015 12:22 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5732
Location: Canada
It might be a better idea to run the CPU until it hits an instruction that writes to or depends on the PPU, then run the PPU as many clocks as it needs to catch up to where the CPU reached, so you can resolve the interdependency.


Top
 Profile  
 
PostPosted: Mon Oct 12, 2015 12:40 am 
Offline

Joined: Thu Aug 20, 2015 3:09 am
Posts: 284
My emulator doesn't have a PPU, so I'm not sure exactly how applicable this is to you, but my APU is run inside my memory access routines.

The CPU emulator calls one of two functions for each bus cycle, cpu_read() and cpu_write(), passing them the current master clock cycle. If either of those functions detect access to an APU address, they invoke the function apu_run() to run the APU up to the current master cycle. At the end of a "frame" (no actual frames since it's audio-only, but any regular interval will do) the CPU emulation loop returns, and apu_run() is invoked again to synchronise the two.

Since the APU and CPU use the same clock rate, my clock cycle counter is simply incremented by one each cycle, but for different clock speeds you can use different increments: have the CPU add 3 to its counter each cycle while the PPU adds one, for instance.


Top
 Profile  
 
PostPosted: Mon Oct 12, 2015 2:43 pm 
Offline
User avatar

Joined: Wed Aug 26, 2015 8:24 am
Posts: 21
Location: Ontario, Canada
rainwarrior wrote:
It might be a better idea to run the CPU until it hits an instruction that writes to or depends on the PPU, then run the PPU as many clocks as it needs to catch up to where the CPU reached, so you can resolve the interdependency.


I don't understand. As far as I've read, the PPU operates at the same time as the CPU drawing frames in the rendering phase, and gets written to during the Vblank phase. Are you saying write to the PPU during vblank, then do whatever game logic in what would be rendering phase, then as soon as you would normally write to vblank again, run the ppu on its own all the way through the rendering phase until it catches up to vblank again?

Thanks

_________________
Aliasmk- GitHub :: Twitter :: Website
Current ALIAneS Emulator Progress: CPU complete, PPU indev - we have graphics and sprites!


Top
 Profile  
 
PostPosted: Mon Oct 12, 2015 3:09 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5732
Location: Canada
No, I'm saying that even though they are synchronized, they do not affect each other on every clock, so you don't need to bounce back and forth every clock. You only need to synchronize them when there is some communication between the two units.

You can just keep executing CPU, and counting the clocks elapsed, until you hit something that interacts with the PPU. Then you just execute the same amount of clocks on the PPU to catch up to that point and get synchronized for the interaction. Here are some examples of synch points:
  • CPU reads or writes to PPU registers at $2000-2007.
  • Write to a mapper register that affects PPU rendering (e.g. CHR bankswitch).

Of course, if you want to emulate clock by clock, that's actually an alright approach. It might be easier to emulate certain things that way, but it also might be more complicated. I think you would have to treat each CPU instruction as a series of steps to be executed over 2-7 clocks to get much accuracy benefit from this.

A lot of emulators just treat each instruction as a single operation. This is why the "catch up later" approach is common. Since instructions are variable length, you have to deal with catching up on 2 cycles or 7 cycles, etc. and once you're dealing with this issue it's usually not a big deal whether you need to catch up on say 5 or 105 cycles at once, and longer batches like that can help with optimization.


Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 1:03 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7234
Location: Chexbres, VD, Switzerland
tokumaru wrote:
If you plan to support PAL eventually, you can't do it like this, since the PAL consoles have 3.2 PPU cycles per CPU cycle.


Of course it could support PAL :
Code:
void loop()
{
   while(running)
   {
      tickCPU();
      tickPPU();
      tickPPU();
      tickPPU();

      tickCPU();
      tickPPU();
      tickPPU();
      tickPPU();

      tickCPU();
      tickPPU();
      tickPPU();
      tickPPU();


      tickCPU();
      tickPPU();
      tickPPU();
      tickPPU();

      tickCPU();
      tickPPU();
      tickPPU();
      tickPPU();
      tickPPU();
   }
}


I never liked that "3.2 PPU Cycles per CPU cycle" saying, I think it's better to word it by saying "16 PPU Cycles while there is 5 CPU cycles"; it makes the thing clearer and involves only integer numbers.

Quote:
As far as I've read, the PPU operates at the same time as the CPU drawing frames in the rendering phase, and gets written to during the Vblank phase.

You are yet another victim of NESDev people misnaming things :) VRAM gets written to during VBlank, not the PPU. The PPU can be written to and read from anytime using $2000-$2007.


Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 5:37 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10066
Location: Rio de Janeiro - Brazil
Bregalad wrote:
Of course it could support PAL :

That's another loop though.


Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 8:35 am 
Offline
User avatar

Joined: Wed Dec 06, 2006 8:18 pm
Posts: 2801
Bregalad wrote:
I never liked that "3.2 PPU Cycles per CPU cycle" saying, I think it's better to word it by saying "16 PPU Cycles while there is 5 CPU cycles"; it makes the thing clearer and involves only integer numbers.


Your example sure makes things look nicer for PAL support. I agree that 3.2 : 1 ratio makes it look complicated where as 16:5 does not seem so bad.


Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 8:45 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7234
Location: Chexbres, VD, Switzerland
Quote:
That's another loop though.

What do you mean exactly by that ? I don't understand you.

If that is what you mean, a more elegant way would be :
Code:
void loop()
{
   int i = 0;
   while(running)
   {
      tickCPU();
      tickPPU();
      tickPPU();
      tickPPU();
      if( (++i == 5) && pal_enabled )
      {
         i = 0;
         trickPPU();
      }
   }
}

That way, when "running" becomes clear, the CPU stops to be emulated right away and no not have a 5 cycle jitter. It makes it also easier to switch between NTSC and PAL, as you just need to check if PAL is enabled in the above if() statement.

Note that I am absolutely not an emu author and I have no way to know if this is a good way to do an emulator or not, I just wanted to point out to Tokumaru that he was wrong that this method do not work for PAL emulation.


Last edited by Bregalad on Tue Oct 13, 2015 9:13 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 8:53 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19115
Location: NE Indiana, USA (NTSC)
Quote:
Code:
trickPPU();

Tricky. Is that fourth tick any different from the other ticks with respect to things like CPU/PPU clock alignment?


Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 8:54 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10066
Location: Rio de Janeiro - Brazil
Bregalad wrote:
I just wanted to point out to Tokumaru that he was wrong that this method do not work for PAL emulation.

I didn't mean that the method wouldn't work for PAL, only that the specific loop he wrote was hardcoded to NTSC. And then you wrote another loop, hardcoded to PAL. I think it would be more elegant to make the timing variable.


Top
 Profile  
 
PostPosted: Tue Oct 13, 2015 9:13 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7234
Location: Chexbres, VD, Switzerland
Oh okay, I misunderstood you from the beginning. I've edited my post above to show a variant that works for both NTSC and PAL.


Top
 Profile  
 
PostPosted: Wed Oct 14, 2015 9:43 am 
Offline
Formerly Fx3
User avatar

Joined: Fri Nov 12, 2004 4:59 pm
Posts: 3064
Location: Brazil
AFAIK, there's nothing official about this detail, but the emulation results are much better when the PPU runs prior to CPU.


Top
 Profile  
 
PostPosted: Wed Oct 14, 2015 12:55 pm 
Offline
User avatar

Joined: Wed Aug 26, 2015 8:24 am
Posts: 21
Location: Ontario, Canada
Zepper wrote:
AFAIK, there's nothing official about this detail, but the emulation results are much better when the PPU runs prior to CPU.


I know you say "nothing official about this detail" but you also say "results are much better". Do you have any examples of emulators that demonstrate this because it sounds like a pretty important factor.

_________________
Aliasmk- GitHub :: Twitter :: Website
Current ALIAneS Emulator Progress: CPU complete, PPU indev - we have graphics and sprites!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 7 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