Skip to content

Instantly share code, notes, and snippets.

@TerrorBite
Created April 23, 2012 05:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TerrorBite/2468976 to your computer and use it in GitHub Desktop.
Save TerrorBite/2468976 to your computer and use it in GitHub Desktop.
DCPU-16 emulator written in C++
#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);
}
#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);
};
#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