Gå til innhold

Anbefalte innlegg

Hvordan emulatorer fungerer

Her er en enkel CHIP-8-motor jeg skrev for en stund siden etter inspirasjon fra diverse forums når jeg skrev enkle emulatorer en gang i tiden. Jeg mener å huske at kildekoden her ligner veldig på en annen emulator, men jeg lærte masse uansett, dog kan jeg ikke huske hvor mye jeg supplementerte denne koden (har skrevet mange av de, så jeg bare tok en jeg hadde allerede fremme). Det vil si at jeg ikke vil ta all kreditt for denne koden.

 

Dette er altså slik emulatorer fungerer i praksis, men emulatorer for Nintendo 64 og lignende systemer innehar selvfølgelig mye mer avansert kode. For dere som er interessert i emulatorprogrammering kan dette være et godt utgangspunkt. Merk at dette kun er motoren som parser opcodes, så det er ikke store greiene – men en bitteliten «introduksjon» til enkel emulatorprogrammering.

 

cpu.cpp

 

 

#include "cpu.h"
#include <cstring>
#include <cstdio>
#include <cstdlib>

void CPU::Reset() {
c8_pc = 0x200;
c8_i = 0;

memset(c8_registers,0,sizeof(c8_registers));
memset(c8_ram,0,sizeof(c8_ram));
memset(c8_keys,0,sizeof(c8_keys));

c8_dtimer = 0;
c8_stimer = 0;
}

void CPU::Cls() {
for (int x = 0; x != 640; x++) {
	for (int y = 0; y != 320; y++) {
		c8_screen[x][y][0] = 0;
		c8_screen[x][y][1] = 0;
		c8_screen[x][y][2] = 0;
	}
}
}

bool CPU::LoadROM(const std::string& ROM) {
Reset();
Cls();

FILE* path;
path = fopen(ROM.c_str(), "rb");

if (path == 0) {
	return false;
}

fread(&c8_ram[c8_ep],c8_total_ram, 1, path);
fclose(path);

return true;
}

void CPU::NullTimers() {
if (c8_dtimer > 0) {
	c8_dtimer--;
}

if (c8_stimer >= 2) {
	// SPILL AV LYD
	c8_stimer--;
} else {
	c8_stimer--;
}
}

int CPU::GetKeyPress() {
for (int i = 0; i < 16; i++) {
	if (c8_keys[i] > 0) {
		return i;
	}
}

return -1;
}

int CPU::KeyPress(int key) {
c8_keys[key] = 1;
}

int CPU::KeyRelease(int key) {
c8_keys[key] = 0;
}

WORD CPU::FetchOpcode() {
WORD Opcode = 0;
Opcode = c8_ram[c8_pc];
Opcode <<= 8;
Opcode |= c8_ram[c8_pc+1];
c8_pc += 2;

return Opcode;
}

void CPU::ExecuteOpcode() {
WORD Opcode = FetchOpcode();

switch (Opcode & 0xF000) {
	case 0x0000: _Decode0(Opcode); 	break;
	case 0x1000: _1NNN(Opcode); 	break;
	case 0x2000: _2NNN(Opcode); 	break;
	case 0x3000: _3XNN(Opcode); 	break;
	case 0x4000: _4XNN(Opcode); 	break;
	case 0x5000: _5XY0(Opcode); 	break;
	case 0x6000: _6XNN(Opcode); 	break;
	case 0x7000: _7XNN(Opcode); 	break;
	case 0x8000: _Decode8(Opcode); 	break;
	case 0x9000: _9XY0(Opcode); 	break;
	case 0xA000: _ANNN(Opcode); 	break;
	case 0xB000: _BNNN(Opcode); 	break;
	case 0xC000: _CXNN(Opcode); 	break;
	case 0xD000: _DXYN(Opcode); 	break;
	case 0xE000: _DecodeE(Opcode); 	break;
	case 0xF000: _DecodeF(Opcode); 	break;
	default: 						break;
}
}

void CPU::_Decode0(WORD Opcode) {
switch (Opcode & 0xF) {
	case 0x0: Cls(); 	break;
	case 0xE: _00EE(); 	break;
	default: 			break;
}
}

void CPU::_00EE(WORD Opcode) {
c8_pc = c8_stack.back();
c8_stack.pop_back();
}

void CPU::_1NNN(WORD Opcode) {
c8_pc = Opcode & 0x0FFF;
}

void CPU::_2NNN(WORD Opcode) {
c8_stack.push_back(c8_pc);
c8_pc = Opcode & 0x0FFF;
}

void CPU::_3XNN(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _nn = Opcode & 0x00FF;
_x >>= 8;

if (c8_registers[_x] == _nn) {
	c8_pc += 2;
}
}

