diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c
index c3ecc4b..a39a3bd 100644
--- a/target/loongarch/cpu.c
+++ b/target/loongarch/cpu.c
@@ -379,6 +379,7 @@ static struct TCGCPUOps loongarch_tcg_ops = {
     .initialize = loongarch_tcg_init,
     .synchronize_from_tb = loongarch_cpu_synchronize_from_tb,
     .cpu_exec_interrupt = loongarch_cpu_exec_interrupt,
+    .tlb_fill = loongarch_cpu_tlb_fill,
 };
 #endif /* CONFIG_TCG */
 
diff --git a/target/loongarch/internal.h b/target/loongarch/internal.h
index 09e667c..40454ee 100644
--- a/target/loongarch/internal.h
+++ b/target/loongarch/internal.h
@@ -95,4 +95,13 @@ static inline void compute_hflags(CPULoongArchState *env)
 
 const char *loongarch_exception_name(int32_t exception);
 
+/* tlb_helper.c */
+bool loongarch_cpu_tlb_fill(CPUState *cs,
+                            vaddr address,
+                            int size,
+                            MMUAccessType access_type,
+                            int mmu_idx,
+                            bool probe,
+                            uintptr_t retaddr);
+
 #endif
diff --git a/target/loongarch/tlb_helper.c b/target/loongarch/tlb_helper.c
new file mode 100644
index 0000000..3889109
--- /dev/null
+++ b/target/loongarch/tlb_helper.c
@@ -0,0 +1,109 @@
+/*
+ * LoongArch tlb emulation helpers for qemu.
+ *
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "internal.h"
+#include "cpu-csr.h"
+#include "exec/helper-proto.h"
+#include "exec/exec-all.h"
+#include "exec/cpu_ldst.h"
+#include "exec/log.h"
+
+enum {
+    TLBRET_PE = -7,
+    TLBRET_XI = -6,
+    TLBRET_RI = -5,
+    TLBRET_DIRTY = -4,
+    TLBRET_INVALID = -3,
+    TLBRET_NOMATCH = -2,
+    TLBRET_BADADDR = -1,
+    TLBRET_MATCH = 0
+};
+
+static void raise_mmu_exception(CPULoongArchState *env, target_ulong address,
+                                MMUAccessType access_type, int tlb_error)
+{
+    CPUState *cs = env_cpu(env);
+    int exception = 0, error_code = 0;
+
+    if (access_type == MMU_INST_FETCH) {
+        error_code |= INST_INAVAIL;
+    }
+
+    switch (tlb_error) {
+    default:
+    case TLBRET_BADADDR:
+        exception = EXCP_ADE;
+        break;
+    case TLBRET_NOMATCH:
+        /* No TLB match for a mapped address */
+        if (access_type == MMU_DATA_STORE) {
+            exception = EXCP_TLBS;
+        } else {
+            exception = EXCP_TLBL;
+        }
+        error_code |= TLB_NOMATCH;
+        break;
+    case TLBRET_INVALID:
+        /* TLB match with no valid bit */
+        if (access_type == MMU_DATA_STORE) {
+            exception = EXCP_TLBS;
+        } else {
+            exception = EXCP_TLBL;
+        }
+        break;
+    case TLBRET_DIRTY:
+        exception = EXCP_TLBM;
+        break;
+    case TLBRET_XI:
+        /* Execute-Inhibit Exception */
+        exception = EXCP_TLBXI;
+        break;
+    case TLBRET_RI:
+        /* Read-Inhibit Exception */
+        exception = EXCP_TLBRI;
+        break;
+    case TLBRET_PE:
+        /* Privileged Exception */
+        exception = EXCP_TLBPE;
+        break;
+    }
+
+    if (env->insn_flags & INSN_LOONGARCH) {
+        if (tlb_error == TLBRET_NOMATCH) {
+            env->CSR_TLBRBADV = address;
+            env->CSR_TLBREHI = address & (TARGET_PAGE_MASK << 1);
+            cs->exception_index = exception;
+            env->error_code = error_code;
+            return;
+        }
+    }
+
+    /* Raise exception */
+    env->CSR_BADV = address;
+    cs->exception_index = exception;
+    env->error_code = error_code;
+
+    if (env->insn_flags & INSN_LOONGARCH) {
+        env->CSR_TLBEHI = address & (TARGET_PAGE_MASK << 1);
+    }
+}
+
+bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
+                       MMUAccessType access_type, int mmu_idx,
+                       bool probe, uintptr_t retaddr)
+{
+    LoongArchCPU *cpu = LOONGARCH_CPU(cs);
+    CPULoongArchState *env = &cpu->env;
+    int ret = TLBRET_BADADDR;
+
+    /* data access */
+    raise_mmu_exception(env, address, access_type, ret);
+    do_raise_exception_err(env, cs->exception_index, env->error_code, retaddr);
+}
