diff --git a/arduino/arduino.ino b/arduino/arduino.ino new file mode 100644 index 0000000..ad7264f --- /dev/null +++ b/arduino/arduino.ino @@ -0,0 +1,35 @@ +/* +Copyright 2018 Sam Stevens + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +#include "vm.h" +#include "console.h" + +VM vm; +Console console(&vm); + +void setup() { + // put your setup code here, to run once: + vm_reset(&vm); + Serial.begin(9600); +} + +void loop() { + // put your main code here, to run repeatedly: + console.loop(&Serial); + +} + + diff --git a/arduino/console.cpp b/arduino/console.cpp new file mode 100644 index 0000000..164b435 --- /dev/null +++ b/arduino/console.cpp @@ -0,0 +1,166 @@ +/* +Copyright 2018 Sam Stevens + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +#include +#include +#include "console.h" + +Console::Console(VM* _vm) { + this->state = CONSOLE_NONE; + this->vm = _vm; + this->inputBuffer = new String(""); +} + +void Console::loop(HardwareSerial * serial) { + int i; + char d; + + switch(this->state) { + case CONSOLE_NONE: + serial->println("Press Enter to activate console."); + this->state = CONSOLE_OFF; + break; + case CONSOLE_OFF: + if(serial->available()) { + if (serial->read() == 10) { + serial->println("Console active. Type ? for help."); + this->state = CONSOLE_ACTIVATE; + } + } + break; + case CONSOLE_ACTIVATE: + serial->print("> "); + this->state = CONSOLE_ACTIVE; + break; + case CONSOLE_ACTIVE: + if(serial->available()) { + while((d = (char)serial->read()) != -1) { + if (d == 27) { + //escape, deactivate console + this->inputBuffer = new String(""); + serial->println("Console deactivated."); + this->state = CONSOLE_NONE; + } else if (d == '\n') { + //Finished input + serial->print("\n"); + if (this->inputBuffer->equals("?")) { + serial->print(F( +"Commands\n" +"? | this help\n" +"q | deactivate console\n" +"r | run until halted\n" +"s | step one instruction\n" +"e | examine and update memory\n" +"v | show vm status\n" +"rst | reset PC and registers\n" +"clr | reset and clear memory\n" + )); + serial->print("> "); + } else if (this->inputBuffer->equals("q")) { + serial->println("Console deactivated."); + this->state = CONSOLE_NONE; + } else if (this->inputBuffer->equals("r")) { + this->state = CONSOLE_RUN; + } else if (this->inputBuffer->equals("s")) { + this->state = CONSOLE_STEP; + } else if (this->inputBuffer->equals("e")) { + this->state = CONSOLE_EXAMINE; + } else if (this->inputBuffer->equals("v")) { + this->state = CONSOLE_VIEW; + } else if (this->inputBuffer->equals("rst")) { + this->state = CONSOLE_RESET; + } else if (this->inputBuffer->equals("clr")) { + this->state = CONSOLE_CLEAR; + } else { + serial->println("Unknown command. Type ? for help."); + serial->print("> "); + } + this->inputBuffer = new String(""); + } else if (isAlphaNumeric(d) || isPunct(d) || isSpace(d)) { + //Append input to buffer + this->inputBuffer->concat(d); + serial->print(d); + } + } + } + break; + case CONSOLE_RUN: + break; + case CONSOLE_STEP: + break; + case CONSOLE_EXAMINE: + break; + case CONSOLE_VIEW: + //PC + serial->print( "PC "); + serial->print(this->vm->PC, HEX); + serial->print("\n"); + + //Registers + serial->print( "R "); + for(i = 0; i < VM_REG_SIZE; i++) { + if (this->vm->R[i] < 0x10) { + serial->print("0"); + } + serial->print(this->vm->R[i], HEX); + serial->print(" "); + } + serial->print("\n"); + + //Memory + serial->println(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); + for(i = 0; i < VM_MEM_SIZE; i++) { + if (i % 16 == 0) { + if (i > 0) { + serial->print("\n"); + if (i % 128 == 0 ) { + serial->println(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); + } + } + serial->print("0x"); + if (i < 0x10) { + serial->print("0"); + } + serial->print(i, HEX); + serial->print(" "); + } + uint8_t m = this->vm->M[i]; + if (m < 0x10) { + serial->print("0"); + } + serial->print(m, HEX); + serial->print(" "); + } + serial->print("\n"); + + this->state = CONSOLE_ACTIVATE; + break; + case CONSOLE_RESET: + vm_reset(this->vm); + serial->println("VM Reset"); + this->state = CONSOLE_ACTIVATE; + break; + case CONSOLE_CLEAR: + vm_reset(this->vm); + for(i = 0; i < VM_MEM_SIZE; i++) { + this->vm->M[i] = 0; + } + serial->println("VM Reset & Memory Cleared"); + this->state = CONSOLE_ACTIVATE; + break; + } +} + diff --git a/arduino/console.h b/arduino/console.h new file mode 100644 index 0000000..748e567 --- /dev/null +++ b/arduino/console.h @@ -0,0 +1,46 @@ +/* +Copyright 2018 Sam Stevens + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +#ifndef UVM_CONSOLE_H +#define UVM_CONSOLE_H + +#include +#include "vm.h" + +enum ConsoleState_t { + CONSOLE_NONE, + CONSOLE_OFF, + CONSOLE_ACTIVATE, + CONSOLE_ACTIVE, + CONSOLE_RUN, + CONSOLE_STEP, + CONSOLE_EXAMINE, + CONSOLE_VIEW, + CONSOLE_RESET, + CONSOLE_CLEAR, +}; +typedef enum ConsoleState_t ConsoleState; + +class Console { + ConsoleState state; + VM *vm; + String *inputBuffer; + public: + Console (VM*); + void loop(HardwareSerial*); +}; + +#endif //UVM_CONSOLE_H diff --git a/arduino/vm.c b/arduino/vm.c new file mode 100644 index 0000000..24583d5 --- /dev/null +++ b/arduino/vm.c @@ -0,0 +1,212 @@ +/* +Copyright 2018 Sam Stevens + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +#include +#include +#include +#include "vm.h" + +VM *new_vm() { + VM *vm = calloc(1, sizeof(VM)); + return vm; +} + +void free_vm(VM *vm) { + free(vm); +} + +void vm_reset(VM *vm) { + memset(vm->R, 0, sizeof(uint8_t) * VM_REG_SIZE); + vm->PC = 0; + vm->carry = 0; + vm->halted = false; +} + +void vm_clear(VM *vm) { + vm_reset(vm); + memset(&vm->M, 0, VM_MEM_SIZE); +} + +inline uint8_t vm_get_r(VM *vm, uint8_t r) { + if (r == 0) { + return 0; + } + if (r < VM_REG_SIZE) { + return vm->R[r]; + } + return 0; +} + +inline void vm_put_r(VM *vm, uint8_t r, uint8_t v) { + if (r < VM_REG_SIZE) { + vm->R[r] = v; + } +} + +inline void vm_decode_Q(VM_Instruction *inst, uint16_t raw) { + inst->rd = (uint8_t) ((raw & 0x0F00) >> 8); + inst->imm = (uint8_t) (raw & 0x00FF); +} + +inline void vm_decode_S(VM_Instruction *inst, uint16_t raw) { + inst->rd = (uint8_t) ((raw & 0x0F00) >> 8); + inst->rx = (uint8_t) ((raw & 0x00F0) >> 4); + inst->ry = (uint8_t) ((raw & 0x000F) >> 0); +} + +inline void vm_decode_T(VM_Instruction *inst, uint16_t raw) { + inst->rd = (uint8_t) ((raw & 0x0F00) >> 8); + inst->rx = (uint8_t) ((raw & 0x00F0) >> 4); + inst->imm = (uint8_t) ((raw & 0x000F) >> 0); +} + +inline uint8_t vm_subtract(uint8_t x, uint8_t y, uint8_t *ptr_borrow) { + uint8_t borrow = *ptr_borrow, + difference = 0, + xb, yb; + for (uint8_t i = 0; i < 8; i++) { + xb = (uint8_t) ((x >> i) & 1); + yb = (uint8_t) ((y >> i) & 1); + difference |= (uint8_t) (((xb ^ yb) ^ borrow) & 1) << i; + borrow = (uint8_t) (((~xb & yb) | ~(xb ^ yb) * borrow) & 1); + } + + *ptr_borrow = borrow; + return difference; +} + +void vm_step(VM *vm) { + VM_Instruction inst; + memset(&inst, 0, sizeof(VM_Instruction)); + + uint8_t PC = vm->PC; + if (PC % 2 != 0) { + fprintf(stderr, "Halted. PC not aligned.\n"); + vm->halted = true; + return; + } + uint16_t raw = (vm->M[PC] << 8) + vm->M[PC + 1]; + vm->PC += 2; + inst.op = (uint8_t) (raw >> 12); + + if (vm->halted) { + return; + } + + uint8_t x = 0; + uint8_t y = 0; + uint8_t d = 0; + uint16_t temp16 = 0; + switch (inst.op) { + case OP_LDA: + vm_decode_Q(&inst, raw); + d = vm->M[inst.imm]; + break; + case OP_STA: + vm_decode_Q(&inst, raw); + vm->M[inst.imm] = vm_get_r(vm, inst.rd); + break; + case OP_LDI: + vm_decode_Q(&inst, raw); + d = inst.imm; + break; + case OP_ADD: + vm_decode_S(&inst, raw); + vm->carry = 0; + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + d = x + y; + break; + case OP_ADC: + vm_decode_S(&inst, raw); + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + temp16 = x + y + vm->carry; + vm->carry = (uint8_t) (temp16 >> 8); + d = (uint8_t) temp16; + break; + case OP_SUB: + vm_decode_S(&inst, raw); + vm->carry = 0; + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + d = x - y; + break; + case OP_SBC: + vm_decode_S(&inst, raw); + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + d = vm_subtract(x, y, &vm->carry); + break; + case OP_NOT: + vm_decode_T(&inst, raw); + d = ~x; + break; + case OP_AND: + vm_decode_S(&inst, raw); + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + d = x & y; + break; + case OP_SHL: + vm_decode_T(&inst, raw); + x = vm_get_r(vm, inst.rx); + d = x << vm_get_r(vm, inst.imm); + break; + case OP_SHR: + vm_decode_T(&inst, raw); + x = vm_get_r(vm, inst.rx); + d = x >> vm_get_r(vm, inst.imm); + break; + case OP_SYS: + vm_decode_Q(&inst, raw); + if (vm->syscall != NULL) { + d = vm->syscall(vm, inst.imm); + } else { + printf("SYSCALL #%d\n", inst.imm); + } + break; + case OP_JMP: + vm_decode_Q(&inst, raw); + vm->PC = vm_get_r(vm, inst.rd); + break; + case OP_JEQ: + vm_decode_S(&inst, raw); + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + if (x == y) { + vm->PC = vm_get_r(vm, inst.rd); + } + break; + case OP_JLT: + vm_decode_S(&inst, raw); + x = vm_get_r(vm, inst.rx); + y = vm_get_r(vm, inst.ry); + if (x < y) { + vm->PC = vm_get_r(vm, inst.rd); + } + break; + case OP_HLT: + printf("Halted at 0x%04X\n", PC); + vm->halted = true; + break; + default: + printf("Unknown Instruction at 0x%04X: 0x%hhX\n", PC, inst.op); + vm->halted = true; + break; + } + vm_put_r(vm, inst.rd, d); +} \ No newline at end of file diff --git a/arduino/vm.h b/arduino/vm.h new file mode 100644 index 0000000..bc91f71 --- /dev/null +++ b/arduino/vm.h @@ -0,0 +1,90 @@ +/* +Copyright 2018 Sam Stevens + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +#ifndef UVM_VM_H +#define UVM_VM_H + +#include +#include + +#define VM_MEM_SIZE 256 +#define VM_REG_SIZE 16 + +#define OP_HLT 0x0 +#define OP_LDA 0x1 +#define OP_STA 0x2 +#define OP_LDI 0x3 +#define OP_ADD 0x4 +#define OP_ADC 0x5 +#define OP_SUB 0x6 +#define OP_SBC 0x7 +#define OP_NOT 0x8 +#define OP_AND 0x9 +#define OP_SHL 0xa +#define OP_SHR 0xb +#define OP_SYS 0xc +#define OP_JMP 0xd +#define OP_JEQ 0xe +#define OP_JLT 0xf + +#ifdef __cplusplus +extern "C" { +#endif + +struct VM_Instruction_t { + uint8_t op; + uint8_t rd; + uint8_t rx; + uint8_t ry; + uint8_t imm; +}; +typedef struct VM_Instruction_t VM_Instruction; + +struct VM_t { + uint8_t R[VM_REG_SIZE]; + uint8_t PC; + uint8_t M[VM_MEM_SIZE]; + uint8_t carry; + bool halted; + uint8_t (*syscall)(struct VM_t* vm, uint8_t callno); +}; +typedef struct VM_t VM; + +VM * new_vm(); + +void free_vm(VM *vm); + +void vm_reset(VM *vm); + +void vm_clear(VM *vm); + +uint8_t vm_get_r(VM *vm, uint8_t r); + +void vm_put_r(VM *vm, uint8_t r, uint8_t v); + +void vm_decode_Q(VM_Instruction *inst, uint16_t raw); +void vm_decode_S(VM_Instruction *inst, uint16_t raw); +void vm_decode_T(VM_Instruction *inst, uint16_t raw); + +uint8_t vm_subtract(uint8_t x, uint8_t y, uint8_t *ptr_borrow); + +void vm_step(VM *vm); + +#ifdef __cplusplus +} +#endif + +#endif //UVM_VM_H diff --git a/vm.h b/vm.h index 90af6db..7de55b9 100644 --- a/vm.h +++ b/vm.h @@ -20,8 +20,8 @@ limitations under the License. #include #include -#define VM_MEM_SIZE (1<<8)-1 -#define VM_REG_SIZE 15 +#define VM_MEM_SIZE 256 +#define VM_REG_SIZE 16 #define OP_HLT 0x0 #define OP_LDA 0x1