What's new

Chip 8

bcrew1375

New member
Yeah, Melvo is right. I just posted a quick reply and didn't think it through. Heh, I don't know why I said that because I had to fix the same problem when I was working on my Gameboy emu :p.
 
Last edited:

bronxbomber92

New member
Hey, just started a chip8 emulator to get me started, and I think I'm completely misunderstanding opcodes and how to implement them... This is my CPU (or some of it...)
Code:
unsigned int I;
unsigned int PC;
unsigned char V[16];
unsigned char RAM[0xFFF];
float screen[2048];

unsigned int stack[16]; 
unsigned char stackcount = 0;

unsigned char nnn;
unsigned char n;
unsigned char x;
unsigned char y;
unsigned char kk;


void CPU(){
	unsigned int opcode == ((RAM[PC]<<8) + RAM[PC+1]);
	V[((opcode&0x0f00)>>8)] = opcode&0x00FF;
	switch(opcode&0xF000) {
case: 0x00E0
	for(i=0, i < 2048, i++) {
		screen[i] = 0;
	}
	break;
case: 0x00EE
	PC = stack[stackcount];
	stackcount -= 1;
		break;
case: 0x1nnn
	PC = nnn;
	break;
case: 0x2nnn
	stackcount += 1;
	stack[0] = PC;
	PC = nnn;
	break;
case: 0x3xkk
	if(3x == kk) {
		PC += 2;
	}
	break;
case: 0x4xkk
	if( 4x != kk ) {
		PC += 2;
	}
	break;
case: 0x5xy0
	if( 5x == y0 ) {
		PC += 2;
	}
	break;
case: 0x6xkk
	6x = kk;
	break;
case: 0x7xkk
	7x = 7x + kk;
	break;
case: 0x8xy0
	8x = y0;
	break;
case: 0x8xy1 
	8x = 8x | y1;
	break;
case: 8xy2
	8x = 8x & y2;
	break;
case: 8xy3
	8x = 8x ^ y3;
	break;
...
I You get how I'm going on with the rest of the cpu... Any guidance to show me the light (or what I'm doing wrong and explain what I could do to fix it) would be highly appreciated! Thanks
 

ShizZy

Emulator Developer
That's not C is it? Because if it is, it won't compile ^^

But nonetheless, it looks like you have the right idea. I'd try and help you, but I forget all my chip8 instructions :p
 

bronxbomber92

New member
Yeah its c o_O... Thats not the whole file though, incase you don't think it won't compile because I'm missing headers and other stuff... But if you mean syntatically, then whats wrong?

But I think its wrong because the opcodes don't seem right or I don't have the right registers...?

For example take this
Code:
case: 0x8xy1 
	8x = 8x | y1;
	break;
The doc I read, it explains the opcode '8xy1' like this:
8xy1 - OR Vx, Vy
Set Vx = Vx OR Vy.

Performs a bitwise OR on the values of Vx and Vy, then stores the result in Vx. A bitwise OR compares the corrseponding bits from two values, and if either bit is 1, then the same bit in the result is also 1. Otherwise, it is 0.
Did I implement this correctly?
 

bcrew1375

New member
From what I can tell, you are way off the mark. You're taking the documentation way too literally. As ShizZy said, this has no chance of compiling as is.

* Under void CPU() you should not be immediately loading the V registers with a number. You need to interpret the opcode and if it calls for it then you load a given number into the V register.

* You're not checking opcodes correctly. Letters such as "x", "y" "n", "nnn", and "kk" are variables. The opcodes do not actually contain the letters.

* Your OR is wrong. The documentation you've quoted says that you are to OR the values of Vx and Vy. So, your case should look something like this:
Code:
case 0x8000:
     V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0)];
     break;

Just curious, are you fairly new to programming in general? If so, you shouldn't try emulation, even something like Chip-8, until you are more experienced.
 
Last edited:

bronxbomber92

New member
Well yeah, I just figured out that I was misinterpreting the opcodes... I'm new to emulation, and I have been programming in general for about 5 months(ish)... But yeah, I see where I meant wrong. I understand now as Exophase helped me out and explain to me what the opcodes should be "used". Thanks for your help as well :)
 

aprentice

Moderator
I think making an emulator would be a lot more easier if you have a better understanding of the language your programming in. In the matter of chip8, no knowledge of emulation is even necessary, just a strong foundation in C. In summary, know your programming language first :p
 

Doomulation

?????????????????????????
In C or C++, or whatever language you're planning to do it in.
Emulators are usually not the best way to learn a language.
 

bronxbomber92

