diff --git a/target/riscv/helper.h b/target/riscv/helper.h
index 227c7122ef..048ccf6ac3 100644
--- a/target/riscv/helper.h
+++ b/target/riscv/helper.h
@@ -1136,3 +1136,9 @@ DEF_HELPER_FLAGS_1(aes64im, TCG_CALL_NO_RWG_SE, tl, tl)
 
 DEF_HELPER_FLAGS_3(sm4ed, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl)
 DEF_HELPER_FLAGS_3(sm4ks, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl)
+
+/* Zce helper */
+DEF_HELPER_4(cm_pop, tl, env, tl, tl, tl)
+DEF_HELPER_4(cm_push, void, env, tl, tl, tl)
+DEF_HELPER_4(cm_popret, tl, env, tl, tl, tl)
+DEF_HELPER_4(cm_popretz, tl, env, tl, tl, tl)
diff --git a/target/riscv/insn16.decode b/target/riscv/insn16.decode
index 2c1ae4d92e..941146633d 100644
--- a/target/riscv/insn16.decode
+++ b/target/riscv/insn16.decode
@@ -21,6 +21,8 @@
 %rs1_3     7:3                !function=ex_rvc_register
 %rs2_3     2:3                !function=ex_rvc_register
 %rs2_5     2:5
+%sreg1     7:3                !function=ex_sreg_register
+%sreg2     2:3                !function=ex_sreg_register
 
 # Immediates:
 %imm_ci        12:s1 2:5
@@ -45,6 +47,8 @@
 
 %zcb_b_uimm  5:1 6:1
 %zcb_h_uimm  5:1                     !function=ex_shift_1
+%zcmp_spimm  2:2                     !function=ex_shift_4
+%zcmp_rlist  4:4
 
 # Argument sets imported from insn32.decode:
 &empty                  !extern
@@ -56,7 +60,9 @@
 &u         imm rd       !extern
 &shift     shamt rs1 rd !extern
 &r2        rd rs1       !extern
+&r2_s      rs1 rs2      !extern
 
+&zcmp      zcmp_rlist zcmp_spimm
 
 # Formats 16:
 @cr        ....  ..... .....  .. &r      rs2=%rs2_5       rs1=%rd     %rd
@@ -96,6 +102,8 @@
 @zcb_binary   ... ...  ... .. ... ..  &r  rs2=%rs2_3       rs1=%rs1_3 rd=%rs1_3
 @zcb_b        ... . .. ... .. ... ..  &i  imm=%zcb_b_uimm  rs1=%rs1_3 rd=%rs2_3
 @zcb_h        ... . .. ... .. ... ..  &i  imm=%zcb_h_uimm  rs1=%rs1_3 rd=%rs2_3
