What's new

Chip 8

Facon

I know nothing...
I have now another problem with Counter, I don't know the clock's cycles of Chip8 neither cycles per each instruction.

What I should do?

PD: Sorry for my bad English.
 

elizeucoder

New member
The documentation of chip-8 don't talk about the cycles, because chip-8 hasn't interrupts.
so your cpu cicle code must be something like:

Code:
while(1)
{

fetchopcode(),

interpretopcode();

}


Hope i helped.

Add me on msn to talk better: mixeliseu13_8 <at> hotmail.com
 
Last edited:

Tronix

New member
Hi guys, i development my own chip8 emu in Delphi programming languarge, but i dont' very well C. I get some help from BadMagick Chip8 C sources (thanks to author). And i have some problems with my draw algorithm: when part of sprite need to be drawn within screen, other part don't show (see pic). Can any-body help me, plz?

My draw procedure (at temporary Display[64*32] buffer):
PHP:
Procedure TNewThread.Draw;
Var
      x,y : Byte;
      loop_x,loop_y : Byte;
      x_pos,y_pos : Byte;
      pixel : Byte;
Begin
    // Get X and Y coordinates
    x := ReadRegister((op_code and $0F00) shr 8);
    y := ReadRegister((op_code and $00F0) shr 4);

    // Reset VF register
    WriteRegister($0F, $00);

    for loop_y := 0 to (op_code and $000F)-1 do
      Begin
        // Assign a variable to current line
        pixel := ReadMemory(rI + loop_y);
        for loop_x := 0 to 7 do
          Begin
            // Check each bit to see if it is on or off
            if pixel and ($80 shr loop_x) <> 0 then
            Begin
                x_pos := (x + loop_x);
                y_pos := (y + loop_y);
                if Display[x_pos + y_pos* 64] = $01 then WriteRegister($0F, $01);
                Display[x_pos + y_pos * 64] := Display[x_pos + y_pos * 64] xor 1;
            End;
          End;
      End;

    Render(x,y,(op_code and $000F)-1);
End;

So, next procedure draw Sprite at the canvas :
PHP:
Procedure TNewThread.Render(ppx,ppy,lp:Byte);
Var
      py,px : word;
      x,y : Byte;
Begin
      Form1.Image1.Canvas.Pen.Width := 2;
//      Form1.Image1.Canvas.Pen.Color := clBlack;
      py := ppy*8;
      For y := ppy to ppy+lp do
        Begin
        px := ppx*8;
          For x := ppx to ppx+7 do
            Begin
                        If Display[x+y*64] <> 0 then
                        Begin
                        Form1.Image1.Canvas.Pen.Color := clGreen;
                        Form1.Image1.Canvas.Brush.Color := clGreen;
                        end
                        else
                        begin
                        Form1.Image1.Canvas.Pen.Color := clBlack;
                        Form1.Image1.Canvas.Brush.Color := clBlack;
                        end;
                        Form1.Image1.Canvas.Rectangle(px,py,px+7,py+7);
                        inc(px,8);
            End;
          inc(py,8);
          End;
//      writeln(ff,'end render');
End;

dxdx.png

PS: Sorry for my terrible English plz.
 
Last edited:

Facon

I know nothing...
I'm finally finish my emulator in a dirty C (global variables, etc..., but readable) using SDL for screen and computer's beep for sound (review my code).

License: MIT License / Public Domain

Copy & reuse my code as you like, use as a reference ;) .

