diff --git a/am/include/arch/spike.h b/am/include/arch/spike.h
new file mode 100644
index 0000000..f894606
--- /dev/null
+++ b/am/include/arch/spike.h
@@ -0,0 +1,7 @@
+#ifndef ARCH_H__
+#define ARCH_H__
+
+struct Context {
+};
+
+#endif
diff --git a/am/src/platform/dummy/cte.c b/am/src/platform/dummy/cte.c
new file mode 100644
index 0000000..c3094ab
--- /dev/null
+++ b/am/src/platform/dummy/cte.c
@@ -0,0 +1,19 @@
+#include <am.h>
+
+bool cte_init(Context*(*handler)(Event, Context*)) {
+  return false;
+}
+
+Context *kcontext(Area kstack, void (*entry)(void *), void *arg) {
+  return NULL;
+}
+
+void yield() {
+}
+
+bool ienabled() {
+  return false;
+}
+
+void iset(bool enable) {
+}
diff --git a/am/src/platform/dummy/ioe.c b/am/src/platform/dummy/ioe.c
new file mode 100644
index 0000000..369ab7e
--- /dev/null
+++ b/am/src/platform/dummy/ioe.c
@@ -0,0 +1,11 @@
+#include <am.h>
+#include <klib-macros.h>
+
+static void fail(void *buf) { panic("access nonexist register"); }
+
+bool ioe_init() {
+  return false;
+}
+
+void ioe_read (int reg, void *buf) { fail(buf); }
+void ioe_write(int reg, void *buf) { fail(buf); }
diff --git a/am/src/platform/dummy/mpe.c b/am/src/platform/dummy/mpe.c
new file mode 100644
index 0000000..6715aa2
--- /dev/null
+++ b/am/src/platform/dummy/mpe.c
@@ -0,0 +1,17 @@
+#include <am.h>
+
+bool mpe_init(void (*entry)()) {
+  return false;
+}
+
+int cpu_count() {
+  return 1;
+}
+
+int cpu_current() {
+  return 0;
+}
+
+int atomic_xchg(int *addr, int newval) {
+  return 0;
+}
diff --git a/am/src/platform/dummy/trm.c b/am/src/platform/dummy/trm.c
new file mode 100644
index 0000000..3fd84e2
--- /dev/null
+++ b/am/src/platform/dummy/trm.c
@@ -0,0 +1,10 @@
+#include <am.h>
+
+Area heap = RANGE(NULL, NULL);
+
+void putch(char ch) {
+}
+
+void halt(int code) {
+  while (1);
+}
diff --git a/am/src/platform/dummy/vme.c b/am/src/platform/dummy/vme.c
new file mode 100644
index 0000000..5134154
--- /dev/null
+++ b/am/src/platform/dummy/vme.c
@@ -0,0 +1,18 @@
+#include <am.h>
+
+bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) {
+  return false;
+}
+
+void protect(AddrSpace *as) {
+}
+
+void unprotect(AddrSpace *as) {
+}
+
+void map(AddrSpace *as, void *va, void *pa, int prot) {
+}
+
+Context *ucontext(AddrSpace *as, Area kstack, void *entry) {
+  return NULL;
+}
diff --git a/am/src/spike/atomic.h b/am/src/spike/atomic.h
new file mode 100644
index 0000000..eca1320
--- /dev/null
+++ b/am/src/spike/atomic.h
@@ -0,0 +1,78 @@
+// See LICENSE for license details.
+
+#ifndef _RISCV_ATOMIC_H
+#define _RISCV_ATOMIC_H
+
+//#include "config.h"
+//#include "encoding.h"
+
+// Currently, interrupts are always disabled in M-mode.
+#define disable_irqsave() (0)
+#define enable_irqrestore(flags) ((void) (flags))
+
+typedef struct { int lock; } spinlock_t;
+#define SPINLOCK_INIT {0}
+
+#define mb() asm volatile ("fence" ::: "memory")
+#define atomic_set(ptr, val) (*(volatile typeof(*(ptr)) *)(ptr) = val)
+#define atomic_read(ptr) (*(volatile typeof(*(ptr)) *)(ptr))
+
+#ifdef __riscv_atomic
+# define atomic_add(ptr, inc) __sync_fetch_and_add(ptr, inc)
+# define atomic_or(ptr, inc) __sync_fetch_and_or(ptr, inc)
+# define atomic_swap(ptr, swp) __sync_lock_test_and_set(ptr, swp)
+# define atomic_cas(ptr, cmp, swp) __sync_val_compare_and_swap(ptr, cmp, swp)
+#else
+# define atomic_binop(ptr, inc, op) ({ \
+  long flags = disable_irqsave(); \
+  typeof(*(ptr)) res = atomic_read(ptr); \
+  atomic_set(ptr, op); \
+  enable_irqrestore(flags); \
+  res; })
+# define atomic_add(ptr, inc) atomic_binop(ptr, inc, res + (inc))
+# define atomic_or(ptr, inc) atomic_binop(ptr, inc, res | (inc))
+# define atomic_swap(ptr, inc) atomic_binop(ptr, inc, (inc))
+# define atomic_cas(ptr, cmp, swp) ({ \
+  long flags = disable_irqsave(); \
+  typeof(*(ptr)) res = *(volatile typeof(*(ptr)) *)(ptr); \
+  if (res == (cmp)) *(volatile typeof(ptr))(ptr) = (swp); \
+  enable_irqrestore(flags); \
+  res; })
+#endif
+
+static inline int spinlock_trylock(spinlock_t* lock)
+{
+  int res = atomic_swap(&lock->lock, -1);
+  mb();
+  return res;
+}
+
+static inline void spinlock_lock(spinlock_t* lock)
+{
+  do
+  {
+    while (atomic_read(&lock->lock))
+      ;
+  } while (spinlock_trylock(lock));
+}
+
+static inline void spinlock_unlock(spinlock_t* lock)
+{
+  mb();
+  atomic_set(&lock->lock,0);
+}
+
+static inline long spinlock_lock_irqsave(spinlock_t* lock)
+{
+  long flags = disable_irqsave();
+  spinlock_lock(lock);
+  return flags;
+}
+
+static inline void spinlock_unlock_irqrestore(spinlock_t* lock, long flags)
+{
+  spinlock_unlock(lock);
+  enable_irqrestore(flags);
+}
+
+#endif
diff --git a/am/src/spike/htif.c b/am/src/spike/htif.c
new file mode 100644
index 0000000..f56ba8f
--- /dev/null
+++ b/am/src/spike/htif.c
@@ -0,0 +1,111 @@
+// See LICENSE for license details.
+
+#include "htif.h"
+#include "atomic.h"
+#include <klib.h>
+
+extern uint64_t __htif_base;
+volatile uint64_t tohost __attribute__((section(".htif")));
+volatile uint64_t fromhost __attribute__((section(".htif")));
+volatile int htif_console_buf;
+static spinlock_t htif_lock = SPINLOCK_INIT;
+
+#define TOHOST(base_int)	(uint64_t *)(base_int + TOHOST_OFFSET)
+#define FROMHOST(base_int)	(uint64_t *)(base_int + FROMHOST_OFFSET)
+
+#define TOHOST_OFFSET		((uintptr_t)tohost - (uintptr_t)__htif_base)
+#define FROMHOST_OFFSET		((uintptr_t)fromhost - (uintptr_t)__htif_base)
+
+static void __check_fromhost()
+{
+  uint64_t fh = fromhost;
+  if (!fh)
+    return;
+  fromhost = 0;
+
+  // this should be from the console
+  assert(FROMHOST_DEV(fh) == 1);
+  switch (FROMHOST_CMD(fh)) {
+    case 0:
+      htif_console_buf = 1 + (uint8_t)FROMHOST_DATA(fh);
+      break;
+    case 1:
+      break;
+    default:
+      assert(0);
+  }
+}
+
+static void __set_tohost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
+{
+  while (tohost)
+    __check_fromhost();
+  tohost = TOHOST_CMD(dev, cmd, data);
+}
+
+int htif_console_getchar()
+{
+#if __riscv_xlen == 32
+  // HTIF devices are not supported on RV32
+  return -1;
+#endif
+
+  spinlock_lock(&htif_lock);
+    __check_fromhost();
+    int ch = htif_console_buf;
+    if (ch >= 0) {
+      htif_console_buf = -1;
+      __set_tohost(1, 0, 0);
+    }
+  spinlock_unlock(&htif_lock);
+
+  return ch - 1;
+}
+
+static void do_tohost_fromhost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
+{
+  spinlock_lock(&htif_lock);
+    __set_tohost(dev, cmd, data);
+
+    while (1) {
+      uint64_t fh = fromhost;
+      if (fh) {
+        if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) {
+          fromhost = 0;
+          break;
+        }
+        __check_fromhost();
+      }
+    }
+  spinlock_unlock(&htif_lock);
+}
+
+void htif_syscall(uintptr_t arg)
+{
+  do_tohost_fromhost(0, 0, arg);
+}
+
+void htif_console_putchar(uint8_t ch)
+{
+#if __riscv_xlen == 32
+  // HTIF devices are not supported on RV32, so proxy a write system call
+  volatile uint64_t magic_mem[8];
+  magic_mem[0] = SYS_write;
+  magic_mem[1] = 1;
+  magic_mem[2] = (uintptr_t)&ch;
+  magic_mem[3] = 1;
+  do_tohost_fromhost(0, 0, (uintptr_t)magic_mem);
+#else
+  spinlock_lock(&htif_lock);
+    __set_tohost(1, 1, ch);
+  spinlock_unlock(&htif_lock);
+#endif
+}
+
+void htif_poweroff()
+{
+  while (1) {
+    fromhost = 0;
+    tohost = 1;
+  }
+}
diff --git a/am/src/spike/htif.h b/am/src/spike/htif.h
new file mode 100644
index 0000000..73967d8
--- /dev/null
+++ b/am/src/spike/htif.h
@@ -0,0 +1,24 @@
+// See LICENSE for license details.
+
+#ifndef _RISCV_HTIF_H
+#define _RISCV_HTIF_H
+
+#include <stdint.h>
+
+#if __riscv_xlen == 64
+# define TOHOST_CMD(dev, cmd, payload) \
+  (((uint64_t)(dev) << 56) | ((uint64_t)(cmd) << 48) | (uint64_t)(payload))
+#else
+# define TOHOST_CMD(dev, cmd, payload) ({ \
+  if ((dev) || (cmd)) __builtin_trap(); \
+  (payload); })
+#endif
+#define FROMHOST_DEV(fromhost_value) ((uint64_t)(fromhost_value) >> 56)
+#define FROMHOST_CMD(fromhost_value) ((uint64_t)(fromhost_value) << 8 >> 56)
+#define FROMHOST_DATA(fromhost_value) ((uint64_t)(fromhost_value) << 16 >> 16)
+
+void htif_console_putchar(uint8_t);
+int htif_console_getchar();
+void htif_poweroff() __attribute__((noreturn));
+
+#endif
diff --git a/am/src/spike/ioe.c b/am/src/spike/ioe.c
new file mode 100644
index 0000000..8ab29fc
--- /dev/null
+++ b/am/src/spike/ioe.c
@@ -0,0 +1,27 @@
+#include <am.h>
+#include <klib-macros.h>
+
+void __am_timer_init();
+void __am_timer_rtc(AM_TIMER_RTC_T *);
+void __am_timer_uptime(AM_TIMER_UPTIME_T *);
+
+static void __am_timer_config(AM_TIMER_CONFIG_T *cfg) { cfg->present = true; cfg->has_rtc = true; }
+
+typedef void (*handler_t)(void *buf);
+static void *lut[128] = {
+  [AM_TIMER_CONFIG] = __am_timer_config,
+  [AM_TIMER_RTC   ] = __am_timer_rtc,
+  [AM_TIMER_UPTIME] = __am_timer_uptime,
+};
+
+static void fail(void *buf) { panic("access nonexist register"); }
+
+bool ioe_init() {
+  for (int i = 0; i < LENGTH(lut); i++)
+    if (!lut[i]) lut[i] = fail;
+  __am_timer_init();
+  return true;
+}
+
+void ioe_read (int reg, void *buf) { ((handler_t)lut[reg])(buf); }
+void ioe_write(int reg, void *buf) { ((handler_t)lut[reg])(buf); }
diff --git a/am/src/spike/linker.ld b/am/src/spike/linker.ld
new file mode 100644
index 0000000..9db82aa
--- /dev/null
+++ b/am/src/spike/linker.ld
@@ -0,0 +1,35 @@
+ENTRY(_start)
+
+SECTIONS {
+  . = 0x80000000;
+  .text : {
+    *(entry)
+    *(.text*)
+  }
+  etext = .;
+  _etext = .;
+  .rodata : {
+    *(.rodata*)
+  }
+  .htif : {
+    PROVIDE(__htif_base = . );
+    *(.htif)
+  }
+  .data : {
+    *(.data)
+  }
+  edata = .;
+  _data = .;
+  .bss : {
+	_bss_start = .;
+    *(.bss*)
+    *(.sbss*)
+    *(.scommon)
+  }
+  _stack_top = ALIGN(0x1000);
+  . = _stack_top + 0x8000;
+  _stack_pointer = .;
+  end = .;
+  _end = .;
+  _heap_start = ALIGN(0x1000);
+}
diff --git a/am/src/spike/start.S b/am/src/spike/start.S
new file mode 100644
index 0000000..3e56e5c
--- /dev/null
+++ b/am/src/spike/start.S
@@ -0,0 +1,8 @@
+.section entry, "ax"
+.globl _start
+.type _start, @function
+
+_start:
+  mv s0, zero
+  la sp, _stack_pointer
+  jal _trm_init
diff --git a/am/src/spike/timer.c b/am/src/spike/timer.c
new file mode 100644
index 0000000..abe6783
--- /dev/null
+++ b/am/src/spike/timer.c
@@ -0,0 +1,30 @@
+#include <am.h>
+
+static uint64_t boot_time = 0;
+
+#define CLINT_MMIO 0x2000000ul
+#define TIME_BASE 0xbff8
+
+static uint64_t read_time() {
+  uint32_t lo = *(volatile uint32_t *)(CLINT_MMIO + TIME_BASE + 0);
+  uint32_t hi = *(volatile uint32_t *)(CLINT_MMIO + TIME_BASE + 4);
+  uint64_t time = ((uint64_t)hi << 32) | lo;
+  return time / 10;
+}
+
+void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
+  uptime->us = read_time() - boot_time;
+}
+
+void __am_timer_init() {
+  boot_time = read_time();
+}
+
+void __am_timer_rtc(AM_TIMER_RTC_T *rtc) {
+  rtc->second = 0;
+  rtc->minute = 0;
+  rtc->hour   = 0;
+  rtc->day    = 0;
+  rtc->month  = 0;
+  rtc->year   = 1900;
+}
diff --git a/am/src/spike/trm.c b/am/src/spike/trm.c
new file mode 100644
index 0000000..b193bc9
--- /dev/null
+++ b/am/src/spike/trm.c
@@ -0,0 +1,34 @@
+#include <am.h>
+#include <klib.h>
+#include <klib-macros.h>
+#include "htif.h"
+
+extern char _heap_start;
+int main(const char *args);
+
+extern char _pmem_start;
+#define PMEM_SIZE (128 * 1024 * 1024)
+#define PMEM_END ((uintptr_t)0x80000000 + PMEM_SIZE)
+
+Area heap = RANGE(&_heap_start, PMEM_END);
+#ifndef MAINARGS
+#define MAINARGS ""
+#endif
+static const char mainargs[] = MAINARGS;
+
+void putch(char ch) {
+  htif_console_putchar(ch);
+}
+
+void halt(int code) {
+  printf("Exit with code = %d\n", code);
+  htif_poweroff();
+
+  // should not reach here
+  while (1);
+}
+
+void _trm_init() {
+  int ret = main(mainargs);
+  halt(ret);
+}
diff --git a/scripts/spike.mk b/scripts/spike.mk
new file mode 100644
index 0000000..ca4b918
--- /dev/null
+++ b/scripts/spike.mk
@@ -0,0 +1,19 @@
+include $(AM_HOME)/scripts/isa/riscv64.mk
+
+AM_SRCS := spike/trm.c \
+           spike/ioe.c \
+           spike/timer.c \
+           spike/start.S \
+           spike/htif.S \
+           platform/dummy/cte.c \
+           platform/dummy/vme.c \
+           platform/dummy/mpe.c \
+
+CFLAGS    += -fdata-sections -ffunction-sections
+LDFLAGS   += -T $(AM_HOME)/am/src/spike/linker.ld
+LDFLAGS   += --gc-sections -e _start
+
+CFLAGS += -DMAINARGS=\"$(mainargs)\"
+.PHONY: $(AM_HOME)/am/src/spike/trm.c
+
+image: $(IMAGE).elf