diff --git a/tests/tcg/aarch64/Makefile.softmmu-target b/tests/tcg/aarch64/Makefile.softmmu-target
new file mode 100644
index 0000000000..e6aee856c8
--- /dev/null
+++ b/tests/tcg/aarch64/Makefile.softmmu-target
@@ -0,0 +1,32 @@
+#
+# Aarch64 system tests
+#
+
+AARCH64_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/aarch64/system
+VPATH+=$(AARCH64_SYSTEM_SRC)
+
+# These objects provide the basic boot code and helper functions for all tests
+CRT_OBJS=boot.o
+
+AARCH64_TEST_SRCS=$(wildcard $(AARCH64_SYSTEM_SRC)/*.c)
+AARCH64_TESTS = $(patsubst $(AARCH64_SYSTEM_SRC)/%.c, %, $(AARCH64_TEST_SRCS))
+
+CRT_PATH=$(AARCH64_SYSTEM_SRC)
+LINK_SCRIPT=$(AARCH64_SYSTEM_SRC)/kernel.ld
+LDFLAGS=-Wl,-T$(LINK_SCRIPT)
+TESTS+=$(AARCH64_TESTS) $(MULTIARCH_TESTS)
+CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
+LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc
+
+# building head blobs
+.PRECIOUS: $(CRT_OBJS)
+
+%.o: $(CRT_PATH)/%.S
+	$(CC) $(CFLAGS) -x assembler-with-cpp -c $< -o $@
+
+# Build and link the tests
+%: %.c $(LINK_SCRIPT) $(CRT_OBJS) $(MINILIB_OBJS)
+	$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Running
+QEMU_OPTS+=-M virt -cpu max -display none -semihosting -kernel
diff --git a/tests/tcg/aarch64/system/boot.S b/tests/tcg/aarch64/system/boot.S
new file mode 100644
index 0000000000..03d319e07f
--- /dev/null
+++ b/tests/tcg/aarch64/system/boot.S
@@ -0,0 +1,200 @@
+/*
+ * Minimal AArch64 system boot code.
+ *
+ * Copyright Linaro Ltd 2019
+ *
+ * Loosely based on the newlib/libgloss setup stubs. Using semihosting
+ * for serial output and exit functions.
+ */
+
+/*
+ * Semihosting interface on ARM AArch64
+ * See "Semihosting for AArch32 and AArch64 Relase 2.0" by ARM
+ * w0 - semihosting call number
+ * x1 - semihosting parameter
+ */
+#define semihosting_call hlt 0xf000
+#define SYS_WRITEC      0x03    /* character to debug channel */
+#define SYS_WRITE0      0x04    /* string to debug channel */
+#define SYS_EXIT        0x18
+
+	.align	12
+
+        .macro	ventry	label
+        .align	7
+        b	\label
+        .endm
+
+vector_table:
+	/* Current EL with SP0.  */
+	ventry	curr_sp0_sync		/* Synchronous  */
+	ventry	curr_sp0_irq		/* Irq/vIRQ  */
+	ventry	curr_sp0_fiq		/* Fiq/vFIQ  */
+	ventry	curr_sp0_serror		/* SError/VSError  */
+
+	/* Current EL with SPx.  */
+	ventry	curr_spx_sync		/* Synchronous  */
+	ventry	curr_spx_irq		/* IRQ/vIRQ  */
+	ventry	curr_spx_fiq		/* FIQ/vFIQ  */
+	ventry	curr_spx_serror		/* SError/VSError  */
+
+	/* Lower EL using AArch64.  */
+	ventry	lower_a64_sync		/* Synchronous  */
+	ventry	lower_a64_irq		/* IRQ/vIRQ  */
+	ventry	lower_a64_fiq		/* FIQ/vFIQ  */
+	ventry	lower_a64_serror	/* SError/VSError  */
+
+	/* Lower EL using AArch32.  */
+	ventry	lower_a32_sync		/* Synchronous  */
+	ventry	lower_a32_irq		/* IRQ/vIRQ  */
+	ventry	lower_a32_fiq		/* FIQ/vFIQ  */
+	ventry	lower_a32_serror	/* SError/VSError  */
+
+        .text
+	.align 4
+
+        /* Common vector handling for now */
+curr_sp0_sync:
+curr_sp0_irq:
+curr_sp0_fiq:
+curr_sp0_serror:
+curr_spx_sync:
+curr_spx_irq:
+curr_spx_fiq:
+curr_spx_serror:
+lower_a64_sync:
+lower_a64_irq:
+lower_a64_fiq:
+lower_a64_serror:
+lower_a32_sync:
+lower_a32_irq:
+lower_a32_fiq:
+lower_a32_serror:
+	mov	x0, SYS_WRITE0
+	adr	x1, .error
+        semihosting_call
+	mov	x0, SYS_EXIT
+        mov     x1, 1
+        semihosting_call
+        /* never returns */
+
+.error:
+	.string "Terminated by exception.\n"
+
+        .align 4
+        .global __start
+__start:
+        /* Installs a table of exception vectors to catch and handle all
+           exceptions by terminating the process with a diagnostic.  */
+	adr	x0, vector_table
+	msr	vbar_el1, x0
+
+        /* Page table setup (identity mapping).  */
+	adrp	x0, ttb
+	add	x0, x0, :lo12:ttb
+	msr	ttbr0_el1, x0
+
+        /* Create some (big) pages */
+	adr	x1, .				/* phys address */
+	bic	x1, x1, #(1 << 30) - 1		/* 1GB block alignment */
+
+        add	x2, x0, x1, lsr #(30 - 3)	/* page offset in l1 page table */
+
+	mov	x3, #0x401			/* page table attributes (AF, block) */
+	orr	x1, x1, x3
+
+	str	x1, [x2], #8			/* 1st GB */
+
+	mov	x3, #(1 << 30)			/* 1GB block */
+	add	x1, x1, x3
+
+        str	x1, [x2]			/* 2nd GB */
+
+	/* Setup/enable the MMU.  */
+
+	/*
+         * TCR_EL1 - Translation Control Registers
+         *
+         * IPS[34:32] = 40-bit PA, 1TB
+         * ORGN0[11:10] = Outer: Normal, Outer Write-Back Read-Allocate No Write-Allocate Cacheable
+         * IRGN0[9:8] = Inner: Normal, Inner Write-Back Read-Allocate No Write-Allocate Cacheable.
+         * T0SZ[5:0]  = 2^(64 - 25)
+         */
+	ldr	x0, = (2 << 32) | 25 | (3 << 10) | (3 << 8)
+	msr	tcr_el1, x0
+
+	mov	x0, #0xee			/* Inner/outer cacheable WB */
+	msr	mair_el1, x0
+	isb
+
+        /*
+         * SCTLR_EL1 - System Control Register
+         *
+         * WXN[19] = 0 = no effect, Write does not imply XN (execute never)
+         * I[12] = Instruction cachability control
+         * SA[3] = SP alignment check
+         * C[2] = Data cachability control
+         * M[0] = 1, enable stage 1 address translation for EL0/1
+         */
+	mrs	x0, sctlr_el1
+	ldr	x1, =0x100d			/* bits I(12) SA(3) C(2) M(0) */
+	bic	x0, x0, #(1 << 1)		/* clear bit A(1) */
+	bic	x0, x0, #(1 << 19)		/* clear WXN */
+	orr	x0, x0, x1			/* set bits */
+
+	dsb	sy
+	msr	sctlr_el1, x0
+	isb
+
+        /*
+         * Enable FP registers. The standard C pre-amble will be
+	 * saving these and A-profile compilers will use AdvSIMD
+	 * registers unless we tell it not to.
+        */
+        mrs     x0, cpacr_el1
+        orr     x0, x0, #(3 << 20)
+        msr     cpacr_el1, x0
+
+        /* Setup some stack space and enter the test code.
+         * Assume everthing except the return value is garbage when we
+	 * return, we won't need it.
+         */
+	adrp	x0, stack
+	add	x0, x0, :lo12:stack
+
+        mov      sp, x0
+        bl      main
+
+        /* pass return value to sys exit */
+        mov    x1, x0
+        ldr    x0, =0x20026 /* ADP_Stopped_ApplicationExit */
+        stp    x0, x1, [sp, #-16]!
+        mov    x1, sp
+	mov    x0, SYS_EXIT
+        semihosting_call
+        /* never returns */
+
+        /*
+         * Helper Functions
+         */
+
+        /* Output a single character to serial port */
+        .global __sys_outc
+__sys_outc:
+        stp x0, x1, [sp, #-16]!
+        /* pass address of c on stack */
+        mov x1, sp
+        mov x0, SYS_WRITEC
+        semihosting_call
+        ldp x0, x1, [sp], #16
+        ret
+
+	.data
+	.align	12
+ttb:
+	.space	4096, 0
+
+        .align  12
+stack:
+        .space 65536, 0
+stack_end:
diff --git a/tests/tcg/aarch64/system/kernel.ld b/tests/tcg/aarch64/system/kernel.ld
new file mode 100644
index 0000000000..73d75cae64
--- /dev/null
+++ b/tests/tcg/aarch64/system/kernel.ld
@@ -0,0 +1,22 @@
+ENTRY(__start)
+
+SECTIONS
+{
+    /* virt machine, RAM starts at 1gb */
+    . = (1 << 30);
+    .text : {
+        *(.text)
+    }
+    .data : {
+        *(.data)
+    }
+    .rodata : {
+        *(.rodata)
+    }
+    .bss : {
+        *(.bss)
+    }
+    /DISCARD/ : {
+        *(.ARM.attributes)
+    }
+}
