Execution of main loop in emulator.

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
Post Reply
urbanspr1nter
Posts: 39
Joined: Thu Aug 16, 2012 7:55 pm

Execution of main loop in emulator.

Post by urbanspr1nter »

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

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.
niconii
Posts: 219
Joined: Sun Mar 27, 2016 7:56 pm

Re: Execution of main loop in emulator.

Post by niconii »

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.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Execution of main loop in emulator.

Post by tepples »

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.
creaothceann
Posts: 610
Joined: Mon Jan 23, 2006 7:47 am
Location: Germany
Contact:

Re: Execution of main loop in emulator.

Post by creaothceann »

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

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

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
My current setup:
Super Famicom ("2/1/3" SNS-CPU-GPM-02) → SCART → OSSC → StarTech USB3HDCAP → AmaRecTV 3.10
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Execution of main loop in emulator.

Post by nocash »

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).
urbanspr1nter
Posts: 39
Joined: Thu Aug 16, 2012 7:55 pm

Re: Execution of main loop in emulator.

Post by urbanspr1nter »

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. :)
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Execution of main loop in emulator.

Post by tepples »

Under my suggestion, "now" is the master clock divided by 21.
Post Reply