diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index a72722cfa6..d129a6112a 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -920,6 +920,9 @@ static Property riscv_cpu_extensions[] = {
     DEFINE_PROP_BOOL("zhinxmin", RISCVCPU, cfg.ext_zhinxmin, false),
 
     /* Vendor-specific custom extensions */
+    DEFINE_PROP_BOOL("xtheadba", RISCVCPU, cfg.ext_xtheadba, false),
+    DEFINE_PROP_BOOL("xtheadbb", RISCVCPU, cfg.ext_xtheadbb, false),
+    DEFINE_PROP_BOOL("xtheadbs", RISCVCPU, cfg.ext_xtheadbs, false),
     DEFINE_PROP_BOOL("xtheadcmo", RISCVCPU, cfg.ext_xtheadcmo, false),
     DEFINE_PROP_BOOL("xtheadsync", RISCVCPU, cfg.ext_xtheadsync, false),
     DEFINE_PROP_BOOL("xventanacondops", RISCVCPU, cfg.ext_XVentanaCondOps, false),
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 4ae22cf529..9e2b3d6f56 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -440,6 +440,9 @@ struct RISCVCPUConfig {
     uint64_t mimpid;
 
     /* Vendor-specific custom extensions */
+    bool ext_xtheadba;
+    bool ext_xtheadbb;
+    bool ext_xtheadbs;
     bool ext_xtheadcmo;
     bool ext_xtheadsync;
     bool ext_XVentanaCondOps;
diff --git a/target/riscv/insn_trans/trans_xthead.c.inc b/target/riscv/insn_trans/trans_xthead.c.inc
index 0a6719b2e2..b2d523b905 100644
--- a/target/riscv/insn_trans/trans_xthead.c.inc
+++ b/target/riscv/insn_trans/trans_xthead.c.inc
@@ -70,3 +70,127 @@ NOP_PRIVCHECK(th_sync_i, REQUIRE_PRIV_MHSU)
 NOP_PRIVCHECK(th_sync_is, REQUIRE_PRIV_MHSU)
 NOP_PRIVCHECK(th_sync_s, REQUIRE_PRIV_MHSU)
 
+/*
+ * th.addsl is similar to sh[123]add (from Zba), but not an
+ * alternative encoding: while sh[123] applies the shift to rs1,
+ * th.addsl shifts rs2.
+ */
+
+#define GEN_TH_ADDSL(SHAMT)                                     \
+static void gen_th_addsl##SHAMT(TCGv ret, TCGv arg1, TCGv arg2) \
+{                                                               \
+    TCGv t = tcg_temp_new();                                    \
+    tcg_gen_shli_tl(t, arg2, SHAMT);                            \
+    tcg_gen_add_tl(ret, t, arg1);                               \
+    tcg_temp_free(t);                                           \
+}
+
+GEN_TH_ADDSL(1)
+GEN_TH_ADDSL(2)
+GEN_TH_ADDSL(3)
+
+#define GEN_TRANS_TH_ADDSL(SHAMT)                                       \
+static bool trans_th_addsl##SHAMT(DisasContext *ctx,                    \
+                                  arg_th_addsl##SHAMT * a)              \
+{                                                                       \
+    return gen_arith(ctx, a, EXT_NONE, gen_th_addsl##SHAMT, NULL);      \
+}
+
+GEN_TRANS_TH_ADDSL(1)
+GEN_TRANS_TH_ADDSL(2)
+GEN_TRANS_TH_ADDSL(3)
+
+
+/* th.srri is an alternate encoding for rori (from Zbb) */
+static bool trans_th_srri(DisasContext *ctx, arg_th_srri * a)
+{
+    return gen_shift_imm_fn(ctx, a, EXT_NONE, tcg_gen_rotri_tl, NULL);
+}
+
+/* th.srriw is an alternate encoding for roriw (from Zbb) */
+static bool trans_th_srriw(DisasContext *ctx, arg_th_srriw *a)
+{
+    ctx->ol = MXL_RV32;
+    return gen_shift_imm_fn(ctx, a, EXT_NONE, gen_roriw, NULL);
+}
+
+/* th.ext and th.extu perform signed/unsigned bitfield extraction */
+static bool gen_th_bfextract(DisasContext *ctx, arg_th_bfext *a,
+                             void (*f)(TCGv, TCGv, unsigned int, unsigned int))
+{
+    TCGv dest = dest_gpr(ctx, a->rd);
+    TCGv source = get_gpr(ctx, a->rs1, EXT_ZERO);
+
+    if (a->lsb <= a->msb) {
+        f(dest, source, a->lsb, a->msb - a->lsb + 1);
+        gen_set_gpr(ctx, a->rd, dest);
+    }
+    return true;
+}
+
+static bool trans_th_ext(DisasContext *ctx, arg_th_ext *a)
+{
+    return gen_th_bfextract(ctx, a, tcg_gen_sextract_tl);
+}
+
+static bool trans_th_extu(DisasContext *ctx, arg_th_extu *a)
+{
+    return gen_th_bfextract(ctx, a, tcg_gen_extract_tl);
+}
+
+/* th.ff0: find first zero (clz on an inverted input) */
+static void gen_th_ff0(TCGv ret, TCGv arg1)
+{
+    TCGv t = tcg_temp_new();
+    tcg_gen_not_tl(t, arg1);
+    gen_clz(ret, t);
+    tcg_temp_free(t);
+}
+
+static bool trans_th_ff0(DisasContext *ctx, arg_th_ff0 *a)
+{
+    return gen_unary(ctx, a, EXT_NONE, gen_th_ff0);
+}
+
+/* th.ff1 is an alternate encoding for clz (from Zbb) */
+static bool trans_th_ff1(DisasContext *ctx, arg_th_ff1 *a)
+{
+    return gen_unary(ctx, a, EXT_NONE, gen_clz);
+}
+
+/* th.rev is an alternate encoding for the RV64 rev8 (from Zbb) */
+static bool trans_th_rev(DisasContext *ctx, arg_th_rev *a)
+{
+    return gen_unary(ctx, a, EXT_NONE, tcg_gen_bswap_tl);
+}
+
+/* th.revw is a sign-extended byte-swap of the lower word */
+static void gen_th_revw(TCGv ret, TCGv arg1)
+{
+    tcg_gen_bswap_tl(ret, arg1);
+    tcg_gen_sari_tl(ret, ret, 32);
+}
+
+static bool trans_th_revw(DisasContext *ctx, arg_th_revw *a)
+{
+    return gen_unary(ctx, a, EXT_NONE, gen_th_revw);
+}
+
+/* th.tstnbz is equivalent to an orc.b (from Zbb) with inverted result */
+static void gen_th_tstnbz(TCGv ret, TCGv source1)
+{
+    gen_orc_b(ret, source1);
+    tcg_gen_not_tl(ret, ret);
+}
+
+static bool trans_th_tstnbz(DisasContext *ctx, arg_th_tstnbz *a)
+{
+    return gen_unary(ctx, a, EXT_ZERO, gen_th_tstnbz);
+}
+
+/* th.tst is an alternate encoding for bexti (from Zbs) */
+static bool trans_th_tst(DisasContext *ctx, arg_th_tst *a)
+{
+    return gen_shift_imm_tl(ctx, a, EXT_NONE, gen_bext);
+}
+
diff --git a/target/riscv/meson.build b/target/riscv/meson.build
index f201cc6997..5ee37683cb 100644
--- a/target/riscv/meson.build
+++ b/target/riscv/meson.build
@@ -2,6 +2,9 @@
 gen = [
   decodetree.process('insn16.decode', extra_args: ['--static-decode=decode_insn16', '--insnwidth=16']),
   decodetree.process('insn32.decode', extra_args: '--static-decode=decode_insn32'),
+  decodetree.process('xtheadba.decode', extra_args: '--static-decode=decode_xtheadba'),
+  decodetree.process('xtheadbb.decode', extra_args: '--static-decode=decode_xtheadbb'),
+  decodetree.process('xtheadbs.decode', extra_args: '--static-decode=decode_xtheadbs'),
   decodetree.process('xtheadcmo.decode', extra_args: '--static-decode=decode_xtheadcmo'),
   decodetree.process('xtheadsync.decode', extra_args: '--static-decode=decode_xtheadsync'),
   decodetree.process('XVentanaCondOps.decode', extra_args: '--static-decode=decode_XVentanaCodeOps'),
diff --git a/target/riscv/translate.c b/target/riscv/translate.c
index a63cc3de46..f662e403f8 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -132,6 +132,9 @@ static bool always_true_p(DisasContext *ctx  __attribute__((__unused__)))
         return ctx->cfg_ptr->ext_ ## ext ; \
     }
 
+MATERIALISE_EXT_PREDICATE(xtheadba)
+MATERIALISE_EXT_PREDICATE(xtheadbb)
+MATERIALISE_EXT_PREDICATE(xtheadbs)
 MATERIALISE_EXT_PREDICATE(xtheadcmo)
 MATERIALISE_EXT_PREDICATE(xtheadsync)
 MATERIALISE_EXT_PREDICATE(XVentanaCondOps)
@@ -720,6 +723,9 @@ static int ex_rvc_shifti(DisasContext *ctx, int imm)
 #include "decode-insn32.c.inc"
 
 /* Include decoders for factored-out extensions */
+#include "decode-xtheadba.c.inc"
+#include "decode-xtheadbb.c.inc"
+#include "decode-xtheadbs.c.inc"
 #include "decode-xtheadcmo.c.inc"
 #include "decode-xtheadsync.c.inc"
 #include "decode-XVentanaCondOps.c.inc"
@@ -1042,6 +1048,9 @@ static void decode_opc(CPURISCVState *env, DisasContext *ctx, uint16_t opcode)
         bool (*decode_func)(DisasContext *, uint32_t);
     } decoders[] = {
         { always_true_p,  decode_insn32 },
+        { has_xtheadba_p, decode_xtheadba },
+        { has_xtheadbb_p, decode_xtheadbb },
+        { has_xtheadbs_p, decode_xtheadbs },
         { has_xtheadcmo_p, decode_xtheadcmo },
         { has_xtheadsync_p, decode_xtheadsync },
         { has_XVentanaCondOps_p,  decode_XVentanaCodeOps },
diff --git a/target/riscv/xtheadba.decode b/target/riscv/xtheadba.decode
new file mode 100644
index 0000000000..4e5e3f12f0
--- /dev/null
+++ b/target/riscv/xtheadba.decode
@@ -0,0 +1,46 @@
+#
+# RISC-V instruction decode for the XTheadBa extension
+#
+# Copyright (c) 2022 Dr. Philipp Tomsich, philipp.tomsich@vrull.eu
+#                    Christoph Muellner, christoph.muellner@vrull.eu
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# The XTheadBa extension provides instructions for address calculations,
+# implementing the functional equivalent of a subset of Zba.
+#
+# It is documented in
+# https://github.com/T-head-Semi/thead-extension-spec/releases/download/2.0.0/xthead-2022-09-05-2.0.0.pdf
+#
+# The instruction contained in XTheadBb is:
+# - th.addsl      add rotate-right by immediate
+#                 (similar to sh[123]add, but with rs1 and rs2 switched)
+# This instruction reuses an existing instruction format.
+
+# Fields
+%rs2       20:5
+%rs1       15:5
+%rd        7:5
+
+# Argument sets
+&r         rd rs1 rs2		!extern
+
+# Formats:
+@r          ....... ..... .....  ... ..... ....... &r %rs2 %rs1 %rd
+
+# *** Bitmanip addressing instructions
+# Instead of defining a new encoding, we simply use the decoder to
+# extract the imm[0:1] field and dispatch to separate translation
+# functions (mirroring the `sh[123]add` instructions from Zba and
+# the regular RVI `add` instruction.
+#
+# The only difference between sh[123]add and addsl is that the sohift
+# is applied to rs1 (for addsl) instead of rs2 (for sh[123]add).
+#
+# Note that shift-by-0 is a valid operation according to the manual.
+# This will be equivalent to a regular add.
+add              0000000 ..... ..... 001 ..... 0001011 @r
+th_addsl1        0000001 ..... ..... 001 ..... 0001011 @r
+th_addsl2        0000010 ..... ..... 001 ..... 0001011 @r
+th_addsl3        0000011 ..... ..... 001 ..... 0001011 @r
+
diff --git a/target/riscv/xtheadbb.decode b/target/riscv/xtheadbb.decode
new file mode 100644
index 0000000000..2754a6444b
--- /dev/null
+++ b/target/riscv/xtheadbb.decode
@@ -0,0 +1,62 @@
+#
+# RISC-V instruction decode for the XTheadBb extension
+#
+# Copyright (c) 2022 Dr. Philipp Tomsich, philipp.tomsich@vrull.eu
+#                    Christoph Muellner, christoph.muellner@vrull.eu
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# The XTheadBb extension provides basic bit-manipulation instructions,
+# implementing the functional equivalent of a subset of Zbb.
+#
+# It is documented in
+# https://github.com/T-head-Semi/thead-extension-spec/releases/download/2.0.0/xthead-2022-09-05-2.0.0.pdf
+#
+# The instructions contained in XTheadBb are:
+# - th.srri       rotate-right by immediate (matches rori)
+# - th.srriw      rotate-right by immediate, w-form (matches roriw)
+# - th.rev        byte-reverse register (matches RV64-form of rev8)
+# - th.revw       byte-reverse low word, sign-extend result (no equivalent)
+# - th.ext        signed bitfield-extract (no equivalent)
+# - th.extu       unsigned bitfield-extract (no equivalent)
+# - th.ff0        find-first zero (equivalent to clz on the inverted operand)
+# - th.ff1        find-first one (matches clz)
+# - th.tstnbz     test for zero-bytes (equivalent to the inverted result of orc.b)
+# - th.tst        test for bit (equivalent to bexti)
+#
+# These instructions generally reuse existing instruction formats.
+# Only the th.ext and th.ext introduce a new, vendor-defined instruction format.
+
+# Fields
+%rs2       20:5
+%rs1       15:5
+%rd        7:5
+%sh5       20:5
+%sh6       20:6
+
+# Argument sets
+&r2        rd rs1		!extern
+&shift     shamt rs1 rd		!extern
+&th_bfext  msb lsb rs1 rd
+
+# Formats:
+@r2         ....... ..... .....  ... ..... ....... &r2 %rs1 %rd
+@th_bfext   msb:6  lsb:6  .....  ... ..... ....... &th_bfext %rs1 %rd
+
+# Formats 64:
+@sh5        ....... ..... .....  ... ..... ....... &shift  shamt=%sh5      %rs1 %rd
+
+# Formats 128:
+@sh6        ...... ...... .....  ... ..... ....... &shift shamt=%sh6 %rs1 %rd
+
+# *** Bitmanip instructions
+th_ext           ...... ...... ..... 010 ..... 0001011 @th_bfext
+th_extu          ...... ...... ..... 011 ..... 0001011 @th_bfext
+th_ff0           1000010 00000 ..... 001 ..... 0001011 @r2
+th_ff1           1000011 00000 ..... 001 ..... 0001011 @r2
+th_srri          000100 ...... ..... 001 ..... 0001011 @sh6
+th_srriw         0001010 ..... ..... 001 ..... 0001011 @sh5
+th_rev           1000001 00000 ..... 001 ..... 0001011 @r2
+th_revw          1001000 00000 ..... 001 ..... 0001011 @r2
+th_tstnbz        1000000 00000 ..... 001 ..... 0001011 @r2
+
diff --git a/target/riscv/xtheadbs.decode b/target/riscv/xtheadbs.decode
new file mode 100644
index 0000000000..7aa345b207
--- /dev/null
+++ b/target/riscv/xtheadbs.decode
@@ -0,0 +1,32 @@
+#
+# RISC-V instruction decode for the XTheadBb extension
+#
+# Copyright (c) 2022 Dr. Philipp Tomsich, philipp.tomsich@vrull.eu
+#                    Christoph Muellner, christoph.muellner@vrull.eu
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# XTheadBs provides basic bit-manipulation instructions,
+# implementing the functional equivalent of a subset of Zbs.
+#
+# It is documented in
+# https://github.com/T-head-Semi/thead-extension-spec/releases/download/2.0.0/xthead-2022-09-05-2.0.0.pdf
+#
+# The instruction contained in XTheadBb is:
+# - th.tst        test if bit is set (matches bexti)
+#
+# The instruction reuses an existing instruction format.
+
+# Fields
+%rs1       15:5
+%rd        7:5
+%sh6       20:6
+
+# Argument sets
+&shift     shamt rs1 rd		!extern
+
+# Formats 128:
+@sh6        ...... ...... .....  ... ..... ....... &shift shamt=%sh6 %rs1 %rd
+
+# *** Bitmanip single-bit instructions
+th_tst           100010 ...... ..... 001 ..... 0001011 @sh6
