Emulator's main loop problem

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Emulator's main loop problem

Post by comegordas »

i've got some doubts about the emulator's main loop.

i'm trying to render just the background of Balloon Fight. i know i have to execute 3 PPU cycles for each CPU cycle executed. in code:

Code: Select all

CPUCycles = CyclesTable[Opcode];
PPUCycles = CPUCycles * 3;
but what do i have to do in each PPU cycle? should i fetch background data from the name tables?

by now, what i'm doing is start the emulation with rendering turned off (i mean with bit 8 of $2002 set). so i'm waiting for the rom to make a LDA $2002 to turn on the rendering. after the rendering is set, i fetch 32 * 30 tiles (wich is the entire background), render out those tiles, and then turn the rendering off again, so the emulator can continue executing opcodes.

of course this routine is absolutely worng, because i'm drawing the entire background by fetching the tiles from single name table, and the rom can switch between the name tables to make the background.

so i'm confused about when should i start fetching background tiles and when should i stop. should i always wait for the rom to turn off the rendering via LDA $2002 or shomething similar? or should i force the on/off status of the rendering every N number of cycles?
User avatar
MottZilla
Posts: 2837
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

You don't force anything. The software controls whether or not rendering is enabled or not. What happens on each PPU Cycle depends on various things. It also depends on how you choose to emulate the system on what exactly you need to do. For very simple games (Donkey Kong, Donkey Kong Jr, Mario Bros (not Super), 1942) you can actually just render the entire background at once when scanline 240 finishes rendering.

But the basic idea is you always execute your 3 ppu cycles per 1 cpu cycle. There are usually 341 PPU cycles per scanline, and there are 240 active scanlines, 21 vblank scanlines, and 1 prerender for NTSC. I may be wrong on the numbers, it's been awhile and I didn't look up to confirm.

So depending on the scanline currently being rendered, and the PPU cycle within the scanline, you do different things. And it depends on if rendering is active or not. You render 1 pixel for each PPU cycle for PPU Cycles 1 - 256 of each scanline. Cycles 256 and up modify certain registers at certain times, and fetch certain data. There is alot of information in documents about this sort of thing I believe.

Just remember that alot of games are pretty forgiving of inaccuracy, but there are some games that are pretty brutal if things aren't very accurate to the hardware.
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Post by comegordas »

thanks in advance for your reply MottZilla.

supose i'm using the per-frame rendering method (i guess it'll work for some simple games, once i get the basics i should be moving for a per-scanline method). so far, this is way i've understood i've got to proceed:

* start with the rendering turned off and wait for the game to turn it on
* render a complete frame
* turn off the rendering
* execute some game code keeping trace of the CPU cycles
* since it's known that a frame occurs at a given number of cycles, once the CPU cycles counter exceeds that number i should turn on the rendering and show up the new frame

am i proceeding right? i've tried that way but the background drawed weren't correct. maybe i'm not proceding right, maybe i'm reading from a wrong address.

this is the code i get so far to render the background:

Code: Select all

private void DrawBackground()
{
   int X, Y;

   X = Y = 0;

   // 32x30 is the number of tiles showed up in the screen at the same time
   for (int k = 0; k < 32 * 30; k++)
   {
      if ((k > 0) && (k % 32 == 0))
      {
         Y += (2 * 8);
         X = 0;
      }

      for (int i = 0; i < 8; i++)
      {
         // Fetch a name table entry
         byte NameTableEntry = Data.VRAM[NameTableAddr + i + k];

         // Fetch the corrisponding Attribute Table entry
         byte AttributeTableEntry = Data.VRAM[NameTableAddr + 0x3c0 + i + k];

         // Fetch a low-order byte of the tile
         byte PatternTable_Low = Data.VRAM[NameTableEntry + i];

         // Fetch a high-order byte of the tile
         byte PatternTable_High = Data.VRAM[NameTableEntry + i + 8];

         Color FillColor;

         bool[] TileBits = new bool[16];
         for (int j = 0; j < 8; j++)
         {
            // Fetch the first color bit
            TileBits[j] = Convert.ToBoolean((PatternTable_Low >> (7 - j)) & 0x1);

            // Fetch the second color bit
            TileBits[j + 8] = Convert.ToBoolean((PatternTable_High >> (7 - j)) & 0x1);

             // Combine the color bits into a pallete index
            FillColor = Data.Pallete[(Convert.ToUInt16(TileBits[j + 8]) * 2) + Convert.ToUInt16(TileBits[j])];

            Surface Pixel = new Surface(2, 2);
            Pixel.Fill(FillColor);

            // Blit the pixel into the screen buffer
            Screen.Blit(Pixel, new Point(X + j * 2, Y + i * 2));
         }
      }

      X += (2 * 8);

   }

   // Render the entire frame
   Screen.Update();

   return;
}
NameTableAddr -> the Name Table base address
TileBits -> each one of the bits of a pattern table entry

