emulator's main loop

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

User avatar
ehguacho
Posts: 83
Joined: Tue Mar 09, 2010 11:12 pm
Location: Rosario, Argentina
Contact:

Post by ehguacho »

MottZilla wrote: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.
let me show you my code:

Code: Select all

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <conio2.h>
#include <string.h>

// DECLARACION DE DEFINES

#define LD_IMM(REG) REG = mem[++pc]; pc++; z_flag = !(REG); n_flag = REG & 0x80
#define LD_ABS(REG) REG = ReadMem((mem[pc + 2] << 8) | mem[pc + 1]); pc += 3; z_flag = !(REG); n_flag = REG & 0x80
#define LD_ZP(REG) REG = ReadMem((mem[++pc] << 8) | 0x00); pc++; z_flag = !(REG); n_flag = REG & 0x80
#define LD_ABSIND(REG,IND) REG = ReadMem(((mem[pc + 2] << 8) | mem[pc + 1]) + IND);  pc += 3; z_flag = !(REG); n_flag = REG & 0x80
#define ST_ABS(REG) WriteMem((mem[pc + 2] << 8) | mem[pc + 1],REG); pc += 3;
#define IN(REG) REG++; pc++; z_flag = !(REG); n_flag = REG & 0x80
#define CP_IMM(REG) if(REG < mem[++pc]) { n_flag = 1; z_flag = c_flag = 0; }; \
                    if(REG == mem[pc]) { n_flag = 0; z_flag = c_flag = 1; }; \
                    if(REG > mem[pc]) { n_flag = z_flag = 0; c_flag = 1; };\
                    pc++;
#define BRANCH(FLAG,VALUE) pc += 2; if(FLAG == VALUE) { pc--; (mem[pc] > 0x7f) ? pc -= (~mem[pc] & 0x00ff) : pc += (mem[pc] & 0x00ff); }

// DECLARACION DE FUNCIONES

static inline unsigned char ReadMem(int addr);
static inline void WriteMem(int addr,int value);
static inline void WritePPU(int value);

// DECLARACION DE VARIABLES GLOBALES

unsigned char *mem,*ROM,*VRAM;
unsigned int pc;

// INICIO DEL PROGRAMA PRINCIPAL

