What's new

Chip 8

CodeSlinger

New member
Good job mate, looks impressive. Im looking forward to viewing the source code. If you're having trouble getting a host for your code then I'll happily host it for you. Just PM me if you want.
 

gzaloprgm

New member
Hahahaha, so funny. Hadn't realized that :p

Porting HQ2X / 4X to SDL apps is very easy!

As I stated, here is the code:

http://gontacalc.googlecode.com/files/chip8emu.zip

BTW, gontacalc is a proyect I am making which somehows relates to emulation.

Controls:
L : Shows the (pseudo) filebrowser to choose a rom
Right click in screen: Restart Rom
Wheel Up: Faster emulation
Wheel Down: Slower emulation
Numpad is mapped to CHIP8 Keyboard

Cheers,
Gzaloprgm
 
Last edited:

jaskt7

New member
Hi, I am making a CHIP8 emu in Flash (don't make fun, just testing it) and I am having a few issues. I don't know if any of you know actionscript or not, but if you could just look through the code I would appreciate it.


http://babbage.cs.missouri.edu/~jaskt7/chip8/chip.html

the code for it is posted on the page, if you need to fla file,

http://babbage.cs.missouri.edu/~jaskt7/chip8/chip_cs3.fla


Anyways, the problem I am having is that the game 'brix' is not playing correctly. I haven't tried other games yet, but pong seems to work correctly.

The problem 'brix' is having is that the ball is hitting invisible (non-existent) bricks, including some that wouldn't even be there in the first place. Also, sometimes (a lot of times) the ball will go straight through many bricks without hitting them.
 

jaskt7

New member
Never mind, I fixed the problem. There was an issue in my draw function, I guess the flag was not getting set right or something.
 

Thesuperchang

New member
Bcd

Just in case anyone was losing some sleep, I have an explanation on what BCD is, and its purpose.

Pretty much, its a verbose way to store decimal number in binary form. It is done as stated below;

ZXY is an integer where Z represents 100's, X represents 10's and Y represents 1's.
If we would want to convert ZXY to binary we would do the regular divide and remainder theorem or whatever you want.

If we want the BCD representation of ZXY, it is very simple. Given that
0 <= Z <= 9
0 <= X <= 9
0 <= Y <= 9
We write BCD as
zzzz xxxx yyyy
where z's, x's, y's are binary values for X, Z, Y. As an example;
convert 132 to BCD and Binary;
2's comp. binary = 1000 0100 = 0x84
BCD = 0001 0011 0010 = 0x132

Now you may be asking yourself, why the fuck! Well the idea is that processing is reduced at the expense of memory. A case may arise that numbers are inputted one digit at a time (Calculators for example). It is very simple to capture one digit at a time and the combine right before processing the number.

I hope that helps for some curious people. I know that when simulating the chip8, i8080 and Z80 I always cringed at the existence of BCD opcodes.
 

hernaldo2009

New member
Chip 8 in C# very slow

Hi guys:

I just finished my first emulator in C# 1.0, but works very slowly.
I build a FPS counter and only i can run to 6 FPS!

I Need help with the Timers, because i don't know how accelerate the code

This is my code (of course, is a bit messy):


Code:
using System;
using System.Drawing;
using SdlDotNet;
using SdlDotNet.Sprites;

using System.Runtime.InteropServices;  //beep

using System.IO;


namespace Chip8_2
{
		public class cap1
	{

		#region variables emulador

		const int multiplicadorPixel = 5;
		Surface srf = null;
		int frames = 0;

		const int START_ADDR	= 0x200;
		const int MEMORY_SIZE	= 0xFFF;
		const int MAX_KEYS	= 16;
		const int MAX_STACKSIZE = 16;
		const int MAX_REGISTERS = 16;
		const int RES_X	= 64;  //resolucion original 64x32
		const int RES_Y	= 32;
		const int CHIP8_FNT_Y	= 5;
		const int SPRITE_X	= 8;

		int opCode1 = 0;
		int opCode2 = 0;  //X
		int opCode3 = 0;  //Y
		int opCode4 = 0;

		
		int[,] arrcScreen = new int[RES_X, RES_Y];

		//Chip 8 tiene 2 timers: Delay Timer y Sound Timer
		int	cDelayTimer;
		int	cSoundTimer;


		/// msec per timer tick, default is about 16
		/// </summary>
		public const int TIMER_MSEC_PER_TICK = 16;

		//Used to control the timers in the Chip
		DateTime _lastTime = DateTime.Now;

		//high resolution delay timer implementation
		private double _timerDelay;

		// Delay timer, each value represents is about 16 msec
		public byte TimerDelay
		{
			get { return (byte)_timerDelay; }
			set { _timerDelay = value; }
		}

		// Sound timer - the speaker beeps if non-zero
		// Each value is about 16 msec
		public byte TimerSound
		{
			get { return (byte)_timerSound; }
			set { _timerSound = value; }
		}

		//high resolution sound timer implementation
		private double _timerSound;

		DateTime fps_inicio = DateTime.Now;

		[DllImport("Kernel32.dll")] //para beep
		public static extern bool Beep(UInt32 frequency, UInt32 duration);
		

		int[] arrcMemory = new int[MEMORY_SIZE];
		int[] V = new int[MAX_REGISTERS];
		int	opCode;
		int	sPC;
		int	sI;
		int  sSP;
		int[] sStack = new int[MAX_STACKSIZE];
		int KK;
			
		int[] arrcFontC8 = {
							0xF0, 0x90, 0x90, 0x90, 0xF0,	// Values For 0
							0x60, 0xE0, 0x60, 0x60, 0xF0,	// Values For 1
							0x60, 0x90, 0x20, 0x40, 0xF0,	// Values For 2
							0xF0, 0x10, 0xF0, 0x10, 0xF0,	// Values For 3
							0x90, 0x90, 0xF0, 0x10, 0x10,	// Values For 4
							0xF0, 0x80, 0x60, 0x10, 0xE0,	// Values For 5
							0xF0, 0x80, 0xF0, 0x90, 0xF0,	// Values For 6
							0xF0, 0x10, 0x10, 0x10, 0x10,	// Values For 7
							0xF0, 0x90, 0xF0, 0x90, 0xF0,	// Values For 8
							0xF0, 0x90, 0xF0, 0x10, 0x10,	// Values For 9
							0x60, 0x90, 0xF0, 0x90, 0x90,	// Values For A
							0xE0, 0x90, 0xE0, 0x90, 0xE0,	// Values For B
							0x70, 0x80, 0x80, 0x80, 0x70,	// Values For C
							0xE0, 0x90, 0x90, 0x90, 0xE0, 	// Values For D
							0xF0, 0x80, 0xF0, 0x80, 0xF0,	// Values For E
							0xF0, 0x90, 0xF0, 0x80, 0x80	// Values For F
						   };


		private bool[] KEYS_PRESSED = {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false};
		const int TECLA_1	= 0;
		const int TECLA_2	= 1;
		const int TECLA_3	= 2;
		const int TECLA_4	= 3;
		const int TECLA_Q	= 4;
		const int TECLA_W	= 5;
		const int TECLA_E	= 6;
		const int TECLA_R	= 7;
		const int TECLA_A	= 8;
		const int TECLA_S	= 9;
		const int TECLA_D	= 10;
		const int TECLA_F	= 11;		
		const int TECLA_Z	= 12;
		const int TECLA_X	= 13;
		const int TECLA_C	= 14;
		const int TECLA_V	= 15;

		// mapea el teclado qwerty al teclado del chip8
		private byte[] KeyMap = {
										  0x01,0x02,0x03,0x0C,
										  0x04,0x05,0x06,0x0D,
										  0x07,0x08,0x09,0x0E,
										  0x0A,0x00,0x0B,0x0F };
		// desmapea el teclado del chip8 al teclado qwerty
		private byte[] KeyUnMap = {
											0x0D,0x00,0x01,0x02,
											0x04,0x05,0x06,0x08,
											0x09,0x0A,0x0C,0x0E,
											0x03,0x07,0x0B,0x0F };		

		#endregion

		

		public cap1()
		{
			//resoluci n			
			Video.SetVideoModeWindow(RES_X*multiplicadorPixel, RES_Y*multiplicadorPixel);
			// Limpar la pantalla (dejarla negra)
			Video.Screen.Fill(Color.Black);			
			Video.WindowCaption = "Emulador Chip8 v0.01 - FPS: ?";

			srf = new Surface(RES_X*multiplicadorPixel, RES_Y*multiplicadorPixel);
			srf.Fill(Color.Black);

			ResetHardware();
			CargarROM("PONG");	//PONG PONG2

			//_lastTime = DateTime.Now;			
			Events.Tick += new TickEventHandler(Events_Tick);
			Events.Quit += new QuitEventHandler(Events_Salir);			
		}

		

		private void Events_Salir(object sender, SdlDotNet.QuitEventArgs e)
		{
			Events.QuitApplication();
		}	
				
		public void Run()
		{	
			
			Events.KeyboardDown +=new KeyboardEventHandler(this.KeyboardPressed);
			Events.KeyboardUp +=new KeyboardEventHandler(this.KeyboardReleased);
			Events.Run();
		}

		private void KeyboardPressed(object sender, KeyboardEventArgs e)
		{	
			//Salimos del juego con tecla Escape
			if(e.Key == Key.Escape)
			{
				Events.QuitApplication();
			}

			// activa la tecla presionada
			if(e.Key == Key.One) {KEYS_PRESSED[TECLA_1] = true;}
			if(e.Key == Key.Two) {KEYS_PRESSED[TECLA_2] = true;}
			if(e.Key == Key.Three) {KEYS_PRESSED[TECLA_3] = true;}
			if(e.Key == Key.Four) {KEYS_PRESSED[TECLA_4] = true;}
			if(e.Key == Key.Q) {KEYS_PRESSED[TECLA_Q] = true;}
			if(e.Key == Key.W) {KEYS_PRESSED[TECLA_W] = true;}
			if(e.Key == Key.E) {KEYS_PRESSED[TECLA_E] = true;}
			if(e.Key == Key.R) {KEYS_PRESSED[TECLA_R] = true;}
			if(e.Key == Key.A) {KEYS_PRESSED[TECLA_A] = true;}
			if(e.Key == Key.S) {KEYS_PRESSED[TECLA_S] = true;}
			if(e.Key == Key.D) {KEYS_PRESSED[TECLA_D] = true;}
			if(e.Key == Key.F) {KEYS_PRESSED[TECLA_F] = true;}
			if(e.Key == Key.Z) {KEYS_PRESSED[TECLA_Z] = true;}
			if(e.Key == Key.X) {KEYS_PRESSED[TECLA_X] = true;}
			if(e.Key == Key.C) {KEYS_PRESSED[TECLA_C] = true;}
			if(e.Key == Key.V) {KEYS_PRESSED[TECLA_V] = true;}
		}

		private void KeyboardReleased(object sender, KeyboardEventArgs e)
		{
			// activa la tecla presionada
			if(e.Key == Key.One) {KEYS_PRESSED[TECLA_1] = false;}
			if(e.Key == Key.Two) {KEYS_PRESSED[TECLA_2] = false;}
			if(e.Key == Key.Three) {KEYS_PRESSED[TECLA_3] = false;}
			if(e.Key == Key.Four) {KEYS_PRESSED[TECLA_4] = false;}
			if(e.Key == Key.Q) {KEYS_PRESSED[TECLA_Q] = false;}
			if(e.Key == Key.W) {KEYS_PRESSED[TECLA_W] = false;}
			if(e.Key == Key.E) {KEYS_PRESSED[TECLA_E] = false;}
			if(e.Key == Key.R) {KEYS_PRESSED[TECLA_R] = false;}
			if(e.Key == Key.A) {KEYS_PRESSED[TECLA_A] = false;}
			if(e.Key == Key.S) {KEYS_PRESSED[TECLA_S] = false;}
			if(e.Key == Key.D) {KEYS_PRESSED[TECLA_D] = false;}
			if(e.Key == Key.F) {KEYS_PRESSED[TECLA_F] = false;}
			if(e.Key == Key.Z) {KEYS_PRESSED[TECLA_Z] = false;}
			if(e.Key == Key.X) {KEYS_PRESSED[TECLA_X] = false;}
			if(e.Key == Key.C) {KEYS_PRESSED[TECLA_C] = false;}
			if(e.Key == Key.V) {KEYS_PRESSED[TECLA_V] = false;}
		}

		[STAThread]
		static void Main() 
		{				
			cap1 juego = new cap1();
			
			juego.Run();				
		}

		#region emulador


		private void Events_Tick(object sender, TickEventArgs e)
		{	
			Emula();			
		}

		public void Emula()
		{			
			

			FetchOpcode();
			ExecuteOpcode();			

			//beep
			if (cSoundTimer > 0)
				Beep(500, 1);

			//DateTime now = DateTime.Now;
			// calculate how much time passed since the last update
		//	double elapsedMsec = (now - _lastTime).TotalMilliseconds;

			double tiempoPasado = (DateTime.Now - fps_inicio).TotalMilliseconds;

			if (tiempoPasado > 1000) // cada un segundo grabo los Frames, asi calculo los frames por segundo (deber퀀an ser 60)
			{
				Video.WindowCaption = "Emulador Chip8 v0.01 - FPS: " + frames.ToString();
				frames = 0;
				fps_inicio = DateTime.Now;
			}

			
			if (cDelayTimer > 0) { cDelayTimer--; }
			if (cSoundTimer > 0) { cSoundTimer--; }

	
							
		}

		
		void ResetHardware()
		{
			// Resetting Timers.
			cDelayTimer = 0x0;
			cSoundTimer = 0x0;

			// Resetting Pointing Variables.
			opCode = 0x0;
			sPC = START_ADDR;
			sSP = 0x0;
			sI = 0x0;

			// Clearing Memory.
			for (int nAddr = 0; nAddr < MEMORY_SIZE; nAddr++)
			{
				arrcMemory[nAddr] = 0x0;
			}

			// Clearing Registers.
			for (int nCurrReg = 0; nCurrReg < MAX_REGISTERS; nCurrReg++)
			{
				arrcMemory[nCurrReg] = 0x0;
			}

			// Clearing Stack.
			for (int nCurrItem = 0; nCurrItem < MAX_STACKSIZE; nCurrItem++)
			{
				sStack[nCurrItem] = 0x0;
			}

			// Load Fontset To Memory.
			LoadFontset(); //Las fuentes se usan para pintar palabras, por ej, los marcadores del juego PONG [0-0]
		}


		void LoadFontset()
		{
			for (int nIndex = 0; nIndex < 80; nIndex++)
			{
				arrcMemory[nIndex] = arrcFontC8[nIndex];
			}
		}


		bool CargarROM(string ruta_rom)
		{
			FileStream rom = new FileStream(@ruta_rom,FileMode.Open);		
			
			if (rom.Length == 0)
			{
				Console.Write("Error: Archivo daကado o vac퀀o");
				return false;
			}

			for (int i = 0; i<rom.Length; i++)
				arrcMemory[START_ADDR + i] = (byte) rom.ReadByte();		//comenzamos de 200	
			
			rom.Close();		
	
			return true;

			
		}

		void FetchOpcode()
		{
			// Get Opcode From Memory (2 Bytes).
			opCode = arrcMemory[sPC] << 8 | arrcMemory[sPC+1];

			// Advance Program Counter To Next Opcode.
			sPC += 2;
		}





		void ExecuteOpcode()
		{
			// Logging Opcode to Log File.
			//logOpcode(sPC - 2,opCode);

			KK = (opCode & 0x00FF);
			//REG_Y = (opCode & 0x00F0) >> 4;
			//REG_X = (opCode & 0x0F00) >> 8;

			// separo el opCode en 4 bytes
			opCode1 = (opCode & 0xF000) >> 12;
			opCode2 = (opCode & 0x0F00) >> 8; //X
			opCode3 = (opCode & 0x00F0) >> 4; //Y
			opCode4 = (opCode & 0x000F) >> 0;


			// Identifying Opcodes by Their Left-Most Figure.
			switch (opCode1)
			{
					// 0xxx Opcodes.
				case (0x0):
				{
					// Identifying 0xxx Opcodes.
					switch (opCode & 0x0FFF)
					{
							// Opcode 00E0: Clear Screen.
						case (0x00E0):
						{
							ClearScreen();
							break;
						}
							// Opcode 00EE: Return From Subroutine.
						case (0x00EE):
						{
							ReturnFromSub();
							break;
						}
					}
					break;
				}
					// 1xxx Opcodes.
				case (0x1):
				{
					// Opcode 1NNN: Jump To Address NNN.
					JumpToAddr();
					break;
				}
					// 2xxx Opcodes.
				case (0x2):
				{
					// Opcode 2NNN: Call Subroutine At Address NNN.
					CallSub();
					break;
				}
					// 3xxx Opcodes.
				case (0x3):
				{
					// Opcode 3XKK: Skip Next Instruction If VX == KK
					SkipIfEql();
					break;
				}
					// 4xxx Opcodes.
				case (0x4):
				{
					// Opcode 4XKK: Skip Next Instruction If VX != KK
					SkipIfNotEql();
					break;
				}
					// 5xxx Opcodes.
				case (0x5):
				{
					// Opcode 5XY0: Skip Next Instruction If VX == VY
					SkipIfRegEql();
					break;
				}
					// 6xxx Opcodes.
				case (0x6):
				{
					// Opcode 6XKK: Assign Number KK To Register X.
					AssignNumToReg();
					break;
				}
					// 7xxx Opcodes.
				case (0x7):
				{
					// Opcode 7XKK: Add Number KK To Register X.
					AddNumToReg();
					break;
				}
					// 8xxx Opcodes.
				case (0x8):
				{
					// Identifying 8xxx Opcodes.
					switch (opCode4)
					{
							// Opcode 8XY0: Assign From Register To Register.
						case (0x0):
						{
							AssignRegToReg();
							break;
						}
							// Opcode 8XY1: Bitwise OR Between Registers.
						case (0x1):
						{
							RegisterOR();
							break;
						}
							// Opcode 8XY2: Bitwise AND Between Registers.
						case (0x2):
						{
							RegisterAND();
							break;
						}
							// Opcode 8XY3: Bitwise XOR Between Registers.
						case (0x3):
						{
							RegisterXOR();
							break;
						}
							// Opcode 8XY4: Add Register To Register.
						case (0x4):
						{
							AddRegToReg();
							break;
						}
							// Opcode 8XY5: Sub Register From Register.
						case (0x5):
						{
							SubRegFromReg();
							break;
						}
							// Opcode 8XY6: Shift Register Right Once.
						case (0x6):
						{
							ShiftRegRight();
							break;
						}
							// Opcode 8XY7: Sub Register From Register (Reverse Order).
						case (0x7):
						{
							ReverseSubRegs();
							break;
						}
							// Opcode 8XYE: Shift Register Left Once.
						case (0xE):
						{
							ShiftRegLeft();
							break;
						}
					}
					break;
				}
					// 9xxx Opcodes.
				case (0x9):
				{
					// Opcode 9XY0: Skip Next Instruction If VX != VY
					SkipIfRegNotEql();
					break;
				}
					// Axxx Opcodes.
				case (0xA):
				{
					// Opcode ANNN: Set Index Register To Address NNN.
					AssignIndexAddr();
					break;
				}
					// Bxxx Opcodes.
				case (0xB):
				{
					// Opcode BNNN: Jump To NNN + V0.
					JumpWithOffset();
					break;
				}
					// Cxxx Opcodes.
				case (0xC):
				{
					// Opcode CXKK: Assign Bitwise AND Of Random Number & KK To Register X.
					RandomANDnum();
					break;
				}
					// Dxxx Opcodes.
				case (0xD):
				{
					// Opcode DXYN: Draw Sprite To The Screen.
					DrawSprite();
					break;
				}
					// Exxx Opcodes.
				case (0xE):
				{
					// Identifying Exxx Opcodes.
					switch (KK)//opCode & 0x00FF
					{
							// Opcode 0xEX9E: Skip Next Instruction If Key In VX Is Pressed.
						case (0x9E):
						{
							SkipIfKeyDown();
							break;
						}
							// Opcode 0xEXA1: Skip Next Instruction If Key In VX Is NOT Pressed.
						case (0xA1):
						{
							SkipIfKeyUp();
							break;
						}
					}
					break;
				}
					// Fxxx Opcodes.
				case (0xF):
				{
					// Identifying Fxxx Opcodes.
					switch (KK)//opCode & 0x00FF
					{
							// Opcode FX07: Assign Delay Timer To Register.
						case (0x07):
						{
							AssignFromDelay();
							break;
						}
							// Opcode FX0A: Wait For Keypress And Store In Register.
						case (0x0A):
						{
							StoreKey();
							break;
						}
							// Opcode FX15: Assign Register To Delay Timer.
						case (0x15):
						{
							AssignToDelay();
							break;
						}
							// Opcode FX18: Assign Register To Sound Timer.
						case (0x18):
						{
							AssignToSound();
							break;
						}
							// Opcode FX1E: Add Register To Index.
						case (0x1E):
						{
							AddRegToIndex();
							break;
						}
							// Opcode FX29: Index Points At CHIP8 Font Char In Register.
						case (0x29):
						{
							IndexAtFontC8();
							break;
						}
							// Opcode FX30: Index Points At SCHIP8 Font Char In Register.
						case (0x30):
						{
							IndexAtFontSC8();
							break;
						}
							// Opcode FX33: Store BCD Representation Of Register In Memory.
						case (0x33):
						{
							StoreBCD();
							break;
						}
							// Opcode FX55: Save Registers To Memory.
						case (0x55):
						{
							SaveRegisters();
							break;
						}
							// Opcode FX65: Load Registers From Memory.
						case (0x65):
						{
							LoadRegisters();
							break;
						}
					}
					break;
				}
			}
		}

		// --------------------------------------------------------------------------------------

		void ClearScreen()
		{
			Video.Screen.Fill(Color.Black);			
		}

		void ReturnFromSub()
		{
			// Going To Last Stack Item.
			sSP--;

			// Pointing Next Instruction To The Saved Position In The Stack.
			sPC = sStack[sSP];
		}

		void JumpToAddr()
		{
			// Jumping To Given Address.
			sPC = (opCode & 0x0FFF);
		}

		void CallSub()
		{
			// Saving Current Program Counter.
			sStack[sSP] = sPC;
			sSP++;

			// Jumping To Subroutine At Given Address.
			sPC = (opCode & 0x0FFF);
		}

		void SkipIfEql()
		{
			// Check If The Register Value Equals The Given Value.
			if (V[opCode2] == KK)
			{
				// Skip Next Instruction.
				sPC += 2;
			}
		}

		void SkipIfNotEql()
		{
			// Check If The Register Value Differs From The Given Value.
			if (V[opCode2] != KK)
			{
				// Skip Next Instruction.
				sPC += 2;
			}
		}

		void SkipIfRegEql()
		{
			// Check If The X Register Value Equals The Y Register Value.
			if (V[opCode2] == V[opCode3])
			{
				// Skip Next Instruction.
				sPC += 2;
			}
		}

		void AssignNumToReg()
		{
			// Assign Given Number To Register.
			V[opCode2] = KK;
		}

	
		  
		private char getLower(int number)
		{
			return (char)(number&0xFF);
		}

		void AddNumToReg()
		{
			// Add Given Number To Register.
			V[opCode2] += KK;		
		}

		// --------------------------------------------------------------------------------------

		void AssignRegToReg()
		{
			// Assign Register Y To Register X.
			V[opCode2] = V[opCode3];
		}

		void RegisterOR()
		{
			// Assign Mutual OR On Registers To X.
			V[opCode2] |= V[opCode3];
		}

		void RegisterAND()
		{

			// Assign Mutual AND On Registers To X.
			V[opCode2] &= V[opCode3];
		}

		void RegisterXOR()
		{
			// Assign Mutual XOR On Registers To X.
			V[opCode2] ^= V[opCode3];
		}

		void AddRegToReg()
		{
			//8XY4 Realiza esto: VX = VX + VY, VF = carry

			// Keep Carry Indication In Register F.
			V[0xF] = (V[opCode2] + V[opCode3]) >> 8;

			// Add Register Y To Register X.
			V[opCode2] += V[opCode3];
		}

		void SubRegFromReg()
		{
			// Saving Negated Borrow Indication In Register F.
			if (V[opCode2] >= V[opCode3])
			{
				V[0xF] = 0x1;
			}
			else
			{
				V[0xF] = 0x0;
			}

			// Subtracting Register Y From Register X.
			V[opCode2] -= V[opCode3];
		}

		void ShiftRegRight()
		{
			V[0xF] = V[opCode2] & 0x1;
			V[opCode2] >>= 1;
		}

		void ReverseSubRegs()
		{
			if (V[opCode2] <= V[opCode3])
			{
				V[0xF] = 0x1;
			}
			else
			{
				V[0xF] = 0x0;
			}			
			
		}

		void ShiftRegLeft()
		{
			V[0xF] = V[opCode2] & 0x10;
			V[opCode2] <<= 1;
		}

		// --------------------------------------------------------------------------------------

		void SkipIfRegNotEql()
		{
			// Check If The X Register Value Differs From The Y Register Value.
			if (V[opCode2] != V[opCode3])
			{
				// Skip Next Instruction.
				sPC += 2;
			}
		}

		void AssignIndexAddr()
		{
			// Set Index Register To Point To Address NNN.
			sI = opCode & 0x0FFF;
		}

		void JumpWithOffset()
		{
			// Jump To Address NNN + Offset From Register 0.
			sPC = (opCode & 0x0FFF) + V[0x0];
		}	

		void RandomANDnum()
		{
			// Generating Random Number With Given Number.

			Random r = new Random((int)DateTime.Now.Ticks);			
			int rnd = r.Next();
			if (rnd < 0) rnd = rnd*-1; //siempre positivo
			V[opCode2] = rnd & KK;

		}

		/*screen is 64x62 pixels
		Todos los drawings son hechos en modos XOR. 
		Cuando uno o mas pixels son borrados mientras un sprite es pintado, 
		el registro VF se setea en 01, sino queda en 00.
		*/

		void DrawSprite()
		{
			// Resetting Collision Detection.
			V[0xF] = 0x0;

			if ((opCode & 0x000F) == 0) //opCode & 0x000F =opcode4
			{
				// Draw SCHIP8 Sprite Of Size 16x16.
				// * Not Implanted Yet :P *
			}
			else
			{
				
				// Draw CHIP8 Sprite Of Size 8xN
				
				for (int nSpriteY = 0; nSpriteY < opCode4; nSpriteY++)
				{
					for (int nSpriteX = 0; nSpriteX < SPRITE_X; nSpriteX++)
					{
					
						int x = (arrcMemory[sI + nSpriteY] & (0x80 >> nSpriteX));


						if ( x != 0)
						{
							// Checking For Collision (Overwrite Pixel).
							int xx = (V[opCode2] + nSpriteX);
							int yy = (V[opCode3] + nSpriteY);							
							
//grabaLog ("xx=" + xx.ToString() + " xx%64=" + (xx%64).ToString() + " yy=" + yy.ToString() +  " yy%32=" + (yy%32).ToString());
							if (arrcScreen[xx % 64, yy % 32] == 1)  //% resto o modulo de una division, ejemplo 32%64=32, 0%32=0, 1%32=1, 31%32=31, 32%32=0, 33%32=1 (aqui se caia ya que llegaba a 32 y el max que puede alcanzar es 31)
								V[0xF] = 1;

							arrcScreen[xx % 64, yy % 32] ^= 1; //XOR 0^1=1, 1^0=1, 1^1 =0, 0^0=0

							
							
						} 
					}
				}
			}

			UpdateScreen();
		}

		void SkipIfKeyDown()
		{
			if(KEYS_PRESSED[KeyUnMap[V[opCode2]]] == true)
				sPC += 2;		
		}

		void SkipIfKeyUp()
		{
			if(KEYS_PRESSED[KeyUnMap[V[opCode2]]] == false)
				sPC += 2;
		}

		void AssignFromDelay()
		{
			V[opCode2] = cDelayTimer;
		}	

		void StoreKey()
		{
			for (int nKey = 0; nKey < KEYS_PRESSED.Length; nKey++)
			{
				if (KEYS_PRESSED[nKey] == true)
				{
					V[opCode2] = nKey;
				}
			}			
		}

		void AssignToDelay()
		{
			cDelayTimer = V[opCode2];
		}

		void AssignToSound()
		{
			cSoundTimer = V[opCode2];
		}

		void AddRegToIndex()
		{
			sI += V[opCode2];
		}

		void IndexAtFontC8()
		{
			sI = (V[opCode2] * 0x5);
		}

		void IndexAtFontSC8()
		{
			// Not Implemanted yer, SCHIP8 Function.
		}

		void StoreBCD()
		{
			int nRegister = (int) V[opCode2];
			arrcMemory[sI] = nRegister / 100;
			arrcMemory[sI + 1] = (nRegister / 10) % 10;
			arrcMemory[sI + 2] = nRegister % 10;
		}

		void SaveRegisters()
		{
			for (int nIndex = 0; nIndex <= opCode2; nIndex++)
			{
				arrcMemory[sI++] = V[nIndex];
			}
			sI += 1;
		}

		void LoadRegisters()
		{
			for (int nIndex = 0; nIndex <= opCode2; nIndex++)
			{
				V[nIndex] = arrcMemory[sI++];
			}
			sI += 1;
		}

		

		void UpdateScreen()
		{
			int nDrawX;
			int nDrawY;

			for (nDrawX = 0; nDrawX < RES_X; nDrawX++)
			{				
				for (nDrawY = 0; nDrawY < RES_Y; nDrawY++)
				{					
					if (arrcScreen[nDrawX, nDrawY] != 0)
					{
						Box b = new Box(new Point(nDrawX, nDrawY), new Size(1,1));
						srf.DrawFilledBox(b, Color.White);						
					}
					else
					{
						Box b = new Box(new Point(nDrawX, nDrawY), new Size(1,1));
						srf.DrawFilledBox(b, Color.Black);	
					}
				}				
			}
			
			Surface srf_back = new Surface(srf);
			srf.Scale(multiplicadorPixel);			
			Video.Screen.Blit(srf);		
			srf = srf_back; //para no ir Scalando indefinidamente
			Video.Screen.Update();

			frames ++; //para el calculo de FPS


		}

		#endregion
		


		public void grabaLog(string data)
		{			
			Console.WriteLine(data);			
		}
		
	}



	
}
 
Last edited:

Doomulation

?????????????????????????
I would say that's because languages such as C# is no good for emulators. They are too slow.
But... have you used a profiler?
 

hernaldo2009

New member
Sorry for the question. but what is a Profiler?

other questions:

-¿if i program the same code but in C or C++ i can get best times that in C#?

- if i like develop a Chip 8 emulator, what is more fast: C or C++? I ask for learning the languaje.

- if I use Visual Studio .Net 2003, you recommend to me using VC++ or not?


thanks.
 

swatgod

New member
if you do not know either language than i suggest you forget about making an emulator for right now, making an emulator is hardly the type of learning program you want to be making because of the learning curve required by making one.

if you know neither c or c++, than i suggest if you want an easier time learning a programming language than go for c#, i know some old school coders may flame me for saying that but its a much easier language to learn on and i have proved in terms of drawing, if you play your cards right, can be much faster and if you need such an example i do have one, it can render a frame on xp in 0 ms and the same technique in c/c++ was a lot slower.

edit: i just saw someone used sdldotnet in c#, i HIGHLY suggest you get rid of it and use managed directx, sdldotnet i used for my chip 8 and it was very slow and its horribly made(check memory use when its running). i tried it on my space invaders and its memory use sky rocketed to 200 mb from its buggy nature, so i highly suggest anyone coding an emu in c#, to use managed directx as it made a world of difference in speed and ease of use.

method i used was to create 2 triangles the size of the screen i needed than create a texture and locked the texture and wrote my pixels to it than unlocked and allowed directx to render the texture on top of the triangles. i only used that method on space invaders but i am certain its just as fast as space invaders uses a much higher res than chip 8 does. the setup of managed directx is about just under 200 lines for the entire thing including code to allow it to reinitialize if device was lost.
 
Last edited:

hernaldo2009

New member
thanks.
ok, then I will do the same version of the emulator, but with Managed Direct X + C#. What version of Direct X can I use?
 

hernaldo2009

New member
Yesterday I make the same version of emulator but in console mode, printing ascii characters, and i get 18 FPS, 3 times most fast than sdldotnet. I will see how many FPS can get now.
 

Hellfish

New member
Hi!

New guy here, also working on a CHIP8 interpreter. :)
Like hernaldo2009, I'm using C# with SDLDotNet, though I'm getting better performance (right around 30FPS for most CHIP8 games). However, now I got into adding SCHIP support and was wondering since I couldn't find specific info anywhere (I've checked Zophar's, David Winter's, the archived goldroad,codeslinger.co.uk and Doomulation's document).