Plataform: GNU\Linux (It's code is portable try to compile in Windows and Mac OS X)

Download: http://www.mediafire.com/file/0ro1n4xuyzy/AC8E.zip

Instructions:

1º open application via console:
./chip8
2º click console and put in CASE the name of the game, for i.e.: MAZE
3º click the window
4º buttons:

1 2 3 4
q w e r
a s d f
z x c v

Problems:

1º A very slow emulator
2º Emulator don't detect very well pulsations, you need to repeat pulsation

A Greeting.
 

anikom15

New member
Ok, I'm making a CHIP-8 emulator for linux. I can't seem to get the emulator to read the ROM correctly, it justs loops over and over again and doesn't do anything. I'm suspecting this has to do with the PC variable. This is strict C99, no C++ code please. Also, if you could help me with the TODOs that would be great.

Code:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "SDL.h"
#include "function.h"

/* If we ever needed any constants we'd put 'em here, but we don't need any
 * constants */

main(int argc, char **argv)
{
	/* generic math variables */
	int i;
	int n;
	int len;

	/* SDL specific crap variables */
	SDL_Event event;
	SDL_Surface* screen = NULL;
	SDL_Surface* finalscreen = NULL;
	unsigned int realx = 640;
	unsigned int realy = 320;
	int multiby = realx / 64;

	/* SDL crap */
	printf("Initializing SDL.\n");

	if((SDL_Init(SDL_INIT_EVERYTHING) == -1)) {
		fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
		exit(1);
	}
	atexit(SDL_Quit);

	/* Video crap */
	screen = SDL_SetVideoMode(realx, realy, 24, SDL_SWSURFACE);
	if (screen == NULL) {
		fprintf(stderr, "Couldn't set specified video mode: %s\n", 
				SDL_GetError());
		exit(1);
	}

	printf("Initializing CHIP-8.\n");

	/* All the crap we need for CHIP-8 */
	unsigned int memory[0xfff];
	unsigned int stack[0xf];
	unsigned int V[0xf];
	unsigned int I;	// I ain't telling you what this is for.
	unsigned int DT = 0;
	unsigned int ST = 0;
	unsigned int PC = 0x0;
	unsigned int SP = 0x0;
	unsigned int opcode;

	unsigned int key[0xf];
	/* set all the crappy keys to zero */
	for (i = 0x0; i <= 0xf; i++)
		key[i] = 0x0;

	unsigned int xres = 64;
	unsigned int yres = 32;

	/* Put other crap variables here... */
	Uint32 color0 = SDL_MapRGB(screen->format, 0, 0, 0);
	Uint32 color1 = SDL_MapRGB(screen->format, 170, 170, 170);
	Uint32 cpixel;
	int wrapx = 0, wrapy = 0;

	printf("Opening ROM...\n");

	/* File loading crap */
	FILE *f = fopen(argv[1], "rb");
	fread(&memory, 1, 4096, f);
	fclose(f);
	printf("ROM loaded.\nRunning happily.\n");

	/* The crappy main loop */
	for(;;) {
		/* TODO: program should exit when ESC is hit */
		opcode = memory[PC++];

		/* WARNING: massive switch statement */
		switch (opcode) {
			case 0x0:
				if ((opcode& 0x00ff) == 0xe0) {
					/* I've gone blind!!! */
					SDL_FillRect(SDL_GetVideoSurface(), NULL, color0);
					break;
				}
				if ((opcode& 0x00ff) == 0xee) {
					/* What is this I don't even... */
					PC = stack + SP;
					SP--;
					break;
				}
				break;
			case 0x1:
				/* Dangnabit */
				PC = opcode& 0x0fff;
				break;
			case 0x2:
				/* I hope she made lots of spaghetti code! */
				SP++;
				stack[SP] = PC;
				PC = opcode& 0x0ff;
				break;
			case 0x3:
				/*-*/ // Cassette tape?
				if (V[opcode& 0x0f00] == (opcode& 0x00ff))
					PC += 2;
				break;
			case 0x4:
				/* GYIYG lives. */
				if (V[opcode& 0x0f00] != (opcode& 0x00ff))
					PC += 2;
				break;
			case 0x5:
				/* *fart* */
				if (V[opcode& 0x0f00] == (opcode& 0x00f0))
					PC += 2;
				break;
			case 0x6:
				/* Do I have to put a comment on every one of these? */
				V[opcode& 0x0f00] = (opcode& 0x00ff);
				break;
			case 0x7:
				/* VX = VX + KK */
				V[opcode& 0x0f00] += (opcode& 0x00ff);
				break;
			case 0x8:
				/* Low level programming is fun for the first 10 minutes. */
				/* On a serious note, this section deals with bitwise
				 * operations */
				if ((opcode& 0x000f) == 0) {
					V[opcode& 0x0f00] = (opcode& 0x00f0);
					break;
				}
				if ((opcode& 0x000f) == 0x1) {
					V[opcode& 0x0f00] |= V[opcode& 0x00f0];
					break;
				}
				if ((opcode& 0x000f) == 0x2) {
					V[opcode& 0x0f00] &= V[opcode& 0x00f0];
					break;
				}
				if ((opcode& 0x000f) == 0x3) {
					V[opcode& 0x0f00] ^= V[opcode& 0x00f0];
					break;
				}
				if ((opcode& 0x000f) == 0x4) {
					V[opcode& 0x0f00] += V[opcode& 0x00f0];
					if (V[opcode& 0x0f00] > 255)
						V[0xf] = 1;
					break;
				}
				if ((opcode& 0x000f) == 0x5) {
					V[opcode& 0x0f00] -= V[opcode& 0x00f0];
					if (V[opcode& 0x0f00] >= V[opcode& 0x00f0])
						V[0xf] = 1;
					else
						V[0xf] = 0;
				}
				if ((opcode& 0x000f) == 0x6) {
					if ((V[opcode& 0x0f00]& 0x0) == 1)
						V[0xf] = 1;
					V[opcode& 0x0f00] /= 2;
					break;
				}
				if ((opcode& 0x000f) == 0x7) {
					V[opcode& 0x00f0] -= V[opcode& 0x0f00];
					if (V[opcode& 0x00f0] >= V[opcode& 0x0f00])
						V[0xf] = 1;
					else
						V[0xf] = 0;
					break;
				}
				if ((opcode& 0x000f) == 0xe) {
					V[opcode& 0x0f00] *= 2;
					if ((V[opcode& 0x0f00]& 0xf) == 1)
						V[0xf] = 1;
					break;
				}
				break;
			case 0x9:
				if (V[opcode& 0x0f00] != V[opcode& 0x00f0]) {
					PC += 2;
					break;
				}
				break;
			case 0xa:
				I = (opcode& 0x0fff);
				break;
			case 0xb:
				PC = (opcode& 0x0fff) + V[0];
				break;
			case 0xc:
				/* Oh no random! */
				srand(time(NULL));
				V[opcode& 0x0f00] = rand() & (opcode& 0x00ff);
				break;
			case 0xd:
				/* Some sprite drawing! */
				for (n = (opcode& 0x000f); n > 0; n--) {
					while(V[opcode& 0x0f00] + len - wrapx > xres)
						wrapx += xres;
					while(V[opcode& 0x00f0] + n - wrapy > yres)
						wrapy += yres;
					len = 0;
					PC = memory[I];
					while (len <= 8) {
						if (SDL_MUSTLOCK(screen)) {
							if (SDL_LockSurface(screen) < 0) {
								fprintf(stderr, "Can't lock screen: %s\n",
										SDL_GetError());
								exit(1);
							}
						}
						cpixel = getpixel(screen, V[opcode& 0x0f00] + len,
								V[opcode& 0x00f0] + n);
						if (cpixel == color1)
							V[0xf] = 1;
						if (SDL_MUSTLOCK(screen))
							SDL_UnlockSurface(screen);
						if (SDL_MUSTLOCK(screen)) {
							if (SDL_LockSurface(screen) < 0) {
								fprintf(stderr, "Can't lock screen: %s\n", 
										SDL_GetError());
								exit(1);
							}
						}
						putpixel(screen, V[opcode& 0x0f00] + len - wrapx, 
								V[opcode& 0x00f0] + n - wrapy, color1);
						if (SDL_MUSTLOCK(screen))
							SDL_UnlockSurface(screen);
						len++;
					}
				}
				break;
			case 0xe:
				if ((opcode& 0x00ff) == 0x9e) {
					if (key[opcode& 0x0f00] != 0)
						PC += 2;
					break;
				}
				if ((opcode& 0x00ff) == 0xa1) {
					if (key[opcode& 0x0f00] == 0)
						PC += 2;
					break;
				}
				break;
			case 0xf:
				if ((opcode& 0x00ff) == 0x07) {
					V[opcode& 0x0f00] = DT;
					break;
				}
				if ((opcode& 0x00ff) == 0x0a) {
					while (SDL_PollEvent(&event)) {
						switch (event.type) {
							case SDL_KEYDOWN:
								switch (event.key.keysym.sym) {
									case SDLK_1:
										key[0x1] = 1;
										V[opcode& 0x0f00] = 0x1;
										break;
									case SDLK_2:
										key[0x2] = 1;
										V[opcode& 0x0f00] = 0x2;
										break;
									case SDLK_3:
										key[0x3] = 1;
										V[opcode& 0x0f00] = 0x3;
										break;
									case SDLK_4:
										key[0xc] = 1;
										V[opcode& 0x0f00] = 0xc;
										break;
									case SDLK_QUOTE:
										key[0x4] = 1;
										V[opcode& 0x0f00] = 0x4;
										break;
									case SDLK_COMMA:
										key[0x5] = 1;
										V[opcode& 0x0f00] = 0x5;
										break;
									case SDLK_PERIOD:
										key[0x6] = 1;
										V[opcode& 0x0f00] = 0x6;
										break;
									case SDLK_p:
										key[0xd] = 1;
										V[opcode& 0x0f00] = 0xd;
										break;
									case SDLK_a:
										key[0x7] = 1;
										V[opcode& 0x0f00] = 0x7;
										break;
									case SDLK_o:
										key[0x8] = 1;
										V[opcode& 0x0f00] = 0x8;
										break;
									case SDLK_e:
										key[0x9] = 1;
										V[opcode& 0x0f00] = 0x9;
										break;
									case SDLK_u:
										key[0xe] = 1;
										V[opcode& 0x0f00] = 0xe;
										break;
									case SDLK_SEMICOLON:
										key[0xa] = 1;
										V[opcode& 0x0f00] = 0xa;
										break;
									case SDLK_q:
										key[0x0] = 1;
										V[opcode& 0x0f00] = 0x0;
										break;
									case SDLK_j:
										key[0xb] = 1;
										V[opcode& 0x0f00] = 0xb;
										break;
									case SDLK_k:
										key[0xf] = 1;
										V[opcode& 0x0f00] = 0xf;
										break;
									default:
										break;
								}
							break;
						}
						for (i = 0x0; i <= 0xf; i++)
							key[i] = 0x0;
						break;
					}
					break;
				}
				if ((opcode& 0x00ff) == 0x15) {
					DT = V[opcode& 0x0f00];
					break;
				}
				if ((opcode& 0x00ff) == 0x18) {
					ST = V[opcode& 0x0f00];
					break;
				}
				if ((opcode& 0x00ff) == 0x1e) {
					I += V[opcode& 0x0f00];
					break;
				}
				if ((opcode& 0x00ff) == 0x29) {
					/* TODO: Hexadecimal font */
					break;
				}
				if ((opcode& 0x00ff) == 0x33) {
					/* TODO: BCD */
					break;
				}
				if ((opcode& 0x00ff) == 0x55) {
					for (i = 0x0; i <= (opcode& 0x0f00); i++) {
						memory[I] = V[i];
						I++;
					}
					break;
				}
				if ((opcode& 0x00ff) == 0x65) {
					for (i = 0x0; i <= (opcode& 0xf00); i++) {
						V[i] = memory[I];
						I++;
					}
					break;
				}
				break;
			default:
				break;
		}
		SDL_UpdateRect(finalscreen, 0, 0, 0, 0);
		PC += 2;
	}
	/* printf("THE WHOLE UNIVERSE IS GOING TO DIE!!!\n"); */
	printf("Shutting down...\n");
	
	SDL_Quit();

	printf("Bye.\n");
	exit(0);
}
 

MicBeaudoin

New member
It looks like you increment PC a bit too much.

You are incrementing it here:

<code>opcode = memory[PC++];</code>

and into several opcodes:

<code>PC += 2;</code>

thus increasing it by 3 bytes. Don't forget that all opcodes are 2 bytes long.
It looks like you are missing some " PC += 2" at several places too.
 

Doomulation

?????????????????????????
A better solution might be to increment the PC at one place instead of several places. So after processing an opcode, add 2 to the PC. Not after each opcode.
 

Cyberman

Moderator
Moderator
Good programming practice

A better solution might be to increment the PC at one place instead of several places. So after processing an opcode, add 2 to the PC. Not after each opcode.
What Doom (We are all DOOMED I TELL YA! err...) is trying to tell you it is good debugging/programming practice to be sure you do something in a place where it is most consistent. In this case after you have read a complete opcode (IE the opcode is now in your pile of data) you don't need a pointer to the current instruction. Therefore you can advance the PC there.

It is likely the original Chip 8 does this (as it technically uses an interpreter that reads the 'program').

Cyb
 

SH-2009

New member
hello
I started this days to write a chip8 emulator i read documents about it and I understant how it work
i'm not good in C++ and I want to load file in the memory but i didn't know how please i need your help
 

anikom15

New member
Ok, if I try to run Pong it executes these opcodes:
Code:
c6b026a
b6daeaa2
366d422
7f015f0
87717c7
eaa271d6
a1e00160
27ba1e0
c60b6da
a1e00d60
d6dc028d
94878486
12871f61
82123f46
1690047
1630268
fe688a12
13fd580
13f1580
c812013f
2060c212
d422348e
fe680366
ff791612
179c812
18f00460
6c12fe76
29f165f2
157455d4
8080ee00
80
and then it exits.

perhaps it has to do with the file loading?
 
Last edited:

dstruktiv

New member
Sorry for the thread dig but it's for a good cause :)

I'm new to emulation and tried to start with the GB but got confused so I decided to go with Chip 8.

As far as I can tell I have everything working in terms of cpu and memory.

I load pong to 0x200 and start processing and watching my debug output I can see it processing all the opcodes and just going for ever, every time it polls for input I just give it a random up or down key (Which would move the paddle) and well yeah there's no end to the pong game until it's terminated so that's exactly what I'd expect my emu to do :)