int main(void)
{
    int tecla;
    char romname[50];
    FILE *romfile;
    unsigned long int ciclos_cpu;
    int z_flag,n_flag,c_flag;
    int acc,x,y;
    int opcode;
    int RomBanks16kb;
    unsigned int InitPC;
    const char tabla_ciclos_cpu[256] = // Vector que almacena los ciclos usados por cada opcode
    {
        7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,2,5,2,8,4,4,6,6,2,4,2,7,
        5,5,7,7,6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,2,5,2,8,4,4,6,6,2,4,2,7,
5,5,7,7,6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,2,5,2,8,4,4,6,6,
        2,4,2,7,5,5,7,7,2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,2,6,2,6,4,4,4,4,
2,5,2,5,5,5,5,5,2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,2,5,2,5,
        4,4,4,4,2,4,2,5,4,4,4,4,2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,2,5,2,8,
4,4,6,6,2,4,2,7,5,5,7,7,2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
        2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7
    };

    clrscr();

    strcpy(romname,"demo.nes");
    romfile = fopen(romname,"rb");
    mem = (unsigned char *)malloc(0x10000);
    VRAM = (unsigned char *)malloc(0x4000);
    memset(mem,0,0x10000);
    memset(VRAM,0,0x4000);
    fseek(romfile,16,0);
    fread(&mem[0x8000],1,0x4000,romfile);
    fseek(romfile,16,0);
    fread(&mem[0xc000],1,0x4000,romfile);
    fseek(romfile,16400,0);
    fread(VRAM,1,0x2000,romfile);
    fclose(romfile);

    InitPC = (mem[0xfffd] << 8) | mem[0xfffc];
    pc = InitPC;
    acc = x = y = ciclos_cpu = 0;
    for(;;)
    {
        opcode = mem[pc];
        switch(opcode)
        {
            // LDx IMMEDIATE

            case 0xa9: LD_IMM(acc); break;
            case 0xa2: LD_IMM(x); break;
            case 0xa0: LD_IMM(y); break;

            // LDx ABSOLUTE

            case 0xad: LD_ABS(acc); break;
            case 0xae: LD_ABS(x); break;
            case 0xac: LD_ABS(y); break;

            // LDx ZERO PAGE

            case 0xa5: LD_ZP(acc); break;
            case 0xa6: LD_ZP(x); break;
            case 0xa4: LD_ZP(y); break;

            // LDx ABSOLUTE INDEXED

            case 0xbd: LD_ABSIND(acc,x); break;
            case 0xb9: LD_ABSIND(acc,y); break;
            case 0xbe: LD_ABSIND(x,y); break;
            case 0xbc: LD_ABSIND(y,x); break;

            // STx ABSOLUTE

            case 0x8d: ST_ABS(acc); break;
            case 0x8e: ST_ABS(x); break;
            case 0x8c: ST_ABS(y); break;

            // INx IMPLIED

            case 0xe8: IN(x); break;
            case 0xc8: IN(y); break;

            // CPx IMMEDIATE

            case 0xc9: CP_IMM(y); break;
            case 0xe0: CP_IMM(x); break;
            case 0xc0: CP_IMM(y); break;

            // BRANCH RELATIVE

            case 0xd0: BRANCH(z_flag,0); break;
            case 0xf0: BRANCH(z_flag,1); break;
            case 0x90: BRANCH(c_flag,0); break;
            case 0xb0: BRANCH(c_flag,1); break;
            case 0x10: BRANCH(n_flag,0); break;
            case 0x30: BRANCH(n_flag,1); break;

            default:
                printf("%X: %X\n",pc,mem[pc]);
                pc++;
                break;
        }

        ciclos_cpu = tabla_ciclos_cpu[opcode];

        while(kbhit())
        {
            tecla = getch();
            if(tecla == 27) { free(mem); free(ROM); exit(0); }
        }
    }

    free(mem);
    free(ROM);
    return 0;
}

static inline unsigned char ReadMem(int addr)
{
    unsigned char temp2002,temp2007;

    if(addr < 0x2000) return mem[addr];
    else
    {
        switch(addr)
        {
            case 0x2002:
                temp2002 = VRAM[addr];
                VRAM[addr] &= 0x7f; // Ponemos el bit 7 a 0
                VRAM[0x2005] = VRAM[0x2006] = 0;
                return temp2002;
                break;

            case 0x2004:
                return VRAM[0x2003];
                break;

            case 0x2007:
                temp2007 = VRAM[0x2007];
                (VRAM[0x2000] & 0x4) == 0 ? VRAM[0x2006]++ : VRAM[0x2006] += 32;
                return temp2007;
                break;
        }
    }
    return 0;
}

static inline void WriteMem(int addr,int value)
{
    int temp2006;
    static int PrimeraLectura = 1; // Valor 1 si es la primera lectura, 0 lo contrario

    switch(addr)
    {
        case 0x2000:
        case 0x2001:
            VRAM[addr] = value;
            break;

        case 0x2006:
            if(PrimeraLectura == 1)
            {
                temp2006 = mem[pc + 1] << 8;
                PrimeraLectura = 0;
            }
            else
            {
                temp2006 |= mem[pc + 1];
                PrimeraLectura = 1;
            }
            break;

        case 0x2007:
            if((VRAM[0x2002] & 0x10) == 1) break;
            else WritePPU(value);
            break;
    }
    return;
}

static inline void WritePPU(int value)
{
    return;
}
that all i've done until now. as you can se i only declared 2 block of memry: one for console internal memory and one for PPU memory. code it's not commented but i think its self explanatory. any doubts just ask me! i'm wide open to suggestions too!
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Registers are not RAM. Reads and writes to $2000 - $2007 refer to specific registers that don't behave like RAM. They could be part of the ReadMem function by checking if Address==0x2002 for example.

