Created
          March 4, 2020 15:59 
        
      - 
      
 - 
        
Save untodesu/970864dfbe539f17481468425b20f452 to your computer and use it in GitHub Desktop.  
    Legacy V16 VM v0281 without interrupts. Use it however you want, embed it everywhere you want.
  
        
  
    
      This file contains hidden or 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
    
  
  
    
  | //============================================================================== | |
| // Copyright (c) 2020, undbsd | |
| // All rights reserved. | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| //============================================================================== | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include "v16.h" | |
| #include <stdio.h> | |
| // Read next word (word at IP++) | |
| static const uint16_t v16_next_word(v16_vm_t *vm) | |
| { | |
| // I fucking hate this stupid fucking single-lined functions | |
| return vm->ram[vm->ip++]; | |
| } | |
| // Decode operand | |
| // Writes data to op | |
| static void v16_decode_operand(v16_vm_t *vm, uint8_t code, v16_operand_t *op) | |
| { | |
| // note: this is NOT a opcode, this is OPERAND code | |
| op->code = code; | |
| op->nextw = 0x0000; | |
| op->value = 0x0000; | |
| // Operand - Register (regname) | |
| if(op->code <= 0x03) { | |
| op->value = vm->regs[op->code & 0x03]; | |
| return; | |
| } | |
| // Operand - Register pointer (#regname) | |
| if(op->code <= 0x07) { | |
| op->value = vm->ram[vm->regs[(op->code - 0x04) & 0x03]]; | |
| return; | |
| } | |
| // Operand - Register pointer + offset (#regname + number) | |
| if(op->code <= 0x0B) { | |
| op->nextw = v16_next_word(vm); | |
| op->value = vm->ram[vm->regs[(op->code - 0x08) & 0x03] + op->nextw]; | |
| return; | |
| } | |
| // Operand - Offset pointer (#number) | |
| if(op->code == 0x0C) { | |
| op->nextw = v16_next_word(vm); | |
| op->value = vm->ram[op->nextw]; | |
| return; | |
| } | |
| // Operand - literal (number) | |
| if(op->code == 0x0D) { | |
| op->nextw = v16_next_word(vm); | |
| op->value = op->nextw; | |
| return; | |
| } | |
| // Operand - IP (readonly) (ip) | |
| if(op->code == 0x0E) { | |
| op->value = vm->ip; | |
| return; | |
| } | |
| // Operand - SP (readonly) (sp) | |
| if(op->code == 0x0F) { | |
| op->value = vm->sp; | |
| return; | |
| } | |
| } | |
| // Writes value to specified by op place | |
| // Actually this needed because we write data only after execution | |
| static void v16_set_value(v16_vm_t *vm, v16_operand_t *op, uint16_t value) | |
| { | |
| // Operand - Register (regname) | |
| if(op->code <= 0x03) { | |
| vm->regs[op->code & 0x03] = value; | |
| return; | |
| } | |
| // Operand - Register pointer (#regname) | |
| if(op->code <= 0x07) { | |
| vm->ram[vm->regs[(op->code - 0x04) & 0x03]] = value; | |
| return; | |
| } | |
| // Operand - Register pointer + offset (#regname + number) | |
| if(op->code <= 0x0B) { | |
| vm->ram[vm->regs[(op->code - 0x08) & 0x03] + op->nextw] = value; | |
| return; | |
| } | |
| // Operand - Offset pointer (#number) | |
| if(op->code == 0x0C) { | |
| vm->ram[op->nextw] = value; | |
| return; | |
| } | |
| } | |
| // Swap register files | |
| static void v16_swap_files(v16_vm_t *vm) | |
| { | |
| // If swapped already then use A set | |
| vm->regs = vm->reg_swapped ? vm->reg_a : vm->reg_b; | |
| vm->reg_swapped = !vm->reg_swapped; | |
| } | |
| // Push something to stack | |
| static void v16_push(v16_vm_t *vm, uint16_t value) | |
| { | |
| // I fucking hate this stupid fucking single-lined functions | |
| vm->ram[vm->sp--] = value; | |
| } | |
| // Pop something from stack | |
| static uint16_t v16_pop(v16_vm_t *vm) | |
| { | |
| // I fucking hate this stupid fucking single-lined functions | |
| return vm->ram[++vm->sp]; | |
| } | |
| static int jumps = 0; | |
| // Jump to specified location | |
| static void v16_jump(v16_vm_t *vm, uint16_t to, int cond) | |
| { | |
| jumps++; | |
| if(cond) { | |
| vm->ip = to; | |
| } | |
| } | |
| // Jump to specified location and save previous in stack | |
| static void v16_call(v16_vm_t *vm, uint16_t to, int cond) | |
| { | |
| if(cond) { | |
| v16_push(vm, vm->ip); | |
| vm->ip = to; | |
| } | |
| } | |
| // Return to saved in stack location (jump back) | |
| static void v16_return(v16_vm_t *vm, int cond) | |
| { | |
| if(cond) { | |
| vm->ip = v16_pop(vm); | |
| } | |
| } | |
| // Create new VM instance | |
| v16_vm_t * v16_open(void) | |
| { | |
| v16_vm_t *vm = (v16_vm_t *)malloc(sizeof(v16_vm_t)); | |
| memset(vm->ram, 0x0000, sizeof(uint16_t) * V16_RAMSIZE); | |
| memset(vm->reg_a, 0x0000, sizeof(uint16_t) * V16_FREGC); | |
| memset(vm->reg_b, 0x0000, sizeof(uint16_t) * V16_FREGC); | |
| vm->regs = vm->reg_a; | |
| vm->ip = 0x0000; | |
| vm->sp = 0xFFFF; | |
| vm->ex = 0x0000; | |
| vm->running = 0; | |
| vm->reg_swapped = 0; | |
| return vm; | |
| } | |
| // Close (aka delete) existing VM instance | |
| void v16_close(v16_vm_t *vm) | |
| { | |
| if(vm) { | |
| free(vm); | |
| } | |
| } | |
| // Load some code to VM's address space | |
| void v16_load_code(v16_vm_t *vm, uint16_t offset, uint16_t *code, size_t len) | |
| { | |
| // I do not want to use memcpy here. | |
| for(int ram = offset, cp = 0; (ram < V16_RAMSIZE) && (cp < len); ram++, cp++) { | |
| vm->ram[ram] = code[cp]; | |
| } | |
| } | |
| // Do single tick | |
| void v16_tick(v16_vm_t *vm) | |
| { | |
| uint16_t word = v16_next_word(vm); | |
| uint8_t opcode = ((word >> 8) & 0xFF); | |
| v16_operand_t operand_a = { 0 }; | |
| v16_operand_t operand_b = { 0 }; | |
| // Read operands | |
| v16_decode_operand(vm, ((word >> 4) & 0x0F), &operand_a); | |
| v16_decode_operand(vm, ((word >> 0) & 0x0F), &operand_b); | |
| uint16_t value_a = operand_a.value; | |
| uint16_t value_b = operand_b.value; | |
| int clear_flags = 1; | |
| // This is a little trick how to detect result change without any flags. | |
| // Using V16 we can only make maximum 32-bit result value 0xFFFE0001 (which is 0xFFFF^2, mul 0xFFFF, 0xFFFF). | |
| // So we can just leave it 0xFFFFFFFF and check it after execution. | |
| uint32_t result = 0xFFFFFFFF; | |
| // Do halt logic | |
| if(opcode == OP_HLT) { | |
| vm->running = 0; | |
| goto end; | |
| } | |
| // Compare two things | |
| if(opcode == OP_CMP) { | |
| // Greater flag | |
| if(value_a > value_b) { | |
| vm->rf |= FLG_GF; | |
| } | |
| // Lower flag | |
| if(value_a < value_b) { | |
| vm->rf |= FLG_LF; | |
| } | |
| // Equals&Zero flag | |
| if(value_a == value_b) { | |
| vm->rf |= FLG_EF; | |
| vm->rf |= FLG_ZF; | |
| } | |
| clear_flags = 0; | |
| goto end; | |
| } | |
| // Swap register files | |
| if(opcode == OP_SWR) { | |
| v16_swap_files(vm); | |
| goto end; | |
| } | |
| // Port IO stub | |
| if(opcode == OP_IN) { | |
| goto end; | |
| } | |
| // Port IO stub | |
| if(opcode == OP_OUT) { | |
| goto end; | |
| } | |
| // Push value to stack | |
| if(opcode == OP_PUSH) { | |
| v16_push(vm, value_a); | |
| goto end; | |
| } | |
| // Pop value from stack | |
| if(opcode == OP_POP) { | |
| result = v16_pop(vm); | |
| goto end; | |
| } | |
| // Jump thing | |
| if((opcode >= OP_JMP) && (opcode <= OP_JLE)) { | |
| switch(opcode) { | |
| case OP_JMP: v16_jump(vm, value_a, 1); break; | |
| case OP_JO: v16_jump(vm, value_a, (vm->rf & FLG_OF)); break; | |
| case OP_JNO: v16_jump(vm, value_a, !(vm->rf & FLG_OF)); break; | |
| case OP_JZ: v16_jump(vm, value_a, (vm->rf & FLG_ZF)); break; | |
| case OP_JNZ: v16_jump(vm, value_a, !(vm->rf & FLG_ZF)); break; | |
| case OP_JE: v16_jump(vm, value_a, (vm->rf & FLG_EF)); break; | |
| case OP_JNE: v16_jump(vm, value_a, !(vm->rf & FLG_EF)); break; | |
| case OP_JG: v16_jump(vm, value_a, (vm->rf & FLG_GF)); break; | |
| case OP_JGE: v16_jump(vm, value_a, (vm->rf & FLG_GF) || (vm->rf & FLG_EF)); break; | |
| case OP_JL: v16_jump(vm, value_a, (vm->rf & FLG_LF)); break; | |
| case OP_JLE: v16_jump(vm, value_a, (vm->rf & FLG_LF) || (vm->rf & FLG_EF)); break; | |
| } | |
| goto end; | |
| } | |
| // Call thing | |
| if((opcode >= OP_CALL) && (opcode <= OP_CLE)) { | |
| switch(opcode) { | |
| case OP_CALL: v16_call(vm, value_a, 1); break; | |
| case OP_CO: v16_call(vm, value_a, (vm->rf & FLG_OF)); break; | |
| case OP_CNO: v16_call(vm, value_a, !(vm->rf & FLG_OF)); break; | |
| case OP_CZ: v16_call(vm, value_a, (vm->rf & FLG_ZF)); break; | |
| case OP_CNZ: v16_call(vm, value_a, !(vm->rf & FLG_ZF)); break; | |
| case OP_CE: v16_call(vm, value_a, (vm->rf & FLG_EF)); break; | |
| case OP_CNE: v16_call(vm, value_a, !(vm->rf & FLG_EF)); break; | |
| case OP_CG: v16_call(vm, value_a, (vm->rf & FLG_GF)); break; | |
| case OP_CGE: v16_call(vm, value_a, (vm->rf & FLG_GF) || (vm->rf & FLG_EF)); break; | |
| case OP_CL: v16_call(vm, value_a, (vm->rf & FLG_LF)); break; | |
| case OP_CLE: v16_call(vm, value_a, (vm->rf & FLG_LF) || (vm->rf & FLG_EF)); break; | |
| } | |
| goto end; | |
| } | |
| // Return thing | |
| if((opcode >= OP_RET) && (opcode <= OP_RLE)) { | |
| switch(opcode) { | |
| case OP_RET: v16_return(vm, 1); break; | |
| case OP_RO: v16_return(vm, (vm->rf & FLG_OF)); break; | |
| case OP_RNO: v16_return(vm, !(vm->rf & FLG_OF)); break; | |
| case OP_RZ: v16_return(vm, (vm->rf & FLG_ZF)); break; | |
| case OP_RNZ: v16_return(vm, !(vm->rf & FLG_ZF)); break; | |
| case OP_RE: v16_return(vm, (vm->rf & FLG_EF)); break; | |
| case OP_RNE: v16_return(vm, !(vm->rf & FLG_EF)); break; | |
| case OP_RG: v16_return(vm, (vm->rf & FLG_GF)); break; | |
| case OP_RGE: v16_return(vm, (vm->rf & FLG_GF) || (vm->rf & FLG_EF)); break; | |
| case OP_RL: v16_return(vm, (vm->rf & FLG_LF)); break; | |
| case OP_RLE: v16_return(vm, (vm->rf & FLG_LF) || (vm->rf & FLG_EF)); break; | |
| } | |
| goto end; | |
| } | |
| // Add two numbers | |
| if(opcode == OP_ADD) { | |
| result = value_a + value_b; | |
| goto end; | |
| } | |
| // Subtract two numbers | |
| if(opcode == OP_SUB) { | |
| result = value_b - value_b; | |
| goto end; | |
| } | |
| // Multiply two numbers | |
| if(opcode == OP_MUL) { | |
| result = value_a * value_b; | |
| goto end; | |
| } | |
| // Divide two numbers | |
| if(opcode == OP_DIV) { | |
| result = value_b ? (value_a / value_b) : 0; | |
| goto end; | |
| } | |
| // Module | |
| if(opcode == OP_MOD) { | |
| result = value_b ? (value_a % value_b) : value_a; | |
| goto end; | |
| } | |
| // Bitwise shift left | |
| if(opcode == OP_SHL) { | |
| result = value_a << value_b; | |
| goto end; | |
| } | |
| // Bitwise shift right | |
| if(opcode == OP_SHR) { | |
| result = value_a >> value_b; | |
| goto end; | |
| } | |
| // locigal (per bit) AND | |
| if(opcode == OP_AND) { | |
| result = value_a & value_b; | |
| goto end; | |
| } | |
| // locigal (per bit) NOT | |
| if(opcode == OP_NOT) { | |
| result = ~value_a; | |
| goto end; | |
| } | |
| // locigal (per bit) OR | |
| if(opcode == OP_OR) { | |
| result = value_a | value_b; | |
| goto end; | |
| } | |
| // locigal (per bit) XOR | |
| if(opcode == OP_XOR) { | |
| result = value_a ^ value_b; | |
| goto end; | |
| } | |
| // Invert sign (1234 turns into -1234) | |
| if(opcode == OP_SINV) { | |
| result = ~value_a + 1; | |
| goto end; | |
| } | |
| // Simply move value | |
| if(opcode == OP_MOV) { | |
| result = value_b; | |
| goto end; | |
| } | |
| // Move then increase CX | |
| if(opcode == OP_MCI) { | |
| result = value_b; | |
| vm->regs[REG_CX]++; | |
| goto end; | |
| } | |
| // Move then decrease CX | |
| if(opcode == OP_MCD) { | |
| result = value_b; | |
| vm->regs[REG_CX]--; | |
| goto end; | |
| } | |
| // Get EX | |
| if(opcode == OP_MEX) { | |
| result = vm->ex; | |
| goto end; | |
| } | |
| // Increment | |
| if(opcode = OP_INC) { | |
| result = value_a + 1; | |
| goto end; | |
| } | |
| // Decrement | |
| if(opcode = OP_DEC) { | |
| result = value_a - 1; | |
| goto end; | |
| } | |
| end: | |
| // Write value if it changed | |
| if(result != 0xFFFFFFFF) { | |
| v16_set_value(vm, &operand_a, (result & 0xFFFF)); | |
| vm->ex = ((result >> 16) & 0xFFFF); | |
| vm->rf |= !result ? FLG_ZF : 0; | |
| vm->rf |= vm->ex ? FLG_OF : 0; | |
| } | |
| // Clear flags if needed | |
| if(clear_flags) { | |
| vm->rf &= ~FLG_LF; | |
| vm->rf &= ~FLG_GF; | |
| vm->rf &= ~FLG_EF; | |
| } | |
| } | |
  
    
      This file contains hidden or 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
    
  
  
    
  | //============================================================================== | |
| // Copyright (c) 2020, undbsd | |
| // All rights reserved. | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| //============================================================================== | |
| #ifndef V16_H | |
| #define V16_H | |
| #include <stdint.h> | |
| #define V16_RAMSIZE 0x10000 // Actual RAM(address space) size. | |
| #define V16_FREGC 0x04 // The count of registers in file (we use array for that, so we need to specify its size) | |
| // V16 Register names | |
| // Contains register names associated with their indices in file | |
| enum v16_reg_names { | |
| REG_R0 = 0, | |
| REG_AX = 0, | |
| REG_R1 = 1, | |
| REG_BX = 1, | |
| REG_R2 = 2, | |
| REG_CX = 2, | |
| REG_R3 = 3, | |
| REG_DX = 3, | |
| }; | |
| // V16 flags | |
| // Contains flags names associated with their bits in RF | |
| enum v16_flag_names { | |
| FLG_LF = 1 << 0, // Lower Flag -- Set to 1 if CMP A is lower than CMP B | |
| FLG_GF = 1 << 1, // Greater flag -- Set to 1 if CMP B is lower than CMP A | |
| FLG_EF = 1 << 2, // Equals flag -- Set to 1 if CMP A is equal to CMP B | |
| FLG_ZF = 1 << 3, // Zero flag -- Set to 1 if last instruction result value is zero | |
| FLG_OF = 1 << 4, // Overflow flag -- Set to 1 if last instruction caused writing to EX | |
| }; | |
| // V16 opcode names | |
| // Contains opcode names associated with their bytes | |
| enum v16_opcode_names { | |
| OP_NUL = 0x00, OP_HLT = 0x01, OP_CMP = 0x02, OP_SWR = 0x03, | |
| OP_IN = 0x04, OP_OUT = 0x05, | |
| OP_PUSH = 0x06, OP_POP = 0x07, | |
| OP_JMP = 0x08, | |
| OP_JO = 0x09, OP_JNO = 0x0A, | |
| OP_JZ = 0x0B, OP_JNZ = 0x0C, | |
| OP_JE = 0x0D, OP_JNE = 0x0E, | |
| OP_JG = 0x0F, OP_JGE = 0x10, | |
| OP_JL = 0x11, OP_JLE = 0x12, | |
| OP_CALL = 0x13, | |
| OP_CO = 0x14, OP_CNO = 0x15, | |
| OP_CZ = 0x16, OP_CNZ = 0x17, | |
| OP_CE = 0x18, OP_CNE = 0x19, | |
| OP_CG = 0x1A, OP_CGE = 0x1B, | |
| OP_CL = 0x1C, OP_CLE = 0x1D, | |
| OP_RET = 0x1E, | |
| OP_RO = 0x1F, OP_RNO = 0x20, | |
| OP_RZ = 0x21, OP_RNZ = 0x22, | |
| OP_RE = 0x23, OP_RNE = 0x24, | |
| OP_RG = 0x25, OP_RGE = 0x26, | |
| OP_RL = 0x27, OP_RLE = 0x28, | |
| OP_ADD = 0x29, OP_SUB = 0x2A, | |
| OP_MUL = 0x2B, | |
| OP_DIV = 0x2C, OP_MOD = 0x2D, | |
| OP_SHL = 0x2E, OP_SHR = 0x2F, | |
| OP_AND = 0x30, OP_NOT = 0x31, | |
| OP_OR = 0x32, OP_XOR = 0x33, | |
| OP_SINV = 0x34, | |
| OP_MOV = 0x35, | |
| OP_MCI = 0x36, OP_MCD = 0x37, | |
| OP_MEX = 0x38, | |
| OP_INC = 0x3A, OP_DEC = 0x3B, | |
| }; | |
| // V16 operand structure | |
| // This used because V16 can write data to RAM after instruction execution | |
| typedef struct v16_operand_s { | |
| uint8_t code : 4; // Operand code | |
| uint16_t value; // Read value (from RAM or from register) | |
| uint16_t nextw; // Next read word (next to constrol word) if required | |
| } v16_operand_t; | |
| // V16 virtual machine structure | |
| // Nothing to see here. | |
| typedef struct v16_vm_s { | |
| uint16_t ram[V16_RAMSIZE]; // Address space | |
| uint16_t reg_a[V16_FREGC]; // Register File A (default) | |
| uint16_t reg_b[V16_FREGC]; // Register File B | |
| uint16_t * regs; // Current Register File | |
| uint16_t ip, sp; // Internal readonly registers | |
| uint16_t ex; // Extended (overflow) accumulator (access - mvex, rdex) | |
| uint16_t rf; // Flags register | |
| int running; // Running status | |
| int reg_swapped; // If 1 then using register file B | |
| } v16_vm_t; | |
| v16_vm_t * v16_open(void); | |
| void v16_close(v16_vm_t *vm); | |
| void v16_load_code(v16_vm_t *vm, uint16_t offset, uint16_t *code, size_t len); | |
| void v16_tick(v16_vm_t *vm); | |
| #endif | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment