What's new

Chip8 Emu Help

gergep

New member
i've been trying to program a chip8 emu in c++ but there are somethings i dont get
1. how you can tell what the opcode is
for example if the opcode is 0xE0 ( clear the screen ), then why is it written like
Code:
opcode = (memory[pc]<<8 + memory[pc+1]);

pc+=2;

switch ((opcode&0xF000)>>12)
{
     case 0x0:
     {
          if ((opcode& 0x00FF) == 0xE0)
          {
              // Clear the screen
              memset(chip8.display, 0, 64*32);
          }
          break;
     }
}
how do you tell how to sort the opcodes under 0x0, 0x1, 0x2 ....

2. if the opcode is 1nnn how would you convert that to hex and use it in a case statement

thanks in advance
 

Toasty

Sony battery
Last summer I was writing a Chip8 emulator in C# and used this for my interpreter function. I kind of lost interest before I got around to completing it (plus I'm not that great of a programmer), so there may be bugs, but it had Tetris working, so it's probably a decent example. Obviously since it's a different language and your emulator's other functions are no doubt different, you can't just copy and paste this, but the logic will probably be similar to what you're shooting for. (Yeah, I like goto's.)
Code:
private void CpuLoop2() {

	uint temp;
	pc = 0x200;
	stack = 0x80;

	for(; ; ) {

		int instruction = (ram[pc] << 8) | ram[pc + 1];
		pc += 2;

		int prefix = (instruction & 0xF000) >> 12;
		int suffix = instruction & 0xFFF;
		int modifier = suffix & 0xF;
		int x = suffix >> 8;
		int y = (suffix >> 4) & 0xF;
		int kk = suffix & 0xFF;

		switch(prefix) {

			case 0x0:
				if(suffix == 0x0E0) { //Clear screen
					ClearScreen();
					goto repaint;
				} else if(suffix == 0x0EE) { //Return from subroutine
					pc = PopStack();
					goto end;
				} else goto badOp;

			case 0x1: //Jump
				pc = suffix;
				goto end;

			case 0x2: //Call subroutine
				PushStack(pc);
				pc = suffix;
				goto end;

			case 0x3: //Skip next if reg[x] = kk
				if(reg[x] == kk) pc += 2;
				goto end;

			case 0x4: //Skip next if reg[x] != kk
				if(reg[x] != kk) pc += 2;
				goto end;

			case 0x5:
				if((suffix & 0xF) == 0) { //Skip next if reg[x] = reg[y]
					if(reg[x] == reg[y]) pc += 2;
					goto end;
				} else goto badOp;

			case 0x6: //reg[x] = kk
				reg[x] = (byte) (kk);
				goto end;

			case 0x7: //reg[x] += kk
				reg[x] += (byte) kk;
				goto end;

			case 0x8:
				if(modifier == 0) { //reg[x] = reg[y]
					reg[x] = reg[y];
					goto end;
				} else if(modifier == 1) { //reg[x] |= reg[y]
					reg[x] |= reg[y];
					goto end;
				} else if(modifier == 2) { //reg[x] &= reg[y]
					reg[x] &= reg[y];
					goto end;
				} else if(modifier == 3) { //reg[x] ^= reg[y]
					reg[x] ^= reg[y];
					goto end;
				} else if(modifier == 4) { //reg[x] += reg[y], with carry
					temp = (uint) reg[x] + (uint) reg[y];
					reg[x] = (byte) temp;
					reg[0xF] = (byte) ((temp > 0xFF) ? 1 : 0);
					goto end;
				} else if(modifier == 5) { //reg[x] -= reg[y], with not borrow
					reg[x] -= reg[y];
					reg[0xF] = (byte) ((reg[x] >= reg[y]) ? 1 : 0);
					goto end;
				} else if(modifier == 6) { //reg[x] >>= 1, with reg[f] set to old bit
					reg[0xF] = (byte) (reg[x] & 1);
					reg[x] >>= 1;
					goto end;
				} else if(modifier == 7) { //reg[x] = reg[y] - reg[x], with not borrow
					reg[x] = (byte) (reg[y] - reg[x]);
					reg[0xF] = (byte) ((reg[y] >= reg[x]) ? 1 : 0);
					goto end;
				} else if(modifier == 0xE) { //reg[x] <<= 1, with reg[f] set to old bit
					reg[0xF] = (byte) (reg[x] >> 7);
					reg[x] <<= 1;
					goto end;
				} else goto badOp;

			case 0x9:
				if(modifier == 0) { //Skip next if reg[x] != reg[y]
					if(reg[x] != reg[y]) pc += 2;
					goto end;
				} else goto badOp;

			case 0xA: //i = nnn
				i = suffix;
				goto end;

			case 0xB: //jump to nnn + reg[0]
				pc = suffix + reg[0];
				goto end;

			case 0xC: //reg[x] = random & kk
				reg[x] = (byte) (random.Next() & kk);
				goto end;

			case 0xD: //draw sprite modifier tall at reg[x], reg[y]
				reg[0xF] = (byte) (DrawSprite(reg[x], reg[y], modifier) ? 1 : 0);
				goto repaint;

			case 0xE:
				if(kk == 0x9E) { //Skip next if reg[x] is pressed
					if(iface.IsPressed(reg[x])) pc += 2;
					goto end;
				} else if(kk == 0xA1) { //Skip next if reg[x] isn't pressed
					if(!iface.IsPressed(reg[x])) pc += 2;
					goto end;
				} else goto badOp;

			case 0xF:
				if(kk == 0x07) { //reg[x] = delay timer
					reg[x] = (byte) GetDelay();
					goto end;
				} else if(kk == 0x0A) { //reg[x] = wait for key press
					reg[x] = (byte) (iface.WaitForKeyPress() & 0xF);
					goto end;
				} else if(kk == 0x15) { //delay timer = reg[x]
					SetDelay(reg[x]);
					goto end;
				} else if(kk == 0x18) { //sound timer = reg[x]
					lock(reg) {
						sound = reg[x];
					}
					goto end;
				} else if(kk == 0x1E) { //i += reg[x]
					i += reg[x];
					i &= 0xFFF;
					goto end;
				} else if(kk == 0x29) { //i = &font[x]
					i = GetFontIndex(x);
					goto end;
				} else if(kk == 0x33) { //ram[i...i+2] = bcd of reg[x]
					temp = reg[x];
					ram[i + 2] = (byte) (temp % 10);
					temp /= 10;
					ram[i + 1] = (byte) (temp % 10);
					temp /= 10;
					ram[i] = (byte) temp;
					goto end;
				} else if(kk == 0x55) { //save reg[0...x] to ram[i...i+x]
					Array.Copy(reg, 0, ram, i, x + 1);
					goto end;
				} else if(kk == 0x65) { //load ram[i...i+x] to reg[0...x]
					Array.Copy(ram, i, reg, 0, x + 1);
					goto end;
				} else goto badOp;

		}

		badOp:
		Console.WriteLine("Unsupported opcode encountered: " + instruction.ToString("X").PadLeft(4, '0'));
		Stop();
		Thread.Sleep(0);
		continue;

		end:
		Thread.Sleep(0);
		continue;

		repaint:
		iface.UpdateScreen(screen);
		Thread.Sleep(0);

	}

}
 
