summaryrefslogtreecommitdiffstats
path: root/src/emu
diff options
context:
space:
mode:
authorBenedikt Böhm <bb@xnull.de>2009-05-18 20:53:42 +0200
committerBenedikt Böhm <bb@xnull.de>2009-05-18 20:53:42 +0200
commit49f510d2d60129526832bfcd9c0f4049962bc80e (patch)
tree2828b773c75bdbc4014a3421c8046778a1a2b11b /src/emu
parent144258f0196b69cbdd2f29bd501276942efc3182 (diff)
downloadswppy-49f510d2d60129526832bfcd9c0f4049962bc80e.tar.gz
swppy-49f510d2d60129526832bfcd9c0f4049962bc80e.tar.xz
swppy-49f510d2d60129526832bfcd9c0f4049962bc80e.zip
move stuff around and create initial source structure
Diffstat (limited to '')
-rw-r--r--src/emu/.gitignore3
-rw-r--r--src/emu/Makefile19
-rw-r--r--src/emu/TODO4
-rw-r--r--src/emu/asm.c33
-rw-r--r--src/emu/asm.h8
-rw-r--r--src/emu/cpu.c237
-rw-r--r--src/emu/cpu.h29
-rw-r--r--src/emu/log.h33
-rw-r--r--src/emu/mem.c3
-rw-r--r--src/emu/mem.h8
-rw-r--r--src/emu/opc.c63
-rw-r--r--src/emu/opc.h68
-rw-r--r--src/emu/riscas.c40
-rw-r--r--src/emu/risci.c134
-rw-r--r--src/emu/syscall.c23
-rw-r--r--src/emu/syscall.h16
-rw-r--r--src/emu/test.S15
17 files changed, 736 insertions, 0 deletions
diff --git a/src/emu/.gitignore b/src/emu/.gitignore
new file mode 100644
index 0000000..44067fa
--- /dev/null
+++ b/src/emu/.gitignore
@@ -0,0 +1,3 @@
+riscas
+risci
+test
diff --git a/src/emu/Makefile b/src/emu/Makefile
new file mode 100644
index 0000000..86d4aba
--- /dev/null
+++ b/src/emu/Makefile
@@ -0,0 +1,19 @@
+CC = gcc
+CFLAGS = -std=c99 -D_GNU_SOURCE -Wall -Wextra -pedantic -ggdb3
+LDFLAGS = -lreadline
+
+all: risci riscas
+
+risci: asm.c cpu.c mem.c opc.c syscall.c
+
+riscas: asm.c opc.c
+
+test: riscas test.S
+ ./riscas test.S test
+
+check: risci test
+ ./risci -d test
+
+clean:
+ rm -f test
+ rm -f riscas risci
diff --git a/src/emu/TODO b/src/emu/TODO
new file mode 100644
index 0000000..e3445bc
--- /dev/null
+++ b/src/emu/TODO
@@ -0,0 +1,4 @@
+- define application binary interface
+- add some more usefull system calls
+- enhance trap handling
+- check arithmetic operations for overflow and signed correctness
diff --git a/src/emu/asm.c b/src/emu/asm.c
new file mode 100644
index 0000000..caca72a
--- /dev/null
+++ b/src/emu/asm.c
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <stdint.h>
+
+#include "cpu.h"
+#include "log.h"
+#include "opc.h"
+
+uint32_t compile(const char *line)
+{
+ char mnem[4];
+ int32_t a = 0, b = 0, c = 0;
+
+ /* arithmetic & logic */
+ if (sscanf(line, "%3s r%2d, r%2d, r%2d", mnem, &a, &b, &c) == 4)
+ return mnemonic2opc(mnem) | ((a & 0x1F) << 21) | ((b & 0x1F) << 16) | ((c & 0x1F) << 11);
+
+ /* load/store & branch */
+ if (sscanf(line, "%3s r%2d, r%2d, %d", mnem, &a, &b, &c) == 4)
+ return mnemonic2opc(mnem) | ((a & 0x1F) << 21) | ((b & 0x1F) << 16) | (c & 0xFFFF);
+
+ if (sscanf(line, "%3s r%2d, %d", mnem, &b, &c) == 3)
+ return mnemonic2opc(mnem) | ((b & 0x1F) << 16) | (c & 0xFFFF);
+
+ /* jump */
+ if (sscanf(line, "%3s r%2d", mnem, &a) == 2)
+ return mnemonic2opc(mnem) | ((a & 0x1F) << 21);
+
+ /* misc */
+ if (sscanf(line, "%3s", mnem) == 1)
+ return mnemonic2opc(mnem);
+
+ return 0xFFFFFFFF;
+}
diff --git a/src/emu/asm.h b/src/emu/asm.h
new file mode 100644
index 0000000..9a4fd37
--- /dev/null
+++ b/src/emu/asm.h
@@ -0,0 +1,8 @@
+#ifndef _ASM_H
+#define _ASM_H
+
+#include <stdint.h>
+
+uint32_t compile(const char *line);
+
+#endif
diff --git a/src/emu/cpu.c b/src/emu/cpu.c
new file mode 100644
index 0000000..b0664cd
--- /dev/null
+++ b/src/emu/cpu.c
@@ -0,0 +1,237 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <string.h>
+
+#include "cpu.h"
+#include "log.h"
+#include "mem.h"
+#include "opc.h"
+#include "syscall.h"
+
+// program counter
+uint32_t PC;
+
+// status bits
+bool N, Z;
+
+// 32 general purpose registers
+uint32_t GPR[32];
+
+/* extract operands from the instruction regiter */
+static inline
+uint32_t __OPEXT(uint32_t IR, uint8_t start, uint8_t length)
+{
+ return (IR >> start) & ((1 << length) - 1);
+}
+
+#define OPEXT(start, length) __OPEXT(IR, start, length)
+
+/* operands */
+#define OPCODE OPEXT(26, 6)
+#define REGa OPEXT(21, 5)
+#define REGb OPEXT(16, 5)
+#define REGc OPEXT(11, 5)
+#define IMMc OPEXT(0, 16)
+
+/* cpu traps */
+void trap(int num)
+{
+ switch (num) {
+ case TRP_UNALIGNED:
+ raise(SIGSEGV);
+ break;
+ case TRP_DIVBYZERO:
+ raise(SIGFPE);
+ break;
+ case TRP_SYSCALL:
+ do_syscall();
+ break;
+ case TRP_ILL:
+ raise(SIGILL);
+ break;
+ }
+}
+
+void execute(uint32_t IR)
+{
+ /* decode op-code */
+ uint8_t opcode = OPCODE;
+ int32_t a, b, c;
+
+ if (opcode < OPC_MOV) {
+ /* arithmetic & logic */
+ a = REGa;
+ b = REGb;
+ c = REGc;
+
+ debug("PC@%#08x: %-3s r%i, r%i, r%i", PC,
+ opc2mnemonic(IR), a, b, c);
+ }
+
+ else if (opcode < OPC_J) {
+ /* load/store & branch */
+ a = REGa;
+ b = REGb;
+ c = IMMc;
+
+ /* sign extension */
+ if (c >= 0x8000)
+ c -= 0x10000;
+
+ if (opcode < OPC_LB) {
+ debug("PC@%#08x: %-3s r%i, %i", PC,
+ opc2mnemonic(IR), b, c);
+ } else {
+ debug("PC@%#08x: %-3s r%i, r%i, %i", PC,
+ opc2mnemonic(IR), a, b, c);
+ }
+ }
+
+ else if (opcode < OPC_SYS) {
+ /* jump */
+ a = REGa;
+ b = c = 0;
+
+ debug("PC@%#08x: %-3s r%i", PC,
+ opc2mnemonic(IR), a);
+ }
+
+ else {
+ /* misc */
+ a = b = c = 0;
+
+ debug("PC@%#08x: %-3s", PC,
+ opc2mnemonic(IR));
+ }
+
+ // make sure r0 is zero
+ GPR[0] = 0;
+
+ // buffer for load/store instructions
+ uint8_t tmp8;
+ uint16_t tmp16;
+ uint32_t tmp32;
+
+ switch (opcode) {
+ case OPC_ADD:
+ // XXX: signed/unsigned?
+ GPR[a] = GPR[b] + GPR[c];
+ break;
+ case OPC_SUB:
+ // XXX: signed/unsigned?
+ GPR[a] = GPR[b] - GPR[c];
+ break;
+ case OPC_MUL:
+ // XXX: signed/unsigned?
+ GPR[a] = GPR[b] * GPR[c];
+ break;
+ case OPC_DIV:
+ // XXX: signed/unsigned?
+ if (GPR[c] == 0)
+ trap(TRP_DIVBYZERO);
+ else
+ GPR[a] = GPR[b] / GPR[c];
+ break;
+ case OPC_MOD:
+ // XXX: signed/unsigned?
+ GPR[a] = GPR[b] % GPR[c];
+ break;
+ case OPC_SHL:
+ // XXX: signed/unsigned?
+ GPR[a] = GPR[b] << GPR[c];
+ break;
+ case OPC_SHR:
+ // XXX: signed/unsigned?
+ GPR[a] = GPR[b] >> GPR[c];
+ break;
+ case OPC_AND:
+ GPR[a] = GPR[b] & GPR[c];
+ break;
+ case OPC_OR:
+ GPR[a] = GPR[b] | GPR[c];
+ break;
+ case OPC_XOR:
+ GPR[a] = GPR[b] ^ GPR[c];
+ break;
+ case OPC_NOR:
+ GPR[a] = ~(GPR[b] & GPR[c]);
+ break;
+ case OPC_MOV:
+ GPR[b] = c;
+ break;
+ case OPC_LB:
+ memcpy(&tmp8, &MEM[GPR[a] + c], sizeof(uint8_t));
+ GPR[b] = tmp8;
+ break;
+ case OPC_LH:
+ if ((GPR[a] + c) & 0x1)
+ trap(TRP_UNALIGNED);
+ memcpy(&tmp16, &MEM[GPR[a] + c], sizeof(uint16_t));
+ GPR[b] = tmp16;
+ break;
+ case OPC_LW:
+ if ((GPR[a] + c) & 0x2)
+ trap(TRP_UNALIGNED);
+ memcpy(&tmp32, &MEM[GPR[a] + c], sizeof(uint32_t));
+ GPR[b] = tmp32;
+ break;
+ case OPC_SB:
+ tmp8 = GPR[b];
+ memcpy(&MEM[GPR[a] + c], &tmp8, sizeof(uint8_t));
+ break;
+ case OPC_SH:
+ if ((GPR[a] + c) & 0x1)
+ trap(TRP_UNALIGNED);
+ tmp16 = GPR[b];
+ memcpy(&MEM[GPR[a] + c], &tmp16, sizeof(uint16_t));
+ break;
+ case OPC_SW:
+ if ((GPR[a] + c) & 0x2)
+ trap(TRP_UNALIGNED);
+ tmp32 = GPR[b];
+ memcpy(&MEM[GPR[a] + c], &tmp32, sizeof(uint32_t));
+ break;
+ case OPC_CMP:
+ Z = (GPR[b] == (uint32_t) c);
+ N = (GPR[b] < (uint32_t) c);
+ break;
+ case OPC_BEQ:
+ if (Z)
+ PC += c * sizeof(uint32_t);
+ break;
+ case OPC_BNE:
+ if (!Z)
+ PC += c * sizeof(uint32_t);
+ break;
+ case OPC_BLT:
+ if (N)
+ PC += c * sizeof(uint32_t);
+ break;
+ case OPC_BGE:
+ if (!N)
+ PC += c * sizeof(uint32_t);
+ break;
+ case OPC_BLE:
+ if (Z || N)
+ PC += c * sizeof(uint32_t);
+ break;
+ case OPC_BGT:
+ if (!Z && !N)
+ PC += c * sizeof(uint32_t);
+ case OPC_J:
+ PC = GPR[a];
+ break;
+ case OPC_JAL:
+ GPR[31] = PC + sizeof(uint32_t);
+ PC = GPR[a];
+ case OPC_SYS:
+ trap(TRP_SYSCALL);
+ break;
+ default:
+ /* illegal instruction */
+ trap(TRP_ILL);
+ }
+}
diff --git a/src/emu/cpu.h b/src/emu/cpu.h
new file mode 100644
index 0000000..43ccf00
--- /dev/null
+++ b/src/emu/cpu.h
@@ -0,0 +1,29 @@
+#ifndef _CPU_H
+#define _CPU_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* cpu traps */
+enum {
+ TRP_UNALIGNED,
+ TRP_DIVBYZERO,
+ TRP_SYSCALL,
+ TRP_ILL,
+};
+
+void trap(int num);
+
+/* program counter */
+extern uint32_t PC;
+
+/* status bits */
+extern bool N, Z;
+
+/* 32 general purpose registers */
+extern uint32_t GPR[32];
+
+/* main cpu execution function */
+void execute(uint32_t IR);
+
+#endif
diff --git a/src/emu/log.h b/src/emu/log.h
new file mode 100644
index 0000000..6660c66
--- /dev/null
+++ b/src/emu/log.h
@@ -0,0 +1,33 @@
+#ifndef _LOG_H
+#define _LOG_H
+
+#include <stdbool.h>
+#include <stdio.h>
+
+extern bool is_debug;
+
+#define debug(...) do { \
+ if (is_debug) { \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ } \
+} while (0)
+
+#define error(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+} while (0)
+
+#define die(...) do { \
+ error(__VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+} while (0)
+
+#define pdie(...) do { \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, ": "); \
+ perror(NULL); \
+ exit(EXIT_FAILURE); \
+} while (0)
+
+#endif
diff --git a/src/emu/mem.c b/src/emu/mem.c
new file mode 100644
index 0000000..e102eaf
--- /dev/null
+++ b/src/emu/mem.c
@@ -0,0 +1,3 @@
+#include "mem.h"
+
+uint8_t MEM[4096];
diff --git a/src/emu/mem.h b/src/emu/mem.h
new file mode 100644
index 0000000..34b13fa
--- /dev/null
+++ b/src/emu/mem.h
@@ -0,0 +1,8 @@
+#ifndef _MEM_H
+#define _MEM_H
+
+#include <stdint.h>
+
+extern uint8_t MEM[];
+
+#endif
diff --git a/src/emu/opc.c b/src/emu/opc.c
new file mode 100644
index 0000000..870a272
--- /dev/null
+++ b/src/emu/opc.c
@@ -0,0 +1,63 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "opc.h"
+
+typedef struct opc_mapping {
+ const char *name;
+ uint32_t opcode;
+} opc_mapping_t;
+
+opc_mapping_t opc_map[] = {
+ { "ADD", OPC_ADD },
+ { "SUB", OPC_SUB },
+ { "MUL", OPC_MUL },
+ { "DIV", OPC_DIV },
+ { "MOD", OPC_MOD },
+ { "SHL", OPC_SHL },
+ { "SHR", OPC_SHR },
+ { "AND", OPC_AND },
+ { "OR", OPC_OR },
+ { "XOR", OPC_XOR },
+ { "NOR", OPC_NOR },
+ { "MOV", OPC_MOV },
+ { "LB", OPC_LB },
+ { "LH", OPC_LH },
+ { "LW", OPC_LW },
+ { "SB", OPC_SB },
+ { "SH", OPC_SH },
+ { "SW", OPC_SW },
+ { "CMP", OPC_CMP },
+ { "BEQ", OPC_BEQ },
+ { "BNE", OPC_BNE },
+ { "BLT", OPC_BLT },
+ { "BGE", OPC_BGE },
+ { "BLE", OPC_BLE },
+ { "BGT", OPC_BGT },
+ { "J", OPC_J },
+ { "JAL", OPC_JAL },
+ { "SYS", OPC_SYS },
+ { NULL, 0 }
+};
+
+uint32_t mnemonic2opc(const char *mnemonic)
+{
+ for (uint8_t i = 0; opc_map[i].name; i++) {
+ if (strcmp(opc_map[i].name, mnemonic) == 0)
+ return opc_map[i].opcode << 26;
+ }
+
+ return ~0;
+}
+
+const char *opc2mnemonic(uint32_t IR)
+{
+ uint32_t opcode = IR >> 26;
+
+ for (uint8_t i = 0; opc_map[i].name; i++) {
+ if (opc_map[i].opcode == opcode)
+ return opc_map[i].name;
+ }
+
+ return NULL;
+}
diff --git a/src/emu/opc.h b/src/emu/opc.h
new file mode 100644
index 0000000..45d21a8
--- /dev/null
+++ b/src/emu/opc.h
@@ -0,0 +1,68 @@
+#ifndef _OPC_H
+#define _OPC_H
+
+#include <stdint.h>
+
+/* instructions formats:
+ * ---------------------
+ *
+ * arithmetic:
+ * |000|xxx|aaaaa|bbbbb|ccccc|00000000000|
+ * logic:
+ * |001|xxx|aaaaa|bbbbb|ccccc|00000000000|
+ * load & store:
+ * |010|xxx|aaaaa|bbbbb|cccccccccccccccc|
+ * branch:
+ * |011|xxx|aaaaa|bbbbb|cccccccccccccccc|
+ * jump:
+ * |100|xxx|aaaaa|000000000000000000000|
+ * misc:
+ * |111|xxx|??????????????????????????|
+ *
+ */
+
+/* arithmetic */
+#define OPC_ADD 000
+#define OPC_SUB 001
+#define OPC_MUL 002
+#define OPC_DIV 003
+#define OPC_MOD 004
+#define OPC_SHL 005
+#define OPC_SHR 006
+
+/* logic */
+#define OPC_AND 010
+#define OPC_OR 011
+#define OPC_XOR 012
+#define OPC_NOR 013
+
+/* load & store */
+#define OPC_MOV 020
+#define OPC_LB 021
+#define OPC_LH 022
+#define OPC_LW 023
+#define OPC_SB 024
+#define OPC_SH 025
+#define OPC_SW 026
+
+/* branch instructions */
+#define OPC_CMP 030
+#define OPC_BEQ 031
+#define OPC_BNE 032
+#define OPC_BLT 033
+#define OPC_BGE 034
+#define OPC_BLE 035
+#define OPC_BGT 036
+
+/* jump instructions */
+#define OPC_J 040
+#define OPC_JAL 041
+
+/* misc */
+#define OPC_SYS 070
+
+/* conversion functions */
+uint32_t mnemonic2opc(const char *mnemonic);
+const char *opc2mnemonic(uint32_t IR);
+
+#endif
diff --git a/src/emu/riscas.c b/src/emu/riscas.c
new file mode 100644
index 0000000..e4180b2
--- /dev/null
+++ b/src/emu/riscas.c
@@ -0,0 +1,40 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "asm.h"
+#include "log.h"
+
+static
+void usage(int rc)
+{
+ fprintf(stderr, "Usage: riscas <source> <program>\n");
+ exit(rc);
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 3) {
+ usage(EXIT_FAILURE);
+ }
+
+ FILE *sfd;
+ if ((sfd = fopen(argv[1], "r")) == NULL)
+ pdie("could not open source %s", argv[1]);
+
+ FILE *pfd;
+ if ((pfd = fopen(argv[2], "w")) == NULL)
+ pdie("could not open program %s", argv[2]);
+
+ char line[128];
+ while (fgets(line, 128, sfd)) {
+ uint32_t IR = compile(line);
+
+ if (IR == 0xFFFFFFFF)
+ die("illegal instruction: %s", line);
+
+ fwrite(&IR, sizeof(uint32_t), 1, pfd);
+ }
+
+ return 0;
+}
diff --git a/src/emu/risci.c b/src/emu/risci.c
new file mode 100644
index 0000000..8c4e30f
--- /dev/null
+++ b/src/emu/risci.c
@@ -0,0 +1,134 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <readline/readline.h>
+
+#include "asm.h"
+#include "cpu.h"
+#include "log.h"
+#include "opc.h"
+
+bool is_debug = false;
+bool is_interactive = false;
+
+/* global program buffer */
+uint8_t *P = NULL;
+
+static
+void usage(int rc)
+{
+ fprintf(stderr, "Usage: risci [-dhi] <program>\n");
+ exit(rc);
+}
+
+static
+void signal_handler(int sig)
+{
+ switch (sig) {
+ case SIGILL:
+ /* SIGILL is raised by the cpu for an unknown instruction */
+ error("ERROR: illegal instruction.");
+ if (!is_interactive)
+ exit(-1);
+ break;
+ case SIGSEGV:
+ /* SIGSEGV is raised for unaligned memory access */
+ error("ERROR: unaligned memory access.");
+ if (!is_interactive)
+ exit(-1);
+ break;
+ case SIGFPE:
+ /* SIGFPE is raised by devision with zero */
+ error("ERROR: division by zero.");
+ exit(-1);
+ break;
+ }
+}
+
+static
+void read_program(const char *program)
+{
+ struct stat sb;
+ if (lstat(program, &sb) == -1)
+ pdie("cannot stat program");
+
+ if (sb.st_size % sizeof(uint32_t))
+ die("program does not align to op-code size of %u bytes", sizeof(uint32_t));
+
+ int pfd;
+ if ((pfd = open(program, O_RDONLY)) == -1)
+ pdie("could not open program");
+
+ P = malloc(sb.st_size + sizeof(uint32_t));
+ if (read(pfd, P, sb.st_size) != sb.st_size)
+ die("premature end of program");
+
+ memset(P + sb.st_size, 0xFF, sizeof(uint32_t));
+}
+
+static
+uint32_t next_instruction(void)
+{
+ if (is_interactive) {
+ /* read next instruction from stdin */
+ printf("%03d", PC/4);
+ return compile(readline("> "));
+ }
+
+ uint32_t tmp;
+ memcpy(&tmp, &P[PC], sizeof(uint32_t));
+ return tmp;
+}
+
+int main(int argc, char *argv[])
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "dhi")) != -1) {
+ switch (ch) {
+ case 'd':
+ is_debug = true;
+ break;
+ case 'h':
+ usage(EXIT_SUCCESS);
+ case 'i':
+ is_interactive = true;
+ break;
+ case '?':
+ default:
+ usage(EXIT_FAILURE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* catch cpu signal traps */
+ signal(SIGILL, signal_handler);
+ signal(SIGFPE, signal_handler);
+ signal(SIGSEGV, signal_handler);
+
+ /* load program from file if we're not in interactive mode */
+ if (!is_interactive) {
+ if (argc < 1)
+ usage(EXIT_FAILURE);
+
+ read_program(argv[0]);
+ }
+
+ /* reset program counter to first instruction */
+ PC = 0;
+
+ /* start instruction loop */
+ while (1) {
+ execute(next_instruction());
+ PC += 4;
+ }
+
+ /* not reached, program is terminated by signal traps from the cpu */
+}
diff --git a/src/emu/syscall.c b/src/emu/syscall.c
new file mode 100644
index 0000000..fe0d83e
--- /dev/null
+++ b/src/emu/syscall.c
@@ -0,0 +1,23 @@
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "cpu.h"
+#include "mem.h"
+#include "syscall.h"
+
+void do_syscall(void)
+{
+ switch (GPR[1]) {
+ case SYS_exit:
+ exit(GPR[2]);
+ break;
+ case SYS_read:
+ GPR[2] = read(GPR[2], &MEM[GPR[3]], GPR[4]);
+ break;
+ case SYS_write:
+ GPR[2] = write(GPR[2], &MEM[GPR[3]], GPR[4]);
+ break;
+ default:
+ GPR[2] = -1;
+ }
+}
diff --git a/src/emu/syscall.h b/src/emu/syscall.h
new file mode 100644
index 0000000..7c7265c
--- /dev/null
+++ b/src/emu/syscall.h
@@ -0,0 +1,16 @@
+#ifndef _SYSCALL_H
+#define _SYSCALL_H
+
+/* calling convention:
+ * - pass syscall number in GPR[1]
+ * - pass arguments in GPR[2]-GPR[9]
+ * - return code is passed in GPR[2]
+ */
+
+#define SYS_exit 0x00
+#define SYS_read 0x01
+#define SYS_write 0x02
+
+void do_syscall(void);
+
+#endif
diff --git a/src/emu/test.S b/src/emu/test.S
new file mode 100644
index 0000000..747295f
--- /dev/null
+++ b/src/emu/test.S
@@ -0,0 +1,15 @@
+MOV r1, 102
+SB r0, r1, 0
+MOV r1, 111
+SB r0, r1, 1
+SB r0, r1, 2
+MOV r1, 10
+SB r0, r1, 3
+MOV r1, 2
+MOV r2, 0
+MOV r3, 0
+MOV r4, 4
+SYS
+MOV r1, 0
+MOV r2, 1
+SYS