diff --git a/tests/TestSuite_vf_pmd_stacked_bonded.py b/tests/TestSuite_vf_pmd_stacked_bonded.py
new file mode 100644
index 00000000..5765481e
--- /dev/null
+++ b/tests/TestSuite_vf_pmd_stacked_bonded.py
@@ -0,0 +1,613 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Intel Corporation
+#
+
+import time
+import traceback
+
+# import dts/framework libs
+import framework.utils as utils
+
+# import bonding lib
+import tests.bonding as bonding
+from framework.exception import VerifyFailure
+from framework.test_case import TestCase
+
+from .bonding import (
+    FRAME_SIZE_64,
+    MODE_ACTIVE_BACKUP,
+    MODE_ALB_BALANCE,
+    MODE_BROADCAST,
+    MODE_LACP,
+    MODE_ROUND_ROBIN,
+    MODE_TLB_BALANCE,
+    MODE_XOR_BALANCE,
+)
+
+
+class TestVFPmdStackedBonded(TestCase):
+
+    #
+    # On dut, dpdk bonding
+    #
+    def check_bonded_device_queue_config(self, *devices):
+        """
+        check if master bonded device/slave device queue configuration
+        is the same.
+        """
+        # get master bonded device queue configuration
+        master = self.bond_inst.get_port_info(devices[0], "queue_config")
+        # get slave device queue configuration
+        for port_id in devices[1:]:
+            config = self.bond_inst.get_port_info(port_id, "queue_config")
+            if config == master:
+                continue
+            msg = (
+                "slave bonded port [{0}] is " "different to top bonded port [{1}]"
+            ).format(port_id, devices[0])
+            raise VerifyFailure("bonded device queue config:: " + msg)
+
+    def set_stacked_bonded(self, slaveGrpOne, slaveGrpTwo, bond_mode, ignore=False):
+        """
+        set stacked bonded mode for a custom bonding mode
+        """
+        inst = self.bond_inst
+        socket_id = self.dut.get_numa_id(self.bond_slave)
+        # create first bonded device 1, add slaves in it
+        bond_port_1 = inst.create_bonded_device(bond_mode, socket_id)
+        inst.add_slave(bond_port_1, False, "", *slaveGrpOne)
+        # create second bonded device 2, add slaves in it
+        bond_port_2 = inst.create_bonded_device(bond_mode, socket_id)
+        inst.add_slave(bond_port_2, False, "", *slaveGrpTwo)
+        # create master bonded device 3, which is the top bonded device
+        master_bond_port = inst.create_bonded_device(bond_mode, socket_id)
+        # add bond bonded device 1 to bonded device 3
+        # check bonding config status
+        inst.add_slave(master_bond_port, False, "", *[bond_port_1])
+        # add bonded device 2 to bonded device 3
+        # check bonding config status
+        inst.add_slave(master_bond_port, False, "", *[bond_port_2])
+        # check if master bonding/each slaves queue configuration is the same.
+        if not ignore:
+            self.check_bonded_device_queue_config(
+                *[master_bond_port, bond_port_1, bond_port_2]
+            )
+
+        return [bond_port_1, bond_port_2, master_bond_port]
+
+    def set_third_stacked_bonded(self, bond_port, bond_mode):
+        """
+        set third level stacked bonded to check if stacked level can be set
+        more than 2
+        """
+        inst = self.bond_inst
+        socket_id = self.dut.get_numa_id(self.bond_slave)
+        third_bond_port = inst.create_bonded_device(bond_mode, socket_id)
+        inst.add_slave(third_bond_port, False, "", *[bond_port])
+
+    def duplicate_add_stacked_bonded(self, bond_port_1, bond_port_2, master_bond_port):
+        """
+        check if adding duplicate stacked bonded device is forbidden
+        """
+        inst = self.bond_inst
+        # check exception process
+        expected_str = "Slave device is already a slave of a bonded device"
+        # add bonded device 1 to bonded device 3
+        # check bonding config status
+        inst.add_slave(master_bond_port, False, expected_str, *[bond_port_1])
+        # add bonded device 2 to bonded device 3
+        # check bonding config status
+        inst.add_slave(master_bond_port, False, expected_str, *[bond_port_2])
+
+    def preset_stacked_bonded(self, slaveGrpOne, slaveGrpTwo, bond_mode):
+        bond_port_1, bond_port_2, master_bond_port = self.set_stacked_bonded(
+            slaveGrpOne, slaveGrpTwo, bond_mode, ignore=True
+        )
+        portList = [
+            slaveGrpOne[0],
+            slaveGrpTwo[0],
+            bond_port_1,
+            bond_port_2,
+            master_bond_port,
+        ]
+        cmds = [
+            ["port stop all", ""],
+            ["set portlist " + ",".join([str(port) for port in portList]), ""],
+            # start top level bond port only, and let it propagate the start
+            # action to slave bond ports and its the real nics.
+            ["port start {}".format(master_bond_port), " ", 15],
+        ]
+        self.bond_inst.d_console(cmds)
+        # blank space command is used to skip LSC event to avoid core dumped issue
+        time.sleep(5)
+        cmds = [[" ", ""], ["start", ""]]
+        self.bond_inst.d_console(cmds)
+        time.sleep(5)
+
+        return bond_port_1, bond_port_2, master_bond_port
+
+    def send_packets_by_scapy(self, **kwargs):
+        tx_iface = kwargs.get("port topo")[0]
+        # set interface ready to send packet
+        self.dut1 = self.dut.new_session()
+        cmd = "ifconfig {0} up".format(tx_iface)
+        self.dut1.send_expect(cmd, "# ", 30)
+        # stream config
+        send_pkts = kwargs.get("stream")
+        # stream config
+        stream_configs = kwargs.get("traffic configs")
+        count = stream_configs.get("count")
+        interval = stream_configs.get("interval", 0.01)
+        # run traffic
+        self.dut1.send_expect("scapy", ">>> ", 30)
+        cmd = (
+            "sendp("
+            + send_pkts[0].command()
+            + f',iface="{tx_iface}",count={count},inter={interval},verbose=False)'
+        )
+        out = self.dut1.send_expect(cmd, ">>> ")
+        self.verify("Error" not in out, "scapy failed to send packets!!!")
+        self.dut1.send_expect("quit()", "# ")
+        self.dut.close_session(self.dut1)
+
+    #
+    # packet transmission
+    #
+    def traffic(self, traffic_config, ports, tport_is_up=True):
+        # get ports statistics before sending packets
+        stats_pre = self.bond_inst.get_all_stats(ports)
+        # send packets
+        if tport_is_up:
+            self.bond_inst.send_packet(traffic_config)
+        else:
+            self.send_packets_by_scapy(**traffic_config)
+        # get ports statistics after sending packets
+        stats_post = self.bond_inst.get_all_stats(ports)
+        # calculate ports statistics result
+        for port_id in ports:
+            stats_post[port_id]["RX-packets"] -= stats_pre[port_id]["RX-packets"]
+            stats_post[port_id]["TX-packets"] -= stats_pre[port_id]["TX-packets"]
+
+        return stats_post
+
+    def config_port_traffic(self, tx_port, rx_port, total_pkt):
+        """set traffic configuration"""
+        traffic_config = {
+            "port topo": [tx_port, rx_port],
+            "stream": self.bond_inst.set_stream_to_slave_port(rx_port),
+            "traffic configs": {
+                "count": total_pkt,
+            },
+        }
+
+        return traffic_config
+
+    def active_slave_rx(self, slave, bond_port, mode):
+        msg = "send packet to active slave port <{0}>".format(slave)
+        self.logger.info(msg)
+        tx_intf = self.tester.get_interface(
+            self.tester.get_local_port(self.dut_ports[slave])
+        )
+        # get traffic config
+        traffic_config = self.config_port_traffic(tx_intf, slave, self.total_pkt)
+        # select ports for statistics
+        ports = [slave, bond_port]
+        # run traffic
+        stats = self.traffic(traffic_config, ports)
+        # check slave statistics
+        msg = "port <{0}> Data not received by port <{1}>".format(tx_intf, slave)
+        self.verify(stats[slave]["RX-packets"] >= self.total_pkt, msg)
+        msg = "tester port {0}  <----> dut port {1} is ok".format(tx_intf, slave)
+        self.logger.info(msg)
+        # check bond port statistics
+        self.verify(
+            stats[slave]["RX-packets"] == self.total_pkt,
+            "Bond port have error RX packet in XOR",
+        )
+
+    def inactive_slave_rx(self, slave, bond_port, mode):
+        msg = "send packet to inactive slave port <{0}>".format(slave)
+        self.logger.info(msg)
+        dport_info0 = self.dut.ports_info[self.dut_ports[slave]]
+        tx_intf = dport_info0["intf"]
+        # get traffic config
+        traffic_config = self.config_port_traffic(tx_intf, slave, self.total_pkt)
+        # select ports for statistics
+        ports = [slave, bond_port]
+        # run traffic
+        stats = self.traffic(traffic_config, ports, tport_is_up=False)
+        # check slave statistics
+        msg = ("port <{0}> Data received by port <{1}>, " "but should not.").format(
+            tx_intf, slave
+        )
+        self.verify(stats[slave]["RX-packets"] == 0, msg)
+        msg = "tester port {0}  <-|  |-> VF port {1} is blocked".format(tx_intf, slave)
+        self.logger.info(msg)
+        # check bond port statistics
+        self.verify(
+            stats[slave]["RX-packets"] == 0,
+            "Bond port have error RX packet in {0}".format(mode),
+        )
+
+    def set_port_status(self, vfs_id, tport_inface, status):
+        # stop slave link by force
+        cmd = "ifconfig {0} {1}".format(tport_inface, status)
+        self.tester.send_expect(cmd, "# ")
+        time.sleep(3)
+        vfs_id = vfs_id if isinstance(vfs_id, list) else [vfs_id]
+        for vf in vfs_id:
+            cur_status = self.bond_inst.get_port_info(vf, "link_status")
+            self.logger.info("port {0} is [{1}]".format(vf, cur_status))
+            self.verify(cur_status == status, "expected status is [{0}]".format(status))
+
+    def check_traffic_with_one_slave_down(self, mode):
+        """
+        Verify that transmitting packets correctly when set one slave of
+        the bonded device link down.
+        """
+        results = []
+        # -------------------------------
+        # boot up testpmd
+        self.bond_inst.start_testpmd(self.eal_param)
+        try:
+            slaves = {"active": [], "inactive": []}
+            # -------------------------------
+            # preset stacked bonded device
+            slaveGrpOne = self.slaveGrpOne
+            slaveGrpTwo = self.slaveGrpTwo
+            bond_port_1, bond_port_2, master_bond_port = self.preset_stacked_bonded(
+                slaveGrpOne, slaveGrpTwo, mode
+            )
+            # ---------------------------------------------------
+            # set one slave of first bonded device link down
+            primary_slave = slaveGrpOne[0]
+            tester_port = self.tester.get_local_port(primary_slave)
+            tport_iface = self.tester.get_interface(tester_port)
+            self.set_port_status(
+                vfs_id=primary_slave, tport_inface=tport_iface, status="down"
+            )
+            slaves["inactive"].append(primary_slave)
+            # get slave status
+            primary_port, active_slaves = self.bond_inst.get_active_slaves(bond_port_1)
+            slaves["active"].extend(active_slaves)
+            if primary_slave in slaves["active"]:
+                msg = "{0} should not be in active slaves list".format(primary_slave)
+                raise Exception(msg)
+            # ---------------------------------------------------
+            # set one slave of second bonded device link down
+            primary_slave = slaveGrpTwo[0]
+            tester_port = self.tester.get_local_port(primary_slave)
+            tport_iface = self.tester.get_interface(tester_port)
+            self.set_port_status(
+                vfs_id=primary_slave, tport_inface=tport_iface, status="down"
+            )
+            slaves["inactive"].append(primary_slave)
+            # check active slaves
+            primary_port_2, active_slaves_2 = self.bond_inst.get_active_slaves(
+                bond_port_2
+            )
+            slaves["active"].extend(active_slaves_2)
+            if primary_slave in slaves["active"]:
+                msg = "{0} should not be in active slaves list".format(primary_slave)
+                raise Exception(msg)
+            # traffic testing
+            # active slave traffic testing
+            for slave in slaves["active"]:
+                self.active_slave_rx(slave, master_bond_port, mode)
+            # inactive slave traffic testing
+            for slave in slaves["inactive"]:
+                self.inactive_slave_rx(slave, master_bond_port, mode)
+        except Exception as e:
+            results.append(e)
+            self.logger.error(traceback.format_exc())
+        finally:
+            self.bond_inst.close_testpmd()
+
+        return results
+
+    def check_traffic(self, mode):
+        """normal traffic with all slaves are under active status.
+        verify the RX packets are all correct with stacked bonded device.
+        bonded device's statistics should be the sum of slaves statistics.
+        """
+        self.bond_inst.start_testpmd(self.eal_param)
+        slaveGrpOne = self.slaveGrpOne
+        slaveGrpTwo = self.slaveGrpTwo
+        bond_port_1, bond_port_2, master_bond_port = self.preset_stacked_bonded(
+            slaveGrpOne, slaveGrpTwo, mode
+        )
+        results = []
+        # check first bonded device
+        try:
+            self.logger.info("check first bonded device")
+            # active slave traffic testing
+            for slave in slaveGrpOne:
+                self.active_slave_rx(slave, bond_port_1, mode)
+        except Exception as e:
+            results.append(e)
+        # check second bonded device
+        try:
+            self.logger.info("check second bonded device")
+            # active slave traffic testing
+            for slave in slaveGrpOne:
+                self.active_slave_rx(slave, bond_port_2, mode)
+        except Exception as e:
+            results.append(e)
+
+        # check top bonded device
+        try:
+            self.logger.info("check master bonded device")
+            # active slave traffic testing
+            for slave in slaveGrpOne + slaveGrpTwo:
+                self.active_slave_rx(slave, master_bond_port, mode)
+        except Exception as e:
+            results.append(e)
+
+        self.bond_inst.close_testpmd()
+
+        return results
+
+    def backup_check_traffic(self):
+        mode = MODE_ACTIVE_BACKUP
+        msg = "begin checking bonding backup(stacked) mode transmission"
+        self.logger.info(msg)
+        results = self.check_traffic(mode)
+        if results:
+            for item in results:
+                self.logger.error(item)
+            raise VerifyFailure("backup(stacked) mode: rx failed")
+
+    def backup_check_traffic_with_slave_down(self):
+        mode = MODE_ACTIVE_BACKUP
+        self.logger.info(
+            "begin checking bonding backup(stacked) "
+            "mode transmission with one slave down"
+        )
+        results = self.check_traffic_with_one_slave_down(mode)
+        if results:
+            for item in results:
+                self.logger.error(item)
+            msg = "backup(stacked) mode: rx with one slave down failed"
+            raise VerifyFailure(msg)
+
+    def xor_check_rx(self):
+        mode = MODE_XOR_BALANCE
+        msg = "begin checking bonding xor(stacked) mode transmission"
+        self.logger.info(msg)
+        results = self.check_traffic(mode)
+        if results:
+            for item in results:
+                self.logger.error(item)
+            raise VerifyFailure("xor(stacked) mode: rx failed")
+
+    def xor_check_stacked_rx_one_slave_down(self):
+        mode = MODE_XOR_BALANCE
+        self.logger.info(
+            "begin checking bonding xor(stacked) mode "
+            "transmission with one slave down"
+        )
+        results = self.check_traffic_with_one_slave_down(mode)
+        if results:
+            for item in results:
+                self.logger.error(item)
+            msg = "xor(stacked) mode: rx with one slave down failed"
+            raise VerifyFailure(msg)
+
+    #
+    # Test cases.
+    #
+    def set_up_all(self):
+        """
+        Run before each test suite
+        """
+        self.verify("bsdapp" not in self.target, "Bonding not support freebsd")
+        self.dut_ports = self.dut.get_ports()
+        self.dport_info0 = self.dut.ports_info[self.dut_ports[0]]
+        self.dport_ifaces = self.dport_info0["intf"]
+        num_ports = len(self.dut_ports)
+        self.verify(num_ports == 2 or num_ports == 4, "Insufficient ports")
+        tester_port0 = self.tester.get_local_port(self.dut_ports[0])
+        self.tport_iface0 = self.tester.get_interface(tester_port0)
+        self.flag = "link-down-on-close"
+        self.default_stats = self.tester.get_priv_flags_state(
+            self.tport_iface0, self.flag
+        )
+        # enable the peer port "link-down-on-close"
+        if self.default_stats:
+            for port in self.dut_ports:
+                tester_port = self.tester.get_local_port(port)
+                tport_iface = self.tester.get_interface(tester_port)
+                self.tester.send_expect(
+                    "ethtool --set-priv-flags %s %s on" % (tport_iface, self.flag), "# "
+                )
+        sep_index = len(self.dut_ports) // 2
+        # separate ports into two group as first level bond ports' slaves
+        self.slaveGrpOne = self.dut_ports[:sep_index]
+        self.slaveGrpTwo = self.dut_ports[sep_index:]
+        self.bond_slave = self.dut_ports[0]
+        # initialize bonding common methods name
+        self.total_pkt = 100
+        config = {
+            "parent": self,
+            "pkt_name": "udp",
+            "pkt_size": FRAME_SIZE_64,
+            "src_mac": "52:00:00:00:00:03",
+            "src_ip": "10.239.129.65",
+            "src_port": 61,
+            "dst_ip": "10.239.129.88",
+            "dst_port": 53,
+        }
+        self.bond_inst = bonding.PmdBonding(**config)
+
+    def set_up(self):
+        """
+        Run before each test case.
+        """
+        self.create_vfs(pf_list=self.dut_ports, vf_num=1)
+        self.eal_param = ""
+        for pci in self.vfs_pci:
+            self.eal_param += " -a %s" % pci
+
+    def create_vfs(self, pf_list, vf_num):
+        self.sriov_vfs_port = []
+        self.vfs_pci = []
+        self.dut.bind_interfaces_linux(self.kdriver)
+        pf_list = pf_list if isinstance(pf_list, list) else [pf_list]
+        for pf_id in pf_list:
+            self.dut.generate_sriov_vfs_by_port(pf_id, vf_num)
+            self.sriov_vfs_port += self.dut.ports_info[self.dut_ports[pf_id]][
+                "vfs_port"
+            ]
+            dport_iface = self.dut.ports_info[self.dut_ports[pf_id]]["intf"]
+            self.dut.send_expect(
+                "ip link set %s vf 0 spoofchk off" % (dport_iface), "# "
+            )
+        for vf in self.sriov_vfs_port:
+            self.vfs_pci.append(vf.pci)
+        try:
+            for port in self.sriov_vfs_port:
+                port.bind_driver(self.drivername)
+        except Exception as e:
+            self.dut.destroy_all_sriov_vfs()
+            raise Exception(e)
+
+    def test_basic_behav(self):
+        """
+        Test Case: basic behavior
+        allow a bonded device to be added to another bonded device.
+        There's two limitations to create master bonding:
+
+         - Total depth of nesting is limited to two levels,
+         - 802.3ad mode is not supported if one or more slaves is a bond device
+
+        note: There 802.3ad mode can not be supported on this bond device.
+
+        This case is aimed at testing basic behavior of stacked bonded commands.
+
+        """
+        # ------------------------------------------------
+        # check stacked bonded status, except mode 4 (802.3ad)
+        mode_list = [
+            MODE_ROUND_ROBIN,
+            MODE_ACTIVE_BACKUP,
+            MODE_XOR_BALANCE,
+            MODE_BROADCAST,
+            MODE_TLB_BALANCE,
+            MODE_ALB_BALANCE,
+        ]
+        slaveGrpOne = self.slaveGrpOne
+        slaveGrpTwo = self.slaveGrpTwo
+        check_result = []
+        for bond_mode in mode_list:
+            self.logger.info("begin mode <{0}> checking".format(bond_mode))
+            # boot up testpmd
+            self.bond_inst.start_testpmd(self.eal_param)
+            try:
+                self.logger.info("check bonding mode <{0}>".format(bond_mode))
+                # set up stacked bonded status
+                bond_port_1, bond_port_2, master_bond_port = self.set_stacked_bonded(
+                    slaveGrpOne, slaveGrpTwo, bond_mode
+                )
+                # check duplicate add slave
+                self.duplicate_add_stacked_bonded(
+                    bond_port_1, bond_port_2, master_bond_port
+                )
+                # check stacked limitation
+                self.set_third_stacked_bonded(master_bond_port, bond_mode)
+                # quit testpmd, it is not supported to reset testpmd
+                self.logger.info("mode <{0}> done !".format(bond_mode))
+                check_result.append([bond_mode, None])
+            except Exception as e:
+                check_result.append([bond_mode, e])
+                self.logger.error(e)
+            finally:
+                self.bond_inst.close_testpmd()
+                time.sleep(5)
+        # ------------------------------------------------
+        # 802.3ad mode is not supported
+        # if one or more slaves is a bond device
+        # so it should raise a exception
+        msg = ""
+        try:
+            # boot up testpmd
+            self.bond_inst.start_testpmd(self.eal_param)
+            # set up stacked bonded status
+            self.set_stacked_bonded(slaveGrpOne, slaveGrpTwo, MODE_LACP)
+            # quit testpmd, it is not supported to reset testpmd
+            msg = "802.3ad mode hasn't been forbidden to " "use stacked bonded setting"
+            check_result.append([MODE_LACP, msg])
+        except Exception as e:
+            check_result.append([MODE_LACP, None])
+        finally:
+            self.bond_inst.close_testpmd()
+
+        exception_flag = False
+        for bond_mode, e in check_result:
+            msg = "mode <{0}>".format(bond_mode)
+            if e:
+                self.logger.info(msg)
+                self.logger.error(e)
+                exception_flag = True
+            else:
+                self.logger.info(msg + " done !")
+        # if some checking item is failed, raise exception
+        if exception_flag:
+            raise VerifyFailure("some test items failed")
+        else:
+            self.logger.info("all test items have done !")
+
+    def test_mode_backup_rx(self):
+        """
+        Test Case: active-backup stacked bonded rx traffic
+        """
+        self.backup_check_traffic()
+
+    def test_mode_backup_one_slave_down(self):
+        """
+        Test Case: active-backup stacked bonded rx traffic with slave down
+        """
+        self.verify(self.default_stats, "tester port not support '%s'" % self.flag)
+        self.backup_check_traffic_with_slave_down()
+
+    def test_mode_xor_rx(self):
+        """
+        Test Case: balance-xor stacked bonded rx traffic
+        """
+        self.xor_check_rx()
+
+    def test_mode_xor_rx_one_slave_down(self):
+        """
+        Test Case: balance-xor stacked bonded rx traffic with slave down
+        """
+        self.verify(self.default_stats, "tester port not support '%s'" % self.flag)
+        self.xor_check_stacked_rx_one_slave_down()
+
+    def tear_down(self):
+        """
+        Run after each test case.
+        """
+        try:
+            self.bond_inst.close_testpmd()
+        except Exception:
+            self.dut.kill_all()
+        self.dut.destroy_all_sriov_vfs()
+        for port in self.dut_ports:
+            tport = self.tester.get_local_port(port)
+            tport_iface = self.tester.get_interface(tport)
+            cmd = "ifconfig {0} up".format(tport_iface)
+            self.tester.send_expect(cmd, "# ")
+
+    def tear_down_all(self):
+        """
+        Run after each test suite.
+        """
+        self.dut.kill_all()
+        if self.default_stats:
+            for port in self.dut_ports:
+                tester_port = self.tester.get_local_port(port)
+                tport_iface = self.tester.get_interface(tester_port)
+                self.tester.send_expect(
+                    "ethtool --set-priv-flags %s %s %s"
+                    % (tport_iface, self.flag, self.default_stats),
+                    "# ",
+                )