The DXYN opcode: If in CHIP8 mode, the resulting sprite will be 8xN.And in SCHIP mode, if N is 0, the sprite will be 16x16. But in SCHIP mode, if N isn't 0, will the sprite be 8xN or 16xN? (Also, in CHIP8 mode, if N is 0, what will draw?Nothing?)

Thanks for a quality thread, so far! :)

EDIT: Also, I've been working on some test ROMs for various opcodes that can be difficult to verify as working, BCD (FX33) one is done so far but I don't know wether I should post it, considering the rules.

EDIT 2: I figured I'll attach the emulator. It requires .NET 2.0 and SdlDotNet Runtime (cs-sdl.sourceforge.net). I don't think it works with Mono. It allows for key remapping inside the program. Target FPS is what FPS SDLDotNet will attempt to render at.After a little modification, I get between 25-60 fps depending on which ROM.

Source (Does not include the source of the virtual listbox component.)
Binary
 
Last edited:

serge2k

New member
i've finally got mine to the point where it is capable of loading and running all the games with proper graphics, sound (sorta, it beeps anyway), and collision detection.

So pong(2), breakout, brix, and a couple of others are all playable.

I still need to add timing and figure out some optimizations (space invaders is very clumsy and slow, basically it's incredibly difficult/impossible to pass the first level)

Still, I'm pretty happy. I've attempted this before but always got stuck at the graphics stage. Getting some good C++ practice for my OS course next year (everything up to this point is java, but the prof let us make the choice).

I was wondering if there is an easy way to cut down on the flickering? I really don't know much directX at this ponit.
 

Doomulation

?????????????????????????
I was wondering if there is an easy way to cut down on the flickering? I really don't know much directX at this ponit.

No reliable way. It's a flaw of the hardware itself - it simply lacked instructions for collision, so the games tend to draw on already drawn parts every frame or so, and then checks the collision bits.

There might be a way to "hack" this system by, for example, guessing if the game did it to know if there was a collision or not, and if it was merely for checking collision, then skipping the erasing part and the subsequent draw to redraw the part that was erased.
But sometimes games just wants to erase pixels on the screen, which can also be done via the draw opcode.

So no, no reliable way.
 

AceHack

New member
No reliable way. It's a flaw of the hardware itself - it simply lacked instructions for collision, so the games tend to draw on already drawn parts every frame or so, and then checks the collision bits.

There might be a way to "hack" this system by, for example, guessing if the game did it to know if there was a collision or not, and if it was merely for checking collision, then skipping the erasing part and the subsequent draw to redraw the part that was erased.
But sometimes games just wants to erase pixels on the screen, which can also be done via the draw opcode.

So no, no reliable way.


Hey Doom I'm writing a silverlight Chip-8 Emu to learn to write emulators because I want to write a SNES or NES silverlight emu. My Chip-8 is working great on many games but I noticed on PONG some flickering. How did you fix that on yours. Thanks so much for your help. Also do you have VS2008 and Silverlight 3 Beta. I'd love to let you look over my source but it's in Silverlight 3 Beta. Thanks again.

Also how do you set your timing for the CPU cycle, it works so great on every game. On mine some games seem slow and some seem fast. Thanks.

Also do you have any idea why blinky is not working? It drawing some really weird stuff.

Also in your source it looks like you update your delay register only after every 20 op codes is this correct? or what am I missing when I read your source. Thanks.
 
Last edited:

Doomulation

?????????????????????????
Hey Doom I'm writing a silverlight Chip-8 Emu to learn to write emulators because I want to write a SNES or NES silverlight emu. My Chip-8 is working great on many games but I noticed on PONG some flickering. How did you fix that on yours.
I used a game-specific hack.

Thanks so much for your help. Also do you have VS2008 and Silverlight 3 Beta. I'd love to let you look over my source but it's in Silverlight 3 Beta. Thanks again.
Sorry no. I don't program Silverlight. I only do C/C++ (preferably C++).

Also how do you set your timing for the CPU cycle, it works so great on every game. On mine some games seem slow and some seem fast. Thanks.
I can't remember 100%, but I believe I fixed the number of opcodes per second or some such. I would have to re-read and re-test the code to understand it now :p

Also do you have any idea why blinky is not working? It drawing some really weird stuff.
No idea. It could be a thousand things wrong.
You may want to invest in a small debugger.
That was how I found and fixed most problems.

Also in your source it looks like you update your delay register only after every 20 op codes is this correct? or what am I missing when I read your source. Thanks.
It has to do with the timing, but again, I'm not sure how I did it, exactly.
 

elizeucoder

New member
help in chip8 emulator

Hi guys this is my first emulator i've made.
it's written in java
It's working well with the game tic-tac-toe, but games like space invaders outside always the screen coordinates getting always an error of array bound excetion.
I need some help of you guys,mainly in the the DRW_Vx_Vy_nibble() method in the source code below the links of the source and emulator


Sorry for my english.


SOURCE: LINK
EMULATOR: LINK

this is the method that sometimes have problems.
Code:
public void DRW_Vx_Vy_nibble()
	{
		int x=(op & 0xf00)>>8;
		int y=(op & 0xf0)>>4;
		
		int _x=V[x],_y=V[y];
		
		int n=(op & 0xf);
		String _I=Integer.toHexString(I);
		String tmp=" x=V"+x+" y=V"+y;
		Debug("Display "+n+"-byte sprite starting at memory location 0x"+_I+" at ("+V[x]+", "+V[y]+"), "+tmp+"  set VF = collision.");
		PC+=2;
		/*
		System.out.print("data to be written: ");
		for(int k=0;k<x;k++)
		{
			System.out.print(MEMORY[I]+" ");
		}
		System.out.println();
		
		
		for(int _y=0;_y<n;_y++)
		{
			
			DrawPixelRow(V[x],V[y]+_y,MEMORY[I+_y],x,y);
			//System.out.println();
		}
		*/
		
		
		int pixel=0;
		for(int i=0;i<n;i++)
		{
			int data=MEMORY[I+i];
			
			for(int j=0;j<8;j++)
			{
				
				if((data & (0x80 >> j))!=0)
				{
					if(pixel==1)
							V[0xf]=1;
					
					pixel=video[_x+j][_y+i];
					
				
					
					video[_x+j][_y+i]^=1;
					
					
				} //End child for
				
				
			}//End parent for
			
		}//End draw method
		
		//video[100][100]=1;
		this.VideoChanged=true;
		
	}
 

Facon

I know nothing...
Question!!

I have one problem.

I have in memory:

0x00E3

The instruction is 00E0 - CLS.

I should interpret it as instruction or I ignore it?

PD: Sorry for my bad English.
 

Top