From 2ba17c52bd0a4861b416ee2bacc6405ab85ca16e Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Fri, 26 Oct 2018 12:29:48 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 + Makefile | 15 ++++ design.txt | 81 +++++++++++++++++++ programs/hello_world.m16 | 1 + programs/jump.m16 | Bin 0 -> 20 bytes programs/test.m16 | Bin 0 -> 20 bytes programs/write_mem.m16 | Bin 0 -> 10 bytes src/cpu.c | 169 +++++++++++++++++++++++++++++++++++++++ src/cpu.h | 34 ++++++++ src/instr.c | 52 ++++++++++++ src/instr.h | 74 +++++++++++++++++ src/mm.c | 69 ++++++++++++++++ src/mm.h | 37 +++++++++ src/syscall.c | 37 +++++++++ src/syscall.h | 32 ++++++++ src/vm.c | 80 ++++++++++++++++++ 16 files changed, 683 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 design.txt create mode 100644 programs/hello_world.m16 create mode 100644 programs/jump.m16 create mode 100644 programs/test.m16 create mode 100644 programs/write_mem.m16 create mode 100644 src/cpu.c create mode 100644 src/cpu.h create mode 100644 src/instr.c create mode 100644 src/instr.h create mode 100644 src/mm.c create mode 100644 src/mm.h create mode 100644 src/syscall.c create mode 100644 src/syscall.h create mode 100644 src/vm.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..563e7eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +m16vm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f88a008 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ + +CC = gcc +CFLAGS = -DMEM_SIZE=32 +LD = $(CC) + +VM = m16vm + +$(VM) : src/vm.o src/cpu.o src/mm.o src/instr.o src/syscall.o + $(LD) $(LDFLAGS)-o $@ $^ + +clean : + $(RM) src/*.o + +distclean : clean + $(RM) $(VM) diff --git a/design.txt b/design.txt new file mode 100644 index 0000000..15fe5e4 --- /dev/null +++ b/design.txt @@ -0,0 +1,81 @@ + +* 8 16-bit registers + +* Memory + 4096 bytes. address aligned by 2 bytes (16 bit) + + +* instruction set (16 bit) + +0001 - add +0010 - movl (move low byte) +0011 - movh (move high byte) +0100 - load word +0101 - store word +0110 - beq +0111 - jmp +1000 - jr (jump register) + +1111 - I/O Write + +I/O = only 8 bits, syscall? + +--- Format --- + +Register operation format (R): + + opcode (4) | rs (4) | r0 (4) | r1 (4) + + r0,r1 - Operand registers. + rs - save register + +Register operation format (RI): + + opcode (4) | rs (4) | r0 (4) | offset (signed 4) + + r0 - Operand registers. + offset - constant offset from r0 value. + rs - save register + +Constant operation format (I): + + opcode (4) | reg (4) | data (signed 8) + + reg - Register + data - Constant data to insert into reg. + +Jump format (J): + + opcode (4) | addr (signed 12) + + + +* load ascii hex from file. +* jump, branch instructions + +start: +add r0, r4 # r4 = 1 + +beq r0, r1 +jmp 2 +jmp 3 +load r3 +jr r3 +write r0 + + +if (r0 == r1) + jmp(b1); +else + jmp(b2); +b1: + load(r3); + jmpl(r3); +b2: + write(r0); + + +while (r0 == r1) + r0++; + +beq r0 r1 # diff --git a/programs/hello_world.m16 b/programs/hello_world.m16 new file mode 100644 index 0000000..c705a4d --- /dev/null +++ b/programs/hello_world.m16 @@ -0,0 +1 @@ + Hð Eð !Lñ ñ "Oò ð Wð ò Rð ñ Dð \ No newline at end of file diff --git a/programs/jump.m16 b/programs/jump.m16 new file mode 100644 index 0000000000000000000000000000000000000000..fd29e38fbe46375a4053ba4e9eb7f8888c366148 GIT binary patch literal 20 bcmY#jP-Il%Qf5q44G<6%7GN%5VPF6N7QX@{ literal 0 HcmV?d00001 diff --git a/programs/test.m16 b/programs/test.m16 new file mode 100644 index 0000000000000000000000000000000000000000..59467cd55b0d99146aa6abf613c49064d1c5f71f GIT binary patch literal 20 bcmY%RXYgNPvB8l5fe#D?tjY|=48aNjRka3_ literal 0 HcmV?d00001 diff --git a/programs/write_mem.m16 b/programs/write_mem.m16 new file mode 100644 index 0000000000000000000000000000000000000000..86cab72c87597fa067fc54a35dc7a57d4f4f98ab GIT binary patch literal 10 RcmY%RugE3L_?aQZ000oG0@eTk literal 0 HcmV?d00001 diff --git a/src/cpu.c b/src/cpu.c new file mode 100644 index 0000000..a64320a --- /dev/null +++ b/src/cpu.c @@ -0,0 +1,169 @@ +/* cpu.c + * + * Copyright (C) 2012,2014-2015 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cpu.h" +#include "mm.h" +#include "syscall.h" +#include "instr.h" + +// Registers r0, r15 +int16_t reg[16] = { 0 }; + +/* Program */ +unsigned char *instr_mem = NULL; +unsigned long instr_cnt = 0; + +/* Program counter */ +uint16_t pc = 0; + +/* CPU flags */ +#define CPU_FLAGS_HALT (1<<0) +unsigned char cpu_flags = CPU_FLAGS_HALT; + +#define debug(...) fprintf(stderr, __VA_ARGS__) + +static void execute(struct instr *instr) { + + switch(instr->opcode) { + case OP_NOOP : + debug("noop\n"); + /* Do nothing */ + break; + case OP_ADD : + debug("add\tr%i r%i r%i\n", instr->r.rs, instr->r.r0, instr->r.r1); + reg[instr->r.rs] = reg[instr->r.r0] + reg[instr->r.r1]; + break; + case OP_MOVL : + debug("movl\tr%i #%i\n", instr->i.rs, instr->i.imm); + reg[instr->r.rs] = (reg[instr->r.rs] & 0xFF00) | instr->i.imm; + break; + case OP_MOVH : + debug("movh\tr%i #%i\n", instr->i.rs, instr->i.imm); + reg[instr->r.rs] = (reg[instr->r.rs] & 0x00FF) | (((uint16_t) instr->i.imm) << 8); + break; + case OP_LW : + debug("lw\tr%i r%i #%i\n", instr->r.rs, instr->r.r0, instr->r.r1); + reg[instr->r.rs] = mm_lw(reg[instr->r.r0] + instr->r.r1); + break; + case OP_SW : + debug("sw\tr%i r%i #%i\n", instr->r.rs, instr->r.r0, instr->r.r1); + mm_sw(reg[instr->r.rs] + instr->r.r1, reg[instr->r.r0]); + break; + case OP_JMP : + debug("jmp\t#%i\n", instr->j.addr); + cpu_set_pc(instr->j.addr); + break; + case OP_JR : + debug("jr\t#r%i(#%i)\n", instr->i.rs, instr->i.imm); + break; + case OP_BEQ : + debug("beq\tr%i r%i #%i\n", instr->ri.rs, instr->ri.r0, instr->ri.offset); + // Compare rs, r0 + if (reg[instr->ri.rs] == reg[instr->ri.r0]) + cpu_set_pc(pc + instr->ri.offset); + break; + case OP_SYSC_WR : + debug("I/O write r%i: %i\n", instr->r.rs, reg[instr->r.rs]); + syscall_write(reg[instr->i.rs], instr->i.imm); + break; + default : + fprintf(stderr, "Invalid instruction (%.2X)\n", instr->opcode); + cpu_flags |= CPU_FLAGS_HALT; + break; + } +} + +void print_memory() { + + int i = 0; + + printf("\n"); + + for(i = 0; i < 32; i++) { + printf("%.2X ", memory[i]); + } + printf("\n"); +} + +static unsigned char* instr_fetch() { + + if (pc + 1 >= instr_cnt >> 1) + cpu_flags |= CPU_FLAGS_HALT; + + return instr_mem + (pc++ << 1); +} + +int cpu_instr_load(void *ptr, unsigned len) { + + if (len % 2) { + fprintf(stderr, "Error: Instruction length must be a multiple of 2\n"); + return -1; + } + + instr_mem = ptr; + instr_cnt = len; + return len; +} + +void cpu_instr_unload() { + + instr_mem = NULL; + instr_cnt = 0; +} + +void cpu_set_pc(uint16_t addr) { + + if (addr > instr_cnt / 2) { + fprintf(stderr, "Runtime error: Invalid instruction address %ui\n", addr); + cpu_flags |= CPU_FLAGS_HALT; + return; + } + pc = addr; +} + +void cpu_run() { + + mm_init(); + + cpu_flags &= ~CPU_FLAGS_HALT; + + while(!(cpu_flags & CPU_FLAGS_HALT)) { + struct instr instr; + unsigned char* next = instr_fetch(); + + // decode instruction. + instr_decode(next, &instr); + + // Execute it. + execute(&instr); + } + + print_memory(); + + mm_exit(); +} diff --git a/src/cpu.h b/src/cpu.h new file mode 100644 index 0000000..c9eef2e --- /dev/null +++ b/src/cpu.h @@ -0,0 +1,34 @@ +/* cpu.h + * + * Copyright (C) 2012,2014-2015 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#ifndef CPU_H +#define CPU_H + +#include +#include "instr.h" + +int cpu_instr_load(void *ptr, unsigned len); + +void cpu_instr_unload(); + +void cpu_set_pc(uint16_t addr); + +void cpu_run(); + +#endif /* CPU_H */ diff --git a/src/instr.c b/src/instr.c new file mode 100644 index 0000000..9e5636b --- /dev/null +++ b/src/instr.c @@ -0,0 +1,52 @@ +/* instr.c + * + * Copyright (C) 2012,2014 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include "instr.h" + +void instr_decode(unsigned char *instr, struct instr *out) { + + out->opcode = *instr >> 4; + + if (out->opcode == OP_NOOP) + return; + + // J-Type + if (out->opcode == OP_JMP) { + + out->j.addr = ((*instr & 0xF) << 8) + *(instr + 1); + + // if MSB (bit 12) is set + // perform 2s complement by setting bit 13-16 to 1 + /* + if (out->j.addr & 0x0800) + out->j.addr |= 0xF000; */ + } else { + out->r.rs = *instr & 0xF; + + // I-Type + if (out->opcode == OP_MOVL || out->opcode == OP_MOVH || out->opcode == OP_SYSC_WR) { + out->i.imm = *(instr + 1); + } + // R/RI-Type + else { + out->r.r0 = *(instr + 1) >> 4; + out->r.r1 = *(instr + 1) & 0xF; + } + } +} diff --git a/src/instr.h b/src/instr.h new file mode 100644 index 0000000..070fc51 --- /dev/null +++ b/src/instr.h @@ -0,0 +1,74 @@ +/* instr.h + * + * Copyright (C) 2012,2014 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#ifndef INSTR_H +#define INSTR_H + +#include + +/* Opcodes */ +#define OP_NOOP 0 +#define OP_ADD 1 +#define OP_MOVL 2 +#define OP_MOVH 3 +#define OP_LW 4 +#define OP_SW 5 +#define OP_BEQ 6 +#define OP_JMP 7 +#define OP_JR 8 + +/* System calls */ +#define OP_SYSC_RD 14 /* read char from stdin */ +#define OP_SYSC_WR 15 /* write char to stdout */ + +/* Register type */ +struct instr_R { + uint8_t rs; + uint8_t r0; + uint8_t r1; +}; + +struct instr_RI { + uint8_t rs; + uint8_t r0; + int8_t offset; +}; + +struct instr_I { + uint8_t rs; + int8_t imm; +}; + +struct instr_J { + uint16_t addr; +}; + +struct instr { + uint8_t opcode; + union { + struct instr_R r; + struct instr_RI ri; + struct instr_I i; + struct instr_J j; + }; +}; + +void instr_decode(unsigned char *nibble, struct instr *instr); + +#endif /* INSTR_H */ diff --git a/src/mm.c b/src/mm.c new file mode 100644 index 0000000..bb2d250 --- /dev/null +++ b/src/mm.c @@ -0,0 +1,69 @@ +/* mm.c + * + * Copyright (C) 2012,2014 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include +#include +#include "mm.h" + +#ifndef MEM_SIZE +/* Set a default memory size. (2^16) */ +#define MEM_SIZE 65536 +#endif + +#define EXCEPTION_STRING "Runtime exception: Memory address '%u' is out of bounds.\n" + +int16_t* base_addr = NULL; +uint8_t *memory = NULL; + +static void check_bounds(uint16_t addr) { + + if (addr > MEM_SIZE) { + fprintf(stderr, EXCEPTION_STRING, addr); + exit(1); + } +} + +void mm_init() { + + base_addr = malloc(MEM_SIZE); + memory = (uint8_t*) base_addr; +} + +void mm_exit() { + + if (base_addr) { + free(base_addr); + base_addr = NULL; + memory = NULL; + } +} + +void mm_sw(uint16_t addr, int16_t value) { + + check_bounds(addr); + + base_addr[addr] = value; +} + +int16_t mm_lw(uint16_t addr) { + + check_bounds(addr); + + return base_addr[addr]; +} diff --git a/src/mm.h b/src/mm.h new file mode 100644 index 0000000..7e58952 --- /dev/null +++ b/src/mm.h @@ -0,0 +1,37 @@ +/* mm.h + * + * Copyright (C) 2012,2014 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#ifndef MM_H +#define MM_H + +#include + +extern uint8_t *memory; + +void mm_init(); + +void mm_exit(); + +/* Save a word to memory. */ +void mm_sw(uint16_t addr, int16_t value); + +/* Load a word from memory. */ +int16_t mm_lw(uint16_t addr); + +#endif /* MM_H */ diff --git a/src/syscall.c b/src/syscall.c new file mode 100644 index 0000000..5f4207f --- /dev/null +++ b/src/syscall.c @@ -0,0 +1,37 @@ +/* syscall.h +* +* Copyright (C) 2012,2014-2015 Henrik Hautakoski +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +* MA 02110-1301, USA. +*/ +#include +#include "syscall.h" + +void syscall_write(int16_t value, int8_t op) { + + switch(op) { + case SYSW_INT16 : + printf("%i", value); + break; + case SYSW_INT8 : + printf("%i", (int8_t) value); + break; + case SYSW_CHAR : + default: + printf("%c", (unsigned char) value); + break; + } +} diff --git a/src/syscall.h b/src/syscall.h new file mode 100644 index 0000000..ced4461 --- /dev/null +++ b/src/syscall.h @@ -0,0 +1,32 @@ +/* syscall.h +* +* Copyright (C) 2012,2014-2015 Henrik Hautakoski +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +* MA 02110-1301, USA. +*/ + +#ifndef SYSCALL_H +#define SYSCALL_H + +#include + +#define SYSW_INT16 0x0 +#define SYSW_INT8 0x1 +#define SYSW_CHAR 0x2 + +void syscall_write(int16_t value, int8_t op); + +#endif /* SYSCALL_H */ diff --git a/src/vm.c b/src/vm.c new file mode 100644 index 0000000..df4ce97 --- /dev/null +++ b/src/vm.c @@ -0,0 +1,80 @@ +/* vm.c + * + * Copyright (C) 2012,2014 Henrik Hautakoski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include "cpu.h" + +// Load program from file +unsigned long load_program(int fd, unsigned char **buf) { + + struct stat st; + ssize_t rc; + + if (fstat(fd, &st) < 0) + return -1; + + *buf = malloc(st.st_size); + if (*buf == NULL) + return -1; + + rc = read(fd, *buf, st.st_size); + if (rc < 0) + free(*buf); + return rc; +} + +int main(int argc, char **argv) { + + int fd; + unsigned char *instr_ptr; + unsigned instr_len; + + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Could not open file %s: %s\n", + argv[1], strerror(errno)); + return 1; + } + + instr_len = load_program(fd, &instr_ptr); + close(fd); + + if (instr_len < 0) + return 1; + + cpu_instr_load(instr_ptr, instr_len); + + cpu_run(); + + if (instr_ptr) + free(instr_ptr); + + return 0; +}