VRAM is not a single "block" of memory. Neither is CPU address space.

From $0000 to $1FFF is RAM. However, there is only $800 bytes of RAM. But because of mirroring the same RAM appears in that area. $2000-$2007 are registers that are special memory, not RAM. The address range of $6000 to $7FFF is often PRG-RAM in many cartridge types, but not always. Sometimes it is PRG-ROM. Most of the time it's nothing. $8000-$FFFF is almost always PRG-ROM.

For the PPU you have $0000 - $2000 containing the Pattern Tables, the graphics. $2000 - $2FFF contain the nametables, the background array data. $3F00 - $3FFF contains the Palette RAM.

You should read all the relevant documents on the memory map for sure.

The main thing to realize is that you can't have one big array containing the entire address space for CPU and PPU.
User avatar
ehguacho
Posts: 83
Joined: Tue Mar 09, 2010 11:12 pm
Location: Rosario, Argentina
Contact:

Post by ehguacho »

actualy i've declared 2 blocks of memory: one for the console (0x10000 kb) and one for PPU (0x4000). is that right?
sorry about my english, i'm from argentina...

http://nestate.uuuq.com
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

What do you plan to do when the cartridge decides to switch what part of memory appears in CPU $8000-$BFFF? What do you plan to do when the cartridge decides to switch what part of memory appears in PPU $0000-$0FFF?
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

See the NES games besides NROM, can instantly change what appears at certain CPU and PPU address space. It is HORRIBLE performance to keep an array for $8000-$FFFF as many games switch banks many times per frame.

The thing to do is to implement a system that can change what memory you make appear at any address space so you just change a variable rather than copy a bunch of memory. The common thing to do (atleast I think it is) is to use Pointers, or a Page Value variable. For example:

unsigned char ReadMem(int Address)
{
if(Address>=0x8000 && Address<0xA000)
{
return PRGROM[(PRGPAGENUMBER*0x2000)+Address&0x1FFF];
}
}

The concept is now you can change the PRGPAGENUMBER to different parts of PRG-ROM and when you implement memory mappers you'll have a system in place that lets you do it. This is just an example of one way to do it. Another way is to use Pointers.

You do not declare 0x10000 for the "console". As I said, registers aren't regular RAM. You probably should make the registers variables in your program. With PPU memory, again don't declare 0x4000 of memory for it. It's split up too.

Since you don't seem to have looked at enough documents I'll paste a memory map for you.

CPU Memory Map

Code: Select all

    +---------+-------+-------+-----------------------+
    | Address | Size  | Flags | Description           |
    +---------+-------+-------+-----------------------+
    | $0000   | $800  |       | RAM                   |
    | $0800   | $800  | M     | RAM                   |
    | $1000   | $800  | M     | RAM                   |
    | $1800   | $800  | M     | RAM                   |
    | $2000   | 8     |       | Registers             |
    | $2008   | $1FF8 |  R    | Registers             |
    | $4000   | $20   |       | Registers             |
    | $4020   | $1FDF |       | Expansion ROM         |
    | $6000   | $2000 |       | SRAM                  |
    | $8000   | $4000 |       | PRG-ROM               |
    | $C000   | $4000 |       | PRG-ROM               |
    +---------+-------+-------+-----------------------+
PPU Memory Map. The first is a basic version, the 2nd is more detailed.

