Initial commit of NED1 front panel code I've been hacking on.
[screensavers] / hacks / NEDsim / simulator.c
diff --git a/hacks/NEDsim/simulator.c b/hacks/NEDsim/simulator.c
new file mode 100644 (file)
index 0000000..4857946
--- /dev/null
@@ -0,0 +1,515 @@
+/* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com>                  */
+/* See LICENSE.txt file for copyright and license details.                    */
+
+/* -------------------------------------------------------------------------- */
+/* NED1 Simulator                                                             */
+/* -------------------------------------------------------------------------- */
+
+// TODO: Make a bunch of functions private in this file.
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <termios.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "a.out.h"
+#include "simulator.h"
+
+int
+is_stdin_nonempty(void)
+{
+    fd_set read_fds;
+    FD_ZERO(&read_fds);
+    FD_SET(STDIN_FILENO, &read_fds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+
+    int retval = select(1, &read_fds, NULL, NULL, &timeout);
+
+    if (retval == -1) {
+        /* TODO: How do I want to handle this error? */
+    }
+
+    return retval;
+}
+
+uint32_t
+generate_binary_psw(struct NEDstate * state)
+{
+    uint32_t psw = 0;
+    if (state->active_thread->psw->zero) psw |= 0b1;
+    if (state->active_thread->psw->negative) psw |= 0b10;
+    return psw;
+}
+
+void
+ram_w_byte(struct NEDstate * state, uint32_t address, uint8_t data)
+{
+    state->ram[address] = data;
+}
+
+uint8_t
+ram_r_byte(struct NEDstate * state, uint32_t address)
+{
+    return state->ram[address];
+}
+
+/* For now, with only a terminal for IO, we pick off IO requests when accessing RAM. */
+/* TODO: Improve this before adding any other IO devices like disks. */
+
+void
+ram_w_word(struct NEDstate * state, uint32_t address, uint32_t data)
+{
+    /* TODO: Since PC and PSW are memory mapped, they should accept writes. */
+    /*       Should writes to the PC automatically reset the syllable counter? */
+    if (address == 0x8000000) { /* SLU: XBUF */
+        printf("%c", data);
+        fflush(stdout);
+    } else if (address == 0x0 || address == 0x4) {
+        /* Intentionally empty */
+    } else if (address >= 0x20000000) {
+        for (int i=3; i>=0; i--) {
+            uint8_t tmp_byte = ((data >> (8*(3-i))) & 0xff);
+            ram_w_byte(state,address+i,tmp_byte);
+        }
+    }
+}
+
+uint32_t
+ram_r_word(struct NEDstate * state, uint32_t address)
+{
+    if (address == 0x0) {               /* Zero register */
+        return 0b0;
+    } else if (address == 0x4) {        /* 0x80000000 register */
+        return 0x80000000;
+    } else if (address == 0x8) {        /* PC register */
+        return state->active_thread->pc;
+    } else if (address == 0xC) {        /* PSW register */
+        return generate_binary_psw(state);
+    } else if (address == 0x8000004) {  /* SLU: XCSR */
+        /* TODO: Should I artificially restrict printing in the simulator? */
+        /*       It might help catch bugs like the GCC bug that slipped past SIMH. */
+        return 0b1;
+    } else if (address == 0x8000008) {  /* SLU: RBUF */
+        if (is_stdin_nonempty()) {
+            return getchar();
+        } else {
+            return (uint8_t)rand();
+        }
+    } else if (address == 0x800000C) {  /* SLU: RCSR */
+        if (is_stdin_nonempty()) {
+            return 0b1;
+        } else {
+            return 0b0;
+        }
+    } else if (address >= 0x20000000) { /* RAM */
+        uint32_t word = 0;
+        for (int i=0; i<4; i++) word |= (ram_r_byte(state,address+i)) << (8*(3-i));
+        return word;
+    }
+    return 0b0;
+}
+
+uint32_t
+fetch_instruction_word(struct NEDstate * state)
+{
+    uint32_t word = ram_r_word(state, state->active_thread->pc);
+    state->active_thread->pc += BPW;
+    return word;
+}
+
+void
+stack_w(struct NEDthread * thread, uint32_t value, uint8_t offset)
+{
+    thread->stack[thread->sp - (offset + 1)] = value;
+}
+
+uint32_t
+stack_r(struct NEDthread * thread, uint8_t offset)
+{
+    return thread->stack[thread->sp - (offset + 1)];
+}
+
+void
+stack_push(struct NEDthread * thread, uint32_t value)
+{
+    thread->stack[thread->sp++] = value;
+}
+
+uint32_t
+stack_pop(struct NEDthread * thread)
+{
+    return thread->stack[--thread->sp];
+}
+
+void
+set_psw_flags(uint32_t word, struct NEDstate * state)
+{
+    if (word == 0) {
+        state->active_thread->psw->zero = true;
+    } else {
+        state->active_thread->psw->zero = false;
+    }
+    if (word & 0x80000000) {
+        state->active_thread->psw->negative = true;
+    } else {
+        state->active_thread->psw->negative = false;
+    }
+}
+
+void
+ned_instruction_and(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 & operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_or(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 | operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_not(struct NEDstate * state)
+{
+    stack_push(state->active_thread, ~stack_pop(state->active_thread));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_xor(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 ^ operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_add(struct NEDstate * state)
+{
+    uint32_t operand1 = stack_pop(state->active_thread);
+    uint32_t operand2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, (operand1 + operand2));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_mvstck(struct NEDstate * state)
+{
+    uint32_t new_id = stack_pop(state->active_thread);
+    if (new_id < THREAD_COUNT) {
+        state->active_thread = state->thread[new_id];
+    } else {
+        printf("ERROR: Attempted MVSTCK to ID higher than THREAD_COUNT.\n");
+        state->halted = true;
+    }
+}
+
+void
+ned_instruction_shift(struct NEDstate * state)
+{
+    /* TODO: Bounds check: Either all inputs are valid OR shift_by < 32. */
+    /* I guess this also depends if I'm shifting-and-dropping, or barrel-shifting. */
+    /* How should I pad for a right shift if I shift-and-drop? Sign extend? */
+    uint32_t shift_by = stack_pop(state->active_thread);
+    uint32_t word = stack_pop(state->active_thread);
+    if (shift_by & 0x80000000) {
+        stack_push(state->active_thread, (word << (shift_by & 0x7fffffff)));
+    } else {
+        stack_push(state->active_thread, (word >> shift_by));
+    }
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_test(struct NEDstate * state)
+{
+    uint32_t word = stack_pop(state->active_thread);
+    set_psw_flags(word, state);
+}
+
+void
+ned_instruction_jmp(struct NEDstate * state)
+{
+    state->active_thread->pc = stack_pop(state->active_thread);
+    // The SC is caught and reset by the main loop since the PC changed.
+}
+
+void
+ned_instruction_swap(struct NEDstate * state)
+{
+    uint32_t temp1 = stack_pop(state->active_thread);
+    uint32_t temp2 = stack_pop(state->active_thread);
+    stack_push(state->active_thread, temp1);
+    stack_push(state->active_thread, temp2);
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_brz(struct NEDstate * state)
+{
+    uint32_t new_pc = stack_pop(state->active_thread);
+    uint32_t test_word = stack_pop(state->active_thread);
+    if (test_word == 0) {
+        state->active_thread->pc = new_pc;
+        // The SC is caught and reset by the main loop since the PC changed.
+    }
+}
+
+void
+ned_instruction_load(struct NEDstate * state)
+{
+    uint32_t address = stack_pop(state->active_thread);
+    stack_push(state->active_thread, ram_r_word(state, address));
+    set_psw_flags(stack_r(state->active_thread,0), state);
+}
+
+void
+ned_instruction_store(struct NEDstate * state)
+{
+    uint32_t address = stack_pop(state->active_thread);
+    uint32_t data = stack_pop(state->active_thread);
+    ram_w_word(state, address, data);
+}
+
+void
+ned_instruction_halt(struct NEDstate * state)
+{
+    printf("Halting.\n");
+    state->halted = true;
+}
+
+void
+execute_syllable(struct NEDstate * state, enum syllables syllable)
+{
+    if (syllable & 0b100000) { /* Check the first bit of the syllable. 1 means IM_x. */
+        stack_push(state->active_thread, (uint32_t)(syllable & 0b11111));
+    } else if (syllable & 0b10000) { /* 1 in 2nd bit means LDSP+x or STSP+x instruction. */
+        if (syllable & 0b1000) { /* LDSP+x */
+            stack_push(state->active_thread,stack_r(state->active_thread,(syllable & 0b111)));
+            set_psw_flags(stack_r(state->active_thread,0), state);
+        } else { /* STSP+x */
+            stack_w(state->active_thread,stack_pop(state->active_thread),(syllable & 0b111));
+        }
+    } else {
+        switch (syllable) {
+            case AND:       ned_instruction_and(state);     break;
+            case OR:        ned_instruction_or(state);      break;
+            case NOT:       ned_instruction_not(state);     break;
+            case XOR:       ned_instruction_xor(state);     break;
+            case ADD:       ned_instruction_add(state);     break;
+            case MVSTCK:    ned_instruction_mvstck(state);  break;
+            case SHIFT:     ned_instruction_shift(state);   break;
+            case CMPSWP:    /* TODO */                      break;
+            case TEST:      ned_instruction_test(state);    break;
+            case JMP:       ned_instruction_jmp(state);     break;
+            case SWAP:      ned_instruction_swap(state);    break;
+            case BRZ:       ned_instruction_brz(state);     break;
+            case LOAD:      ned_instruction_load(state);    break;
+            case STORE:     ned_instruction_store(state);   break;
+            case NOP:       /* Intentionally blank */       break;
+            case HALT:      ned_instruction_halt(state);    break;
+            default:
+                printf("ERROR: Attempted to execute illegal syllable: 0o%o\n", syllable);
+                state->halted = true;
+                break;
+        }
+    }
+}
+
+uint8_t
+extract_syllable_from_word(uint32_t word, uint8_t index)
+{
+    uint32_t mask = 0b111111 << 6*(4-index);
+    return (word & mask) >> 6*(4-index);
+}
+
+void
+parse_aout_file(FILE * input, struct exec * aout_exec, uint8_t * text_segment,
+                struct nlist ** symbol_table, uint32_t * symbol_count)
+{
+    uint32_t read_count = 0;
+
+    /* Read in and check the a.out header. */
+    for (uint32_t i=0; i<8; i++) {
+        switch (i) {
+            case 0: read_count = fread(&(aout_exec->a_midmag), 4, 1, input); break;
+            case 1: read_count = fread(&(aout_exec->a_text),   4, 1, input); break;
+            case 2: read_count = fread(&(aout_exec->a_data),   4, 1, input); break;
+            case 3: read_count = fread(&(aout_exec->a_bss),    4, 1, input); break;
+            case 4: read_count = fread(&(aout_exec->a_syms),   4, 1, input); break;
+            case 5: read_count = fread(&(aout_exec->a_entry),  4, 1, input); break;
+            case 6: read_count = fread(&(aout_exec->a_trsize), 4, 1, input); break;
+            case 7: read_count = fread(&(aout_exec->a_drsize), 4, 1, input); break;
+        }
+        if (read_count != 1) {
+            fprintf(stderr, "ERROR: Invalid a.out header.\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+    if (N_BADMAG(*aout_exec)) {
+        fprintf(stderr, "ERROR: Invalid magic number in a.out header.\n");
+        exit(EXIT_FAILURE);
+    } else if (N_GETMID(*aout_exec) != MID_NED) {
+        fprintf(stderr, "ERROR: Executable not intended for NED Machine ID.\n");
+        exit(EXIT_FAILURE);
+    }
+
+    /* Read in the text segment. */
+    uint32_t text_segment_size = (N_DATOFF(*aout_exec) - N_TXTOFF(*aout_exec));
+    read_count = fread(text_segment, 1, text_segment_size, input);
+    if (read_count != text_segment_size) {
+        fprintf(stderr, "ERROR: Failed to read entire text segment.\n");
+        exit(EXIT_FAILURE);
+    }
+
+    /* Correct the byte order. */
+    for (uint32_t i=0; i < (text_segment_size / 4); i++) {
+        uint8_t temp_word[4];
+        for (uint8_t j=0; j<4; j++) temp_word[j] = text_segment[((i*4)+j)];
+        for (uint8_t j=0; j<4; j++) text_segment[((i*4)+j)] = temp_word[(3-j)];
+    }
+
+    /* Read in the symbol table. */
+    *symbol_count = ((N_STROFF(*aout_exec) - N_SYMOFF(*aout_exec)) / 20); /* 20 bytes per symbol. */
+    *symbol_table = malloc((*symbol_count) * sizeof(struct nlist));
+    for (uint32_t i=0; i < *symbol_count; i++) {
+        for (uint32_t j=0; j<5; j++) {
+            switch (j) {
+                case 0: read_count = fread(&((*symbol_table)[i].n_un.n_strx), 4, 1, input); break;
+                case 1: read_count = fread(&((*symbol_table)[i].n_type),      4, 1, input); break;
+                case 2: read_count = fread(&((*symbol_table)[i].n_other),     4, 1, input); break;
+                case 3: read_count = fread(&((*symbol_table)[i].n_desc),      4, 1, input); break;
+                case 4: read_count = fread(&((*symbol_table)[i].n_value),     4, 1, input); break;
+            }
+            if (read_count != 1) {
+                fprintf(stderr, "ERROR: Unable to read entire symbol table.\n");
+                exit(EXIT_FAILURE);
+            }
+        }
+    }
+
+    /* Read in the string table and update the symbol table entries with pointers to new strings. */
+    uint32_t string_table_size;
+    read_count = fread(&string_table_size, 4, 1, input);
+    if (read_count != 1) {
+        fprintf(stderr, "ERROR: Failed to read string table size.\n");
+        exit(EXIT_FAILURE);
+    }
+    for (uint32_t i=0; i < *symbol_count; i++) {
+        uint32_t len = 0;
+        if (i < ((*symbol_count)-1)) {
+            len = ((*symbol_table)[i+1].n_un.n_strx - (*symbol_table)[i].n_un.n_strx);
+        } else {
+            len = (string_table_size - (*symbol_table)[i].n_un.n_strx);
+        }
+        (*symbol_table)[i].n_un.n_name = malloc(len);
+        read_count = fread((*symbol_table)[i].n_un.n_name, 1, len, input);
+        if (read_count != len) {
+            fprintf(stderr, "ERROR: Failed to read a string from the string table.\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+
+}
+
+struct NEDstate *
+init_simulator(void)
+{
+    struct NEDstate * state = malloc(sizeof(struct NEDstate));
+    state->hack = malloc(sizeof(struct NEDhack));
+    for (size_t i=0; i < THREAD_COUNT; i++) {
+        state->thread[i] = malloc(sizeof(struct NEDthread));
+        state->thread[i]->psw = malloc(sizeof(struct NEDpsw));
+    }
+    state->thread[0]->pc = 0;
+    state->thread[0]->sc = 0;
+    state->thread[0]->sp = 0;
+    state->thread[0]->psw->zero = false;
+    state->thread[0]->psw->negative = false;
+    state->thread[0]->pc = 0x20000000;  /* Data region starts 512 MB into address space. */
+    state->active_thread = state->thread[0];  /* By convention, use thread 0 for init. */
+    state->halted = false;
+    state->hack->resume_word = false;
+
+// TODO: This needs to be passed in as a CLI option.
+#define AOUT_PATH "./test.out"
+
+    /* Load an initial image into memory. */
+    uint32_t address = 0x20000000;
+    struct exec aout_exec;
+    struct nlist * symbol_table;
+    uint32_t symbol_count;
+    FILE * input = NULL;
+    if ((input = fopen(AOUT_PATH, "r")) == NULL) {
+        fprintf(stderr, "ERROR: %s: %s\n", AOUT_PATH, strerror(errno));
+        state->halted = true;
+    }
+    parse_aout_file(input, &aout_exec, &(state->ram[address]), &symbol_table, &symbol_count);
+    fclose(input);
+
+    return state;
+}
+
+struct NEDstate *
+run_simulator(struct NEDstate * state)
+{
+    if (state->halted) return state;
+
+    /* Fetch instruction word. */
+    uint32_t iw;
+    if (state->hack->resume_word) {
+        iw = state->hack->iw;
+    } else {
+        iw = fetch_instruction_word(state);
+    }
+
+    /* Decode instruction word format and execute. */
+    if (iw & (0b1 << 31)) {                /* Instruction word is type A. */
+        stack_push(state->active_thread, (iw << 1));
+    } else if ((iw & (0b11 << 30)) == 0) { /* Instruction word is type C. */
+        uint8_t syllable = extract_syllable_from_word(iw, state->active_thread->sc);
+        state->active_thread->sc++; // TODO: Should this be part of extract_syllable_from_word()? After all, incrementing the PC is done in fetch_instruction_word().
+        uint32_t pre_execution_pc = state->active_thread->pc; // TODO: This is so we can catch JMP/JSR/etc subroutines that need the SC to be reset to zero.
+        execute_syllable(state, syllable);
+        if (state->active_thread->pc != pre_execution_pc) {
+            // Jumped to a new address, so prepare to execute a new instruction word.
+            state->active_thread->sc = 0;
+            state->hack->resume_word = false;
+        } else if (state->active_thread->sc >= SPW) {
+            // Just executed the last syllable in this word, time to follow the PC to the next word.
+            state->active_thread->sc = 0;
+            state->hack->resume_word = false;
+        } else {
+            // More syllables remain to be executed in this instruction word.
+            state->hack->resume_word = true;
+            state->hack->iw = iw;
+        }
+    } else {
+        state->halted = true;
+        fprintf(stderr, "WARNING: Halting due to attempted execution of illegal instruction.\n");
+    }
+
+    return state;
+}