Question on how to integrate CPU and APU for SNES emulator

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Question on how to integrate CPU and APU for SNES emulator
by on (#190698)
Hi all, for about a month now, I've been working on an SNES emulator and have gotten most of my 65c816 core emulated. I would like to say I am deeply grateful for having a community like NesDev to go to along with great members who have put up with most of my questions recently. Thank you all for that! :beer:

There are lots of resources available in terms of being able to emulate a CPU. I have most of the opcodes and addressing modes emulated aside from things like WAI, STP, MVN, MVP, but looking at Super Mario World (my test ROM), it doesn't seem to be needed for now.


My CPU core is emulated at the opcode level and is essentially one big infinite loop with a switch statement that will execute the appropriate opcode logic based on the 1 byte opcode read in.


I've been running into an issue lately while testing this core. There are parts of code where there is there are two instructions:

Code:
    cmp $2140
    bne xx


I read that $2140 is actually mapped to the memory in the SPC700, which is part of the APU of the SNES. Once I figured that out, I faked values into $2140 and got a little further.


I am now at this point where I would actually like to stop doing this and start developing the APU system for the SNES. The problem is that I'm having a hard time conceptually understanding when the APU comes into play during CPU execution...


Some questions are...

1) When do we know when to play the sound in our CPU execution loop?

2) When do we transfer data back and forth between main and APU memory?


Is it by every other CPU cycles? For instance in my loop am I doing something like this?

Code:
    while(true) {
        switch(opcode) {
            // CPU stuff and count cycles
        }
        if(current_cycles >= some_limit) {
            // DO APU STUFF ---????
        }
    }


My question may be convoluted but essentially I'm just having trouble in terms of how to get the APU and CPU to talk together in code. Any help and suggestions would be greatly appreciated! Thanks!
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190705)
On real hardware, all the different chips run in parallel, each one doing their thing and communicating with each other when necessary. In an emulator, the typical approach is to alternate between all the chips, running each each one for a fixed amount of time, which could be a frame, an scanline or even a CPU cycle. Accuracy is easier at the cycle level, because the state of all chips is up to date at any given time, which is good for when they interact with each other. If you work with larger chunks of time, you need special ways to handle changes that affect other chips, such as interrupts or register writes, so that they *appear* to work at the correct times.

EDIT: Also, so you can handle different mappings of memory and devices (these things change from game to game), none of the chips should have direct access to memory. All reads and writes should go through a dedicated entity that will analyze the addresses being accessed and select the appropriate memory or device for each read/write. This way you can keep each chip mostly isolated from the system, doing their business without worrying about the rest of the system, as is the case with actual parts.
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190720)
Would it then make sense to say that after every number of X cycles my CPU has been running, I can switch off and handle APU/PPU/etc processing? Is this what you mean by "alternating"? And to emit the sound by the APU, i would just need to let's say for example, at a 32KHz sample rate, just make sure I fire off 32,000 samples within 1 second?

I actually have both APU and CPU memories separate as classes in Java. There is a main memory that will then do reads/writes to the appropriate object based on the mapped address. For example if the address is $2140, I would write to APU memory for the appropriate IO register. Hope this is what you meant also. :)
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190731)
urbanspr1nter wrote:
Would it then make sense to say that after every number of X cycles my CPU has been running, I can switch off and handle APU/PPU/etc processing? Is this what you mean by "alternating"?

Yes, you basically have to make it look like all parts are running at the same time, and the most common way to do that is to run each part for a little bit and then move on to the next. Sounds simple, but the interactions between the different parts makes this significantly more complicated. For example, you can't simply run 1 frame worth of CPU instructions and then draw the entire screen at once, because the CPU might have done something that affects the display when it was running, and those changes must show up exactly where they would if the chips were running in parallel. One common technique is to emulate another part whenever something that affect that part happens. For example, if the CPU changes the scroll mid-frame, switch to emulating the PPU and draw the picture all the way until the point where the scroll changes, then go back to emulating the CPU. You also have to predict when other things will affect the CPU state (such as interrupts), so that so you can apply the changes at the correct times.

I'm no emulator author so I'm not 100% sure of the best ways to implement these things, but for interrupts, I imagine the CPU could have a queue of timestamps, populated by the other components (PPU, APU) indicating when interrupts are supposed to happen, and the CPU would then check the current timestamp against the timestamp of the next interrupt on every instruction in order to decide whther to trigger an interrupt, and remove that timestamp from the queue when it does. If the CPU does something that causes those predictions to change, they have to be updated/removed somehow.

A lot of these complications are eliminated when you emulate everything cycle by cycle, since any changes that happen in any component are immediately visible to the entire system, but there's a lot of overhead in switching between all emulated parts every cycle. This shouldn't be a problem for a modern desktop computer, but some devices will struggle with this model.

Quote:
And to emit the sound by the APU, i would just need to let's say for example, at a 32KHz sample rate, just make sure I fire off 32,000 samples within 1 second?

Sound is not my area of expertise either, but I think that's basically it. Common audio-related problems I see a lot in the forums are related to resampling (consoles often generate sound at a much higher frequency than computers work with, so you have to scale the waves down somehow) and synchronization. Some people say it's better to make the audio steady and synchronize the video to it, others say to synchronize to the refresh rate and compress or expand the audio to fit... there are many possible ways to approach this problem, and hopefully you'll get more detailed responses than mine.

Quote:
I actually have both APU and CPU memories separate as classes in Java. There is a main memory that will then do reads/writes to the appropriate object based on the mapped address. For example if the address is $2140, I would write to APU memory for the appropriate IO register. Hope this is what you meant also. :)

Yeah, sounds like that'll do.
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190738)
An audio chip cycle is ~21 master cycles.
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190741)
Everything I'm saying is pretty generic, not limited to a specific machine. If anyone has any SNES-specific tips, please do share.
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190780)
Scheduling and synchronization is possibly the trickiest part for a software based emulator to get right, and harder yet to get efficient (I specify "software" here, because with FPGA based emulation the situation is quite different).

There was an interesting discussion on the topic on this board some months ago: Schedulers (attn: AWJ)

A high level overview of the MAME scheduler is available here.
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190793)
Thanks for the additional answers all.. I have another question and was wondering if this could be confirmed for me...

It looks like the SPC700 has its instructions streamed by the CPU to the IO ports 0x2140-0x2143. Does this mean that it is the CPU that sends the bytes over these ports INTO the SPC700 RAM to tell the SPC700 what to do?

Sorry if it seems like a silly question but a lot of the documents don't seem to be too clear on this fact.

Thanks
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190794)
The SPC700 has a tiny tiny 64-byte bootstrap ROM, which is basically just enough to allow the SNES CPU to send the program/data to it.

See fullsnes.
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190796)
thanks, I was trying to interpret this boot rom and it looks it does wait for signals from the CPU to start storing data received from the IO ports into SPC700 RAM. Do I have this right?

Also does it mean then upon boot when this ROM runs it looks like it will run infinitely waiting for the $CC value to be loaded to start transferring? It looks like the last instruction of the ROM jumps back to the beginning. Then when a transfer is completed... do we jump the the address of the SPC program and then once the SPC program is completed... Does one manually jump back to the ROM's start address?
Re: Question on how to integrate CPU and APU for SNES emulat
by on (#190797)
The SPC program doesn't have a "is completed", really. It can explicitly choose to jump back into the boot ROM whenever, but that's it.