Code: Select all

      +---------+-------+--------------------+
      | Address | Size  | Description        |
      +---------+-------+--------------------+
      | $0000   | $1000 | Pattern Table #0   |
      | $1000   | $1000 | Pattern Table #1   |
      | $2000   | $800  | Name Tables        |
      | $3F00   | $20   | Palettes           |
      +---------+-------+--------------------+


        Programmer Memory Map
      +---------+-------+-------+--------------------+
      | Address | Size  | Flags | Description        |
      +---------+-------+-------+--------------------+
      | $0000   | $1000 | C     | Pattern Table #0   |
      | $1000   | $1000 | C     | Pattern Table #1   |
      | $2000   | $3C0  |       | Name Table #0      |
      | $23C0   | $40   |  N    | Attribute Table #0 |
      | $2400   | $3C0  |  N    | Name Table #1      |
      | $27C0   | $40   |  N    | Attribute Table #1 |
      | $2800   | $3C0  |  N    | Name Table #2      |
      | $2BC0   | $40   |  N    | Attribute Table #2 |
      | $2C00   | $3C0  |  N    | Name Table #3      |
      | $2FC0   | $40   |  N    | Attribute Table #3 |
      | $3000   | $F00  |   R   |                    |
      | $3F00   | $10   |       | Image Palette #1   |
      | $3F10   | $10   |       | Sprite Palette #1  |
      | $3F20   | $E0   |    P  |                    |
      | $4000   | $C000 |     F |                    |
      +---------+-------+-------+--------------------+
User avatar
ehguacho
Posts: 83
Joined: Tue Mar 09, 2010 11:12 pm
Location: Rosario, Argentina
Contact:

Post by ehguacho »

i've seen those memory maps, its are currently on marat fayzullin's doc "nintendo entertainment system architecture".
obviusly you're right about the bankswitching and all that stuff, sooner or later i'll have to deal with it, but i really don't what to get at such a complex point right now. i'm still trying to understand the PPU operation... that's why i think that now it's a little early to worry about that... or maybe it isn't...
thanks again tepples and motzilla, you are really helping me! so please keep on touch, i really need that help!
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

Even if you don't implement any bankswitching for now, you still have to handle the registers differently than the RAM and the PRGROM.

PPU operation isn't terribly hard to figure out unless you want to try for absolute cycle accuracy.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

ehguacho wrote:but i really don't what to get at such a complex point right now.
Nobody is forcing you to implement those complex things, they are just warning you that if you go through with your current architecture you are gonna have to rewrite most of the program later on. They are just telling you to be prepared for the complex things, so that adding them later on is easier.
User avatar
ehguacho
Posts: 83
Joined: Tue Mar 09, 2010 11:12 pm
Location: Rosario, Argentina
Contact:

Post by ehguacho »

ok, so this is what i'm gonna do: i'll try to finish the 6502 core emulation and then you'll help me out with the PPU emulation and with all those complex things :D
sorry about my english, i'm from argentina...

http://nestate.uuuq.com
User avatar
MottZilla
Posts: 2835
Joined: Wed Dec 06, 2006 8:18 pm

Post by MottZilla »

It is important to get your CPU core together first. When I did my emulator though I built it implementing new opcodes as my test roms ran into them and as soon as enough were implemented I worked on PPU emulation as I was in a rush to get playable results. It worked out for me. The key thing really is just to stick with it and not give up.
User avatar
ehguacho
Posts: 83
Joined: Tue Mar 09, 2010 11:12 pm
Location: Rosario, Argentina
Contact:

Post by ehguacho »

i won't give up! sooner or later my emulator will be out on the road... i just need to keep on trying and learning. time and work, time and work, time and work... :D
sorry about my english, i'm from argentina...

http://nestate.uuuq.com
User avatar
Petruza
Posts: 311
Joined: Mon Dec 22, 2008 10:45 pm
Location: Argentina

Post by Petruza »

The one memory zone you can safely access without handler functions, is the zero page.

At least, in my emu, I allocate RAM[0x800] to hold the RAM, so using the absolute address in the 6502 address space as the index to the RAM[] array will result in accessing the same byte.

If you have a separate code for each addressing mode, as I do, there's some point in the code where you know for sure you will be accessing the zero page, then there's no possible side-effect, writing to registers, etc.
Then you simply access RAM[ address & 0xFF ] for reading and writing
Post Reply