Page 1 of 2

emulator's main loop

Posted: Sun Mar 14, 2010 1:25 am
by ehguacho
hello again folks! i've a newbie question to solve about the CPU main loop. what i don't know is how to implement the memory handlers in the main loop. as far as i could code my main loop, it looks like this:

Code: Select all

for(;;)
{
     pc = InitPC;
     opcode = rom[pc]
     switch(opcode)
     {
          /* opcode emulation */
     }
     cycles = cycles_opcode[opcode];
}
as you can see, i only have covered the 6502 emulation. so the question is: where in this main loop should i been writing the memory handlers? i mean that blocks of code like "read_memory()" and "write_memory()"[/code]

Posted: Sun Mar 14, 2010 11:31 am
by koitsu
Are you aware that this loop will result in 100% CPU being used at all times? This isn't good.

Posted: Sun Mar 14, 2010 11:40 am
by ehguacho
koitsu wrote:Are you aware that this loop will result in 100% CPU being used at all times? This isn't good.
yes, but it can be easily solved. i could put the opcodes emulation in another file, or put it in a "while" loop to limit the times it's being executed.

Posted: Sun Mar 14, 2010 11:47 am
by MottZilla
You can make read and write memory functions and just call them in the opcode emulation.

Posted: Sun Mar 14, 2010 11:53 am
by koitsu
ehguacho wrote:
koitsu wrote:Are you aware that this loop will result in 100% CPU being used at all times? This isn't good.
yes, but it can be easily solved. i could put the opcodes emulation in another file, or put it in a "while" loop to limit the times it's being executed.
That's not how you go about solving it. :-)

You need to make your main loop basically do the equivalent of a sleep() statement, while you have an interrupt handler or routine that gets called/induced by the kernel every X times a second (usually tied to an internal timer in Windows. In the *IX world you can use something like kqueue on BSD or SIGIO on Linux).

I'll poke Disch and ask him to provide some examples here, as Windows coding really isn't my forte.

Posted: Sun Mar 14, 2010 11:57 am
by ehguacho
MottZilla wrote:You can make read and write memory functions and just call them in the opcode emulation.
you mean something like this?

Code: Select all

switch(opcode)
{
...
     case 0xAD: // LDA Absolute
          addr = (rom[pc + 2] << 8) | rom[pc + 1];
          acc = read_mem[addr];
...
}
...where "read_mem" it's suposed to be memory handler that handles memory readings.

Posted: Sun Mar 14, 2010 12:00 pm
by ehguacho
koitsu wrote:
ehguacho wrote:
koitsu wrote:Are you aware that this loop will result in 100% CPU being used at all times? This isn't good.
yes, but it can be easily solved. i could put the opcodes emulation in another file, or put it in a "while" loop to limit the times it's being executed.
That's not how you go about solving it. :-)

You need to make your main loop basically do the equivalent of a sleep() statement, while you have an interrupt handler or routine that gets called/induced by the kernel every X times a second (usually tied to an internal timer in Windows. In the *IX world you can use something like kqueue on BSD or SIGIO on Linux).

I'll poke Disch and ask him to provide some examples here, as Windows coding really isn't my forte.
you mean like using a function that stops 6502 emulation and then do stuff. when finished, then turn on again the 6502 emulation... isn't it?

Posted: Sun Mar 14, 2010 12:46 pm
by Disch

Code: Select all

opcode = rom[pc] 
This is where you'd use a read handler. You generally don't want to access ROM directly for many reasons (one reason being that the code might not be in ROM, it might be in RAM. Or there might be bankswapping going on, or whatever).

Pretty much for every read/write you'd call your read/write function instead of accessing ROM/RAM directly.
I'll poke Disch and ask him to provide some examples here, as Windows coding really isn't my forte.
Wasn't there another thread about frame limiters on here?

http://nesdev.com/bbs/viewtopic.php?t=6107

you shouldn't have any sleeping code in a CPU loop. You should just have the CPU run for X number of cycles (like a frame or something). Then sleep between frames.

Posted: Sun Mar 14, 2010 3:37 pm
by ehguacho
thanks Disch!
one reason being that the code might not be in ROM, it might be in RAM
i'm taking this project slowly, so first i'll try to get running mapper #0 games with only 1 (one) PRG ROM data and no Trainer. in that case all i've to do is to copy that bank into my ROM memory array, like this:

Code: Select all

...
FILE *romfile;
unsing char *rom;
...
fseek(romfile,16,0)
rom = (unsigned char *)malloc((ROMBanks + 1) * 16 * 1024);
fread(&rom[0x8000],1,0xc000,romfile);
...
what i'm traying to say is that all the data that has to be executed is stored on the PRG ROM memory and there's no memory copied from the cartridge to the RAM's console memory, at least in mapper #0 games. is that right?

Posted: Sun Mar 14, 2010 4:07 pm
by tepples
A mapper 0 game can certainly copy code to, say, $0400-$04FF and jump there.

Posted: Sun Mar 14, 2010 4:20 pm
by tokumaru
There probably are a few mapper 0 games executing code from RAM. It's not extremely common, but it happens.

If you ignore these possibilities, it will be very hard to modify your emulator in the future to support more complex games. Be prepared for what you know is a possibility, and since we just told you that code can execute from RAM then you should consider this possibility.

IMO, you shouldn't build your emulator around the ROM, which is what it looks like you are doing. A ROM file is much more complex than, say, an image file, that you can simply load, decode its contents and call it a day. A ROM is a program, so it actively controls all its surroundings, as opposed to the surroundings controlling it. So I suggest you plug the ROM into your emulator instead.

Model the CPU as accurately as you can. The 6502 has a 64KB addressing space, so give it that. Devise a memory-mapping scheme that will allow you to map whatever you want to that addressing space: RAM, ROM, registers, nothing, whatever. This would be plugging the ROM into your emulator.

Posted: Sun Mar 14, 2010 6:56 pm
by ehguacho
thanks again tepples and tokumaru!
let me say that by now i'm keeping it very simple because all trying to do is to get the point while i have to emulate de PPU. that's why i'm testing my emulator (or whatever it looks like XD) using a very very simple NES demo. actually i've emulated all the instructions that appears in the demo, so now i've gotta move to the PPU emulation and thats where i'm having problems with memory handlers.

Posted: Sun Mar 14, 2010 8:32 pm
by MottZilla
Just an example for read/write functions.

unsigned char ReadMem(int Address)
{
if(Address<0x2000)
{
//return ram value
}
if(Address>=0x8000)
{
// return rom value
}
// exception should be noted if dummy value is returned
return 0;
}

So whenever you access any memory from the emulated CPU's perspective you'd just be calling ReadMem(Address).

You may also want to make PPU Read and Write memory functions too.

Posted: Sun Mar 14, 2010 8:47 pm
by ehguacho
thanks MottZilla! so i should been using that "ReadMem" function whenever an instruction of reading appears? i.e.:

Code: Select all

....
case 0xAD: // LDA ABSOLUTE
/* LDA Absolute emulation goes here*/
accumulator = ReadMem(address);
...
writes to memory could be handled in a similar way.

by the way, what about making a two large blocks of memory (one for the console internal memory and the other for PPU memory)? by doing this i might be forgetting about make several independents arrays (ROM,RAM,VROM,etc...)

Posted: Sun Mar 14, 2010 9:50 pm
by MottZilla
There are many ways to do various parts of the emulator. Using a ReadMem type function to handle reading is one way. And yes your example seems correct.

I'm not sure what you mean about large blocks of memory.