├── .gitignore ├── lc3os.obj ├── tests ├── hello-world │ ├── hello-world.obj │ ├── hello-world.asm │ └── hello-world.sym └── CMakeLists.txt ├── .github └── workflows │ └── ubuntu.yml ├── vm.h ├── CMakeLists.txt ├── main.c ├── README.md └── vm.c /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /build/ 3 | /cmake-build-*/ 4 | -------------------------------------------------------------------------------- /lc3os.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpendleton/lc3sim-c/HEAD/lc3os.obj -------------------------------------------------------------------------------- /tests/hello-world/hello-world.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rpendleton/lc3sim-c/HEAD/tests/hello-world/hello-world.obj -------------------------------------------------------------------------------- /tests/hello-world/hello-world.asm: -------------------------------------------------------------------------------- 1 | .orig x3000 2 | 3 | lea r0, msg 4 | puts 5 | halt 6 | 7 | msg .stringz "hello world!" 8 | 9 | .end 10 | -------------------------------------------------------------------------------- /tests/hello-world/hello-world.sym: -------------------------------------------------------------------------------- 1 | // Symbol table 2 | // Scope level 0: 3 | // Symbol Name Page Address 4 | // ---------------- ------------ 5 | // msg 3003 6 | 7 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_test(lc3sim_hello-world 2 | ../lc3sim ${CMAKE_CURRENT_SOURCE_DIR}/hello-world/hello-world.obj 3 | ) 4 | 5 | set_tests_properties(lc3sim_hello-world PROPERTIES 6 | PASS_REGULAR_EXPRESSION "hello world!\n\n--- halting the LC-3 ---\n\n" 7 | ) 8 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - name: Build 9 | run: | 10 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 11 | cmake --build build 12 | - name: Run Tests 13 | run: ctest --test-dir build 14 | -------------------------------------------------------------------------------- /vm.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Ryan Pendleton on 6/28/18. 3 | // Copyright © 2018 Ryan Pendleton. All rights reserved. 4 | // 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | typedef struct vm_impl* vm_ctx; 11 | 12 | typedef enum { 13 | VM_LOAD_SUCCESS, 14 | VM_LOAD_INPUT_NOT_FOUND, 15 | VM_LOAD_INPUT_TOO_LARGE, 16 | } vm_load_result; 17 | 18 | typedef enum { 19 | VM_RUN_SUCCESS, 20 | VM_RUN_UNIMPLEMENTED_OPCODE, 21 | } vm_run_result; 22 | 23 | vm_ctx vm_create(void); 24 | void vm_destroy(vm_ctx vm); 25 | 26 | void vm_load_os(vm_ctx vm); 27 | vm_load_result vm_load_file(vm_ctx vm, const char *file); 28 | vm_load_result vm_load_data(vm_ctx vm, unsigned const char *data, size_t length); 29 | 30 | vm_run_result vm_run(vm_ctx vm); 31 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(lc3sim-c) 3 | 4 | set(CMAKE_C_COMPILER gcc) 5 | set(CMAKE_C_STANDARD 17) 6 | 7 | option(ENABLE_TRACING "Enable trace-level logging of the VM state") 8 | 9 | add_custom_command( 10 | COMMAND xxd -i lc3os.obj > ${CMAKE_CURRENT_BINARY_DIR}/lc3os.c 11 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/lc3os.obj 12 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lc3os.c 13 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 14 | ) 15 | 16 | add_executable(lc3sim 17 | main.c 18 | vm.h 19 | vm.c 20 | ${CMAKE_CURRENT_BINARY_DIR}/lc3os.c 21 | ) 22 | 23 | if(ENABLE_TRACING) 24 | target_compile_definitions(lc3sim PRIVATE TRACE) 25 | endif() 26 | 27 | target_compile_options(lc3sim PRIVATE -Wall -Wextra) 28 | 29 | include(CTest) 30 | add_subdirectory(tests) 31 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Ryan Pendleton on 6/28/18. 3 | // Copyright © 2018 Ryan Pendleton. All rights reserved. 4 | // 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "vm.h" 17 | 18 | enum { 19 | VM_EXIT_SUCCESS, 20 | VM_EXIT_USAGE, 21 | VM_EXIT_INPUT_INVALID, 22 | VM_EXIT_OPCODE_INVALID, 23 | }; 24 | 25 | static struct termios original_tio; 26 | 27 | static void disable_input_buffering() { 28 | tcgetattr(STDIN_FILENO, &original_tio); 29 | struct termios new_tio = original_tio; 30 | new_tio.c_lflag &= ~ICANON & ~ECHO; 31 | tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); 32 | } 33 | 34 | static void restore_input_buffering() { 35 | tcsetattr(STDIN_FILENO, TCSANOW, &original_tio); 36 | } 37 | 38 | static void handle_signal(int signal) { 39 | if (signal != SIGINT) return; 40 | 41 | restore_input_buffering(); 42 | printf("\n"); 43 | exit(-2); 44 | } 45 | 46 | int main(int argc, const char * argv[]) { 47 | if (argc != 2) { 48 | fprintf(stderr, "usage: %s \n", argv[0]); 49 | return VM_EXIT_USAGE; 50 | } 51 | 52 | vm_ctx vm = vm_create(); 53 | vm_load_os(vm); 54 | 55 | vm_load_result load_result = vm_load_file(vm, argv[1]); 56 | 57 | switch (load_result) { 58 | case VM_LOAD_SUCCESS: 59 | break; 60 | 61 | case VM_LOAD_INPUT_NOT_FOUND: 62 | fprintf(stderr, "%s: Failed to load input: %s\n", argv[0], strerror(errno)); 63 | return VM_EXIT_INPUT_INVALID; 64 | 65 | case VM_LOAD_INPUT_TOO_LARGE: 66 | fprintf(stderr, "%s: Failed to load input: Input exceeded memory space\n", argv[0]); 67 | return VM_EXIT_INPUT_INVALID; 68 | } 69 | 70 | disable_input_buffering(); 71 | signal(SIGINT, handle_signal); 72 | 73 | vm_run_result run_result = vm_run(vm); 74 | 75 | restore_input_buffering(); 76 | 77 | switch (run_result) { 78 | case VM_RUN_SUCCESS: 79 | break; 80 | 81 | case VM_RUN_UNIMPLEMENTED_OPCODE: 82 | fprintf(stderr, "%s: Failed to execute input: Attempted to execute unimplemented opcode\n", argv[0]); 83 | return VM_EXIT_OPCODE_INVALID; 84 | } 85 | 86 | vm_destroy(vm); 87 | 88 | return VM_EXIT_SUCCESS; 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lc3sim-c 2 | 3 | A C implementation of the LC-3 virtual machine, an educational computer architecture. 4 | 5 | [Read tutorial here](https://www.jmeiners.com/lc3-vm/) 6 | 7 | ## How to build 8 | 9 | This project uses CMake to support a variety of build systems, platforms, and IDEs. Many IDEs 10 | support opening CMake projects natively, and others support opening CMake projects through the use 11 | of IDE plugins or CMake generators. You can also use CMake's command line generators to build using 12 | `make` or `ninja`. 13 | 14 | For details on building and running CMake projects, I'd recommend reading [An Introduction to Modern 15 | CMake: Running CMake][running-cmake], checking your IDE's documentation for details about its 16 | compatibility with CMake, or reading the CMake manual for details about supported [IDEs][cmake-ides] 17 | and [generators][cmake-generators]. 18 | 19 | [running-cmake]: https://cliutils.gitlab.io/modern-cmake/chapters/intro/running.html 20 | [cmake-ides]: https://cmake.org/cmake/help/latest/guide/ide-integration/index.html#ides-with-cmake-integration 21 | [cmake-generators]: https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#cmake-generators 22 | 23 | ### Build using command line 24 | 25 | If your IDE isn't supported or you'd prefer to build using a command line generator, it's easy to 26 | get started. For example, to build in debug mode: 27 | 28 | ```sh 29 | # initialize the build scripts 30 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 31 | 32 | # build or rebuild as often as needed 33 | cmake --build build 34 | 35 | # run the vm 36 | ./build/lc3sim /path/to/lc3-program.obj 37 | ``` 38 | 39 | If you find yourself in need of trace-level logs for the VM's state, you can enable them by turning 40 | on the `ENABLE_TRACING` flag and then rebuilding: 41 | 42 | ```sh 43 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=ON 44 | cmake --build build 45 | ``` 46 | 47 | ## Running tests 48 | 49 | At the time of writing these instructions, this project only has a single "hello world!" unit test. 50 | Even so, this simple test can still detect some more obvious failures if something goes really 51 | wrong. 52 | 53 | The unit tests for this project are defined using standard CMake/CTest conventions, so to run them, 54 | you can either use your IDE's integrated unit test runner (if it has one), or by running the 55 | following on command line: 56 | 57 | ```sh 58 | # initialize the build scripts if you haven't already 59 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 60 | 61 | # build or rebuild if you haven't already 62 | cmake --build build 63 | 64 | # run the tests 65 | ctest --test-dir build 66 | ``` 67 | -------------------------------------------------------------------------------- /vm.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Ryan Pendleton on 6/28/18. 3 | // Copyright © 2018 Ryan Pendleton. All rights reserved. 4 | // 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "vm.h" 19 | 20 | #ifdef TRACE 21 | # define DEBUG_TRACE(...) fprintf(stderr, __VA_ARGS__) 22 | #else 23 | # define DEBUG_TRACE(...) 24 | #endif 25 | 26 | extern unsigned char lc3os_obj[]; 27 | extern unsigned int lc3os_obj_len; 28 | 29 | // MARK: - Types 30 | 31 | enum { 32 | VM_ADDR_MAX = UINT16_MAX, 33 | VM_ADDR_INITIAL = 0x3000, 34 | VM_SIGN_BIT = 1 << 15, 35 | VM_STATUS_BIT = 1 << 15, 36 | }; 37 | 38 | typedef uint16_t vm_byte; 39 | typedef uint16_t vm_addr; 40 | 41 | typedef enum { 42 | VM_OPCODE_ADD = 0b0001, 43 | VM_OPCODE_AND = 0b0101, 44 | VM_OPCODE_BR = 0b0000, 45 | VM_OPCODE_JMP = 0b1100, 46 | VM_OPCODE_JSR = 0b0100, 47 | VM_OPCODE_LD = 0b0010, 48 | VM_OPCODE_LDI = 0b1010, 49 | VM_OPCODE_LDR = 0b0110, 50 | VM_OPCODE_LEA = 0b1110, 51 | VM_OPCODE_NOT = 0b1001, 52 | VM_OPCODE_RTI = 0b1000, 53 | VM_OPCODE_ST = 0b0011, 54 | VM_OPCODE_STI = 0b1011, 55 | VM_OPCODE_STR = 0b0111, 56 | VM_OPCODE_TRAP = 0b1111, 57 | VM_OPCODE_RESERVED = 0b1101, 58 | } vm_opcode; 59 | 60 | typedef enum { 61 | VM_ADDR_KBSR = 0xfe00, 62 | VM_ADDR_KBDR = 0xfe02, 63 | VM_ADDR_DSR = 0xfe04, 64 | VM_ADDR_DDR = 0xfe06, 65 | VM_ADDR_MCR = 0xfffe, 66 | } vm_addr_special; 67 | 68 | typedef enum { 69 | VM_REG_0 = 0, 70 | VM_REG_1, 71 | VM_REG_2, 72 | VM_REG_3, 73 | VM_REG_4, 74 | VM_REG_5, 75 | VM_REG_6, 76 | VM_REG_7, 77 | VM_REG_PC, 78 | VM_REG_PSR, 79 | VM_REG_COUNT 80 | } vm_reg; 81 | 82 | typedef enum { 83 | VM_FLAG_NEGATIVE = 0b100, 84 | VM_FLAG_ZERO = 0b010, 85 | VM_FLAG_POSITIVE = 0b001, 86 | } vm_flag; 87 | 88 | struct vm_impl { 89 | vm_byte mem[VM_ADDR_MAX]; 90 | vm_byte reg[VM_REG_COUNT]; 91 | }; 92 | 93 | // MARK: - Helpers 94 | 95 | static uint16_t swap16(uint16_t val) { 96 | return (val << 8) | (val >> 8); 97 | } 98 | 99 | static uint16_t sextend(uint16_t val, uint16_t n) { 100 | uint16_t m = 1 << (n - 1); 101 | val &= ((1 << n) - 1); 102 | return (val ^ m) - m; 103 | } 104 | 105 | // MARK: - Creation 106 | 107 | vm_ctx vm_create(void) { 108 | vm_ctx vm = calloc(1, sizeof(struct vm_impl)); 109 | 110 | vm->reg[VM_REG_PC] = VM_ADDR_INITIAL; 111 | vm->reg[VM_REG_PSR] = VM_FLAG_ZERO; 112 | vm->mem[VM_ADDR_MCR] = VM_STATUS_BIT; 113 | 114 | return vm; 115 | } 116 | 117 | void vm_destroy(vm_ctx vm) { 118 | free(vm); 119 | } 120 | 121 | // MARK: - Memory 122 | 123 | static vm_byte vm_read(vm_ctx vm, vm_addr addr) { 124 | assert(vm != NULL); 125 | 126 | if (addr == VM_ADDR_KBSR) { 127 | static fd_set readfds; 128 | FD_ZERO(&readfds); 129 | FD_SET(STDIN_FILENO, &readfds); 130 | 131 | struct timeval timeout; 132 | timeout.tv_sec = 0; 133 | timeout.tv_usec = 0; 134 | 135 | return select(1, &readfds, NULL, NULL, &timeout) ? VM_STATUS_BIT : 0; 136 | } 137 | else if (addr == VM_ADDR_KBDR) { 138 | if (vm_read(vm, VM_ADDR_KBSR)) { 139 | return getchar(); 140 | } 141 | else { 142 | return 0; 143 | } 144 | } 145 | else if (addr == VM_ADDR_DSR) { 146 | return VM_STATUS_BIT; 147 | } 148 | else if (addr == VM_ADDR_DDR) { 149 | return 0; 150 | } 151 | 152 | return vm->mem[addr]; 153 | } 154 | 155 | static void vm_write(vm_ctx vm, vm_addr addr, vm_byte val) { 156 | assert(vm != NULL); 157 | 158 | if (addr == VM_ADDR_KBSR || addr == VM_ADDR_KBDR || addr == VM_ADDR_DSR) { 159 | return; 160 | } 161 | else if (addr == VM_ADDR_DDR) { 162 | putchar(val); 163 | fflush(stdout); 164 | return; 165 | } 166 | 167 | vm->mem[addr] = val; 168 | } 169 | 170 | void vm_load_os(vm_ctx vm) { 171 | vm_load_result res = vm_load_data(vm, lc3os_obj, lc3os_obj_len); 172 | assert(res == VM_LOAD_SUCCESS); 173 | } 174 | 175 | vm_load_result vm_load_file(vm_ctx vm, const char *file) { 176 | int fd, ret; 177 | struct stat statbuf; 178 | unsigned char *data; 179 | 180 | if ((fd = open(file, O_RDONLY)) < 0) { 181 | return VM_LOAD_INPUT_NOT_FOUND; 182 | } 183 | 184 | if ((ret = fstat(fd, &statbuf)) < 0) { 185 | return VM_LOAD_INPUT_NOT_FOUND; 186 | } 187 | 188 | if ((data = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { 189 | return VM_LOAD_INPUT_NOT_FOUND; 190 | } 191 | 192 | vm_load_result result = vm_load_data(vm, data, statbuf.st_size); 193 | 194 | munmap(data, statbuf.st_size); 195 | close(fd); 196 | 197 | return result; 198 | } 199 | 200 | vm_load_result vm_load_data(vm_ctx vm, unsigned const char *data, size_t length) { 201 | assert(vm != NULL); 202 | 203 | vm_addr load_addr = swap16(*((vm_addr*)data)); 204 | size_t load_length = (length - sizeof(vm_addr)) / sizeof(vm_byte); 205 | 206 | assert(load_addr + load_length < VM_ADDR_MAX); 207 | 208 | vm_byte *dest = vm->mem + load_addr; 209 | vm_byte *source = (vm_byte*)(data + sizeof(vm_addr)); 210 | 211 | if (dest + load_length >= vm->mem + VM_ADDR_MAX) { 212 | return VM_LOAD_INPUT_TOO_LARGE; 213 | } 214 | 215 | while (load_length-- > 0) { 216 | *(dest++) = swap16(*(source++)); 217 | } 218 | 219 | vm->reg[VM_REG_PC] = load_addr; 220 | 221 | return VM_LOAD_SUCCESS; 222 | } 223 | 224 | // MARK: - Execution 225 | 226 | static vm_flag vm_sign_flag(uint16_t val) { 227 | if (val == 0) { 228 | return VM_FLAG_ZERO; 229 | } 230 | else if (val & VM_SIGN_BIT) { 231 | return VM_FLAG_NEGATIVE; 232 | } 233 | else { 234 | return VM_FLAG_POSITIVE; 235 | } 236 | } 237 | 238 | static void vm_setcc(vm_ctx vm, vm_reg reg) { 239 | assert(vm != NULL); 240 | 241 | vm->reg[VM_REG_PSR] = vm_sign_flag(vm->reg[reg]); 242 | } 243 | 244 | static vm_run_result vm_perform(vm_ctx vm, vm_byte instr) { 245 | assert(vm != NULL); 246 | 247 | DEBUG_TRACE("DEBUG vm_perform instr %x REG_PC %x\n", instr, vm->reg[VM_REG_PC]); 248 | 249 | switch ((vm_opcode)(instr >> 12)) { 250 | case VM_OPCODE_ADD: { 251 | vm_reg dr = (instr >> 9) & 0b111; 252 | vm_reg sr1 = (instr >> 6) & 0b111; 253 | 254 | if (instr & (1 << 5)) { 255 | vm_byte imm5 = sextend(instr, 5); 256 | DEBUG_TRACE("VM_OPCODE_ADD dr %x sr1 %x imm5 %x\n", dr, sr1, imm5); 257 | 258 | vm->reg[dr] = vm->reg[sr1] + imm5; 259 | } 260 | else { 261 | vm_reg sr2 = instr & 0b111; 262 | DEBUG_TRACE("VM_OPCODE_ADD dr %x sr1 %x sr2 %x\n", dr, sr1, sr2); 263 | 264 | vm->reg[dr] = vm->reg[sr1] + vm->reg[sr2]; 265 | } 266 | 267 | vm_setcc(vm, dr); 268 | break; 269 | } 270 | 271 | case VM_OPCODE_AND: { 272 | vm_reg dr = (instr >> 9) & 0b111; 273 | vm_reg sr1 = (instr >> 6) & 0b111; 274 | 275 | if (instr & (1 << 5)) { 276 | vm_byte imm5 = sextend(instr, 5); 277 | DEBUG_TRACE("VM_OPCODE_AND dr %x sr1 %x imm5 %x\n", dr, sr1, imm5); 278 | 279 | vm->reg[dr] = vm->reg[sr1] & imm5; 280 | } 281 | else { 282 | vm_reg sr2 = instr & 0b111; 283 | DEBUG_TRACE("VM_OPCODE_AND dr %x sr1 %x sr2 %x\n", dr, sr1, sr2); 284 | 285 | vm->reg[dr] = vm->reg[sr1] & vm->reg[sr2]; 286 | } 287 | 288 | vm_setcc(vm, dr); 289 | break; 290 | } 291 | 292 | case VM_OPCODE_BR: { 293 | vm_byte current_nzp = vm->reg[VM_REG_PSR] & 0b111; 294 | vm_byte desired_nzp = (instr >> 9) & 0b111; 295 | vm_addr pc_offset9 = sextend(instr, 9); 296 | DEBUG_TRACE("VM_OPCODE_BR current_nzp %x desired_nzp %x pc_offset9 %x\n", current_nzp, desired_nzp, pc_offset9); 297 | 298 | if (current_nzp & desired_nzp) { 299 | vm->reg[VM_REG_PC] += pc_offset9; 300 | } 301 | 302 | break; 303 | } 304 | 305 | case VM_OPCODE_JMP: { 306 | vm_reg baser = (instr >> 6) & 0b111; 307 | DEBUG_TRACE("VM_OPCODE_JMP baser %x\n", baser); 308 | 309 | vm->reg[VM_REG_PC] = vm->reg[baser]; 310 | break; 311 | } 312 | 313 | case VM_OPCODE_JSR: { 314 | // If this is a JSR R7 instruction, we need to make sure we don't accidentally overwrite the value in R7 315 | // before using it for the jump. These steps don't strictly match the ones in the 2nd edition of the book, 316 | // but according to documentation in the official lc3tools simulator, these steps perform the instruction 317 | // how it was intended. Future editions of the book will address this inconsistency. 318 | vm_addr original_pc = vm->reg[VM_REG_PC]; 319 | 320 | if (instr & (1 << 11)) { 321 | vm_addr pc_offset11 = sextend(instr, 11); 322 | DEBUG_TRACE("VM_OPCODE_JSR pc_offset11 %x\n", pc_offset11); 323 | 324 | vm->reg[VM_REG_PC] += pc_offset11; 325 | } 326 | else { 327 | vm_reg baser = (instr >> 6) & 0b111; 328 | vm_reg baser_value = vm->reg[baser]; 329 | DEBUG_TRACE("VM_OPCODE_JSR baser %x baser_value %x\n", baser, baser_value); 330 | 331 | vm->reg[VM_REG_PC] = baser_value; 332 | } 333 | 334 | vm->reg[7] = original_pc; 335 | 336 | break; 337 | } 338 | 339 | case VM_OPCODE_LD: { 340 | vm_reg dr = (instr >> 9) & 0b111; 341 | vm_addr pc_offset9 = sextend(instr, 9); 342 | DEBUG_TRACE("VM_OPCODE_LD dr %x pc_offset9 %x\n", dr, pc_offset9); 343 | 344 | vm->reg[dr] = vm_read(vm, vm->reg[VM_REG_PC] + pc_offset9); 345 | vm_setcc(vm, dr); 346 | 347 | break; 348 | } 349 | 350 | case VM_OPCODE_LDI: { 351 | vm_reg dr = (instr >> 9) & 0b111; 352 | vm_addr pc_offset9 = sextend(instr, 9); 353 | DEBUG_TRACE("VM_OPCODE_LDI dr %x pc_offset9 %x\n", dr, pc_offset9); 354 | 355 | vm->reg[dr] = vm_read(vm, vm_read(vm, vm->reg[VM_REG_PC] + pc_offset9)); 356 | vm_setcc(vm, dr); 357 | 358 | break; 359 | } 360 | 361 | case VM_OPCODE_LDR: { 362 | vm_reg dr = (instr >> 9) & 0b111; 363 | vm_reg baser = (instr >> 6) & 0b111; 364 | vm_addr offset6 = sextend(instr, 6); 365 | DEBUG_TRACE("VM_OPCODE_LDR dr %x baser %x offset6 %x\n", dr, baser, offset6); 366 | 367 | vm->reg[dr] = vm_read(vm, vm->reg[baser] + offset6); 368 | vm_setcc(vm, dr); 369 | 370 | break; 371 | } 372 | 373 | case VM_OPCODE_LEA: { 374 | vm_reg dr = (instr >> 9) & 0b111; 375 | vm_addr pc_offset9 = sextend(instr, 9); 376 | DEBUG_TRACE("VM_OPCODE_LEA dr %x pc_offset9 %x\n", dr, pc_offset9); 377 | 378 | vm->reg[dr] = vm->reg[VM_REG_PC] + pc_offset9; 379 | vm_setcc(vm, dr); 380 | 381 | break; 382 | } 383 | 384 | case VM_OPCODE_NOT: { 385 | vm_reg dr = (instr >> 9) & 0b111; 386 | vm_reg sr = (instr >> 6) & 0b111; 387 | DEBUG_TRACE("VM_OPCODE_NOT dr %x sr %x\n", dr, sr); 388 | 389 | vm->reg[dr] = ~vm->reg[sr]; 390 | vm_setcc(vm, dr); 391 | 392 | break; 393 | } 394 | 395 | case VM_OPCODE_RTI: { 396 | DEBUG_TRACE("VM_OPCODE_RTI\n"); 397 | return VM_RUN_UNIMPLEMENTED_OPCODE; 398 | } 399 | 400 | case VM_OPCODE_ST: { 401 | vm_reg sr = (instr >> 9) & 0b111; 402 | vm_addr pc_offset9 = sextend(instr, 9); 403 | DEBUG_TRACE("VM_OPCODE_ST sr %x pc_offset9 %x\n", sr, pc_offset9); 404 | 405 | vm_write(vm, vm->reg[VM_REG_PC] + pc_offset9, vm->reg[sr]); 406 | 407 | break; 408 | } 409 | 410 | case VM_OPCODE_STI: { 411 | vm_reg sr = (instr >> 9) & 0b111; 412 | vm_addr pc_offset9 = sextend(instr, 9); 413 | DEBUG_TRACE("VM_OPCODE_STI sr %x pc_offset9 %x\n", sr, pc_offset9); 414 | 415 | vm_write(vm, vm_read(vm, vm->reg[VM_REG_PC] + pc_offset9), vm->reg[sr]); 416 | 417 | break; 418 | } 419 | 420 | case VM_OPCODE_STR: { 421 | vm_reg sr = (instr >> 9) & 0b111; 422 | vm_reg baser = (instr >> 6) & 0b111; 423 | vm_addr offset6 = sextend(instr, 6); 424 | DEBUG_TRACE("VM_OPCODE_STR sr %x baser %x offset6 %x\n", sr, baser, offset6); 425 | 426 | vm_write(vm, vm->reg[baser] + offset6, vm->reg[sr]); 427 | 428 | break; 429 | } 430 | 431 | case VM_OPCODE_TRAP: { 432 | vm_addr trapvect8 = instr & 0xff; 433 | DEBUG_TRACE("VM_OPCODE_TRAP trapvect8 %x\n", trapvect8); 434 | 435 | if (trapvect8 == 0x20) { 436 | // handle GETC efficiently to prevent high CPU usage when idle 437 | vm->reg[0] = getchar(); 438 | } 439 | else { 440 | // fallback to OS implementation of remaining traps 441 | vm->reg[7] = vm->reg[VM_REG_PC]; 442 | vm->reg[VM_REG_PC] = vm_read(vm, trapvect8); 443 | } 444 | 445 | break; 446 | } 447 | 448 | case VM_OPCODE_RESERVED: 449 | DEBUG_TRACE("VM_OPCODE_RESERVED\n"); 450 | return VM_RUN_UNIMPLEMENTED_OPCODE; 451 | } 452 | 453 | return VM_RUN_SUCCESS; 454 | } 455 | 456 | vm_run_result vm_run(vm_ctx vm) { 457 | assert(vm != NULL); 458 | 459 | while (vm_read(vm, VM_ADDR_MCR) & VM_STATUS_BIT) { 460 | vm_run_result res = vm_perform(vm, vm_read(vm, vm->reg[VM_REG_PC]++)); 461 | 462 | if (res != VM_RUN_SUCCESS) { 463 | return res; 464 | } 465 | } 466 | 467 | return VM_RUN_SUCCESS; 468 | } 469 | --------------------------------------------------------------------------------