OP
G

gergep

New member
thanks for that great example, but i still dont understand two things:

1. if the opcode was 1nnn, how did you know that it was really 0x01

2. if the opcode was 0x00, how did you know that it contained the opcodes 0x0E0 and 0x0EE

sorry if im not getting a really big thing here
 

Doomulation

?????????????????????????
It's about hacking out the bits of the opcode. Each digit contains information.
You should learn how the shift operators (<< and >>) and the bitwise and operator (&) works and you'll understand why it's done this way.
 
OP
G

gergep

New member
i do understand how they workd though

x << y is x * 2^y

x >> y is x / 2^y

x & y is the binary value of x and the binary value of x compared by 1s
so if x is 011101 and y is 101011 then the result would be 001001

i still dont get it though
thanks anyway
 

Toasty

Sony battery
Shifts aren't just a way to do multiplication/division by powers of two. They are one way that you can isolate individual bit sequences in an integer. For example, let's say I have the a 16-bit integer whose value is 65,280 (0xFF00). It's binary representation is like so:

1111 1111 0000 0000

Now what happens if we shift that value right by 1? Yes, we divide the integer by 2, but look at what happens to the binary representation:

0111 1111 1000 0000

We basically shifted the value over by 1 bit. Using shifts in conjunction with AND operations, we can isolate any part of the integer.

