It is currently Wed Aug 23, 2017 4:53 am

All times are UTC - 7 hours



Forum rules


Related:



Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Sun Mar 19, 2017 9:16 am 
Offline

Joined: Thu Aug 16, 2012 7:55 pm
Posts: 27
Hi, I've finally implemented my SPC700 core, but no DSP yet... I've gotten it up to the point where I can bypass the CMP $2140/BNE $FB loop where it checks for the SPC700 ready value without hard coding everything... Basically, the CPU and APU can now sort of "talk to each other" by reading the memory-mapped IO ports.

But I'm trying to wrap my head around how the main CPU and APU loop should execute... Right now I am doing it in a dumb way by executing one instruction of the CPU and APU at a time... Kind of like this:

Code:
while(true) {
    cpu.run();
    apu.run();
}


So basically I am running 1 instruction of the CPU, then handing off to 1 instruction of the APU. I am counting the instruction cycles internally. My wonder is... How am I supposed to actually correctly execute the main system loop?

Is there a set number of instructions, or cycles I should be executing the CPU before executing the APU? I read something about emulator "catch-up" on the NESDev wiki, but I am not sure how to implement it.

Also I am aware that the chips on the SNES do execute in parallel... so I figure that executing each chip one instruction at a time sort of "emulates" this effect on a much faster machine. I am developing on a dual-core Core i7 3.0GHz (2014 MacBook Pro).

I have actually implemented by CPU and APU at the opcode level.. Which basically is a big switch statement that interprets the current opcode byte and increments the program counter registers by the length of the expected instruction.


Top
 Profile  
 
PostPosted: Sun Mar 19, 2017 11:59 am 
Offline

Joined: Sun Mar 27, 2016 7:56 pm
Posts: 132
So, the main issue here is that the CPU and APU don't run at the same rate at all; they don't even use the same clock. For NTSC, everything with the CPU and PPU is based on master cycles with a rate of 21.44727 MHz. So, for instance, a fast CPU cycle is six master cycles (~3.58 MHz), and it takes the PPU four master cycles per pixel as the screen is displayed. However, the APU uses an entirely separate oscillator at 24.576 MHz, and the SPC700 runs at 1/24 of that rate, at 1.024 MHz.

Because of that, there's not really any clean ratio between the CPU and APU rates, which makes timing tricky. (It can even be a little tricky from the perspective of a SNES game developer, not just an emulator writer, because these rates aren't perfect; they can vary slightly from SNES to SNES because of the separate clocks.)

So, simply running one instruction of the CPU and then one of the APU isn't gonna cut it in terms of actual accuracy. That said, I'm not sure what the best way to approach the problem would be, not having much experience writing emulators myself.


Top
 Profile  
 
PostPosted: Sun Mar 19, 2017 1:04 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18836
Location: NE Indiana, USA (NTSC)
The APU clock has a much wider tolerance than the CPU/PPU master clock. Treating the two oscillators as having exactly a 7:8 ratio is within this tolerance and may simplify emulation. This means that for every 21 cycles of the CPU/PPU master clock, you run 24 cycles of the APU master clock, which consists of one S-SMP cycle and two S-DSP cycles.


Top
 Profile  
 
PostPosted: Sun Mar 19, 2017 3:15 pm 
Offline
User avatar

Joined: Mon Jan 23, 2006 7:47 am
Posts: 64
tepples wrote:
The APU clock has a much wider tolerance than the CPU/PPU master clock. Treating the two oscillators as having exactly a 7:8 ratio is within this tolerance and may simplify emulation. This means that for every 21 cycles of the CPU/PPU master clock, you run 24 cycles of the APU master clock, which consists of one S-SMP cycle and two S-DSP cycles.

Specifically, something like this:

Code:
procedure Run;
begin
repeat
        // emulate an entire field
        repeat State.Step until     State.SNES.PPU.VBLANK;  Screen.Render;  // get the field to the host display as soon as it's done
        repeat State.Step until not State.SNES.PPU.VBLANK;
        Application.ProcessMessages;
until Application.Terminated;
end;


procedure State.Step;  inline;
begin
SNES.CPU.Step;
SNES.APU.Step;
Inc(APU_ExtraStepCounter);
if (APU_ExtraStepCounter = 7) then begin
        APU_ExtraStepCounter := 0;
        SNES.APU.Step;
end;
end;


Alternatively you can call SNES.APU.Step (and SNES.PPU.Step etc.) inside each opcode handler, and then process the result also in the opcode handler. This means that the flow of execution would go

Code:
Run
        → State.Step
                → CPU.Step
                        → CPU opcode handler
                                → State.Yield
                                        APU.Step, CART.Step, PPU.Step, WRAM.Step, ...
                                ← State.Yield
                        ← CPU opcode handler
                ← CPU.Step
        ← State.Step
Run


Top
 Profile  
 
PostPosted: Sun Mar 19, 2017 3:40 pm 
Offline

Joined: Fri Feb 24, 2012 12:09 pm
Posts: 520
The nice thing about emulating SNES main CPU and sound CPU is that the CPU's can't throw interrupts at each other. Instead, they can only communicate via port 214xh. That's making the timing/emulation very simple:

All you need is a "run_apu_till_now" function, which is computing how much time has ellapsed on the main CPU, and then runs the sound CPU for the same amount of time.

Essentially, you need to execute that "run_apu_till_now" function ONLY when the main CPU is reading/writing port 214xh (first call the function, and then apply the 214xh read/write).

Apart from that, you should also call "run_apu_till_now" once or then, eg. upon Vblank (to keep generating sound even in periods where the main CPU doesn't access port 214xh).


Top
 Profile  
 
PostPosted: Tue Mar 21, 2017 4:17 pm 
Offline

Joined: Thu Aug 16, 2012 7:55 pm
Posts: 27
nocash wrote:
The nice thing about emulating SNES main CPU and sound CPU is that the CPU's can't throw interrupts at each other. Instead, they can only communicate via port 214xh. That's making the timing/emulation very simple:

All you need is a "run_apu_till_now" function, which is computing how much time has ellapsed on the main CPU, and then runs the sound CPU for the same amount of time.

Essentially, you need to execute that "run_apu_till_now" function ONLY when the main CPU is reading/writing port 214xh (first call the function, and then apply the 214xh read/write).

Apart from that, you should also call "run_apu_till_now" once or then, eg. upon Vblank (to keep generating sound even in periods where the main CPU doesn't access port 214xh).


Hm interesting. I never thought of it that way. How do we determine "till now". I assume that's the parameter of the number of nanoseconds in which the CPU was executing before the 214x access? So let's say the CPU was running for 500ns (arbritrary number) before accessing $2140, does that mean we run the apu for 500ns? Sorry if that sounds kind of dumb to ask. I just wanted to confirm. :)


Top
 Profile  
 
PostPosted: Tue Mar 21, 2017 7:56 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18836
Location: NE Indiana, USA (NTSC)
Under my suggestion, "now" is the master clock divided by 21.


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: Google Adsense [Bot], Stef and 9 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