Now I'm at the point where I want to write the display code, it MUST be done using DX9 as my reason for learning emulation is to port stuff to the Xbox 360.

My emu actually runs on the 360 at the moment, just no screen output :p

At the moment my screen info is stored in a char array, ready to be output somehow...

I have got as far as creating a D3D device and implementing a simple update and render loop that clears out the old scene and displays the new one - At the moment that's simply a blue background.

I've been told I need to create a texture, create a an object to apply that texture to (A rect), then each update lock my texture, clear it out, plot the char array to it some how, unlock it, then render it.

I've been on Google for the last few hours and really don't seem to be getting anywhere. For things as simple as this the Xbox DX9 is identical to the PC DX9.

I have found a few open source Chip 8 emus that use SDL, so I can see how it's done using SDL which appears to be similar to what I'm trying to do using DX9 but obviously there is some differences.

Anyone that has any experience with emulation and DX9 I would really appreciate some help.
 

Doomulation

?????????????????????????
You should just need to draw triangles to the screen. Nothing more complicated.
Any DX9 tutorial should should help you with that, I think.
I know I wrote my emulator using DX9. And I did it using some basic triangles.
 

dstruktiv

New member
In case anyone ever need to do this with D3D9, create a quad, create a texture, each update call lock your texture, manually modify the pixel info as per below, unlock it, then apply it to your quad object and render it.

I still have some things to work out with some items coming and going with each alternating update call (As you can see below the ball is either coming or going) but below we have the first ever Chip 8 emulator running on the Xbox 360 :)

Code:
m_pd3dDevice ->CreateTexture(64, 32, 1, 0, D3DFMT_LIN_A8R8G8B8, D3DPOOL_DEFAULT, &display, 0);

		display->LockRect(0,&rect,NULL, NULL);

		BYTE* pByte =(BYTE*)rect.pBits;

		for (DWORD y=0;y<32;++y)
		{
			for (DWORD x=0;x<64;++x)
			{
				DWORD index = (x*4)+(y*rect.Pitch);
				pByte[index] 	= (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
				pByte[index+1] 	= (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
				pByte[index+2] 	= (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
				pByte[index+3]	= (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
			}
		}

	display->UnlockRect(0);

IMG_2060.jpg
 

CHR15x94

Local nut
Never mind. I think I've found my problem. Sorry for the waste of space. :p


- CHR15x94
 
Last edited:

Top