Let's say I want to isolate all four hex digits in that 16-bit number. (Bare in mind that every four bits are represented by one hexadecimal digit.) We can do so like this:

a = x >> 12 & 0xF;
b = x >> 8 & 0xF;
c = x >> 4 & 0xF;
d = x & 0xF;

In the end, A's value will be 0xF, B's value will be 0xF, C's value will be 0x0 and D's value will be 0x0. (We've effectively split the 16-bit number into four parts.) Let's review why this works:

A:
For A, we shift the number right by 12 bits:
Code:
1111 1111 0000 0000 -> 0000 0000 0000 1111
Then we AND that value with 0xF. (Note that this step actually isn't required for A, since only the last four bits can be non-zero after a 16-bit integer is shifted right by 12 bits.):
Code:
  0000 0000 0000 1111
& 0000 0000 0000 1111
---------------------
  0000 0000 0000 1111
So our result for A is 0000 0000 0000 1111 (0x000F).

B:
For B, we shift the number right by 8 bits:
Code:
1111 1111 0000 0000 -> 0000 0000 1111 1111
Then we AND that value with 0xF. (This time this step is required to clear all but the last four bits, since there may have been set bits to the left of our bits of interest.):
Code:
  0000 0000 1111 1111
& 0000 0000 0000 1111
---------------------
  0000 0000 0000 1111
So our result for B is 0000 0000 0000 1111 (0x000F).

C:
For C, we shift the number right by 4 bits:
Code:
1111 1111 0000 0000 -> 0000 1111 1111 0000
Then we AND that value with 0xF.:
Code:
  0000 1111 1111 0000
& 0000 0000 0000 1111
---------------------
  0000 0000 0000 0000
So our result for C is 0000 0000 0000 0000 (0x0000).

D:
For D, we needn't shift the value at all, since the integer is already shifted to the bit sequence we want. (This is the equivalent to shifting the integer right by 0.)
So we just AND that value with 0xF.:
Code:
  1111 1111 0000 0000
& 0000 0000 0000 1111
---------------------
  0000 0000 0000 0000
So our result for D is 0000 0000 0000 0000 (0x0000).


As you can see, we used bit shifts to 'slide' the bits of interest to the end of the integer, then we used AND to trim off the part we didn't want.
 
Last edited:
OP
G

gergep

New member
thanks i finally understand and have almost completed the cpu emulation
ill put the code here if u wanna look

Code:
void Execute()
{
	// Fetch opcode
	chip8.opcode = memory[(chip8.pc&0x0FF)<<8] | memory[chip8.pc+1];
	
	// Set Some Values
	VX = chip8.regs[(opcode & 0x0F00) >> 8];
	VY = chip8.regs[(opcode & 0x00F0) >> 4];
	VF = chip8.regs[15];
	KK = opcode & 0x00FF;
	
	// Next Instruction for next execute
	chip8.pc += 2;
	
	// Emulate the opcode
	switch (((chip8.opcode & 0xF000)>>12))
	{
		case 0x0:
		{
			switch ((chip8.opcode&0x00FF))
			{
				case 0xE0:		// Clear the screen
				{
					memset(chip8.display, 0, 64*32);
					break;
				}
				case 0xEE:		// Return from a subroutine
				{
					chip8.pc = chip8.stack[chip8.sp];
					chip8.sp--;
					break;
				}
				default:
				{
					UnkownOpcode();
					break;
				}
			}
			break;
		}
		case 0x1:		// Jump to address NNN
		{
			chip8.pc = (chip8.opcode&0x0FFF);
			break;
		}
		case 0x2:		// Cals subroutine at NNN
		{
			chip8.sp++;
			chip8.stack[chip8.sp] = chip8.pc;
			chip8.pc - (chip8.opcode&0x0FFF);
			break;
		}
		case 0x3:		// Skips next instruction if VX == NN
		{
			if (VX == KK)
				pc += 2;
			
			break;
		}
		case 0x4:		// Skips next intruction if VX != NN
		{
			if (VX != KK)
				pc += 2;
			
			break;
		}
		case 0x5:		// Skips next intruction if VX == VY
		{
			if (VX == VY)
				pc += 2;
			
			break;
		}
		case 0x6:		// Set VX to NN
		{
			VX = KK;
			
			break;
		}
		case 0x7:		// Adds NN to VX
		{
			VX += KK;
			
			break;
		}
		case 0x8:
		{
			switch ((chip8.opcode&0x00FF))
			{
				case 0x0:	// Sets VX to VY
					VX = VY;
					break;
				case 0x1:	// Sets VX to VX or VY
					VX |= VY;
					break;
				case 0x2:	// Sets VX to VX and VY
					VX &= VY;
					break;
				case 0x3:	// Sets VX to VX xor VY
					VX ^= VY;
					break;
				case 0x4:	// Adds VY to VX.  VF is set to 1 if there's a carry, otherwise VF = 0
					VF = (VX += VY) >> 8;
					break;
				case 0x5:	// VY is subtracted from VX.  VF is set 0 if there is borrow, else VF = 1
					VF = (VX > VY);
					VX -= VY;
					break;
				case 0x6:	// VX Shifts right by one.  VF = least significant bit value of VX before shift
					VF = (VX & 0x1);
					VX >>= 0x1;
					break;
				case 0x7:	// VX -= VY* VF is set to 0 if theres a borrow, otherwise VF = 1
					VF = (VY > VX);
					VX = VY - VX;
					break;
				case 0x0E:	// Shifts VX to left by one.  Vf is set to the value of the most significant bit of VX before shift
					VF = (VX & 0x80);
					VX <<= 0x1;
					break;
				default:	// Unkown Opcode
					UnkownOpcode();
					break;
			}
			break;
		}
		case 0x09:		// Skips next instruction if VX != VY
			if (VX != VY)
				pc += 2;
			break;
		case 0x0A:		// Sets I to the address NNN
			chip8.I = (chip8.opcode&0x0FFF);
			break;
		case 0x0B:		// Jumps to address NNN + V0
			chip8.pc = (chip8.opcode&0x0FFF)+chip8.regs[0];
			break;
		case 0x0C:		// Sets VX to a random number and NN
			VX = (unsigned int)(rand() & KK );
			break;
		case 0x0D:	
		{
			/* Draws a sprite at (VX, VY) with a width of 8 pixels and a height of N pixels
			 * VF is set to 1 if any screen pixels are flipped from set to unset when the
			 * sprite is drawn, and to 0 if that doesn't happen.
			 */
			
			break;
		}
		case 0x0E:
		{
			switch ((chip8.opcode&0x00FF))
			{
				case 0x9E:		// Skips the next instruction if the key stored in VX is pressed
					if (keys[VX])
						pc += 2;
					break;
				case 0xA1:		// Skips the next instruction if the key stored in VX isn't pressed
					if (!keys[VX])
						pc += 2;
					break;
				default:
					UnkownOpcode();
					break;
					
			}
			break;
		}
		case 0x0F:
		{
			switch ((chip8.opcode&0x00FF))
			{
				case 0x07:		// Sets VX to the vaue of the delay timer
					VX = chip8.delayTimer;
					break;
				case 0x0A:		// A key press is awaited, then stored in VX;
					if (key == -1)
						pc -= 2;
					else
						VX = key;
					break;
				case 0x15:		// Sets the delay timer to VX
					chip8.delayTimer = VX;
					break;
				case 0x18:		// Sets the sound timer to VX
					chip8.soundTimer = VX;
					break;
				case 0x1E:		// Adds Vx to I
					VX += I;
					break;
				case 0x29:		
				{
					/* Sets I to the location of the sprite for the character in VX
					 * Characters 0-F (in hexadecimal) are represented by a 4x5 font
					 */
					
					I = VX*5;
					
					break;
				}
				case 0x33:	
				{
					/* Stores the binary coded decimal representation of VX at the a
					 * the adresses I, I + 1, and I + 2
					 */
					
					memory[I] = VX / 100;
					memory[I+1] = (VX/10) % 10;
					memory[I+2] = x % 10;
					 
					break;
				}
				case 0x55:	// Stores V0 to VX in memory starting at address I
					
					break;
				case 0x65:	// Fills V- to VX with values from memory starting at adress I
					break;
				default:
					UnkownOpcode();
					break;
			}
			break;
		}
		default:
		{
			UnkownOpcode();
			break;
		}
	}
}

its almost done, but still has a couple of errors that i did. if you look at it could you tell me if there is anything wrong. I couldn't have done this without you guys
thanks :)
 

Exophase

Emulator Developer
Code:
chip8.pc - (chip8.opcode&0x0FFF);

You probably meant =, not -.

Code:
VX = KK;

You do this everywhere you're supposed to be setting registers. It isn't going to work - you're supposed to be modifying the register array, not this temporary variable. Instead you need to do something like this:

Code:
chip8.regs[(opcode & 0x0F00) >> 8] = KK;

Code:
VX = (unsigned int)(rand() & KK );

Using & won't work like you expect it to unless KK is a power of 2, which it doesn't have to be.

Code:
memory[I+2] = x % 10;

You mean VX, not x. Not sure what x is, that allows this to compile (does it compile?)

Other than that, you're obviously missing opcodes that are required to run games, so not much more can be said w/o them in place.
 
OP
G

gergep

New member
thanks for pointing those things out, but now i have two more problems ...

1. Every time i get the unknown opcode on the first cycle is there a problem in my loading rom code (its in objective-c/cocoa but you can read it if you know c)

Code:
- (void) loadRom
{
	NSOpenPanel* panel = [ NSOpenPanel openPanel ];
	
	if ([ panel runModalForTypes:nil ])
	{
		// Load filename from the openPanel
		filename = [ NSString stringWithString:[ [ panel filenames ] objectAtIndex:0 ] ];
		
		// Load rom into memory
		FILE* file = fopen([ filename UTF8String ], "rb");
		
		// Get the file size
		fseek(file, 0, SEEK_END);
		unsigned long size = ftell(file);
		rewind(file);
		
		if (size > (4096-512))
		{
			// File is not valid
			NSRunAlertPanel(@"Error", @"Invalid Rom", @"Ok", nil, nil);
			[ NSApp terminate:self ];
		}
		
		[ console Log:[ NSString stringWithFormat:@"Rom Selected - %@\n\n", filename ] ];
		
		// Read the daa into memory
		fread(&memory[0x200], 1, size, file);
		
		// Release the file
		fclose(file);
		
		// Log the memory that has a value
		for (int x = 0; x < 4096; x++)
		{
			if (memory[x] != 0)
			{
				[ console Log:[ NSString stringWithFormat:@"Memory[%i] = %i\n", x, memory[x] ] ];
			}
		}
		
		[ NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:@selector( startGLWindow )
										userInfo:nil repeats:NO ];
	}
	else		// Exit the app if they don't open a file
		[ NSApp	terminate:self ];
}

2. I don't really understand the draw code - i basically understand it as
Making a sprite with a width of 8 pixels at location VX, VY and VF is set to 1 if there is a collision otherwise its 1. Can somebody confirm and give a better example on how to do this

thanks
 

Doomulation

?????????????????????????
Width is 8, height varies depending on the opcode.
The collision register (F) is cleared before the opcode and is set to 1 on collision.
 
OP
G

gergep

New member
thanks everyone i have finally gotten my emu to get something to draw on the screen, but a lot of it is incorrect. Oh well. the only game the i think draws almost fully correct is brix and some games dont work like blinky.

here is the thing if u want to try it.

PS. u havta close it using command prompt cause theres a bug in which it closes the window but not the application so...

http://www.megaupload.com/?d=B118YLV4
 

Doomulation

?????????????????????????
Just remember that improper opcode implementation or lacking opcode implementation can cause graphics to be drawn incorrectly, as well.
 

Top