roughly coded in C# + SDL. note that i'm still not fetching the two upper color bits from the Attribute Table data, that's up to be done later.
the rest of the code should be self explanatory, anyway feel free to ask.

note that i'm just trying to draw the background, wich is the lowest point of the video emulation.
User avatar
MottZilla
Posts: 2837
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

First off, you don't "turn off rendering" if you mean modifying the NES registers to turn it off. Only the game software should control if rendering is active. But remember, there is the emulated NES's "rendering" and your emulator's rendering. Two separate things.

The thing I recommend you doing for now (you can improve and change this later) would be to execute 113 (or 114) CPU cycles, and then increase a Scanline variable. This way you can track what your emulated NES is doing video wise. So you can start at 0 and work your way up to 261 (giving you 262 scanlines). You could make scanlines 0 - 239 your "active" scanlines where the screen is being drawn. Then scanlines 240 - 260 are your VBlank scanlines where nothing is drawn, and the game will be informed its in VBlank by your emulator setting the Vertical Blank flag in register $2002 and also by executing a NMI interrupt on the CPU. On scanline 261 nothing needs to happen other than just letting time pass, letting the cpu execute cycles.

So what you can do then after this is after scanline 239 is done and you increase the counter to 240, you can set the VBlank flag in $2002, trigger your NMI, and execute your emulator's code to draw the screen/nametable/background. Assuming your CPU code is good and you implement the registers needed correctly you should be able to get simple games like Donkey Kong running and showing their backgrounds.

By the way, this is a good way to start, by just drawing the background. And then later you can add decoding the Attribute Table so the backgrounds use the right colors. Then you can add Sprite rendering. After that simple games would be playable if you add input emulation. Then maybe you'll try adding scrolling. After that maybe you'll consider doing scanline rendering instead of full frame rendering. There is alot you can do but you should build up to it since I assume this is your first emulator.
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Post by comegordas »

thank you very very much MottZilla, you've been as clear as water. on to code now!
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Post by comegordas »

back again MotZilla. i tried to follow your advices, but i'm still having problems with the background rendering.

i'll let my code explain it for me, since i consider it's self explanatory. my apologize in advance if someone considers that i'm being insulting by doing this. no intention to get pissed anybody. i'm not being lazzy, it's just sometimes it's not easy when you are a non-native english speaker. so "go take an english class" are going to be consider a constructive reply XD

this is my emulator's main loop:

Code: Select all

while(CPU_Running )
{
    if(Scanline < 240) // the active scanlines
    {
        RunScanline(); // draw a single scanline
        Scanline++; // increment the scanlines counter
    }
    else if(Scanline < 262) // the VBlank scanlines
    {
        PPU_2002 |= 0x80; // tell the game we're in VBlank so it can execute code
        Scanline++; // increment the scanlines counter
        CyclesCPU -= 114; // decrement the CPU cc's counter

        while(CyclesCPU <= 114) // execute 114 CPU cc's
        {
            OpCode = RAM[PC]; // fetch opcode
            EjecuarOpCode(OpCode); // execute opcode
            CyclesCPU += CyclesTable[OpCode]; // increment the CPU cc's counter
        }
    }
    else if(Scanline == 262)  // if it's the last scanline, reset the scanlines counter
    {
        Scanline = 0;
    }
}
and this is my (so very basic) ppu engine:

Code: Select all