+@zcmp         ... ...  ........   ..  &zcmp  %zcmp_rlist   %zcmp_spimm
+@cm_mv        ... ...  ... .. ... ..  &r2_s  rs2=%sreg2    rs1=%sreg1
 
 # *** RV32/64C Standard Extension (Quadrant 0) ***
 {
@@ -175,6 +183,14 @@ slli              000 .  .....  ..... 10 @c_shift2
 {
   sq              101  ... ... .. ... 10 @c_sqsp
   c_fsd           101   ......  ..... 10 @c_sdsp
+
+  # *** RV64 and RV32 Zcmp Extension ***
+  cm_push         101  11000  .... .. 10 @zcmp
+  cm_pop          101  11010  .... .. 10 @zcmp
+  cm_popret       101  11110  .... .. 10 @zcmp
+  cm_popretz      101  11100  .... .. 10 @zcmp
+  cm_mva01s       101  011 ... 11 ... 10 @cm_mv
+  cm_mvsa01       101  011 ... 01 ... 10 @cm_mv
 }
 sw                110 .  .....  ..... 10 @c_swsp
 
diff --git a/target/riscv/insn_trans/trans_rvzce.c.inc b/target/riscv/insn_trans/trans_rvzce.c.inc
index 0947190f2d..b0055934ed 100644
--- a/target/riscv/insn_trans/trans_rvzce.c.inc
+++ b/target/riscv/insn_trans/trans_rvzce.c.inc
@@ -1,5 +1,5 @@
 /*
- * RISC-V translation routines for the Zcb Standard Extension.
+ * RISC-V translation routines for the Zc[b,mp] Standard Extension.
  *
  * Copyright (c) 2021-2022 PLCT Lab
  *
@@ -21,6 +21,11 @@
         return false;           \
 } while (0)
 
+#define REQUIRE_ZCMP(ctx) do {   \
+    if (!ctx->cfg_ptr->ext_zcmp) \
+        return false;            \
+} while (0)
+
 static bool trans_c_zext_b(DisasContext *ctx, arg_c_zext_b *a)
 {
     REQUIRE_ZCB(ctx);
@@ -131,3 +136,103 @@ static bool trans_c_sh(DisasContext *ctx, arg_c_sh *a)
     MemOp memop = MO_UW;
     return gen_zce_store(ctx, a, memop);
 }
+
+static bool gen_zcmp_check(DisasContext *ctx, arg_zcmp *a)
+{
+    /* rlist 0 to 3 are reserved for a future EABI variant called cm.popret.e */
+    if (a->zcmp_rlist < 4) {
+        return false;
+    }
+
+    /* rlist <= 6 when RV32E/RV64E */
+    if (ctx->cfg_ptr->ext_e && a->zcmp_rlist > 6) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool gen_pop(DisasContext *ctx, arg_zcmp *a, bool ret,
+                    void (*func)(TCGv, TCGv_env, TCGv, TCGv, TCGv))
+{
+    if (!gen_zcmp_check(ctx, a)) {
+        return false;
+    }
+
+    TCGv sp = get_gpr(ctx, xSP, EXT_NONE);
+    TCGv rlist = tcg_const_tl(a->zcmp_rlist);
+    TCGv spimm = tcg_const_tl(a->zcmp_spimm);
+
+    func(cpu_pc, cpu_env, sp, spimm, rlist);
+
+    if (ret) {
+        gen_set_pc(ctx, cpu_pc);
+        tcg_gen_lookup_and_goto_ptr();
+        ctx->base.is_jmp = DISAS_NORETURN;
+    }
+    tcg_temp_free(spimm);
+    tcg_temp_free(rlist);
+    return true;
+}
+
+static bool trans_cm_push(DisasContext *ctx, arg_cm_push *a)
+{
+    REQUIRE_ZCMP(ctx);
+
+    if (!gen_zcmp_check(ctx, a)) {
+        return false;
+    }
+
+    TCGv sp = get_gpr(ctx, xSP, EXT_NONE);
+    TCGv rlist = tcg_const_tl(a->zcmp_rlist);
+    TCGv spimm = tcg_const_tl(a->zcmp_spimm);
+    gen_helper_cm_push(cpu_env, sp, spimm, rlist);
+
+    tcg_temp_free(spimm);
+    tcg_temp_free(rlist);
+    return true;
+}
+
+static bool trans_cm_pop(DisasContext *ctx, arg_cm_pop *a)
+{
+    REQUIRE_ZCMP(ctx);
+    return gen_pop(ctx, a, false, gen_helper_cm_pop);
+}
+
+static bool trans_cm_popret(DisasContext *ctx, arg_cm_popret *a)
+{
+    REQUIRE_ZCMP(ctx);
+    return gen_pop(ctx, a, true, gen_helper_cm_popret);
+}
+
+static bool trans_cm_popretz(DisasContext *ctx, arg_cm_popret *a)
+{
+    REQUIRE_ZCMP(ctx);
+    return gen_pop(ctx, a, true, gen_helper_cm_popretz);
+}
+
+static bool trans_cm_mva01s(DisasContext *ctx, arg_cm_mva01s *a)
+{
+    REQUIRE_ZCMP(ctx);
+
+    TCGv src1 = get_gpr(ctx, a->rs1, EXT_NONE);
+    TCGv src2 = get_gpr(ctx, a->rs2, EXT_NONE);
+
+    gen_set_gpr(ctx, xA0, src1);
+    gen_set_gpr(ctx, xA1, src2);
+
+    return true;
+}
+
+static bool trans_cm_mvsa01(DisasContext *ctx, arg_cm_mvsa01 *a)
+{
+    REQUIRE_ZCMP(ctx);
+
+    TCGv a0 = get_gpr(ctx, xA0, EXT_NONE);
+    TCGv a1 = get_gpr(ctx, xA1, EXT_NONE);
+
+    gen_set_gpr(ctx, a->rs1, a0);
+    gen_set_gpr(ctx, a->rs2, a1);
+
+    return true;
+}
diff --git a/target/riscv/meson.build b/target/riscv/meson.build
index ba25164d74..4bf9c9e632 100644
--- a/target/riscv/meson.build
+++ b/target/riscv/meson.build
@@ -18,7 +18,8 @@ riscv_ss.add(files(
   'bitmanip_helper.c',
   'translate.c',
   'm128_helper.c',
-  'crypto_helper.c'
+  'crypto_helper.c',
+  'zce_helper.c'
 ))
 riscv_ss.add(when: 'CONFIG_KVM', if_true: files('kvm.c'), if_false: files('kvm-stub.c'))
 
diff --git a/target/riscv/translate.c b/target/riscv/translate.c
index 7cd35058d3..90e9ea1136 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -738,6 +738,11 @@ static int ex_rvc_register(DisasContext *ctx, int reg)
     return 8 + reg;
 }
 
