diff --git a/disas/riscv.c b/disas/riscv.c
index aaf85b2aba..590cdba0f6 100644
--- a/disas/riscv.c
+++ b/disas/riscv.c
@@ -19,6 +19,7 @@
 
 #include "qemu/osdep.h"
 #include "disas/dis-asm.h"
+#include "target/riscv/custom.h"
 
 
 /* types */
@@ -562,6 +563,11 @@ typedef enum {
     rv_op_xperm8 = 398,
 } rv_op;
 
+typedef enum {
+    Ventana_op_vt_maskc = 0,
+    Ventana_op_vt_maskcn = 1,
+} rv_Ventana_op;
+
 /* structures */
 
 typedef struct {
@@ -602,6 +608,7 @@ typedef struct {
     uint8_t   bs;
     uint8_t   rnum;
     const rv_opcode_data *used_opcode_data;
+    const rv_opcode_data *custom_opcode_data;
 } rv_decode;
 
 /* register names */
@@ -1287,6 +1294,11 @@ const rv_opcode_data opcode_data[] = {
     { "xperm8", rv_codec_r, rv_fmt_rd_rs1, NULL, 0, 0, 0 }
 };
 
+const rv_opcode_data Ventana_opcode_data[] = {
+    { "vt.maskc", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 },
+    { "vt.maskcn", rv_codec_r, rv_fmt_rd_rs1_rs2, NULL, 0, 0, 0 },
+};
+
 /* CSR names */
 
 static const char *csr_name(int csrno)
@@ -2244,6 +2256,18 @@ static void decode_inst_opcode(rv_decode *dec, rv_isa isa)
             case 0: op = rv_op_addd; break;
             case 1: op = rv_op_slld; break;
             case 5: op = rv_op_srld; break;
+            case 6: /* Todo: Move custom decode to sperate decode function */
+                if (dec->custom_opcode_data == Ventana_opcode_data) {
+                    op = Ventana_op_vt_maskc;
+                    dec->used_opcode_data = dec->custom_opcode_data;
+                }
+                break;
+            case 7:
+                if (dec->custom_opcode_data == Ventana_opcode_data) {
+                    op = Ventana_op_vt_maskcn;
+                    dec->used_opcode_data = dec->custom_opcode_data;
+                }
+                break;
             case 8: op = rv_op_muld; break;
             case 12: op = rv_op_divd; break;
             case 13: op = rv_op_divud; break;
@@ -3190,15 +3214,43 @@ static void decode_inst_decompress(rv_decode *dec, rv_isa isa)
     }
 }
 
+static const struct {
+    enum RISCVCustom ext;
+    const rv_opcode_data *opcode_data;
+} custom_opcode_table[] = {
+    { VENTANA_CUSTOM,   Ventana_opcode_data },
+};
+
+static const rv_opcode_data *
+get_custom_opcode_data(struct disassemble_info *info)
+{
+    for (size_t i = 0; i < ARRAY_SIZE(custom_opcode_table); ++i) {
+        if (info->target_info & (1ULL << custom_opcode_table[i].ext)) {
+            return custom_opcode_table[i].opcode_data;
+        }
+    }
+    return NULL;
+}
+
 /* disassemble instruction */
 
 static void
-disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_inst inst)
+disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_inst inst,
+            struct disassemble_info *info)
 {
     rv_decode dec = { 0 };
     dec.pc = pc;
     dec.inst = inst;
+
+    /*
+     * Set default opcode_data.
+     * Only overide the default opcode_data only when
+     * 1. There is a custom opcode data.
+     * 2. The instruction belongs to the custom extension.
+     */
     dec.used_opcode_data = opcode_data;
+    dec.custom_opcode_data = get_custom_opcode_data(info);
+
     decode_inst_opcode(&dec, isa);
     decode_inst_operands(&dec);
     decode_inst_decompress(&dec, isa);
@@ -3253,7 +3305,7 @@ print_insn_riscv(bfd_vma memaddr, struct disassemble_info *info, rv_isa isa)
         break;
     }
 
-    disasm_inst(buf, sizeof(buf), isa, memaddr, inst);
+    disasm_inst(buf, sizeof(buf), isa, memaddr, inst, info);
     (*info->fprintf_func)(info->stream, "%s", buf);
 
     return len;
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index a5f84f211d..cc6ef9303f 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -31,6 +31,7 @@
 #include "fpu/softfloat-helpers.h"
 #include "sysemu/kvm.h"
 #include "kvm_riscv.h"
+#include "custom.h"
 
 /* RISC-V CPU definitions */
 
@@ -504,11 +505,29 @@ static void riscv_cpu_reset(DeviceState *dev)
 #endif
 }
 
+static bool has_Ventana_ext(const RISCVCPUConfig *cfg_ptr)
+{
+    return cfg_ptr->ext_XVentanaCondOps;
+}
+
 static void riscv_cpu_disas_set_info(CPUState *s, disassemble_info *info)
 {
     RISCVCPU *cpu = RISCV_CPU(s);
     CPURISCVState *env = &cpu->env;
 
+    static const struct {
+        bool (*guard_func)(const RISCVCPUConfig *);
+        enum RISCVCustom ext;
+    } customs[] = {
+        { has_Ventana_ext, VENTANA_CUSTOM },
+    };
+
+    for (size_t i = 0; i < ARRAY_SIZE(customs); ++i) {
+        if (customs[i].guard_func(&(cpu->cfg))) {
+            info->target_info |= 1ULL << customs[i].ext;
+        }
+    }
+
     switch (env->xl) {
     case MXL_RV32:
         info->print_insn = print_insn_riscv32;
diff --git a/target/riscv/custom.h b/target/riscv/custom.h
new file mode 100644
index 0000000000..1a161984c0
--- /dev/null
+++ b/target/riscv/custom.h
@@ -0,0 +1,25 @@
+/*
+ * QEMU RISC-V CPU -- custom extensions
+ *
+ * Copyright (c) 2022 T-Head Semiconductor Co., Ltd. All rights reserved.
+ *
+ * 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/>.
+ */
+#ifndef RISCV_CPU_CUSTOM_H
+#define RISCV_CPU_CUSTOM_H
+
+enum RISCVCustom {
+    VENTANA_CUSTOM = 0,
+};
+
+#endif