void RunScanline()
{
    int i, j, Y;
    byte NT_Entry, PT_Entry_Low, PT_Entry_High;
    word PT_Addr, NT_Addr;
    byte ColorBit0, ColorBit1, ColorIndex;

    if(Scanline == 0) // the dummy scanline
    {
        // TODO. Nothing here yet...
    }
    else if((Scanline > 0 ) && (Scanline <= 240)) // the active scanlines
    {
        switch(READ_BIT(PPU_2000,4)) // fetch base pattern table address
        {
            case 0: PT_Addr = 0x0000; break;
            case 1: PT_Addr = 0x1000; break;
        }

        switch(PPU_2000 & 0x3) // fetch base name table address
        {
            case 0: NT_Addr = 0x2000; break;
            case 1: NT_Addr = 0x2400; break;
            case 2: NT_Addr = 0x2800; break;
            case 3: NT_Addr = 0x2c00; break;
        }

        for(i = 0; i < 32; i++) // the basic background drawing loop
        {
            NT_Entry = VRAM[NT_Addr + i]; // fetch tile number

            Y = (Scanline - 1) % 8; // fetch tile byte-line number
            PT_Entry_Low = VRAM[PT_Addr + (NT_Entry * 16) + Y]; // fetch the lower sliver from the pattern table
            PT_Entry_High = VRAM[PT_Addr + (NT_Entry * 16) + Y + 8]; // fetch the upper sliver from the pattern table
            Y++; // increment the tile byte-line number

            for(j = 7; j >= 0; j--) // render the 1x8 pixel sliver of the tile
            {
                ColorBit0 = READ_BIT(PT_Entry_Low,j); // fetch the lower color bit
                ColorBit1 = READ_BIT(PT_Entry_High,j); // fetch the upper color bit
                ColorIndex = (ColorBit1 << 1) | ColorBit0; // combine the color bits into a pallete index

                switch(ColorIndex) // render a single pixel
                {
                    case 0: putpixel(screen,(i * 8) + ((j * (-1)) + 7),Scanline - 1,COLOR_0); break;
                    case 1: putpixel(screen,(i * 8) + ((j * (-1)) + 7),Scanline - 1,COLOR_1); break;
                    case 2: putpixel(screen,(i * 8) + ((j * (-1)) + 7),Scanline - 1,COLOR_2); break;
                    case 3: putpixel(screen,(i * 8) + ((j * (-1)) + 7),Scanline - 1,COLOR_3); break;
                }
            }
        }
    }

    return;
}
as you can see, there's no attribute table data implementation yet.
after that, this is what the screen shows up (the game is Donkey Kong Jr, but the i'm getting the same crappy results on every game):

Image

and after a little while, that image turns into this one:

Image

i tryed it by setting the register $2002 to 0 at startup, and by setting the bit #7 to 1, and by setting the bit #7 to 0, and there's no difference between one way or another. it shows the same images or either nothing.

by the way, i'm setting the bit #7 of $2002 every time the game reads from it, as NESTECH says. also, there's no implementation of scrolling yet in my emu. maybe i'm too stupid but i really don't understand Loopy's document, wich seems has to very important. anyway, there's no scrolling in the game's background that i'm testing my emu with (at least in the title screen). or maybe i'm wrong? there's scrolling and that's way i'm not showing the tiles correctly?

since all the tiles that comes up to screen are the same, it's clear that i'm not fetching the tile address correctly, or i'm fetching it in wrong a moment. also, i tried it by executing some game code for the first 20 scanlines before the program goes into the main loop, so it have enough time to modify the VRAM and set up the background, but nothing changed.

this is getting me so frustrated... i'm spending hours and hours in coding, reading and changing things into my program, but all i'm getting is my head hitting the wall. oh lord have mercy! XD

i'm not asking for code, i'm just looking for someone to shed some light here. any reply will be highly apreciated.

peace out!

EDIT: just in case, this is what FCEUXDSP Name Table viewer show:

Image

so gues yeah... i'm totally fetching the tiles from anywhere but the VRAM :(
Hamburgler
Posts: 36
Joined: Wed Jul 04, 2007 8:40 am

Post by Hamburgler »

Code: Select all

NT_Entry = VRAM[NT_Addr + i]; // fetch tile number 
You're only drawing the topmost line's worth of tiles on every scanline.

Code: Select all

NT_Entry = VRAM[NT_Addr + ((scanline / 8) * 32) + i]; // fetch tile number 
It also looks like you may have a mirroring issue, or something else that's making you draw from the wrong page with all the '0' tiles.

Try forcing all reads and writes to one nametable (0x2000) and see what that turns up for you.
User avatar
MottZilla
Posts: 2837
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

First up, the PPU $2002 VBlank flag is set ONCE. When VBlank begins, you set the flag. You do not continue to set the flag for every scanline in Vblank. Next, be sure that when the CPU reads $2002, that after you return the value, you CLEAR the VBlank flag. That is the correct behavior.

Next your main loop seems confusing to me. Are you executing CPU Cycles before drawing the line? Or are you even executing CPU Cycles for all scanlines and not just for VBlank scanlines?

Next, you are setting the VBlank flag in $2002, that's good. But I see no mention of triggering a NMI interrupt. If you don't do this, you won't see anything but those 0 tiles and stuff like that. Be sure to properly implement the NMI interrupt before expecting games to do very much.

So I would do those things first. Then let us know if you still have problems after that or have questions about it.
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Post by comegordas »

Hamburgler: thanks! didn't note that. my brain went fried last night! the results are still the same, but you we're right, i was reading always the topmost line of every tile :wink:

MottZilla:
MottZilla wrote:the PPU $2002 VBlank flag is set ONCE. When VBlank begins, you set the flag
dind't know that. so i guess it can be easily corrected like:

Code: Select all

if(Scanline < 240) // the active scanlines
{
    RunScanline();
    Scanline++;

    // and that's all... no manullay setting/resetting of the VBL flag

}
else if(Scanline < 262) // the VBlank scanlines
{
    if(Scanline == 240) PPU_2002 |= 0x80; // manually set the VBL flag at start of VBlank

    // execute some game code here, but no manually handling
    // of the VBL flag

}
MottZilla wrote:be sure that when the CPU reads $2002, that after you return the value, you CLEAR the VBlank flag
yes, i'm reseting the VBL flag AFTER the read occurs and returning the correct value.

Code: Select all

tmp2002 = PPU_2002;
PPU_2002 &= 0x7f;
return tmp2002;
so the behavior is right.
MottZilla wrote:Are you executing CPU Cycles before drawing the line?
yes, that's what i'm doing. as you can see in the code, i'm firstly rendering the first 240 scanlines, and then execute code for the scanlines #240 to #262, wich are the VBlank ones.

as for the NMI's, i've been reading about them but didn't know that we're indispensable in such a low point of rendering. sorry about that. so on NMI handling now...

as far as i read, a NMI is triggered at the start of a new frame. since my main loop is scanline based, i should be triggering a NMI at the end of scanline #262. but bit #7 of $2000 is wich controls if the NMI is triggered or not when VBlank is reached, so i should be checking if it is set before i go into the NMI routine. therefore, the basics should be like:

Code: Select all

...
else if(Scanline == 262)
{
    Scanline = 0; // reset scanline counter
    TriggerNMI();
}
am i being correct? sorry if my question is stupid, but there's a LOT of things involved here and sometimes i got confused :\
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

comegordas wrote:as for the NMI's, i've been reading about them but didn't know that we're indispensable in such a low point of rendering.
Lots of games do all of their rendering in the NMI, and some have all of their code running inside the NMI, so if you don't emulate NMIs nothing will ever show up on the screen for those games.
as far as i read, a NMI is triggered at the start of a new frame.
The NMI is triggered when VBlank starts.
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Post by comegordas »

well well well... spent last day reading 'n coding and finally got some good results.

i've added a simply NMI implementation to my emu. what i'm doing is at scanline #240, when VBlank starts, i set $2002.7 to 1 so the game knows that VBlank has started. after that if $2000.7 is set, i push the PC and the status byte and go into the NMI routine. important: as i read, after push the status byte i've got to raise the interrupt flag status byte BEFORE i go into the NMI routine. is it ok? don't remember now in wich doc i read it (read that yesterday too late at night), but that was the only one doc wich mentioned that, that's why i'm asking.

have a simply question now: assuming that the game setted $2000.7 to 0 (so no NMI will be triggered at start of VBlank) and i went into the NMI routine, do i have to force that bit to 1 at the END of VBlank, saying to the game that in the NEXT VBlank an NMI will be triggered if it doesn't disable it? i've tried setting and resseting that bit and worked for some games but not for others. maybe it's an opcode emulation problem, as i mention at the end of the post.

finally, balloon fight gave me this beautiful image:

Image

donkey kong jr. wasn't bad at all...

Image

...but 10 Yard Fight :\
nothing showed up with this last rom. seems like the game falls into an infinite bucle, so probably is an opcode emulation problem.

so i've to say THANKS to all the people here who helped me out. this is something i always wanted to do since i was 14 and started to learn programming. but there's a lot yet to do... ;)
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

comegordas wrote:...but 10 Yard Fight :\
nothing showed up with this last rom. seems like the game falls into an infinite bucle
A lot of early NES games run entirely in the NMI handler, where the main thread is just

Code: Select all

forever: jmp forever
Super Mario Bros. is the same way.
so probably is an opcode emulation problem.
If you suspect CPU problems, the next step is probably to run nestest.
User avatar
MottZilla
Posts: 2837
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Again, do not "force" bits. If the software disables NMI on VBlank, that's what happens. The game may intend for this to happen and if you force it to happen differently you may crash the game.

You should definitely run the NES CPU test rom next. It can spot potential problems with your CPU core which most likely you will have a few errors. Or maybe you have some addressing mode errors. These can be found or atleast get you pointed in the right direction by the test rom.

http://blargg.parodius.com/nes-tests/instr_test-v2.zip
User avatar
comegordas
Posts: 53
Joined: Sat Aug 14, 2010 7:12 pm

Post by comegordas »

ok thanks! let's test it! :D
Post Reply