From 49f510d2d60129526832bfcd9c0f4049962bc80e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benedikt=20B=C3=B6hm?= <bb@xnull.de>
Date: Mon, 18 May 2009 20:53:42 +0200
Subject: move stuff around and create initial source structure

---
 src/emu/.gitignore |   3 +
 src/emu/Makefile   |  19 +++++
 src/emu/TODO       |   4 +
 src/emu/asm.c      |  33 ++++++++
 src/emu/asm.h      |   8 ++
 src/emu/cpu.c      | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/emu/cpu.h      |  29 +++++++
 src/emu/log.h      |  33 ++++++++
 src/emu/mem.c      |   3 +
 src/emu/mem.h      |   8 ++
 src/emu/opc.c      |  63 ++++++++++++++
 src/emu/opc.h      |  68 +++++++++++++++
 src/emu/riscas.c   |  40 +++++++++
 src/emu/risci.c    | 134 ++++++++++++++++++++++++++++++
 src/emu/syscall.c  |  23 ++++++
 src/emu/syscall.h  |  16 ++++
 src/emu/test.S     |  15 ++++
 17 files changed, 736 insertions(+)
 create mode 100644 src/emu/.gitignore
 create mode 100644 src/emu/Makefile
 create mode 100644 src/emu/TODO
 create mode 100644 src/emu/asm.c
 create mode 100644 src/emu/asm.h
 create mode 100644 src/emu/cpu.c
 create mode 100644 src/emu/cpu.h
 create mode 100644 src/emu/log.h
 create mode 100644 src/emu/mem.c
 create mode 100644 src/emu/mem.h
 create mode 100644 src/emu/opc.c
 create mode 100644 src/emu/opc.h
 create mode 100644 src/emu/riscas.c
 create mode 100644 src/emu/risci.c
 create mode 100644 src/emu/syscall.c
 create mode 100644 src/emu/syscall.h
 create mode 100644 src/emu/test.S

(limited to 'src/emu')

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
-- 
cgit v1.2.3