New member
Im not learning the language (I just mixed up the switch statement).. I may not have the best undetanding of the language, but I have a compitent knowledge...Anyways, I understand wht I am doing wrong........
 

hap

New member
Part of the process of writing an emulator is creating a small debugger or tracelogger. I suggest you write one, and carefully compare each register change to what you had in mind after stepping through the program opcodes step by step.
 

bronxbomber92

New member
How would I write one since tis run on the PSP (playstation portable)? And this crashes right away. Almost as if it doesn't exucute any to hardly amount of code.
 

Doomulation

?????????????????????????
Do we look like psp devs? I know very few of us are. And let's face it - we're building emulators for PCs here in this Chip8 thread. If it runs on your PC, then I don't know what's wrong if it doesn't run on the psp.
 

SG57

New member
Hey, Ive ported the ChipAintGL to the PSP. The CPU core works fine, as in all error checking is flawless. Than, it loads the ROM, and should display it. instead, it doesnt show anything.

Here is my main CPU, Render function, as well as CPU Init and ROM loading function so maybe its one of them...

Oh and no, its not because of the double buffering needing to be swapped, since i hae a pause menu implemented and it displays it just fine, going off the same display buffer flipping line...

Code:
int Initialise1802Cpu(void)
{
	int i;
	//FILE fp;
     //fp=fopen("debuglog.txt","w");

	ZeroMemory(&CpuContext,sizeof(i1802Context));	// Clear the context struct before using it

	CpuContext.CodeIndex=PROGRAM_ENTRY;	// Set the code index to point to the rom's
										// program entry point.
	for(i=0;i<80;i++)					// Set up the chip-8 font
	{
		CpuContext.ProgramMemory[i]=font[i];
	}

	memset(screen,0,sizeof(unsigned char)*64*32);

	bCpuHasBeenInitialised=TRUE;
	
	LoadControls();
	
	//fclose(fp);
	return TRUE;
}

void Render(void)
{
	int x,y;

	for(y=0;y<32;y++)
	{
		for(x=0;x<64;x++)
		{
			if(screen[x+(y*64)]==1)
			{
				gpuPutPixelWhite(x*3,y*3);
			}
			else
			{
				gpuPutPixelBlack(x*3,y*3);
			}
		}
	}
}