+static int ex_sreg_register(DisasContext *ctx, int reg)
+{
+    return reg < 2 ? reg + 8 : reg + 16;
+}
+
 static int ex_rvc_shiftli(DisasContext *ctx, int imm)
 {
     /* For RV128 a shamt of 0 means a shift by 64. */
diff --git a/target/riscv/zce_helper.c b/target/riscv/zce_helper.c
new file mode 100644
index 0000000000..1346de1367
--- /dev/null
+++ b/target/riscv/zce_helper.c
@@ -0,0 +1,210 @@
+/*
+ * RISC-V Zc* extension Helpers for QEMU.
+ *
+ * Copyright (c) 2021-2022 PLCT Lab
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "exec/exec-all.h"
+#include "exec/helper-proto.h"
+#include "exec/cpu_ldst.h"
+
+#define X_S0    8
+#define X_S1    9
+#define X_Sn    16
+#define X_RA    1
+#define X_A0    10
+#define X_S4_E  7
+#define X_S3_E  6
+#define X_S2_E  14
+
+static inline void update_push_pop_list(target_ulong rlist, bool *xreg_list)
+{
+    switch (rlist) {
+    case 15:
+        xreg_list[X_Sn + 11] = true;
+        xreg_list[X_Sn + 10] = true;
+        /* FALL THROUGH */
+    case 14:
+        xreg_list[X_Sn + 9] = true;
+        /* FALL THROUGH */
+    case 13:
+        xreg_list[X_Sn + 8] = true;
+        /* FALL THROUGH */
+    case 12:
+        xreg_list[X_Sn + 7] = true;
+        /* FALL THROUGH */
+    case 11:
+        xreg_list[X_Sn + 6] = true;
+        /* FALL THROUGH */
+    case 10:
+        xreg_list[X_Sn + 5] = true;
+        /* FALL THROUGH */
+    case 9:
+        xreg_list[X_Sn + 4] = true;
+        /* FALL THROUGH */
+    case 8:
+        xreg_list[X_Sn + 3] = true;
+        /* FALL THROUGH */
+    case 7:
+        xreg_list[X_Sn + 2] = true;
+        /* FALL THROUGH */
+    case 6:
+        xreg_list[X_S1] = true;
+        /* FALL THROUGH */
+    case 5:
+        xreg_list[X_S0] = true;
+        /* FALL THROUGH */
+    case 4:
+        xreg_list[X_RA] = true;
+        break;
+    }
+}
+
+static inline target_ulong caculate_stack_adj(int bytes, target_ulong rlist,
+                                              target_ulong spimm)
+{
+    target_ulong stack_adj_base = 0;
+    switch (rlist) {
+    case 15:
+        stack_adj_base = bytes == 4 ? 64 : 112;
+        break;
+    case 14:
+        stack_adj_base = bytes == 4 ? 48 : 96;
+        break;
+    case 13:
+    case 12:
+        stack_adj_base = bytes == 4 ? 48 : 80;
+        break;
+    case 11:
+    case 10:
+        stack_adj_base = bytes == 4 ? 32 : 64;
+        break;
+    case 9:
+    case 8:
+        stack_adj_base = bytes == 4 ? 32 : 48;
+        break;
+    case 7:
+    case 6:
+        stack_adj_base = bytes == 4 ? 16 : 32;
+        break;
+    case 5:
+    case 4:
+        stack_adj_base = 16;
+        break;
+    }
+
+    return stack_adj_base + spimm;
+}
+
+static inline target_ulong zcmp_pop(CPURISCVState *env, target_ulong sp,
+                                    target_ulong rlist, target_ulong spimm,
+                                    bool ret_val, bool ret)
+{
+    bool xreg_list[32] = {false};
+    target_ulong addr;
+    target_ulong stack_adj;
+    int bytes = riscv_cpu_xlen(env) == 32 ? 4 : 8;
+    int i;
+
+    update_push_pop_list(rlist, xreg_list);
+    stack_adj = caculate_stack_adj(bytes, rlist, spimm);
+    addr = sp + stack_adj - bytes;
+    for (i = 31; i >= 0; i--) {
+        if (xreg_list[i]) {
+            switch (bytes) {
+            case 4:
+                env->gpr[i] = cpu_ldl_le_data(env, addr);
+                break;
+            case 8:
+                env->gpr[i] = cpu_ldq_le_data(env, addr);
+                break;
+            default:
+                break;
+            }
+            addr -= bytes;
+        }
+    }
+
+    if (ret_val) {
+        env->gpr[xA0] = 0;
+    }
+
+    env->gpr[xSP] = sp + stack_adj;
+    if (ret) {
+        return env->gpr[xRA];
+    } else {
+        return env->pc;
+    }
+}
+
+static inline void zcmp_push(CPURISCVState *env, target_ulong sp,
+                             target_ulong rlist, target_ulong spimm)
+{
+    target_ulong addr = sp;
+    bool xreg_list[32] = {false};
+    target_ulong stack_adj;
+    int bytes = riscv_cpu_xlen(env) == 32 ? 4 : 8;
+    int i;
+
+    update_push_pop_list(rlist, xreg_list);
+    stack_adj = caculate_stack_adj(bytes, rlist, spimm);
+    addr -= bytes;
+    for (i = 31; i >= 0; i--) {
+        if (xreg_list[i]) {
+            switch (bytes) {
+            case 4:
+                cpu_stl_le_data(env, addr, env->gpr[i]);
+                break;
+            case 8:
+                cpu_stq_le_data(env, addr, env->gpr[i]);
+                break;
+            default:
+                break;
+            }
+            addr -= bytes;
+        }
+    }
+    env->gpr[xSP] = sp - stack_adj;
+}
+
+void HELPER(cm_push)(CPURISCVState *env, target_ulong sp, target_ulong spimm,
+                     target_ulong rlist)
+{
+    return zcmp_push(env, sp, rlist, spimm);
+}
+
+target_ulong HELPER(cm_pop)(CPURISCVState *env, target_ulong sp,
+                            target_ulong spimm, target_ulong rlist)
+{
+    return zcmp_pop(env, sp, rlist, spimm, false, false);
+}
+
+target_ulong HELPER(cm_popret)(CPURISCVState *env, target_ulong sp,
+                               target_ulong spimm, target_ulong rlist)
+{
+    return zcmp_pop(env, sp, rlist, spimm, false, true);
+}
+
+target_ulong HELPER(cm_popretz)(CPURISCVState *env, target_ulong sp,
+                                target_ulong spimm, target_ulong rlist)
+{
+    return zcmp_pop(env, sp, rlist, spimm, true, true);
+}
+#undef X_S0
+#undef X_Sn
+#undef ZCMP_POP
+#undef ZCMP_PUSH