void CPU::_4XNN(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _nn = Opcode & 0x00FF;
_x >>= 8;

if (c8_registers[_x] != _nn) {
	c8_pc += 2;
}
}

void CPU::_5XY0(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

if (c8_registers[_x] == c8_registers[_y]) {
	c8_pc += 2;
}
}

void CPU::_6XNN(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _nn = Opcode & 0x00FF;
_x >>= 8;

c8_registers[_x] = _nn;
}

void CPU::_7XNN(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _nn = Opcode & 0x00FF;
_x >>= 8;

c8_registers[_x] += _nn;
}

void CPU::_Decode8(WORD Opcode) {
switch (Opcode & 0xF) {
	case 0x0: _8XY0(Opcode); 	break;
	case 0x1: _8XY1(Opcode); 	break;
	case 0x2: _8XY2(Opcode); 	break;
	case 0x3: _8XY3(Opcode); 	break;
	case 0x4: _8XY4(Opcode);	break;
	case 0x5: _8XY5(Opcode);	break;
	case 0x6: _8XY6(Opcode);	break;
	case 0x7: _8XY7(Opcode);	break;
	case 0xE: _8XYE(Opcode); 	break;
	default: 					break;
}
}

void CPU::_8XY0(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

c8_registers[_x] = c8_registers[_y];
}

void CPU::_8XY1(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

c8_registers[_x] |= c8_registers[_y];
}

void CPU::_8XY2(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

c8_registers[_x] &= c8_registers[_y];
}

void CPU::_8XY3(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

c8_registers[_x] ^= c8_registers[_y];
}

void CPU::_8XY4(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

int temp = c8_registers[_x] + c8_registers[_y];
if (temp > 255) {
	c8_registers[0xF] = 1;
} else {
	c8_registers[0xF] = 0;
}

c8_registers[_x] += c8_registers[_y];
}

void CPU::_8XY5(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

if (c8_registers[_x] < c8_registers[_y]) {
	c8_registers[0xF] = 0;
} else {
	c8_registers[0xF] = 1;
}

c8_registers[_x] -= c8_registers[_y];
}

void CPU::_8XY6(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_registers[0xF] = c8_registers[_x] & 0x1;
c8_registers[_x] >>= 1;
}

void CPU::_8XY7(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

if (c8_registers[_x] > c8_registers[_y]) {
	c8_registers[0xF] = 0;
} else {
	c8_registers[0xF] = 1;
}

c8_registers[_x] = c8_registers[_y] - c8_registers[_x];
}

void CPU::_8XYE(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_registers[0xF] = c8_registers[_x] >> 7;
c8_registers[_x] <<= 1;
}

void CPU::_9XY0(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;

_x >>= 8;
_y >>= 4;

if (c8_registers[_x] != c8_registers[_y]) {
	c8_pc += 2;
}
}

void CPU::_ANNN(WORD Opcode) {
c8_i = Opcode & 0x0FFF;
}

void CPU::_BNNN(WORD Opcode) {
int _nnn = Opcode & 0x0FFF;

c8_pc = _nnn + c8_registers[0x0];
}

void CPU::_CXNN(WORD Opcode) {
int _x = Opcode & 0x0F00;
int _nn = Opcode & 0x00FF;

_x >>= 8;

c8_registers[_x] = rand() & _nn;
}

void CPU::_DXYN(WORD Opcode) {
const int scale = 10;
int _x = Opcode & 0x0F00;
int _y = Opcode & 0x00F0;
int _n = Opcode & 0x000F;

_x >>= 8;
_y >>= 4;

int _xx = c8_registers[_x] * scale;
int _yy = c8_registers[_y] * scale;

c8_registers[0xF] = 0;

for (int _yyy = 0; _yyy < _n; _yyy++) {
	BYTE data = (c8_ram[c8_i+_yyy]);

	int xpixel = 0;
	int lexipx = 7;

	for (xpixel = 0; xpixel < 8; xpixel++, lexipx--) {
		int mask = 1 << lexipx;
		if (data & mask) {
			int x = (xpixel*scale) + _xx;
			int y = _yy + (_yyy*scale);

			int color = 0;

			if (c8_screen[x][y][0] == 0) {
				color = 255;
				c8_registers[0xF] = 1;
			}

			for (int i = 0; i < scale; i++) {
				for (int j = 0; j < scale; j++) {
					c8_screen[y+i][x+j][0] = color;
					c8_screen[y+i][x+j][1] = color;
					c8_screen[y+i][x+j][2] = color;
				}
			}
		}
	}
}
}

void CPU::_DecodeE(WORD Opcode) {
switch (Opcode & 0xF) {
	case 0xE: _EX9E(Opcode); 	break;
	case 0x1: _EXA1(Opcode); 	break;
	default: 					break;
}
}