void ExecuteCpu(void)
{
	unsigned short Op=CpuContext.ProgramMemory[CpuContext.CodeIndex];
	unsigned char Opcode;

	Op=((Op<<8)|CpuContext.ProgramMemory[CpuContext.CodeIndex+1]);
	Opcode=(Op&0xF000)>>12;

	// NOTE: almost every opcode we will increment the code index
	// by 2.  We increment by 2 in order to reach the next opcode.
	// If we skip something, we increment by 4 instead. When
	// jumping, we don't need to (I think).
	switch(Opcode)
	{
	case 0x00:
		{
			switch(Op&0x00FF)
			{
			case 0xE0:	// Clear the screen
                gpuClearScreen();
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;

			case 0xEE:	// Return from subroutine
				CpuContext.StackPointer--;	// Decrement the stack pointer
				// Pop the value off the stack.
				CpuContext.CodeIndex=CpuContext.Stack[CpuContext.StackPointer];
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;

			case 0xFB:	// The remaining are schip-8 opcodes
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;

			case 0xFC:
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;

			case 0xFE:
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;

			case 0xFF:
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;

			default:
				CpuContext.CodeIndex+=2;
				Log(Opcode,&CpuContext,Yes);
				break;
			}
		}
		break;

	case 0x01:	// Jump instruction
		{
			// There is no need to mess with the stack here
			CpuContext.CodeIndex=(Op&0x0FFF);
			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x02:
		{
			CpuContext.Stack[++CpuContext.StackPointer]=CpuContext.CodeIndex;
			CpuContext.CodeIndex=(Op&0x0FFF);
			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x03:
		{
			if(GetRegister1(Op)==(Op&0x00FF))
				CpuContext.CodeIndex+=4;
			else
				CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x04:
		{
			if(GetRegister1(Op)!=(Op&0x00FF))
				CpuContext.CodeIndex+=4;
			else
				CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x05:
		{
			if(GetRegister1(Op)==GetRegister2(Op))
				CpuContext.CodeIndex+=4;
			else
				CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x06:
		{
			GetRegister1(Op)=Op&0x00FF;
			CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x07:
		{
			GetRegister1(Op)+=Op&0x00FF;
			CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x08:
		{
			switch(Op&0x000F)
			{
			case 0x00:	// mov vx,vy
				{
					GetRegister1(Op)=GetRegister2(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x01:	// or vx,vy
				{
                    GetRegister1(Op) |=GetRegister2(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x02:	// and vx,vy
				{
					GetRegister1(Op) &=GetRegister2(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x03:	// xor vx,vy
				{
					GetRegister1(Op) ^=GetRegister2(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x04:
				{
					/*
					if(GetRegister2(Op) > (0xFF-GetRegister1(Op)))
						CpuContext.regs.v[0xF]=1;
					else
						CpuContext.regs.v[0xF]=0;
						*/
					// previsously in wrong order
					GetRegister1(Op)+=GetRegister2(Op);
					CpuContext.regs.v[0xF]=GetRegister1(Op)&0x01;
					
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x05:
				{
					// previously in wrong order
					GetRegister1(Op)-=GetRegister2(Op);

					if(GetRegister1(Op) >= GetRegister2(Op))
						CpuContext.regs.v[0xF]=1;
					else
						CpuContext.regs.v[0xF]=0;

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x06:	// Fixed ?
				{
					/*
					CpuContext.regs.v[0xF]=Op&0x1;
					GetRegister1(Op)>>=1;
					*/

					GetRegister1(Op) /= 2;
					CpuContext.regs.v[0xF] >>= 1;

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x07:
				{
					GetRegister1(Op)=GetRegister2(Op)-GetRegister1(Op);

					if(GetRegister2(Op) >= GetRegister1(Op))
						CpuContext.regs.v[0xF]=1;
					else
						CpuContext.regs.v[0xF]=0;

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x0E:	// Fixed ?
				{
					CpuContext.regs.v[0xF]=(GetRegister1(Op)>>7)&0x01;
					GetRegister1(Op)<<=1;

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			default:
				{
					Log(Opcode,&CpuContext,No);
					break;
				}
			}
		}
		break;

	case 0x09:
		{
			if(GetRegister1(Op) != GetRegister2(Op))
				CpuContext.CodeIndex+=4;
			else
				CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x0A:
		{
			CpuContext.regs.I=Op&0x0FFF;
			CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x0B:
		{
			CpuContext.CodeIndex+=CpuContext.regs.v[0]+(Op&0x0FFF);
			CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}
	
	case 0x0C:
		{
			GetRegister1(Op)=rand()&(Op&0x00FF);
			CpuContext.CodeIndex+=2;

			Log(Opcode,&CpuContext,Yes);
			break;
		}

	case 0x0D:
		{
			switch(Op&0x00FF)	// Fixed
			{
			case 0x00:
				{
					x=GetRegister1(Op);y=GetRegister2(Op);


					for(row=0;row<16;row++)
					{
						pixel=CpuContext.ProgramMemory[CpuContext.regs.I+row];
						for(line=0;line<16;line++)
						{
							if((pixel & (line >> 0x80))==1)
							{
								if(screen[(line+x+((y+row)*64))]==1)
								{
									CpuContext.regs.v[0xf]=1;
								}

								screen[(line+x+((y+row)*64))]^=1;
							}
						}
					}

					CpuContext.CodeIndex+=2;
					Render();

					Log(Opcode,&CpuContext,Yes);
				}
				break;

			default:
				{
					x=GetRegister1(Op);
                         y=GetRegister2(Op);
					
                         height=Op&0x000F;
					

					for(row=0;row<height;row++)
					{
						pixel=CpuContext.ProgramMemory[CpuContext.regs.I+row];
						for(line=0;line<8;line++)
						{
							if((pixel & (line >> 0x80))!=0)
							{
								if(screen[(line+x+((y+row)*64))]==1)
								{
									CpuContext.regs.v[0xf]=1;
								}

								screen[(line+x+((y+row)*64))]^=1;
							}
						}
					}

					CpuContext.CodeIndex+=2;
					Render();

					Log(Opcode,&CpuContext,Yes);
				}
				break;

			}
			//break;
		}

	case 0x0E:
		{
			switch(Op&0x00FF)
			{
			case 0x9E:
				{
                    if(GetRegister1(Op)==GetKey())
						CpuContext.CodeIndex+=4;
					else
						CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0xA1:
				{
					if(GetRegister1(Op)!=GetKey())
						CpuContext.CodeIndex+=4;
					else
						CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			default:
				{
					Log(Opcode,&CpuContext,No);
					break;
				}
			}
		}
		break;
		
	case 0x0F:
		{
			switch(Op&0x00FF)
			{
			case 0x07:
				{
					GetRegister1(Op)=CpuContext.DelayTimer;
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x0A:
				{
                    do
					{
						GetRegister1(Op)=GetKey();
					}while(GetKey()!=(-1));

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x15:
				{
					CpuContext.DelayTimer=GetRegister1(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x18:
				{
					CpuContext.SoundTimer=GetRegister1(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x1E:
				{
					CpuContext.regs.I+=GetRegister1(Op);
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x29:
				{
					CpuContext.regs.I=GetRegister1(Op)*5;
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}
				
			case 0x30:
				{
					CpuContext.regs.I=GetRegister1(Op)*10;
					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x33:
				{
					unsigned short i=GetRegister1(Op);

					CpuContext.ProgramMemory[CpuContext.regs.I]=i/100;
					CpuContext.ProgramMemory[CpuContext.regs.I+1]=(i/10)%100;
					CpuContext.ProgramMemory[CpuContext.regs.I+2]=(i%100)%10;

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x55:
				{
					int i;

					//for(i=0;i<=GetRegister1(Op);i++)
					for(i=0;i<=((Op&0x0F00)>>8);i++)
					{
						CpuContext.ProgramMemory[CpuContext.regs.I+i]=CpuContext.regs.v[i];
					}

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x65:
				{
					int i;

					//for(i=0;i<=GetRegister1(Op);i++)
					for(i=0;i<=((Op&0x0F00)>>8);i++)
					{
						CpuContext.regs.v[i]=CpuContext.ProgramMemory[CpuContext.regs.I+i];
					}

					CpuContext.CodeIndex+=2;

					Log(Opcode,&CpuContext,Yes);
					break;
				}

			case 0x75:
			case 0x85:
				// SCHIP8 opcodes
				Log(Opcode,&CpuContext,Yes);
				break;

			default:
				{
					Log(Opcode,&CpuContext,No);
					break;
				}
			}
		}

		default:
		{
			Log(Opcode,&CpuContext,No);
			break;
		}
	}
}
int LoadRom(char* filename)
{
	FILE fpRom;	// File handle to the rom
	//FILE fp;		// File handle to hex dump file
	int i;

	fpRom=fopen(filename,PSP_O_RDONLY);	// Attempt to open the rom file
								// We are only going to read from
								// the memory so we specify "rb"
	if(!fpRom)	return FALSE;	// If the rom did not exist, return

	dwRomSize=fseek(fpRom,0,PSP_SEEK_END);	// Determine the size of the rom
	if(!dwRomSize)	return FALSE;	// If we can't determine the rom's 
									// size, then exit.
	fseek(fpRom,0,PSP_SEEK_SET);	// Read the rom memory from the
								// PROGRAM_ENTRY (0x200).  The locations
								// 0x000-0x1FF are reserved, and I rec-
								// comend not trifling with it unless
								// you really have to.
	fread(CpuContext.ProgramMemory+PROGRAM_ENTRY,1,dwRomSize-PROGRAM_ENTRY,fpRom);

	fclose(fpRom);				// We got loaded
	
	bRomWasLoaded=TRUE;
	return TRUE;
}
Thx for any help or advice!

P.S. I commented some fprintf chunks out since as i said, im limited dramatically... Bronx however isnt, i dunno why he doesnt debug a little with it...

Oh and one more thing, what in your opinion, is the BEST Chip 8 emulator out there in compatibility and quality? ChipAintGL cant even play Pong (the PC version), so im porting this Miracle Chip 8 over, but I cant get past the CPU initalizing (buffer overflow maybe?).
 

Garstyciuks

New member
In my opinion hap's emulator can pretty much run any game. My emulator can't run hap's sokoban, and I have no idea why :p I may remake my own emulator some time, and hopefully make it run sokoban.
 

Doomulation

?????????????????????????
Yes, hap's may well be one compatible emulator with sources available AFAIK. Though there are others. You could look through hap's first and others later if you need it.
 

SG57

New member
Alright, will do. Thanks!

P.S. Am i going out on a limb wanting to start to emulate the most BASIC NES after Chip 8? I dont want sound, multiple controllers, save states (unless there as easy as reading/writing a dumped memory array to a file), etc. Just one to play NES games with no features, just prioriites... ? If so, what is the best thing to emulate after? I jsut want the most basic emulator of something thatd be best to try after Chip 8.
 

hap

New member
The NES is documented very well, it's just that the good stuff is scattered.

I'd say the fun of developing emulators is the complexity. But if you want something easy, try Space Invaders (arcade); I had one up and running in 2 days :p (will make a topic with info about it soon)
 

Top