/* Creating a DCPU-16 (version 1.7) emulator! ҂!! */ /* For all information, see http://dcpu.com/ ҂ ՅԄƖֆׇ */ #include /* Using libSDL for graphics & keyboard handling */ #include #include #include #include #include #include #include #include typedef uint8_t u8; typedef int8_t s8; typedef uint16_t u16; typedef int16_t s16; typedef uint32_t u32; typedef int32_t s32; // Graphics settings and memory addresses. static constexpr int WIDTH=128, HEIGHT=96, SCALE=4, GFX_BASE = 0x8000, // end at 0x8540 INPUT_BASE = 0x9000, // end at 0x9010 DISPLAY_BASE = 0x9040, // end at 0x9041 SPRITE_BASE = 0x9050, // end at 0x9070 N_SPRITES = 16; static const bool DisassemblyTrace = false; static const bool DisassemblyListing = false; enum reg_index { A,B,C, X,Y,Z, I,J, PC,SP,EX, IA, noreg=0xF, mem=0x80, imm=0x40 }; static const char regnames[][5] = {"A","B","C","X","Y","Z","I","J","PC","SP","EX","IA"}; static const u8 reg_specs[0x20] = {A ,B ,C ,X ,Y ,Z ,I ,J, // regs A |mem,B |mem,C |mem,X |mem,Y |mem,Z |mem,I |mem,J |mem, A|imm|mem,B|imm|mem,C|imm|mem,X|imm|mem,Y|imm|mem,Z|imm|mem,I|imm|mem,J|imm|mem, SP, SP|mem, SP|imm|mem, SP, PC, EX, noreg|imm|mem, noreg|imm}; static const char ins_set[4*16*3+1] = "000JSR...............HCFINTIAGIASRFIIAQ........." "HWNHWQHWI......................................." "nbiSETADDSUBMULMLIDIVDVIMODMDIANDBORXORSHRASRSHL" "IFBIFCIFEIFNIFGIFAIFLIFU......ADXSBX......STISTD"; /* Disassemble() produces textual disassembly of single DCPU instruction at memory[pc] */ static std::unordered_multimap SymbolLookup; // ^ This global array maps addresses into labels. Used by disassembler later. static std::string Disassemble(unsigned pc, const u16* memory) { unsigned v = memory[pc++], o = (v & 0x1F), bb = (v>>5) & 0x1F, aa = (v>>10) & 0x3F; std::string opcode(&ins_set[3*(o ? 32+o : bb)], 3); auto doparam = [&](char which, unsigned v) -> std::string { std::string result(" "); char Buf[32], sep[4] = "\0+ "; if(v == 0x18) return result + (which=='b' ? "PUSH" : "POP"); if(v >= 0x20) { std::sprintf(Buf, " 0x%X", u16(int(v)-0x21)); return Buf; } if(reg_specs[v] & mem) result += '['; if((reg_specs[v] & 0xFu)!=noreg) { result += regnames[(reg_specs[v] & 0xFu)]; sep[0] = ' '; } if(reg_specs[v] & imm) { sprintf(Buf, "%s0x%X", sep, memory[pc++]); result += Buf; } if(reg_specs[v] & imm) for(auto i = SymbolLookup.equal_range(memory[pc-1]); i.first != i.second; ++i.first) result += "=" + i.first->second; if(reg_specs[v] & mem) result += ']'; return result; }; for(auto i = SymbolLookup.equal_range(pc-1); i.first != i.second; ++i.first) opcode = i.first->second + ": " + opcode; std::string aparam = doparam('a', aa); if(o) { opcode += doparam('b', bb); opcode += ','; } return opcode += aparam; } /* Assembler() reads an entire assembler file and translates it into a DCPU memory file */ class Assembler { std::vector memory; // Expression is a list of multiplicative terms. Each is a constant + list of summed symbols. typedef std::vector>>> expression; // List of forward declarations, which can be patched later. std::vector> forward_declarations; // pclist is used for side-by-side disassembly & source code listing. std::vector> pclist; // Remember known labels (name -> address). std::unordered_map symbols; std::unordered_map defines; // name->content // Macro: Macro name -> { macro content, list of parameter names } std::unordered_map>> macros; // Macro call: Macro name, list of parameter values std::pair> macro_call; /* Define all reserved words */ std::unordered_map bops, operands; struct operand_type { bool set = false, brackets = false; u16 addr = 0, shift = 0; expression terms; std::string string; void clear() { set=brackets=false; terms={{}}; } // Reset everything except addr&shift } op, op0; bool comment = false, label = false, sign = false, dat = false, string = false; std::string id, in_meta, recording_define, define_contents, recording_macro; unsigned fill_count = 1; u16 pc=0; std::pair simplify_expression(expression& expr, bool require_known = false) { // For each identifier in the term that is not a register, add it to the sum int register_number = -1; long const_total = 1; for(std::size_t b=0; bsecond; } else { // Not a CPU register. Is it a previously defined label? auto si = symbols.find(v.second); if(si == symbols.end()) { // No, it's not known yet. if(require_known) std::fprintf(stderr, "Error: Unresolved forward declaration of '%s'\n", v.second.c_str()); // Keep it for later. ++a; continue; } // Yes. Treat it as an integer offset. sum.first += (sign ? (-si->second) : (si->second)); } sum.second.erase( sum.second.begin() + a); } if(!sum.second.empty()) ++b; else { const_total *= sum.first; expr.erase( expr.begin() + b); } } // Add back the constant sum if it still will be needed. if(!expr.empty() && const_total != 1) expr.push_back( {const_total, {{}}} ); if(!expr.empty()) const_total = 0; return {const_total,register_number}; } void encode_operand(operand_type& op) { if(op.set && !op.string.empty()) { if(in_meta == "INCLUDE") { in_meta.clear(); op.set = false; std::string s; s.swap(op.string); FILE* fp = std::fopen(s.c_str(), "rt"); if(!fp) std::perror(s.c_str()); else { char Buf[131072]; int len = std::fread(Buf, 1, sizeof(Buf), fp); std::fclose(fp); parse_code( {Buf,Buf+len} ); } } std::string s; s.swap(op.string); for(char c: s) { op.terms = {{}}; op.terms[0].first = (u8)c; op.set = true; encode_operand(op); } } else if(op.set) { // The assembler code contained a parameter at this point. // A parameter may be an identifier (from flush_id()), // or an integer constant. // Identifiers may also be names of CPU registers. // Calculate the identifiers. auto r = simplify_expression(op.terms); u16 value = r.first; int register_index = r.second; bool resolved = op.terms.empty(); if(!resolved) forward_declarations.emplace_back( pc, std::move(op.terms) ); // Determine the type of operand to synthesize bool has_register = register_index >= 0; bool has_brackets = op.brackets; bool has_offset = !resolved || value || !has_register; bool offset_is_small = resolved && u16(value+1) <= 0x1F && op.shift == 10; // Sanity checking if((has_brackets | has_register) && (dat || !in_meta.empty())) std::fprintf(stderr, "Error: Cannot use brackets or registers in DAT, .ORG or .FILL\n"); if(has_register && has_offset && !has_brackets && register_index != 0x1A) std::fprintf(stderr, "Error: Register + index without brackets is invalid.\n"); if(has_register && has_offset && (register_index >= 8 && register_index != 0x1B && register_index != 0x1A)) std::fprintf(stderr, "Error: Register + index are only valid with base registers and SP.\n"); if(has_register && has_brackets && (register_index >= 8 && register_index != 0x1B)) std::fprintf(stderr, "Error: Register references are only valid with base registers and SP.\n"); if(in_meta == "ORG") { pc = value; in_meta.clear(); return; } if(in_meta == "FILL") { fill_count = value; in_meta.clear(); return; } if(!has_register && !has_brackets && has_offset && offset_is_small && !dat) { // It's a small integer that fits into the instruction verbatim. memory[op.addr] |= ((value + 0x21) & 0x3F) << op.shift; } else { // It contains a register, an integer, or both. unsigned code = has_brackets ? 0x1E : 0x1F; if(register_index == 0x1A && !has_offset) register_index = 0x19; // Encode PICK 0 as [SP] if(has_register) code = register_index; if(has_register && has_brackets) code |= (has_offset ? 0x10 : 0x08); if(register_index == 0x1B && has_brackets) code = has_offset ? 0x1A : 0x19; // PICK/PEEK if(!dat) memory[op.addr] |= code << op.shift; // Omit opcode for DAT if(has_offset) memory[pc++] = value; } } } void flush_operands() { for(; fill_count > 1; --fill_count) { auto b = op; encode_operand(b); } encode_operand(op); op.clear(); encode_operand(op0); op0.clear(); sign = false; } std::string flush_id() { if(!id.empty()) { // The assembler code contained an identifier at this point. // Determine what to do with it. // Was it a define-substitution? auto di = defines.find(id); if(di != defines.end()) { // Yes. Parse that code. id.clear(); return di->second; } // Does it begin a macro-substitution? auto mi = macros.find(id); if(mi != macros.end()) { macro_call.first = id; id.clear(); return {}; } if(id == ".ENDMACRO") { id.clear(); return {}; } if(id == ".DAT") id = "DAT"; // cbm-basic uses .DAT for some reason // Was it a metacommand? if(id[0] == '.' || id[0] == '#') { in_meta = id.substr(1); id.clear(); if(in_meta == "DEFINE" || in_meta == "ORG" || in_meta == "FILL" || in_meta == "MACRO" || in_meta == "INCLUDE") {} else std::fprintf(stderr, "Error: Unknown metacommand: %s\n", in_meta.c_str()); return {}; } // Did we just get an identifier for a ".define" command? if(in_meta == "DEFINE") { in_meta.clear(); // Ok. Let's record a define! recording_define.swap(id); return {}; } // Did we just get a macro name or a macro parameter name? if(in_meta == "MACRO") { if(recording_macro.empty()) recording_macro.swap(id); // Got a macro name else macros[recording_macro].second.push_back(id); // Got a parameter name id.clear(); // Ok, let's continue collecting macro data! return {}; } // Was it a label? Add it as a known symbol. if(label) { if(!symbols.insert( {id,pc} ).second) std::fprintf(stderr, "Error: Duplicate definition of '%s'\n", id.c_str()); SymbolLookup.insert( {pc,id} ); flush_operands(); label = false; } else { // Was it an assembler mnemonic (i.e. name of an instruction)? auto ib = bops.find(id); if(ib != bops.end()) { // Yes. Place the opcode into the memory. flush_operands(); if(DisassemblyListing) pclist.emplace_back( pc,"" ); auto b = ib->second; // This is an index of a string in ins_set[] dat = b == 0; op.addr = pc; op.shift = b < 32 ? 10 : 5; if(!dat) memory[pc++] = b < 32 ? b*32 : b%32; } else { // No. Remember the operand. flush_operand() will deal with it then. op.terms.back().second.emplace_back( sign,id ); op.set = true; sign = false; } } id.clear(); } return {}; } std::string line; void parse_code(const std::string& code) { std::size_t p=0, a, b=code.size(); for(a=0; a= 'A' && c <= 'Z') || c == '_' || (id.empty() && (c == '.' || c == '#')) || (!id.empty() && c >= '0' && c <= '9')) if(macro_call.first.empty()) { id += c; ++a; continue; } // Anything else ends an identifier. parse_code(flush_id()); // How about calling a macro? if(!macro_call.first.empty()) { switch(c) { case '(': case ',': // Add a parameter macro_call.second.emplace_back(); break; default: // Record macro parameter if(!macro_call.second.empty()) macro_call.second.back() += code[a]; break; case ')': // Invoke the macro auto backup = defines; auto m = macros.find(macro_call.first); for(std::size_t n=0; nsecond.second.size(); ++n) defines.insert( {m->second.second[n], macro_call.second[n]} ); macro_call = {}; parse_code(m->second.first); defines.swap(backup); } ++a; continue; } // A colon marks the current identifier as a label if(c == ':') { label=true; ++a; continue; } // Spaces will be ignored, but they do end an identifier if(std::isspace(c)) { if(!in_meta.empty()) flush_operands(); ++a; continue; } // Some self-evident parsing of special characters if(c == ',') { if(dat) flush_operands(); op0 = op; op.clear(); op.shift = 10; ++a; continue; } if(c == '-') { sign = !sign; ++a; continue; } if(c == '*') { op.terms.emplace_back(); sign = false; ++a; continue; } if(c == '+' || c == ']') { ++a; continue; } if(c == '[') { op.brackets=true; ++a; continue; } // Parentheses are ignored in general. if(c == '(' || c == ')') { ++a; continue; } // Anything else: If it isn't a number, it's an error size_t endptr; try { long l = std::stoi(code.substr(a,30), &endptr, 0); a += endptr; // Was a number. Remember it as an operand. if(sign) l = -l; op.terms.back().first += l; op.set = true; sign = false; } catch(...) { // Deal with invalid characters in input. std::fprintf(stderr, "Error: Invalid character: '%c'\n", c); ++a; continue; } } } public: explicit Assembler(const std::string& file_contents) : memory(0x10000) { // Build the list of reserved words. // Instructions: bops.insert( {"DAT",0} ); for(unsigned a=0; a() && { return std::move(memory); } }; struct Emulator { u16 memory[0x10000] = {}, reg[12] = {}; SDL_Surface* s = nullptr; Emulator() { // Initialize the SDL 1.2 library for graphics output. SDL_Init(SDL_INIT_VIDEO); SDL_InitSubSystem(SDL_INIT_VIDEO); std::signal(SIGINT, SIG_DFL); s = SDL_SetVideoMode(WIDTH*SCALE,HEIGHT*SCALE, 32,0); // For now, I am not using SDL 2.0, because it requires // considerably more lines of code to accomplish the // same things that I'm doing here. } void RenderGraphics(u8 pixbuf[WIDTH*HEIGHT]) { // Render the screen background. // It consists of 3x3 pixel cells, where each 3x3 cell can have // total of two colors from the global selection of 16 colors. for(unsigned y=0; y> ((x%3) + (y%3)*3)); if(x > 126) pixel = false; pixbuf[y*WIDTH+x] = (bg_cell >> (pixel ? 12 : 8)) & 0xF; } // Render the sprites atop that background const u16* ctrl = &memory[SPRITE_BASE], *ctrlend = ctrl + 2*N_SPRITES; for(; ctrl < ctrlend; ctrl += 2) { // Read the sprite header unsigned mode = (ctrl[1] >> 14) & 0x3; unsigned color = (ctrl[1] >> 10) & 0xF; unsigned bitsperpixel = ((mode&2)?3:4)-mode; // 4,2,2,1 // Calculate the sprite location and size int sx = -64 + (ctrl[0] >> 8 ); unsigned w = (mode&2) ? 16 : 8; int sy = -64 + (ctrl[0] & 0xFF); unsigned h = (mode&1) ? 16 : 8; // Render the sprite graphics. unsigned addr = GFX_BASE + 2*(ctrl[1] & 0x3FF); for(unsigned pixno=0, y=0; y> (16-bitsperpixel); if(pix != (mode ? color : 0)) if(sx >= 0 && sy >= 0 && sx < WIDTH && sy < HEIGHT) pixbuf[sy*WIDTH+sx] = (mode==3 ? color : pix); } } } void RenderText(u8 pixbuf[WIDTH*HEIGHT]) { static const u32 font4x8[256] = {0x00000000,0x06f99f96,0x069ff9f6,0x044eeea0,0x044eee44,0x0e44aa44,0x0e4eee44,0x004ee400,0xffb11bff,0x004aa400,0xffb55bff, 0xffb55bff,0x04e4aa40,0x0cc44757,0x0ff55557,0x02b6f6d4,0x08cefec8,0x0137f731,0x06f666f6,0x0a0aaaaa,0x03337bb7,0xe1699687, 0x0ff00000,0xf6f666f6,0x066666f6,0x06f66666,0x002ff200,0x004ff400,0x00f80000,0x0006f600,0x0fff6660,0x0666fff0,0x00000000, 0x04044444,0x00000aaa,0x00aeaea0,0x04e248e4,0x0a84442a,0x3ea54aa4,0x00000426,0x02488842,0x08422248,0x00a4e4a0,0x0044e440, 0x42600000,0x0000e000,0x04400000,0x08844422,0x04aaeea4,0x0e4444c4,0x0e8442a4,0x04a242a4,0x0222eaa2,0x04a22c8e,0x04aac8a4, 0x0884422e,0x04aa4aa4,0x04a26aa4,0x04400440,0x84400440,0x01248421,0x000e0e00,0x08421248,0x040442a4,0x068eeaa4,0x0aaaeaa4, 0x0caacaac,0x04a888a4,0x0caaaaac,0x0e88c88e,0x0888c88e,0x04aae8a4,0x0aaaeaaa,0x0e44444e,0x04a22222,0x0aaaccaa,0x0e888888, 0x0aaaeeea,0x0aaeeea2,0x0eaaaaae,0x0888caac,0x24aaaaa4,0x0aaacaac,0x04a248a4,0x0444444e,0x0eaaaaaa,0x044aaaaa,0x0aeeaaaa, 0x0aae4eaa,0x04444aaa,0x0e84442e,0x06444446,0x02244880,0x06222226,0x0000ae40,0xf0000000,0x00000246,0x06a62c00,0x0caaac88, 0x04a8a400,0x06aaa622,0x068ea400,0x0444e442,0xc26aa600,0x0aaaac88,0x04444c04,0x4a222202,0x0aacaa88,0x0e44444c,0x0aaeea00, 0x0aaaac00,0x04aaa400,0x88caac00,0x326aa600,0x0888ac00,0x0c248600,0x02444e40,0x06aaaa00,0x044aaa00,0x0aeeaa00,0x0aa4aa00, 0xc26aaa00,0x0e842e00,0x02448442,0x04440444,0x08442448,0x000000a5,0x0eaa4400,0x24a888a4,0x06aaaa0a,0x068ea442,0x06a62ca4, 0x06a62c0a,0x06a62c48,0x06a62cee,0xc4688600,0x068ea4a4,0x068ea40a,0x068ea448,0x0444c00a,0x0444c0a4,0x0444c048,0x0aaeaa4a, 0x0aaeaa4e,0x0e8c8e42,0x02cc6680,0x0baafaa7,0x04aaa4a4,0x04aaa40a,0x04aaa448,0x06aaa0a4,0x06aaa048,0xc26aaa0a,0x04aaaa4a, 0x04aaaa0a,0x44a88a44,0x0e88c8a4,0x04e4e4aa,0x09afacac,0x0844e452,0x06a62c42,0x0444c042,0x04aa4042,0x06aaa042,0x0aaac0a5, 0x0aeea20e,0x00e06aa6,0x00e04aa4,0x04a84404,0x00470000,0x002e0000,0x0e42ce44,0x02ea2e44,0x04444404,0x0055a550,0x00aa5aa0, 0x28282828,0x5a5a5a5a,0x7d7d7d7d,0x44444444,0x4444c444,0x444c4c44,0xaaaaaaaa,0xaaaae000,0x444c4c00,0xaaaa2aaa,0xaaaaaaaa, 0xaaaa2e00,0x000e2aaa,0x0000eaaa,0x000c4c44,0x4444c000,0x00007444,0x0000f444,0x4444f000,0x44447444,0x0000f000,0x4444f444, 0x44474744,0xaaaabaaa,0x000f8baa,0xaaab8f00,0x000f0baa,0xaaab0f00,0xaaab8baa,0x000f0f00,0xaaab0baa,0x000f0f44,0x0000faaa, 0x444f0f00,0xaaaaf000,0x0000faaa,0x00074744,0x44474700,0xaaaaf000,0xaaaafaaa,0x444f4f44,0x0000c444,0x44447000,0xffffffff, 0xffff0000,0xcccccccc,0x33333333,0x0000ffff,0x05aa5000,0x88caaca4,0x08888ae0,0x0aaaaaf0,0x0f94249f,0x04aaa700,0x84655550, 0x02222a50,0x0e4aaa4e,0x0699f996,0x0f699960,0x06962430,0x069f9600,0x08caea62,0x0068e860,0x0aaaaa40,0x00e0e0e0,0x0e044e44, 0x0e084248,0x0e024842,0x44444520,0x08444444,0x0040e040,0x00a50a50,0x00004aa4,0x00004000,0x00006000,0x046a2223,0x0000aaac, 0x0000e42c,0x00eeee00,0x00000000}; for(unsigned idx=0, y=0; y<12; ++y) for(unsigned x=0; x<32; ++x) { u16 v = memory[GFX_BASE + idx++]; if(v == 13) break; unsigned fg = v>>12, bg = (v>>8)&0xF, c = v&0xFF; if(!fg && !bg) c = 0x0F; c = font4x8[c]; for(unsigned yp=0; yp<8; ++yp) for(unsigned xp=0; xp<4; ++xp, c>>=1) pixbuf[(y*8+yp)*WIDTH + x*4+3-xp] = (c&1) ? fg : bg; } } void tick(unsigned n=1) { // For every 2000 CPU instructions, the screen will be refreshed. static unsigned count=0; count += n; if(count < 2000) return; count -= 2000; u8 pixbuf[WIDTH*HEIGHT]; if(memory[DISPLAY_BASE] & 1) RenderGraphics(pixbuf); else RenderText(pixbuf); // This is our global palette. static const u32 palette[16] = { 0x000000, 0x1B2632, 0x493C2B, 0x2F484E, 0x005784, 0xBE2633, 0x44891A, 0xA46422, 0x31A2F2, 0xE06F8B, 0xEB8931, 0x9D9D9D, 0xA3CE27, 0xB2DCEF, 0xFFE26B, 0xFFFFFF, }; // Place the pixels in the screen surface, also scaling it in the process. constexpr int span=1536, wid = span*12/2048, ywid=wid, iqwid=wid*18/10, buf=wid*2; static float sines[17][span + buf*3], iqkernel[iqwid], Y[16]; static bool cache_valid = false; if(!cache_valid) { for(int x=0; x> 16) + 0.587*((a >> 8)&0xFF) + 0.114*(a & 0xFF))/255.; float i = (0.596*(a >> 16) +-0.274*((a >> 8)&0xFF) +-0.322*(a & 0xFF)); float q = (0.211*(a >> 16) +-0.523*((a >> 8)&0xFF) + 0.312*(a & 0xFF)); // Save the luma in Y[p]; convert the chroma from polar into angular format float d = std::hypot(q, i) / 255., A = std::atan2(q, i); for(int x=0; x255)k=255; } int g; {int&k=g; k = ((I*-0.27f + Q*-0.65f) + Y/ywid)*255; if(k<0) k=0; if(k>255)k=255; } int b; {int&k=b; k = ((I*-1.10f + Q* 1.70f) + Y/ywid)*255; if(k<0) k=0; if(k>255)k=255; } ((u32*)(s->pixels))[ y*WIDTH*SCALE + x] = (r<<16) + (g<<8) + b; } } // Refresh the screen. SDL_Flip(s); // Also check for keyboard input events. SDL_Event event = { }; SDL_PollEvent( &event ); Uint8 *keystate = SDL_GetKeyState(NULL); #define keyonce(n) \ [&keystate]() -> bool { \ static bool old=false; \ bool res = keystate[n] && !old; \ old = keystate[n]; \ return res; }() // Put new keys into the device's keyboard buffer. static unsigned keyptr = 0; if(keyonce(SDLK_UP)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=3; keyptr=(keyptr+1)&0xF; } if(keyonce(SDLK_DOWN)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=4; keyptr=(keyptr+1)&0xF; } if(keyonce(SDLK_LEFT)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=1; keyptr=(keyptr+1)&0xF; } if(keyonce(SDLK_RIGHT)) { if(!memory[INPUT_BASE+keyptr]) memory[INPUT_BASE+keyptr]=2; keyptr=(keyptr+1)&0xF; } } // Return a parameter to an instruction. Parameters are read-write, // i.e. the instruction may either read or write the register/memory location. template u16& value(u16 v) { // When skipping=true, the return value is insignificant; it only matters // whether PC is incremented the right amount, and that there are no side effects. static u16 tmp; if(v == 0x18 && !skipping) return memory[tag == 'a' ? reg[SP]++ : --reg[SP]]; if(v >= 0x20) return tmp = v-0x21; // 20..3F, read-only immediate const auto specs = reg_specs[v]; u16* val = nullptr; tmp = 0; if((specs & 0xFu) != noreg) tmp = *(val = ®[specs & 0xFu]); if(specs & imm) { tick(); val = &(tmp += memory[reg[PC]++]); } if(specs & mem) return memory[tmp]; return *val; // Literals are actually read-only. Because the instruction _may_ // write into the target operand even if it is a literal, the // literals will be first stored into a temporary variable (tmp), // which can then be harmlessly overwritten. } #define PUSH value<'b'>(0x18) #define POP value<'a'>(0x18) template void op() { tick(); if(DisassemblyTrace && !skipping) { int l = std::fprintf(stderr, "%04X %04X|%s", reg[PC], memory[reg[PC]], Disassemble(reg[PC],memory).c_str()); std::fprintf(stderr, "%*s", 50-l, ""); for(int i=0; i<12; ++i) std::fprintf(stderr, " %s:%04X", regnames[i], reg[i]); std::fprintf(stderr, "\n"); } // Read the next instruction from memory: u16 v = memory[reg[PC]++]; // Parse the individual components of the instruction: // Instruction format: aaaaaa bbbbb ooooo u16 o = (v & 0x1F), bb = (v>>5) & 0x1F, aa = (v>>10) & 0x3F; // List of basic instructions. Chosen by the value of "o": enum { nbi,SET,ADD,SUB,MUL,MLI,DIV,DVI,MOD,MDI,AND,BOR,XOR,SHR,ASR,SHL, IFB,IFC,IFE,IFN,IFG,IFA,IFL,IFU, ADX=0x1A,SBX, STI=0x1E,STD }; // List of non-basic instructions (nbi), chosen by the value of "bb": enum { JSR=0x21, HCF=0x27,INT,IAG,IAS,RFI,IAQ, HWN=0x30,HWQ,HWI }; // Parse the two parameters. (Note: "b" is skipped when nbi.) u16& a = value<'a',skipping>(aa); s32 sa = (s16)a, sr; u16& b = o==nbi ? v : value<'b',skipping>(bb); s32 sb = (s16)b; u32 wb = b, wr; // Then execute the instruction (unless the instruction is to be skipped). if(skipping) { if(o>=IFB && o<=IFU) op(); } else switch(o==nbi ? bb+0x20 : o) { // A simple move. It can also be used as // a PUSH/POP/RET/JMP with properly chosen parameters. case SET: tick(0); b = a; break; // Simple binary operations. case AND: tick(0); b &= a; break; case BOR: tick(0); b |= a; break; case XOR: tick(0); b ^= a; break; // Fundamental arithmetic operations. The overflow is stored in the EX register. case ADD: tick(1); wr = b+a; b = wr; reg[EX] = wr>>16; break; case SUB: tick(1); wr = b-a; b = wr; reg[EX] = wr>>16; break; case ADX: tick(1); wr = b+a+reg[EX]; b = wr; reg[EX] = wr>>16; break; case SBX: tick(1); wr = b-a+reg[EX]; b = wr; reg[EX] = wr>>16; break; case MUL: tick(1); wr = wb*a; b = wr; reg[EX] = wr>>16; break; case MLI: tick(1); sr = sb*sa; b = sr; reg[EX] = sr>>16; break; // Bit-shifting left. EX stores the bits that were shifted out from the register. case SHL: tick(0); wr = wb<>16; break; // Division and modulo. If the divisor is 0, result is 0. case MOD: tick(2); b = a ? b%a : 0; break; case MDI: tick(2); b = a ? sb%sa : 0; break; case DIV: tick(2); wr = a ?(wb<<16)/a :0; b = wr>>16; reg[EX] = wr; break; case DVI: tick(2); sr = sa?(sb<<16)/sa:0; b = sr>>16; reg[EX] = sr; break; // Bit-shifting right. EX stores the bits that were shifted out from the register. case SHR: tick(0); wr = (wb<<16) >> a; b = wr>>16; reg[EX] = wr; break; case ASR: tick(0); sr = (sb<<16) >> a; b = sr>>16; reg[EX] = sr; break; // Conditional execution. Skips the next instruction if the condition doesn't match. case IFE: tick(1); if(!(b == a)) op(); break; case IFN: tick(1); if(!(b != a)) op(); break; case IFG: tick(1); if(!(b > a)) op(); break; case IFB: tick(1); if(!(b & a)) op(); break; case IFA: tick(1); if(!(sb> sa)) op(); break; case IFU: tick(1); if(!(sb< sa)) op(); break; case IFL: tick(1); if(!(b < a)) op(); break; case IFC: tick(1); if( (b & a)) op(); break; // Streaming opcodes case STI: tick(1); b=a; ++reg[I]; ++reg[J]; break; case STD: tick(1); b=a; --reg[I]; --reg[J]; break; // Jump-To-Subroutine: Pushes the current program counter and chooses a new one. case JSR: tick(1); PUSH = reg[PC]; reg[PC] = a; break; case IAG: tick(0); /* TODO */ case IAS: tick(0); /* TODO */ case INT: tick(0); /* TODO */ case IAQ: tick(0); /* TODO */ case RFI: tick(0); /* TODO */ case HWN: tick(0); /* TODO */ case HWQ: tick(0); /* TODO */ case HWI: tick(0); /* TODO */ case HCF: break; /* TODO: Halt and Catch Fire */ // Anything else is invalid. default: std::fprintf(stderr, "Invalid opcode %04X at PC=%X\n", v, reg[PC]); } } #undef PUSH #undef POP public: void run() { // Infinite loop! for(;;) op(); } }; int main() { // Declare the emulator. Emulator emu; // Load the initial RAM contents. char Buf[131072]; int len = std::fread(Buf, 1, sizeof(Buf), stdin); std::vector mem = Assembler( {Buf,Buf+len} ); std::copy(mem.begin(), mem.end(), std::begin(emu.memory)); //std::fwrite(emu.memory, 0x10000, 2, stdout); // Launch the emulator. emu.run(); }