void CPU::_EX9E(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

int key = c8_registers[_x];

if (c8_keys[key] == 1) {
	c8_pc += 2;
}
}

void CPU::_EXA1(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

int key = c8_registers[_x];

if (c8_keys[key] == 0) {
	c8_pc += 2;
}
}

void CPU::_DecodeF(WORD Opcode) {
switch (Opcode & 0xFF) {
	case 0x07: _FX07(Opcode); 	break;
	case 0x0A: _FX0A(Opcode); 	break;
	case 0x15: _FX15(Opcode); 	break;
	case 0x18: _FX18(Opcode); 	break;
	case 0x1E: _FX1E(Opcode); 	break;
	case 0x29: _FX29(Opcode); 	break;
	case 0x33: _FX33(Opcode); 	break;
	case 0x55: _FX55(Opcode); 	break;
	case 0x65: _FX65(Opcode);	break;
	default: 					break;
}
}

void CPU::_FX07(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_registers[_x] = c8_dtimer;
}

void CPU::_FX0A(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

int keypress = GetKeyPress();

if (keypress = -1) {
	c8_pc -= 2;
} else {
	c8_registers[_x] = keypress;
}
}

void CPU::_FX15(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_dtimer = c8_registers[_x];
}

void CPU::_FX18(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_stimer = c8_registers[_x];
}

void CPU::_FX1E(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_i += c8_registers[_x];
}

void CPU::_FX29(WORD Opcode) {
const int font = 5;
int _x = Opcode & 0x0F00;
_x >>= 8;

c8_i = c8_registers[_x] * font;
}

void CPU::_FX33(WORD Opcode) {
int _x = Opcode & 0x0F00;
int dec = c8_registers[_x];
_x >>= 8;

int _100 = dec / 100;
int _10 = (dec / 10) % 10;
int _1 = dec % 10;

c8_ram[c8_i] = _100;
c8_ram[c8_i+1] = _10;
c8_ram[c8_i+2] = _1;
}

void CPU::_FX55(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

for (int i = 0; i <= _x; i++) {
	c8_ram[c8_i+i] = c8_registers[i];
}

c8_i += _x + 1;
}

void CPU::_FX65(WORD Opcode) {
int _x = Opcode & 0x0F00;
_x >>= 8;

for (int i = 0; i <= _x; i++) {
	c8_registers[i] = c8_ram[c8_i+i];
}

c8_i += _x + 1;
}

 

 

 

cpu.h

 

 

#ifndef CPU_H_INCLUDED
#define CPU_H_INCLUDED
#endif

#include <vector>
#include <string>

typedef unsigned char BYTE;
typedef unsigned short int WORD;

const int c8_total_ram 		= 0xFFF;
const int c8_ep			 	= 0x200;

class CPU {
public:
BYTE c8_screen[640][320][3];

bool LoadROM(const std::string& ROM);
void ExecuteOpcode();
void NullTimers();
int KeyPress(int key);
int KeyRelease(int key);


private:
BYTE c8_ram[c8_total_ram];
BYTE c8_registers[16];
WORD c8_i;
WORD c8_pc;
std::vector<WORD> c8_stack;
BYTE c8_dtimer;
BYTE c8_stimer;
BYTE c8_keys[16];

void Reset();
void Cls();
int GetKeyPress();
WORD FetchOpcode();
void ExecuteOpcode();
void _Decode0(WORD Opcode);
void _00EE(WORD Opcode);
void _1NNN(WORD Opcode);
void _2NNN(WORD Opcode);
void _3XNN(WORD Opcode);
void _4XNN(WORD Opcode);
void _5XY0(WORD Opcode);
void _6XNN(WORD Opcode);
void _7XNN(WORD Opcode);
void _Decode8(WORD Opcode);
void _8XY0(WORD Opcode);
void _8XY1(WORD Opcode);
void _8XY2(WORD Opcode);
void _8XY3(WORD Opcode);
void _8XY4(WORD Opcode);
void _8XY5(WORD Opcode);
void _8XY6(WORD Opcode);
void _8XY7(WORD Opcode);
void _8XYE(WORD Opcode);
void _9XY0(WORD Opcode);
void _ANNN(WORD Opcode);
void _BNNN(WORD Opcode);
void _CXNN(WORD Opcode);
void _DXYN(WORD Opcode);
void _DecodeE(WORD Opcode);
void _EX9E(WORD Opcode);
void _EXA1(WORD Opcode);
void _DecodeF(WORD Opcode);
void _FX07(WORD Opcode);
void _FX0A(WORD Opcode);
void _FX15(WORD Opcode);
void _FX18(WORD Opcode);
void _FX1E(WORD Opcode);
void _FX29(WORD Opcode);
void _FX33(WORD Opcode);
void _FX55(WORD Opcode);
void _FX65(WORD Opcode);
}

 

 

 

Jeg har alltid vært fascinert av emulatorprogrammering. Det er spennende og utfordrende.

Endret av Ishq
  • Liker 1
Lenke til kommentar
  • 2 år senere...
Videoannonse
Annonse

Templates og annet svineri.

 

For en generell join-funksjon (á de du finner i perl, python, javascript og sikkert mer): kombiner en liste med elementer til en string.

 

Benytter seg av C++11-features og vil naturlig kun fungere med en fornuftig og ganske ny kompilator. Testet med gcc 4.7.2

 

#include <vector>
#include <iostream>
#include <sstream>

template< typename D >
static inline std::string join( std::stringstream& ss, const D& ) {
return std::string( ss.str() );
}

template< typename D, typename T >
static inline std::string join( std::stringstream& ss, const D&, const T& curr ) {
ss << curr;
return std::string( ss.str() );
}

template< typename D, typename T, typename... Args >
static inline std::string join(
	std::stringstream& ss,
	const D& delim,
	const std::vector< T >& list,
	const Args&... params ) {

auto item = list.cbegin();

for( ; item != list.cend(); ++item )
	ss << *item << delim;

ss << *( ++item );

return join( ss, delim, params... );
}

template< typename D, typename T, typename... Args >
static inline std::string join( std::stringstream& ss, const D& delim, const T& curr, const Args&... params ) {
ss << curr << delim;
return join( ss, delim, params... );
}

template< typename D, typename... Args >
static inline std::string join( const D& delim, const Args&... params ) {
std::stringstream ss;
return join( ss, delim, params... );
}

template< typename... Args >
static inline std::string join( const Args&... params ) {
std::stringstream ss;
return join( ss, ' ', params... );
}

int main() {
std::cout << join( " :: ", "This is", "a test. Now let's", "numeric this", 423 ) << std::endl;
std::cout << join( "This is", "another test" ) << std::endl;
std::vector< int > L = { 10, 9, 8, 7, 6, 5, 4, 2, 3, 1 };
std::cout << join( "-", "This is the list:", L ) << std::endl;
}

 

Og output:

 

This is :: a test. Now let's :: numeric this :: 423
another test
This is the list:-10-9-8-7-6-5-4-2-3-1-0

 

Dette går fint bortgjemt i en headerfil der du henter det frem ved behov. :)

 

Du trenger bare å eksponere de to nederste som er det eksterne interfacet. Siden alt annet er inlinet og i headers blir det nokså effektivt også.

Endret av Lycantrophe
  • Liker 1
Lenke til kommentar
  • 1 år senere...

Denne tråden trenger mer kjærleik.

 

Si du trenger explicit operator bool() const, men ikke har tilgang på C++11. En liten mixin kan løse dette:

 

class bool_base {
    public:
        operator bool() const = delete;
        void unsupported_comparison_between_types() const {}; 

    protected:
        bool_base() = default;
        bool_base( const bool_base& ) = default;
        bool_base& operator=( const bool_base& ) = default;
};

template< typename T > 
class explicit_bool_conversion : private bool_base {
    public:
        operator bool() const {
            return explicit_boolean_test( static_cast< const T& >( *this ) ) ? 
                &bool_base::unsupported_comparison_between_types : false;
        }
};

template < typename T >
bool operator==( const explicit_bool_conversion< T >& lhs, bool b) {
    return b == static_cast< bool >( lhs );
}

template < typename T >
bool operator==( bool b, const explicit_bool_conversion< T >& rhs ) {
    return b == static_cast< bool >( rhs );
}

template < typename T, typename U >
bool operator==( const explicit_bool_conversion< T >& lhs,
        const explicit_bool_conversion< U >& rhs ) {

    lhs.unsupported_comparison_between_types();
    return false;
}

template < typename T, typename U >
bool operator!=( const explicit_bool_conversion< T >& lhs,
        const explicit_bool_conversion< U >& rhs ) {

    lhs.unsupported_comparison_between_types();
    return false;
}
Har du ikke delete gjør du operator bool() private og ikke implementerer den.

 

Bruk:

 

struct A : explicit_bool_conversion< A > {};

bool explicit_boolean_test( const A& a ) {
    // Logikk for å boolean conversion, altså om denne instansen skal tolkes som true eller false
    return true;
}
A a;
if( a ) { /* kompilerer */ }
if( !a ) { /* kompilerer */ }
if( a == true ) { /* kompilerer */ }
int x = 1;
if( a == 1 ) { /* kompilerer ikke */ }
Kalles også "safe bool idiom". Endret av Lycantrophe
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...