Created
April 23, 2012 05:30
-
-
Save TerrorBite/2468976 to your computer and use it in GitHub Desktop.
DCPU-16 emulator written in C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "DCPU.h" | |
using namespace std; | |
DCPU::DCPU(void) { | |
// Allocate memory space | |
mem = new uint16_t[0x10000]; | |
cout << "Allocated 128k memory" << endl; | |
} | |
DCPU::~DCPU() { | |
// Deallocate memory | |
delete[] mem; | |
cout << "Deallocated memory" << endl; | |
} | |
void DCPU::init_cpu(void) { | |
// Zero registers | |
reg = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; | |
// Zero program counter | |
code = 0x0; | |
// Set stack counter | |
stack = 0xffff; | |
// Reset cycle counter | |
cycles = 0; | |
} | |
dcword DCPU::get_value(dcword val) { | |
/* | |
* Values have 6 bits. As follows: | |
* | |
* 000xxx = Value of a register | |
* 001xxx = Value pointed to by register | |
* 010xxx = Value pointed to by (register + next word) | |
* 011xxx = Special value or operation | |
* 1xxxxx = Value is a 5-bit literal between 0x00 and 0x1F | |
*/ | |
if( val & 0x20 ) { | |
// Literal value | |
return (val & 0x1F); | |
} | |
if( val & 0x10 ) { | |
if( val & 0x08 ) { | |
// Special value (0x18 to 0x1F inclusive) | |
switch(val) { | |
case 0x18: // POP, pop and return current word on stack | |
return mem[stack++]; | |
case 0x19: // PEEK, read current word on stack | |
return mem[stack]; | |
case 0x1A: // PUSH, not very useful to read | |
return mem[--stack]; | |
case 0x1B: // Stack pointer value | |
return stack; | |
case 0x1C: // Program counter | |
return code; | |
case 0x1D: // Overflow | |
return overflow; | |
case 0x1E: // Value that next word points to | |
++cycles; // Code pointer cost | |
return mem[mem[code++]]; | |
case 0x1F: // Value of next word itself | |
++cycles; // Code pointer cost | |
return mem[code++]; | |
} | |
} | |
// else: *(next word + register) (0x10 to 0x17 inclusive) | |
++cycles; // Code pointer cost | |
return mem[ mem[code++] + reg[val & 0x7] ]; | |
} | |
// Value pointed to by register (0x08 to 0x0F inclusive), | |
// or register value (0x00 to 0x07 inclusive) | |
return (val & 0x08) ? mem[reg[val & 0x7]] : reg[val]; | |
} | |
void DCPU::set_value(dcword val, dcword data) { | |
uint16_t next = instr+1; | |
if( val & 0x20 ) { | |
// Attempted to set a literal: fail silently | |
return; | |
} | |
if( val & 0x10 ) { | |
if( val & 0x08 ) { | |
// Set a special value (0x18 to 0x1F inclusive) | |
switch(val) { | |
case 0x18: // POP, not very useful to set | |
mem[stack++] = data; // Does this make sense? | |
break; | |
case 0x19: // PEEK, set current stack value | |
mem[stack] = data; | |
break; | |
case 0x1A: // PUSH, push a new value to the stack | |
mem[--stack] = data; | |
break; | |
case 0x1B: // Stack pointer value, probably dangerous to set | |
stack = data; | |
break; | |
case 0x1C: // Set program counter, effectively a JMP | |
code = data; | |
break; | |
case 0x1D: // Set overflow register | |
overflow = data; | |
break; | |
case 0x1E: // Set value that next word points to | |
mem[mem[next]] = data; | |
break; | |
case 0x1F: // Set value of next word itself | |
mem[next] = data; | |
break; | |
default: break; | |
} | |
} | |
else { | |
// Set the value at the location that (next word + a register) points to | |
mem[ mem[next] + reg[val & 0x7] ] = data; | |
} | |
} | |
else { | |
// Set value at location pointed to by a register (0x08 to 0x0F inclusive), | |
if(val & 0x08) { | |
mem[reg[val & 0x7]] = data; | |
} | |
// else set a register (0x00 to 0x07 inclusive) | |
else reg[val] = data; | |
} | |
} | |
bool DCPU::cycle(void) { | |
// Performs a single CPU cycle. | |
// Get word to be executed, saving current location and incrementing code pointer | |
instr = code++; | |
uint16_t word = mem[instr]; | |
// STEP 2: Parse word into a basic opcode and two values. | |
uint8_t opcode = word & 0xF; | |
uint16_t dest = (word>>4) & 0x3F, argb = (word>>10); | |
// Vars used for temporary calculations | |
uint32_t temp = 0; | |
uint16_t a = 0; | |
if(opcode != 0) { | |
// Evaluate argument a | |
a = get_value(dest); | |
} | |
// Evaluate argument b | |
uint16_t b = get_value(argb); | |
switch(opcode) { | |
case 0x0: // Non-basic opcode | |
return extended_opcode(dest, b); | |
break; | |
case 0x1: // SET (Set value) | |
set_value(dest, b); | |
break; | |
case 0x2: // ADD (Addition) | |
temp = a + b; | |
overflow = temp >> 16; | |
set_value(dest, (temp & 0xFFFF)); | |
++cycles; // cost | |
break; | |
case 0x3: // SUB (Subtraction) | |
temp = a - b; | |
overflow = temp >> 16; | |
set_value(dest, (temp & 0xFFFF)); | |
++cycles; // cost | |
break; | |
case 0x4: // MUL (Multiplication) | |
temp = a * b; | |
overflow = temp >> 16; | |
set_value(dest, (temp & 0xFFFF)); | |
++cycles; // cost | |
break; | |
case 0x5: // DIV (Division) | |
if(b) { | |
overflow = (a << 16) / (float)b; | |
set_value(dest, (uint16_t)((float)a/(float)b)); | |
} | |
else { | |
set_value(dest, 0x0); overflow = 0x0; | |
} | |
cycles += 2; // cost | |
break; | |
case 0x6: // MOD (Modulus) | |
set_value(dest, b ? a%b : 0x0); | |
cycles += 2; // cost | |
break; | |
case 0x7: // SHL (Shift Left) | |
overflow = a << (16-b); | |
set_value(dest, a<<b); | |
++cycles; // cost | |
break; | |
case 0x8: // SHR (Shift Right) | |
overflow = a >> (16-b); | |
set_value(dest, a>>b); | |
++cycles; // cost | |
break; | |
case 0x9: // AND (Bitwise AND) | |
set_value(dest, a & b); | |
break; | |
case 0xA: // BOR (Bitwise OR) | |
set_value(dest, a | b); | |
break; | |
case 0xB: // XOR (Bitwise Exclusive OR) | |
set_value(dest, a ^ b); | |
break; | |
case 0xC: // IFE (Execute If Equal) - Executes next instruction only if a==b | |
++cycles; | |
if(a != b) skip_next(); | |
break; | |
case 0xD: // IFN (Execute If Not Equal) - Executes next instruction only if a!=b | |
++cycles; | |
if(a == b) skip_next(); | |
break; | |
case 0xE: // IFG (If Greater Than) - Executes next instruction only if a>b | |
++cycles; | |
if(a <= b) skip_next(); | |
break; | |
case 0xF: // IFB (If Binary) - Performs next instruction only if (a&b) != 0 | |
if(!(a & b)) skip_next(); | |
break; | |
default: break; | |
} | |
// Begin debugging code (outputs the ASM disassembly as it executes) | |
cout << hex << setfill('0') << setw(4) << instr << ": " << OPCODES[opcode] << " "; | |
if(dest == 0x1E) cout << "[0x" << mem[instr+1] << "]"; | |
else if(!((dest & 0x18) ^ 0x10)) cout << "[0x" << setw(2) << mem[instr+1] << "+" << DESTS[opcode&0x7] << "]"; | |
else cout << DESTS[dest]; | |
cout << ", 0x" << setw(2) << b << endl; | |
// End debugging code | |
// Increment cycle counter | |
++cycles; | |
// "Crash detection": Halt on infinite loop. | |
if(code == instr) { | |
cout << "CPU halt detected" << endl; | |
return false; | |
} | |
return true; | |
} | |
bool DCPU::extended_opcode(uint16_t opcode, uint16_t a) { | |
switch(opcode) { | |
case 0x00: // Reserved for future expansion | |
cout << "CRASH: Reserved extended opcode 00" << endl; | |
return false; // halt CPU | |
case 0x01: // JSR (Jump to Subroutine) | |
cout << "> JSR " << a << endl; | |
mem[--stack] = code; // Push next instruction address onto stack | |
code = a; // Set next instruction to given address | |
++cycles; | |
break; | |
default: // 0x02 - 0x3f are reserved | |
cout << "CRASH: Unknown extended opcode" << endl; | |
return false; | |
} | |
} | |
void DCPU::skip_next(void) { | |
++cycles; // count this as a cycle | |
// Retrieve next instruction | |
uint16_t word = mem[code++]; | |
uint8_t opcode = word & 0xF; | |
cout << "Skipped " << OPCODES[opcode] << " at " << (code - 1) << endl; | |
// Move the code pointer past literal data, if any, to find next opcode | |
word = word >> 4; | |
if(opcode && | |
( | |
!((word & 0x3e) ^ 0x1e) // 0x1e and 0x1f | |
|| | |
!((word & 0x18) ^ 0x10) // 0x10 to 0x17 | |
) | |
) | |
{ | |
cout << "Skipped arg for A" << endl; | |
++code; | |
} | |
word = word >> 6; | |
if( | |
!((word & 0x3e) ^ 0x1e) // 0x1e and 0x1f | |
|| | |
!((word & 0x18) ^ 0x10) // 0x10 to 0x17 | |
) | |
{ | |
cout << "Skipped arg for B" << endl; | |
++code; | |
} | |
// Check if "a" value has a "next word", unless opcode is 0x0 | |
//if(opcode && !( ((word & 0x170) ^ 0x100) | ((word & 0x1e0) ^ 0x1e0) ) ) ++code; | |
// Check if "b" value has a "next word" | |
//if( !( ((word & 0x5c00) ^ 0x4000) | ((word & 0x7800) ^ 0x7800) ) ) ++code; | |
} | |
void DCPU::run(void) { | |
// Init registers | |
init_cpu(); | |
// Start executing cycles | |
do ; while(cycle() && cycles < 1024); | |
cout << "Ran for 0x" << setw(4) << hex << cycles << " cycles" << endl; | |
cout << "A = " << reg[0] << ", B = " << reg[1] << ", C = " << reg[2] << endl; | |
cout << "X = " << reg[3] << ", Y = " << reg[4] << ", Z = " << reg[5] << endl; | |
cout << "I = " << reg[6] << ", J = " << reg[7] << ", PC = " << code << endl; | |
cout << "Stack dump:" << endl; | |
for(int i = stack; i < 0x10000; i++) { | |
cout << i << ": " << mem[i] << endl; | |
} | |
cout << endl; | |
} | |
void DCPU::load(uint16_t* data, uint16_t size) { | |
memcpy((void*)mem, (void*)data, size); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ctime> | |
#include <cstring> | |
#include <iostream> | |
#include <iomanip> | |
#include <cstdint> | |
using namespace std; | |
#define A 0 | |
#define B 1 | |
#define C 2 | |
#define X 3 | |
#define Y 4 | |
#define Z 5 | |
#define I 6 | |
#define J 7 | |
// debug use | |
static const char* OPCODES[] = {"EXT", "SET", "ADD", "SUB", "MUL", "DIV", "MOD", "SHL", "SHR", "AND", "BOR", "XOR", "IFE", "IFN", "IFG", "IFB"}; | |
static const char* DESTS[] = { | |
"A", "B", "C", "X", "Y", "Z", "I", "J", | |
"[A]", "[B]", "[C]", "[X]", "[Y]", "[Z]", "[I]", "[J]", | |
"[+A]", "[+B]", "[+C]", "[+X]", "[+Y]", "[+Z]", "[+I]", "[+J]", | |
"POP", "PEEK", "PUSH", "SP", "PC", "O", "[+]", "+", | |
"00", "01", "02", "03", "04", "05", "06", "07", | |
"08", "09", "0a", "0b", "0c", "0d", "0e", "0f", | |
"10", "11", "12", "13", "14", "15", "16", "17", | |
"18", "19", "1a", "1b", "1c", "1d", "1e", "1f" | |
}; | |
// DCPU word | |
typedef uint16_t dcword; | |
class DCPU { | |
private: | |
// Cycle counter, used to control CPU speed | |
unsigned long cycles; | |
// 8 registers plus overflow | |
uint16_t reg[8]; | |
uint16_t overflow; | |
// Code pointer, stack pointer (not actually pointers per se) | |
uint16_t code, stack; | |
// Saved instruction pointer | |
uint16_t instr; | |
// Pointer to dynamically allocated system memory | |
uint16_t* mem; | |
// Functions to get and set values (registers, memory, literals etc) | |
uint16_t get_value(uint16_t); | |
void set_value(uint16_t, uint16_t); | |
// Runs a CPU cycle. Returns false if execution should cease, otherwise returns true. | |
bool cycle(void); | |
// Executes a non-basic opcode. | |
bool extended_opcode(uint16_t opcode, uint16_t a); | |
// Performs minimal processing neccessary to completely skip the next instruction. | |
void skip_next(void); | |
// initialise registers etc | |
void init_cpu(void); | |
public: | |
DCPU(void); | |
~DCPU(); | |
// Runs the virtual CPU. | |
void run(void); | |
// Loads a program into the virtual CPU's memory. | |
void load(uint16_t* data, uint16_t length); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "DCPU.h" | |
#include <iostream> | |
#include <fstream> | |
int main(int argc, char* argv[]) { | |
if(argc < 2) { | |
cout << "No filename given, exiting" << endl; | |
return 1; | |
} | |
ifstream file; | |
file.open(argv[1], ios::in|ios::binary|ios::ate); | |
ifstream::pos_type size; | |
char * memblock; | |
if (file.is_open()) | |
{ | |
size = file.tellg(); | |
memblock = new char [size]; | |
file.seekg (0, ios::beg); | |
file.read (memblock, size); | |
cout << "Loaded " << size << " bytes from " << argv[1] << endl; | |
file.close(); | |
DCPU myCpu = DCPU(); | |
uint16_t data[32] = { | |
/* | |
0x017c, 0x3000, 0xe17d, 0x0010, 0x2000, 0x0378, 0x0010, 0x0dc0, | |
0xc17d, 0x1a00, 0x61a8, 0x017c, 0x0020, 0x6121, 0x0020, 0x6384, | |
0x6d80, 0xc17d, 0x0d00, 0x3190, 0x107c, 0x1800, 0xc17d, 0x1a00, | |
0x3790, 0xc161, 0xc17d, 0x1a00, 0x0000, 0x0000, 0x0000, 0x0000 | |
*/ | |
0x7c01, 0x0030, 0x7de1, 0x1000, 0x0020, 0x7803, 0x1000, 0xc00d, | |
0x7dc1, 0x001a, 0xa861, 0x7c01, 0x2000, 0x2161, 0x2000, 0x8463, | |
0x806d, 0x7dc1, 0x000d, 0x9031, 0x7c10, 0x0018, 0x7dc1, 0x001a, | |
0x9037, 0x61c1, 0x7dc1, 0x001a, 0x0000, 0x0000, 0x0000, 0x0000 | |
}; | |
myCpu.load((uint16_t*)memblock, size); | |
myCpu.run(); | |
delete[] memblock; | |
} | |
else { | |
cout << "Failed to read that file"; | |
return 1; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment