Skip to content

Instantly share code, notes, and snippets.

@untodesu
Created March 4, 2020 15:59
Show Gist options
  • Save untodesu/970864dfbe539f17481468425b20f452 to your computer and use it in GitHub Desktop.
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.
//==============================================================================
// 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;
}
}
//==============================================================================
// 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