diff --git a/src/arch/host/CMakeLists.txt b/src/arch/host/CMakeLists.txt
index 75314f00f556..cad2e88f7ce1 100644
--- a/src/arch/host/CMakeLists.txt
+++ b/src/arch/host/CMakeLists.txt
@@ -18,7 +18,7 @@ if (supports_implicit_fallthrough)
 endif()
 
 # C & ASM flags
-target_compile_options(sof_options INTERFACE -g -O3 -fPIC -DPIC -std=c99 -std=gnu99 -fgnu89-inline
+target_compile_options(sof_options INTERFACE -g -fPIC -DPIC -std=c99 -std=gnu99 -fgnu89-inline
   -Wall -Werror -Wmissing-prototypes ${implicit_fallthrough} -Wno-pointer-to-int-cast
   -Wno-int-to-pointer-cast -Wpointer-arith
   -DCONFIG_LIBRARY "-imacros${CONFIG_H_PATH}")
diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig
index 81226d252c82..0ed133a20631 100644
--- a/src/arch/host/configs/library_defconfig
+++ b/src/arch/host/configs/library_defconfig
@@ -21,7 +21,8 @@ CONFIG_COMP_VOLUME=y
 CONFIG_COMP_VOLUME_LINEAR_RAMP=y
 CONFIG_COMP_VOLUME_WINDOWS_FADE=y
 CONFIG_DEBUG_MEMORY_USAGE_SCAN=n
-CONFIG_IPC_MAJOR_3=y
+#CONFIG_IPC_MAJOR_3=y
+CONFIG_IPC_MAJOR_4=y
 CONFIG_LIBRARY=y
 CONFIG_LIBRARY_STATIC=y
 CONFIG_MATH_IIR_DF2T=y
diff --git a/src/audio/crossover/crossover_ipc4.c b/src/audio/crossover/crossover_ipc4.c
index d0a8fe4adab2..82586c0499d3 100644
--- a/src/audio/crossover/crossover_ipc4.c
+++ b/src/audio/crossover/crossover_ipc4.c
@@ -29,7 +29,7 @@ int crossover_get_sink_id(struct comp_data *cd, uint32_t pipeline_id, uint32_t i
  * kernel know that the base_cfg_ext needs to be appended to the IPC payload. The
  * Extension is needed to know the output pin indices.
  */
-int crossover_init_output_pins(struct processing_module *mod)
+static int crossover_init_output_pins(struct processing_module *mod)
 {
 	struct comp_data *cd = module_get_private_data(mod);
 	struct comp_dev *dev = mod->dev;
diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c
index add2dbd787b6..f3b506ff31c8 100644
--- a/src/audio/pipeline/pipeline-stream.c
+++ b/src/audio/pipeline/pipeline-stream.c
@@ -486,6 +486,7 @@ static int pipeline_comp_trigger(struct comp_dev *current,
 			 * Initialization delay is only used with SSP, where we
 			 * don't use more than one DAI per copier
 			 */
+#if !CONFIG_LIBRARY
 			struct dai_data *dd;
 #if CONFIG_IPC_MAJOR_3
 			dd = comp_get_drvdata(current);
@@ -498,6 +499,7 @@ static int pipeline_comp_trigger(struct comp_dev *current,
 #error Unknown IPC major version
 #endif
 			ppl_data->delay_ms = dai_get_init_delay_ms(dd->dai);
+#endif
 		}
 		break;
 	default:
diff --git a/src/include/ipc/topology.h b/src/include/ipc/topology.h
index b2cd833cf1fb..a83b806a3467 100644
--- a/src/include/ipc/topology.h
+++ b/src/include/ipc/topology.h
@@ -243,8 +243,16 @@ struct sof_ipc_comp_process {
 
 /* IPC file component used by testbench only */
 struct sof_ipc_comp_file {
+	/* These need to be the same as in above sof_ipc_comp_process */
 	struct sof_ipc_comp comp;
 	struct sof_ipc_comp_config config;
+	uint32_t size;	/**< size of bespoke data section in bytes */
+	uint32_t type;	/**< sof_ipc_process_type */
+
+	/* reserved for future use */
+	uint32_t reserved[7];
+
+	/* These are additional parameters for file */
 	uint32_t rate;
 	uint32_t channels;
 	char *fn;
diff --git a/src/include/ipc4/alh.h b/src/include/ipc4/alh.h
index c9658c225d76..37606c5c8691 100644
--- a/src/include/ipc4/alh.h
+++ b/src/include/ipc4/alh.h
@@ -24,6 +24,7 @@
 #ifndef __SOF_IPC4_ALH_H__
 #define __SOF_IPC4_ALH_H__
 
+#include <stddef.h>
 #include <stdint.h>
 #include <stdbool.h>
 #include <ipc4/gateway.h>
diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h
index c10b3bf67858..788d85ae19e4 100644
--- a/src/include/sof/audio/component.h
+++ b/src/include/sof/audio/component.h
@@ -744,6 +744,7 @@ void sys_comp_host_init(void);
 void sys_comp_kpb_init(void);
 void sys_comp_selector_init(void);
 
+void sys_comp_module_copier_interface_init(void);
 void sys_comp_module_crossover_interface_init(void);
 void sys_comp_module_dcblock_interface_init(void);
 void sys_comp_module_demux_interface_init(void);
@@ -751,15 +752,19 @@ void sys_comp_module_drc_interface_init(void);
 void sys_comp_module_dts_interface_init(void);
 void sys_comp_module_eq_fir_interface_init(void);
 void sys_comp_module_eq_iir_interface_init(void);
+void sys_comp_module_gain_interface_init(void);
 void sys_comp_module_google_rtc_audio_processing_interface_init(void);
 void sys_comp_module_google_ctc_audio_processing_interface_init(void);
 void sys_comp_module_igo_nr_interface_init(void);
 void sys_comp_module_mfcc_interface_init(void);
 void sys_comp_module_mixer_interface_init(void);
+void sys_comp_module_mixin_interface_init(void);
+void sys_comp_module_mixout_interface_init(void);
 void sys_comp_module_multiband_drc_interface_init(void);
 void sys_comp_module_mux_interface_init(void);
 void sys_comp_module_asrc_interface_init(void);
 void sys_comp_module_rtnr_interface_init(void);
+void sys_comp_module_selector_interface_init(void);
 void sys_comp_module_src_interface_init(void);
 void sys_comp_module_tdfb_interface_init(void);
 void sys_comp_module_volume_interface_init(void);
diff --git a/src/include/sof/audio/ipc-config.h b/src/include/sof/audio/ipc-config.h
index ccabb8628e35..f0c74f2da2a2 100644
--- a/src/include/sof/audio/ipc-config.h
+++ b/src/include/sof/audio/ipc-config.h
@@ -143,6 +143,7 @@ struct ipc_config_process {
 
 /* file IO ipc comp */
 struct ipc_comp_file {
+	struct ipc_config_process module_header; /* Needed for module_adapter_init_data() */
 	uint32_t rate;
 	uint32_t channels;
 	char *fn;
diff --git a/src/include/sof/lib/dai-legacy.h b/src/include/sof/lib/dai-legacy.h
index 328ac156dc05..e20a6d2e8e53 100644
--- a/src/include/sof/lib/dai-legacy.h
+++ b/src/include/sof/lib/dai-legacy.h
@@ -163,6 +163,10 @@ struct llp_slot_info {
 	uint32_t reg_offset;
 };
 
+typedef int (*channel_copy_func)(const struct audio_stream *src, unsigned int src_channel,
+				 struct audio_stream *dst, unsigned int dst_channel,
+				 unsigned int frames);
+
 /**
  * \brief DAI runtime data
  */
@@ -181,6 +185,12 @@ struct dai_data {
 	int xrun;				/* true if we are doing xrun recovery */
 
 	pcm_converter_func process;		/* processing function */
+	uint32_t chmap;
+	channel_copy_func channel_copy;		/* channel copy func used by multi-endpoint
+						 * gateway to mux/demux stream from/to multiple
+						 * DMA buffers
+						 */
+
 
 	uint32_t period_bytes;			/* number of bytes per one period */
 	uint64_t total_data_processed;
diff --git a/src/ipc/ipc3/helper.c b/src/ipc/ipc3/helper.c
index 13c1824e627d..602230cfaec1 100644
--- a/src/ipc/ipc3/helper.c
+++ b/src/ipc/ipc3/helper.c
@@ -196,10 +196,9 @@ static int comp_specific_builder(struct sof_ipc_comp *comp,
 {
 #if CONFIG_LIBRARY
 	struct sof_ipc_comp_file *file = (struct sof_ipc_comp_file *)comp;
-#else
+#endif
 	struct sof_ipc_comp_host *host = (struct sof_ipc_comp_host *)comp;
 	struct sof_ipc_comp_dai *dai = (struct sof_ipc_comp_dai *)comp;
-#endif
 	struct sof_ipc_comp_volume *vol = (struct sof_ipc_comp_volume *)comp;
 	struct sof_ipc_comp_process *proc = (struct sof_ipc_comp_process *)comp;
 	struct sof_ipc_comp_src *src = (struct sof_ipc_comp_src *)comp;
@@ -211,20 +210,22 @@ static int comp_specific_builder(struct sof_ipc_comp *comp,
 	switch (comp->type) {
 #if CONFIG_LIBRARY
 	/* test bench maps host and DAIs to a file */
-	case SOF_COMP_HOST:
-	case SOF_COMP_SG_HOST:
-	case SOF_COMP_DAI:
-	case SOF_COMP_SG_DAI:
-		if (IPC_TAIL_IS_SIZE_INVALID(*file))
-			return -EBADMSG;
+	case SOF_COMP_FILEREAD:
+	case SOF_COMP_FILEWRITE:
 		config->file.channels = file->channels;
 		config->file.fn = file->fn;
 		config->file.frame_fmt = file->frame_fmt;
 		config->file.mode = file->mode;
 		config->file.rate = file->rate;
 		config->file.direction = file->direction;
+
+		/* For module_adapter_init_data() ipc_module_adapter compatibility */
+		config->file.module_header.type = proc->type;
+		config->file.module_header.size = proc->size;
+		config->file.module_header.data = (uint8_t *)proc->data -
+			sizeof(struct ipc_config_process);
 		break;
-#else
+#endif
 	case SOF_COMP_HOST:
 	case SOF_COMP_SG_HOST:
 		if (IPC_TAIL_IS_SIZE_INVALID(*host))
@@ -241,7 +242,6 @@ static int comp_specific_builder(struct sof_ipc_comp *comp,
 		config->dai.direction = dai->direction;
 		config->dai.type = dai->type;
 		break;
-#endif
 	case SOF_COMP_VOLUME:
 		if (IPC_TAIL_IS_SIZE_INVALID(*vol))
 			return -EBADMSG;
diff --git a/src/ipc/ipc4/handler.c b/src/ipc/ipc4/handler.c
index 54ef9d396b52..75693f87618c 100644
--- a/src/ipc/ipc4/handler.c
+++ b/src/ipc/ipc4/handler.c
@@ -534,6 +534,9 @@ static void ipc_compound_msg_done(uint32_t msg_id, int error)
 
 static int ipc_wait_for_compound_msg(void)
 {
+#if  CONFIG_LIBRARY
+	atomic_set(&msg_data.delayed_reply, 0);
+#else
 	int try_count = 30;
 
 	while (atomic_read(&msg_data.delayed_reply)) {
@@ -545,6 +548,7 @@ static int ipc_wait_for_compound_msg(void)
 			return IPC4_FAILURE;
 		}
 	}
+#endif
 
 	return IPC4_SUCCESS;
 }
diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c
index d427643ef3ea..898c6a763564 100644
--- a/src/ipc/ipc4/helper.c
+++ b/src/ipc/ipc4/helper.c
@@ -981,6 +981,16 @@ static const struct ipc4_module_uuid uuid_map[] = {
 		.d = { 0xa0, 0x8f, 0x97, 0xfc, 0xc4, 0x2e, 0xaa, 0xeb }}}, /* ALSA aplay */
 	{0x99, {.a = 0x66def9f0, .b = 0x39f2, .c = 0x11ed,
 		.d = { 0xf7, 0x89, 0xaf, 0x98, 0xa6, 0x44, 0x0c, 0xc4 }}}, /* ALSA arecord */
+	{0x9a, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8,
+		.d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */
+	{0x9b, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8,
+		.d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */
+	{0x9c, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8,
+		.d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */
+	{0x9d, {.a = 0xbfc7488c, .b = 0x75aa, .c = 0x4ce8,
+		.d = { 0x9d, 0xbe, 0xd8, 0xda, 0x08, 0xa6, 0x98, 0xc2 }}}, /* FILE component */
+	{0x9e, {.a = 0xb809efaf, .b = 0x5681, .c = 0x42b1,
+		.d = { 0x9e, 0xd6, 0x04, 0xbb, 0x01, 0x2d, 0xd3, 0x84 }}}, /* dcblock */
 };
 
 static const struct comp_driver *ipc4_library_get_drv(int module_id)
diff --git a/src/platform/library/include/platform/lib/ll_schedule.h b/src/platform/library/include/platform/lib/ll_schedule.h
index be79ccf15d22..06c918b84fdc 100644
--- a/src/platform/library/include/platform/lib/ll_schedule.h
+++ b/src/platform/library/include/platform/lib/ll_schedule.h
@@ -6,6 +6,7 @@
 #ifndef __LIBRARY_INCLUDE_LIB_SCHEDULE_H__
 #define __LIBRARY_INCLUDE_LIB_SCHEDULE_H__
 
+#include <rtos/task.h>
 #include <stdint.h>
 
 struct task;
diff --git a/tools/test/audio/comp_run.sh b/tools/test/audio/comp_run.sh
index 7e7dd8d5f846..0ade0a4a0176 100755
--- a/tools/test/audio/comp_run.sh
+++ b/tools/test/audio/comp_run.sh
@@ -114,7 +114,8 @@ run_testbench ()
     delete_file_check "$FN_TRACE"
     if [ -z "$FN_TRACE" ]; then
         # shellcheck disable=SC2086
-        $VALGRIND_CMD $CMD
+        #$VALGRIND_CMD $CMD
+	$CMD 2> /dev/null
     else
         $VALGRIND_CMD $CMD 2> "$FN_TRACE" || {
             local ret=$?
diff --git a/tools/testbench/CMakeLists.txt b/tools/testbench/CMakeLists.txt
index 8e95a60d6a14..57c48cc99e93 100644
--- a/tools/testbench/CMakeLists.txt
+++ b/tools/testbench/CMakeLists.txt
@@ -14,8 +14,17 @@ add_executable(testbench
 	common_test.c
 	file.c
 	topology.c
+        topology_ipc4.c
 )
 
+# TODO
+#if(CONFIG_IPC_MAJOR_3)
+#	add_local_sources(testbench topology_ipc3.c)
+#elseif(CONFIG_IPC_MAJOR_4)
+#	add_local_sources(testbench topology_ipc4.c)
+#endif()
+
+
 sof_append_relative_path_definitions(testbench)
 
 target_include_directories(testbench PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
@@ -41,7 +50,7 @@ if (supports_implicit_fallthrough)
     set(implicit_fallthrough -Wimplicit-fallthrough)
 endif()
 
-target_compile_options(testbench PRIVATE -g -O3 -Wall -Werror -Wmissing-prototypes
+target_compile_options(testbench PRIVATE -g -Wall -Werror -Wmissing-prototypes
   ${implicit_fallthrough} -DCONFIG_LIBRARY -DCONFIG_LIBRARY_STATIC -imacros${config_h})
 
 target_link_libraries(testbench PRIVATE -lm)
diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test.c
index c3497ce582c4..859543d4aeaa 100644
--- a/tools/testbench/common_test.c
+++ b/tools/testbench/common_test.c
@@ -1,38 +1,48 @@
 // SPDX-License-Identifier: BSD-3-Clause
 //
-// Copyright(c) 2018 Intel Corporation. All rights reserved.
+// Copyright(c) 2018-2024 Intel Corporation. All rights reserved.
 
-#include <stdint.h>
-#include <stddef.h>
-#include <time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <rtos/string.h>
-#include <math.h>
-#include <rtos/sof.h>
-#include <rtos/task.h>
-#include <rtos/alloc.h>
-#include <sof/lib/notifier.h>
+#include <platform/lib/ll_schedule.h>
+#include <module/module/base.h>
+#include <sof/audio/component_ext.h>
+#include <sof/audio/pipeline.h>
 #include <sof/ipc/driver.h>
 #include <sof/ipc/topology.h>
 #include <sof/lib/agent.h>
 #include <sof/lib/dai.h>
 #include <sof/lib/dma.h>
+#include <sof/lib/notifier.h>
 #include <sof/schedule/edf_schedule.h>
 #include <sof/schedule/ll_schedule.h>
 #include <sof/schedule/ll_schedule_domain.h>
 #include <sof/schedule/schedule.h>
+#include <rtos/alloc.h>
+#include <rtos/sof.h>
+#include <rtos/string.h>
+#include <rtos/task.h>
 #include <rtos/wait.h>
-#include <sof/audio/pipeline.h>
-#include <sof/audio/component_ext.h>
+#include <tplg_parser/topology.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
 #include "testbench/common_test.h"
+#include "testbench/file.h"
 #include "testbench/trace.h"
-#include <tplg_parser/topology.h>
+#include "testbench/topology.h"
 
 #if defined __XCC__
 #include <xtensa/tie/xt_timer.h>
 #endif
 
+SOF_DEFINE_REG_UUID(testbench);
+DECLARE_TR_CTX(testbench_tr, SOF_UUID(testbench_uuid), LOG_LEVEL_INFO);
+LOG_MODULE_REGISTER(testbench, CONFIG_SOF_LOG_LEVEL);
+
 /* testbench helper functions for pipeline setup and trigger */
 
 int tb_setup(struct sof *sof, struct testbench_prm *tp)
@@ -43,8 +53,6 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp)
 
 	/* init components */
 	sys_comp_init(sof);
-	sys_comp_file_init();
-	sys_comp_selector_init();
 
 	/* Module adapter components */
 	sys_comp_module_crossover_interface_init();
@@ -53,12 +61,15 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp)
 	sys_comp_module_drc_interface_init();
 	sys_comp_module_eq_fir_interface_init();
 	sys_comp_module_eq_iir_interface_init();
+	sys_comp_module_file_interface_init();
+	sys_comp_module_gain_interface_init();
 	sys_comp_module_google_rtc_audio_processing_interface_init();
 	sys_comp_module_igo_nr_interface_init();
 	sys_comp_module_mfcc_interface_init();
 	sys_comp_module_multiband_drc_interface_init();
 	sys_comp_module_mux_interface_init();
 	sys_comp_module_rtnr_interface_init();
+	sys_comp_module_selector_interface_init();
 	sys_comp_module_src_interface_init();
 	sys_comp_module_asrc_interface_init();
 	sys_comp_module_tdfb_interface_init();
@@ -74,6 +85,10 @@ int tb_setup(struct sof *sof, struct testbench_prm *tp)
 		return -EINVAL;
 	}
 
+	/* Trace */
+	ipc_tr.level = LOG_LEVEL_INFO;
+	ipc_tr.uuid_p = SOF_UUID(testbench_uuid);
+
 	/* init LL scheduler */
 	if (scheduler_init_ll(&domain) < 0) {
 		fprintf(stderr, "error: edf scheduler init\n");
@@ -122,181 +137,261 @@ void tb_free(struct sof *sof)
 	free(sof->ipc);
 }
 
-/* Get pipeline host component */
-static struct comp_dev *tb_get_pipeline_host(struct pipeline *p)
+/* print debug messages */
+void debug_print(char *message)
 {
-	struct comp_dev *cd;
+	if (host_trace_level >= LOG_LEVEL_DEBUG)
+		printf("debug: %s", message);
+}
 
-	cd = p->source_comp;
-	if (cd->direction == SOF_IPC_STREAM_CAPTURE)
-		cd = p->sink_comp;
+/* enable trace in testbench */
+void tb_enable_trace(unsigned int log_level)
+{
+	host_trace_level = log_level;
+	if (host_trace_level)
+		debug_print("trace print enabled\n");
+	else
+		debug_print("trace print disabled\n");
+}
 
-	return cd;
+void tb_gettime(struct timespec *td)
+{
+#if !defined __XCC__
+	clock_gettime(CLOCK_MONOTONIC, td);
+#else
+	td->tv_nsec = 0;
+	td->tv_sec = 0;
+#endif
 }
 
-/* set up pcm params, prepare and trigger pipeline */
-int tb_pipeline_start(struct ipc *ipc, struct pipeline *p)
+void tb_getcycles(uint64_t *cycles)
 {
-	struct comp_dev *cd;
-	int ret;
+#if defined __XCC__
+	*cycles = XT_RSR_CCOUNT();
+#else
+	*cycles = 0;
+#endif
+}
 
-	/* Get pipeline host component */
-	cd = tb_get_pipeline_host(p);
+#if DISABLED_CODE
+static int tb_get_pipeline_instance_id(struct testbench_prm *tp, int id)
+{
+	struct tplg_pipeline_info *pipe_info;
+	struct tplg_pipeline_list *pipeline_list;
+	int i;
+
+	pipeline_list = &tp->pcm_info->playback_pipeline_list;
+	for (i = 0; i < pipeline_list->count; i++) {
+		pipe_info = pipeline_list->pipelines[i];
+		if (pipe_info->id == id)
+			return pipe_info->instance_id;
+	}
 
-	/* Component prepare */
-	ret = pipeline_prepare(p, cd);
-	if (ret < 0) {
-		fprintf(stderr, "error: Failed prepare pipeline command: %s\n",
-			strerror(ret));
-		return ret;
+	pipeline_list = &tp->pcm_info->capture_pipeline_list;
+	for (i = 0; i < pipeline_list->count; i++) {
+		pipe_info = pipeline_list->pipelines[i];
+		if (pipe_info->id == id)
+			return pipe_info->instance_id;
 	}
 
-	/* Start the pipeline */
-	ret = pipeline_trigger(cd->pipeline, cd, COMP_TRIGGER_PRE_START);
-	if (ret < 0) {
-		fprintf(stderr, "error: Failed to start pipeline command: %s\n",
-			strerror(ret));
-		return ret;
+	return -EINVAL;
+}
+
+static struct pipeline *tb_get_pipeline_by_id(struct testbench_prm *tb, int pipeline_id)
+{
+	struct ipc_comp_dev *pipe_dev;
+	struct ipc *ipc = sof_get()->ipc;
+	int id = tb_get_pipeline_instance_id(tb, pipeline_id);
+
+	pipe_dev = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, id, IPC_COMP_IGNORE_REMOTE);
+	return pipe_dev->pipeline;
+}
+#endif
+
+void tb_show_file_stats(struct testbench_prm *tb, int pipeline_id)
+{
+	struct ipc_comp_dev *icd;
+	struct comp_dev *dev;
+	struct processing_module *mod;
+	struct file_comp_data *fcd;
+	int i;
+
+	for (i = 0; i < tb->input_file_num; i++) {
+		if (tb->fr[i].id < 0 || tb->fr[i].pipeline_id != pipeline_id)
+			continue;
+
+		icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fr[i].id);
+		if (!icd)
+			continue;
+
+		dev = icd->cd;
+		mod = comp_mod(dev);
+		fcd = module_get_private_data(mod);
+		printf("file %s: id %d: type %d: samples %d copies %d\n",
+		       fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n,
+		       fcd->fs.copy_count);
+	}
+
+	for (i = 0; i < tb->output_file_num; i++) {
+		if (tb->fw[i].id < 0 || tb->fw[i].pipeline_id != pipeline_id)
+			continue;
+
+		icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fw[i].id);
+		if (!icd)
+			continue;
+
+		dev = icd->cd;
+		mod = comp_mod(dev);
+		fcd = module_get_private_data(mod);
+		printf("file %s: id %d: type %d: samples %d copies %d\n",
+		       fcd->fs.fn, dev->ipc_config.id, dev->drv->type, fcd->fs.n,
+		       fcd->fs.copy_count);
 	}
 
-	return ret;
 }
 
-/* set up pcm params, prepare and trigger pipeline */
-int tb_pipeline_stop(struct ipc *ipc, struct pipeline *p)
+int tb_set_up_all_pipelines(struct testbench_prm *tb)
 {
-	struct comp_dev *cd;
 	int ret;
 
-	/* Get pipeline host component */
-	cd = tb_get_pipeline_host(p);
+	ret = tb_set_up_pipelines(tb, SOF_IPC_STREAM_PLAYBACK);
+	if (ret) {
+		fprintf(stderr, "error: Failed tb_set_up_pipelines for playback\n");
+		return ret;
+	}
 
-	ret = pipeline_trigger(cd->pipeline, cd, COMP_TRIGGER_STOP);
-	if (ret < 0) {
-		fprintf(stderr, "error: Failed to stop pipeline command: %s\n",
-			strerror(ret));
+	ret = tb_set_up_pipelines(tb, SOF_IPC_STREAM_CAPTURE);
+	if (ret) {
+		fprintf(stderr, "error: Failed tb_set_up_pipelines for capture\n");
+		return ret;
 	}
 
-	return ret;
+	fprintf(stdout, "pipelines set up complete\n");
+	return 0;
 }
 
-/* set up pcm params, prepare and trigger pipeline */
-int tb_pipeline_reset(struct ipc *ipc, struct pipeline *p)
+int tb_load_topology(struct testbench_prm *tb)
 {
-	struct comp_dev *cd;
+	struct tplg_context *ctx = &tb->tplg;
 	int ret;
 
-	/* Get pipeline host component */
-	cd = tb_get_pipeline_host(p);
+	/* setup the thread virtual core config */
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->comp_id = 1;
+	ctx->core_id = 0;
+	ctx->sof = sof_get();
+	ctx->tplg_file = tb->tplg_file;
+	if (tb->ipc_version < 3 || tb->ipc_version > 4) {
+		fprintf(stderr, "error: illegal ipc version\n");
+		return -EINVAL;
+	}
+
+	ctx->ipc_major = tb->ipc_version;
 
-	ret = pipeline_reset(p, cd);
+	/* parse topology file and create pipeline */
+	ret = tb_parse_topology(tb);
 	if (ret < 0)
-		fprintf(stderr, "error: pipeline reset\n");
+		fprintf(stderr, "error: parsing topology\n");
 
-	return ret;
+	debug_print("topology parsing complete\n");
+	return 0;
 }
 
-/* pipeline pcm params */
-int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p,
-		       struct tplg_context *ctx)
+static bool tb_is_file_component_at_eof(struct testbench_prm *tp)
 {
-	struct comp_dev *cd;
-	struct sof_ipc_pcm_params params = {{0}};
-	char message[DEBUG_MSG_LEN];
-	int fs_period;
-	int period;
-	int ret = 0;
-
-	if (!p) {
-		fprintf(stderr, "error: pipeline is NULL\n");
-		return -EINVAL;
+	int i;
+
+	for (i = 0; i < tp->input_file_num; i++) {
+		if (!tp->fr[i].state)
+			continue;
+
+		if (tp->fr[i].state->reached_eof || tp->fr[i].state->copy_timeout)
+			return true;
 	}
 
-	period = p->period;
-
-	/* Compute period from sample rates */
-	fs_period = (int)(0.9999 + tp->fs_in * period / 1e6);
-	sprintf(message, "period sample count %d\n", fs_period);
-	debug_print(message);
-
-	/* set pcm params */
-	params.comp_id = p->comp_id;
-	params.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
-	params.params.frame_fmt = tp->frame_fmt;
-	params.params.rate = tp->fs_in;
-	params.params.channels = tp->channels_in;
-
-	switch (params.params.frame_fmt) {
-	case SOF_IPC_FRAME_S16_LE:
-		params.params.sample_container_bytes = 2;
-		params.params.sample_valid_bytes = 2;
-		break;
-	case SOF_IPC_FRAME_S24_4LE:
-		params.params.sample_container_bytes = 4;
-		params.params.sample_valid_bytes = 3;
-		break;
-	case SOF_IPC_FRAME_S32_LE:
-		params.params.sample_container_bytes = 4;
-		params.params.sample_valid_bytes = 4;
-		break;
-	default:
-		fprintf(stderr, "error: invalid frame format\n");
-		return -EINVAL;
+	for (i = 0; i < tp->output_file_num; i++) {
+		if (!tp->fw[i].state)
+			continue;
+
+		if (tp->fw[i].state->reached_eof || tp->fw[i].state->copy_timeout ||
+		    tp->fw[i].state->write_failed)
+			return true;
 	}
 
-	params.params.host_period_bytes = fs_period * params.params.channels *
-		params.params.sample_container_bytes;
+	return false;
+}
 
-	/* Get pipeline host component */
-	cd = tb_get_pipeline_host(p);
+bool tb_schedule_pipeline_check_state(struct testbench_prm *tp)
+{
+	uint64_t cycles0, cycles1;
 
-	/* Set pipeline params direction from scheduling component */
-	params.params.direction = cd->direction;
+	tb_getcycles(&cycles0);
 
-	printf("test params: rate %d channels %d format %d\n",
-	       params.params.rate, params.params.channels,
-	       params.params.frame_fmt);
+	schedule_ll_run_tasks();
 
-	/* pipeline params */
-	ret = pipeline_params(p, cd, &params);
-	if (ret < 0)
-		fprintf(stderr, "error: pipeline_params\n");
+	tb_getcycles(&cycles1);
+	tp->total_cycles += cycles1 - cycles0;
 
-	return ret;
+	/* Check if all file components are running */
+	return tb_is_file_component_at_eof(tp);
 }
 
-/* print debug messages */
-void debug_print(char *message)
+bool tb_is_pipeline_enabled(struct testbench_prm *tb, int pipeline_id)
 {
-	if (host_trace_level >= LOG_LEVEL_DEBUG)
-		printf("debug: %s", message);
-}
+	int i;
 
-/* enable trace in testbench */
-void tb_enable_trace(unsigned int log_level)
-{
-	host_trace_level = log_level;
-	if (host_trace_level)
-		debug_print("trace print enabled\n");
-	else
-		debug_print("trace print disabled\n");
-}
+	for (i = 0; i < tb->pipeline_num; i++) {
+		if (tb->pipelines[i] == pipeline_id)
+			return true;
+	}
 
-void tb_gettime(struct timespec *td)
-{
-#if !defined __XCC__
-	clock_gettime(CLOCK_MONOTONIC, td);
-#else
-	td->tv_nsec = 0;
-	td->tv_sec = 0;
-#endif
+	return false;
 }
 
-void tb_getcycles(uint64_t *cycles)
+int tb_find_file_components(struct testbench_prm *tb)
 {
-#if defined __XCC__
-	*cycles = XT_RSR_CCOUNT();
-#else
-	*cycles = 0;
-#endif
+	struct ipc_comp_dev *icd;
+	struct processing_module *mod;
+	struct file_comp_data *fcd;
+	int i;
+
+	for (i = 0; i < tb->input_file_num; i++) {
+		if (!tb_is_pipeline_enabled(tb, tb->fr[i].pipeline_id)) {
+			tb->fr[i].id = -1;
+			continue;
+		}
+
+		icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fr[i].id);
+		if (!icd) {
+			tb->fr[i].state = NULL;
+			continue;
+		}
+
+		mod = comp_mod(icd->cd);
+		if (!mod) {
+			fprintf(stderr, "error: null module.\n");
+			return -EINVAL;
+		}
+		fcd = module_get_private_data(mod);
+		tb->fr[i].state = &fcd->fs;
+	}
+
+	for (i = 0; i < tb->output_file_num; i++) {
+		if (!tb_is_pipeline_enabled(tb, tb->fw[i].pipeline_id)) {
+			tb->fw[i].id = -1;
+			continue;
+		}
+
+		icd = ipc_get_comp_by_id(sof_get()->ipc, tb->fw[i].id);
+		if (!icd) {
+			tb->fr[i].state = NULL;
+			continue;
+		}
+
+		mod = comp_mod(icd->cd);
+		fcd = module_get_private_data(mod);
+		tb->fw[i].state = &fcd->fs;
+	}
+
+	return 0;
 }
diff --git a/tools/testbench/file.c b/tools/testbench/file.c
index 1ce8b578d6c7..d2c54cced41f 100644
--- a/tools/testbench/file.c
+++ b/tools/testbench/file.c
@@ -1,33 +1,34 @@
 // SPDX-License-Identifier: BSD-3-Clause
 //
-// Copyright(c) 2018 Intel Corporation. All rights reserved.
+// Copyright(c) 2018-2024 Intel Corporation. All rights reserved.
 
 /* file component for reading/writing pcm samples to/from a file */
 
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <rtos/sof.h>
-#include <sof/list.h>
-#include <sof/audio/stream.h>
-#include <sof/audio/ipc-config.h>
-#include <rtos/clk.h>
-#include <sof/ipc/driver.h>
+#include <sof/audio/module_adapter/module/generic.h>
 #include <sof/audio/component.h>
 #include <sof/audio/format.h>
+#include <sof/audio/ipc-config.h>
 #include <sof/audio/pipeline.h>
+#include <sof/audio/stream.h>
+#include <sof/ipc/driver.h>
 #include <ipc/stream.h>
+#include <rtos/init.h>
+#include <rtos/clk.h>
+#include <rtos/sof.h>
+#include <sof/list.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include "testbench/common_test.h"
 #include "testbench/file.h"
+#include "testbench/file_ipc4.h"
 
 SOF_DEFINE_REG_UUID(file);
 DECLARE_TR_CTX(file_tr, SOF_UUID(file_uuid), LOG_LEVEL_INFO);
-
-static const struct comp_driver comp_file_dai;
-static const struct comp_driver comp_file_host;
+LOG_MODULE_REGISTER(file, CONFIG_SOF_LOG_LEVEL);
 
 /*
  * Helpers for s24_4le data. To avoid an overflown 24 bit to be taken as valid 32 bit
@@ -409,18 +410,16 @@ static int write_samples_s16(struct file_comp_data *cd, struct audio_stream *sou
 }
 
 /* Default file copy function, just return error if called */
-static int file_default(struct comp_dev *dev, struct audio_stream *sink,
+static int file_default(struct file_comp_data *cd, struct audio_stream *sink,
 			struct audio_stream *source, uint32_t frames)
 {
 	return -EINVAL;
 }
 
 /* function for processing 32-bit samples */
-static int file_s32(struct comp_dev *dev, struct audio_stream *sink,
+static int file_s32(struct file_comp_data *cd, struct audio_stream *sink,
 		    struct audio_stream *source, uint32_t frames)
 {
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
 	int nch;
 	int n_samples = 0;
 
@@ -450,11 +449,9 @@ static int file_s32(struct comp_dev *dev, struct audio_stream *sink,
 }
 
 /* function for processing 16-bit samples */
-static int file_s16(struct comp_dev *dev, struct audio_stream *sink,
+static int file_s16(struct file_comp_data *cd, struct audio_stream *sink,
 		    struct audio_stream *source, uint32_t frames)
 {
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
 	int nch;
 	int n_samples;
 
@@ -484,11 +481,9 @@ static int file_s16(struct comp_dev *dev, struct audio_stream *sink,
 }
 
 /* function for processing 24-bit samples */
-static int file_s24(struct comp_dev *dev, struct audio_stream *sink,
+static int file_s24(struct file_comp_data *cd, struct audio_stream *sink,
 		    struct audio_stream *source, uint32_t frames)
 {
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
 	int nch;
 	int n_samples = 0;
 
@@ -530,45 +525,28 @@ static enum file_format get_file_format(char *filename)
 	return FILE_RAW;
 }
 
-static struct comp_dev *file_new(const struct comp_driver *drv,
-				 const struct comp_ipc_config *config,
-				 const void *spec)
+static int file_init(struct processing_module *mod)
 {
-	const struct dai_driver *fdrv;
-	struct comp_dev *dev;
-	const struct ipc_comp_file *ipc_file = spec;
-	struct dai_data *dd;
-	struct dai *fdai;
+	struct comp_dev *dev = mod->dev;
+	struct module_data *mod_data = &mod->priv;
 	struct file_comp_data *cd;
 
-	debug_print("file_new()\n");
-
-	dev = comp_alloc(drv, sizeof(*dev));
-	if (!dev)
-		return NULL;
-	dev->ipc_config = *config;
-
-	/* allocate  memory for file comp data */
-	dd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*dd));
-	if (!dd)
-		goto error_skip_dd;
+#if CONFIG_IPC_MAJOR_4
+	const struct ipc4_file_module_cfg *module_cfg =
+		(const struct ipc4_file_module_cfg *)mod_data->cfg.init_data;
 
-	fdai = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*fdai));
-	if (!fdai)
-		goto error_skip_dai;
-
-	fdrv = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*fdrv));
-	if (!fdrv)
-		goto error_skip_drv;
+	const struct ipc4_file_config *ipc_file = &module_cfg->config;
+#else
+	const struct ipc_comp_file *ipc_file =
+		(const struct ipc_comp_file *)mod_data->cfg.init_data;
+#endif
 
+	debug_print("file_init()\n");
 	cd = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*cd));
 	if (!cd)
-		goto error_skip_cd;
+		return -ENOMEM;
 
-	fdai->drv = fdrv;
-	dd->dai = fdai;
-	comp_set_drvdata(dev, dd);
-	comp_set_drvdata(dd->dai, cd);
+	mod_data->private = cd;
 
 	/* default function for processing samples */
 	cd->file_func = file_default;
@@ -586,10 +564,17 @@ static struct comp_dev *file_new(const struct comp_driver *drv,
 	cd->channels = ipc_file->channels;
 	cd->frame_fmt = ipc_file->frame_fmt;
 	dev->direction = ipc_file->direction;
+	dev->direction_set = true;
 
 	/* open file handle(s) depending on mode */
 	switch (cd->fs.mode) {
 	case FILE_READ:
+		/* Change to DAI type is needed to avoid uninitialized hw params in
+		 * pipeline_params, A file host can be left as SOF_COMP_MODULE_ADAPTER
+		 */
+		if (dev->direction == SOF_IPC_STREAM_CAPTURE)
+			dev->ipc_config.type = SOF_COMP_DAI;
+
 		cd->fs.rfh = fopen(cd->fs.fn, "r");
 		if (!cd->fs.rfh) {
 			fprintf(stderr, "error: opening file %s for reading - %s\n",
@@ -598,6 +583,12 @@ static struct comp_dev *file_new(const struct comp_driver *drv,
 		}
 		break;
 	case FILE_WRITE:
+		/* Change to DAI type is needed to avoid uninitialized hw params in
+		 * pipeline_params, A file host can be left as SOF_COMP_MODULE_ADAPTER
+		 */
+		if (dev->direction == SOF_IPC_STREAM_PLAYBACK)
+			dev->ipc_config.type = SOF_COMP_DAI;
+
 		cd->fs.wfh = fopen(cd->fs.fn, "w+");
 		if (!cd->fs.wfh) {
 			fprintf(stderr, "error: opening file %s for writing - %s\n",
@@ -613,35 +604,23 @@ static struct comp_dev *file_new(const struct comp_driver *drv,
 
 	cd->fs.reached_eof = false;
 	cd->fs.write_failed = false;
+	cd->fs.copy_timeout = false;
 	cd->fs.n = 0;
 	cd->fs.copy_count = 0;
 	cd->fs.cycles_count = 0;
-	dev->state = COMP_STATE_READY;
-	return dev;
+
+	return 0;
 
 error:
 	free(cd);
-
-error_skip_cd:
-	free((void *)fdrv);
-
-error_skip_drv:
-	free(fdai);
-
-error_skip_dai:
-	free(dd);
-
-error_skip_dd:
-	free(dev);
-	return NULL;
+	return -EINVAL;
 }
 
-static void file_free(struct comp_dev *dev)
+static int file_free(struct processing_module *mod)
 {
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
+	struct file_comp_data *cd = module_get_private_data(mod);
 
-	comp_dbg(dev, "file_free()");
+	debug_print("file_free()\n");
 
 	if (cd->fs.mode == FILE_READ)
 		fclose(cd->fs.rfh);
@@ -650,64 +629,98 @@ static void file_free(struct comp_dev *dev)
 
 	free(cd->fs.fn);
 	free(cd);
-	free((void *)dd->dai->drv);
-	free(dd->dai);
-	free(dd);
-	free(dev);
+	return 0;
 }
 
-static int file_verify_params(struct comp_dev *dev,
-			      struct sof_ipc_stream_params *params)
+/*
+ * copy and process stream samples
+ * returns the number of bytes copied
+ */
+static int file_process(struct processing_module *mod,
+			struct input_stream_buffer *input_buffers, int num_input_buffers,
+			struct output_stream_buffer *output_buffers, int num_output_buffers)
 {
-	int ret;
+	struct comp_dev *dev = mod->dev;
+	struct file_comp_data *cd = module_get_private_data(mod);
+	struct audio_stream *source;
+	struct audio_stream *sink;
+	struct comp_buffer *buffer;
+	uint32_t frames;
+	uint64_t cycles0, cycles1;
+	int samples;
+	int ret = 0;
+
+	if (cd->fs.reached_eof)
+		return -ENODATA;
+
+	/* Note: a SOF_COMP_DAI does not have input_buffers and output buffers set */
+	tb_getcycles(&cycles0);
+	switch (cd->fs.mode) {
+	case FILE_READ:
+		/* read PCM samples from file */
+		buffer = list_first_item(&dev->bsink_list, struct comp_buffer, source_list);
+		sink = &buffer->stream;
+		frames = audio_stream_get_free_frames(sink);
+		frames = MIN(frames, cd->max_frames);
+		samples = cd->file_func(cd, sink, NULL, frames);
+		audio_stream_produce(sink, audio_stream_sample_bytes(sink) * samples);
+		break;
+	case FILE_WRITE:
+		/* write PCM samples into file */
+		buffer = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list);
+		source = &buffer->stream;
+		frames = audio_stream_get_avail_frames(source);
+		frames = MIN(frames, cd->max_frames);
+		samples = cd->file_func(cd, NULL, source, frames);
+		audio_stream_consume(source, audio_stream_sample_bytes(source) * samples);
+		break;
+	default:
+		/* TODO: duplex mode */
+		ret = -EINVAL;
+		break;
+	}
 
-	comp_dbg(dev, "file_verify_params()");
+	cd->fs.copy_count++;
 
-	ret = comp_verify_params(dev, 0, params);
-	if (ret < 0) {
-		comp_err(dev, "file_verify_params() error: comp_verify_params() failed.");
-		return ret;
+	if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) {
+		cd->fs.reached_eof = true;
+		debug_print("file_process(): reached EOF or max_copies\n");
 	}
 
-	return 0;
+	if (samples) {
+		cd->copies_timeout = 0;
+	} else {
+		cd->copies_timeout++;
+		if (cd->copies_timeout == FILE_MAX_COPIES_TIMEOUT) {
+			debug_print("file_process(): copies_timeout reached\n");
+			cd->fs.copy_timeout = true;
+		}
+	}
+
+	tb_getcycles(&cycles1);
+	cd->fs.cycles_count += cycles1 - cycles0;
+	return ret;
 }
 
-/**
- * \brief Sets file component audio stream parameters.
- * \param[in,out] dev Volume base component device.
- * \param[in] params Audio (PCM) stream parameters (ignored for this component)
- * \return Error code.
- *
- * All done in prepare() since we need to know source and sink component params.
- */
-static int file_params(struct comp_dev *dev,
-		       struct sof_ipc_stream_params *params)
+static int file_prepare(struct processing_module *mod,
+			struct sof_source **sources, int num_of_sources,
+			struct sof_sink **sinks, int num_of_sinks)
 {
-	struct comp_buffer *buffer;
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
 	struct audio_stream *stream;
-	int periods;
-	int samples;
-	int ret;
-
-	comp_info(dev, "file_params()");
+	struct comp_buffer *buffer;
+	struct comp_dev *dev = mod->dev;
+	struct file_comp_data *cd = module_get_private_data(mod);
 
-	ret = file_verify_params(dev, params);
-	if (ret < 0) {
-		comp_err(dev, "file_params(): pcm params verification failed.");
-		return ret;
-	}
+	debug_print("file_prepare()\n");
 
 	/* file component sink/source buffer period count */
+	cd->max_frames = dev->frames;
 	switch (cd->fs.mode) {
 	case FILE_READ:
 		buffer = list_first_item(&dev->bsink_list, struct comp_buffer, source_list);
-		periods = dev->ipc_config.periods_sink;
 		break;
 	case FILE_WRITE:
 		buffer = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list);
-		periods = dev->ipc_config.periods_source;
 		break;
 	default:
 		/* TODO: duplex mode */
@@ -715,38 +728,16 @@ static int file_params(struct comp_dev *dev,
 		return -EINVAL;
 	}
 
-	/* set downstream buffer size */
+	/* set file function */
 	stream = &buffer->stream;
-	samples = periods * dev->frames * audio_stream_get_channels(stream);
 	switch (audio_stream_get_frm_fmt(stream)) {
 	case SOF_IPC_FRAME_S16_LE:
-		ret = buffer_set_size(buffer, samples * sizeof(int16_t), 0);
-		if (ret < 0) {
-			fprintf(stderr, "error: file buffer size set\n");
-			return ret;
-		}
-
-		/* set file function */
 		cd->file_func = file_s16;
 		break;
 	case SOF_IPC_FRAME_S24_4LE:
-		ret = buffer_set_size(buffer, samples * sizeof(int32_t), 0);
-		if (ret < 0) {
-			fprintf(stderr, "error: file buffer size set\n");
-			return ret;
-		}
-
-		/* set file function */
 		cd->file_func = file_s24;
 		break;
 	case SOF_IPC_FRAME_S32_LE:
-		ret = buffer_set_size(buffer, samples * sizeof(int32_t), 0);
-		if (ret < 0) {
-			fprintf(stderr, "error: file buffer size set\n");
-			return ret;
-		}
-
-		/* set file function */
 		cd->file_func = file_s32;
 		break;
 	default:
@@ -755,148 +746,31 @@ static int file_params(struct comp_dev *dev,
 		return -EINVAL;
 	}
 
-	cd->sample_container_bytes = audio_stream_sample_bytes(stream);
-	buffer_reset_pos(buffer, NULL);
-
 	return 0;
 }
 
-static int fr_cmd(struct comp_dev *dev, struct sof_ipc_ctrl_data *cdata)
+static int file_reset(struct processing_module *mod)
 {
-	fprintf(stderr, "Warning: Set data is not implemented\n");
-	return -EINVAL;
+	struct file_comp_data *cd = module_get_private_data(mod);
+
+	debug_print("file_reset()\n");
+	cd->copies_timeout = 0;
+	return 0;
 }
 
 static int file_trigger(struct comp_dev *dev, int cmd)
 {
-	comp_info(dev, "file_trigger()");
+	debug_print("file_trigger\n");
 	return comp_set_state(dev, cmd);
 }
 
-/* used to pass standard and bespoke commands (with data) to component */
-static int file_cmd(struct comp_dev *dev, int cmd, void *data,
-		    int max_data_size)
-{
-	struct sof_ipc_ctrl_data *cdata = ASSUME_ALIGNED(data, 4);
-	int ret = 0;
-
-	comp_info(dev, "file_cmd()");
-	switch (cmd) {
-	case COMP_CMD_SET_DATA:
-		ret = fr_cmd(dev, cdata);
-		break;
-	default:
-		fprintf(stderr, "Warning: Unknown file command %d\n", cmd);
-		return -EINVAL;
-	}
-
-	return ret;
-}
-
-/*
- * copy and process stream samples
- * returns the number of bytes copied
- */
-static int file_copy(struct comp_dev *dev)
-{
-	struct comp_buffer *buffer;
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
-	uint64_t cycles0, cycles1;
-	int snk_frames;
-	int src_frames;
-	int bytes = cd->sample_container_bytes;
-	int ret = 0;
-
-	tb_getcycles(&cycles0);
-	switch (cd->fs.mode) {
-	case FILE_READ:
-		/* file component sink buffer */
-		buffer = list_first_item(&dev->bsink_list, struct comp_buffer,
-					 source_list);
-
-		/* test sink has enough free frames */
-		snk_frames = MIN(audio_stream_get_free_frames(&buffer->stream), dev->frames);
-		if (snk_frames > 0 && !cd->fs.reached_eof) {
-			/* read PCM samples from file */
-			ret = cd->file_func(dev, &buffer->stream, NULL,
-					    snk_frames);
-
-			/* update sink buffer pointers */
-			if (ret > 0)
-				comp_update_buffer_produce(buffer,
-							   ret * bytes);
-		}
-		break;
-	case FILE_WRITE:
-		/* file component source buffer */
-		buffer = list_first_item(&dev->bsource_list,
-					 struct comp_buffer, sink_list);
-
-		/* test source has enough free frames */
-		src_frames = audio_stream_get_avail_frames(&buffer->stream);
-		if (src_frames > 0) {
-			/* write PCM samples into file */
-			ret = cd->file_func(dev, NULL, &buffer->stream,
-					    src_frames);
-
-			/* update source buffer pointers */
-			if (ret > 0)
-				comp_update_buffer_consume(buffer,
-							   ret * bytes);
-		}
-		break;
-	default:
-		/* TODO: duplex mode */
-		ret = -EINVAL;
-		break;
-	}
-
-	cd->fs.copy_count++;
-	if (cd->fs.reached_eof || (cd->max_copies && cd->fs.copy_count >= cd->max_copies)) {
-		cd->fs.reached_eof = 1;
-		comp_info(dev, "file_copy(): copies %d max %d eof %d",
-			  cd->fs.copy_count, cd->max_copies,
-			  cd->fs.reached_eof);
-		schedule_task_cancel(dev->pipeline->pipe_task);
-	}
-
-	tb_getcycles(&cycles1);
-	cd->fs.cycles_count += cycles1 - cycles0;
-	return ret;
-}
-
-static int file_prepare(struct comp_dev *dev)
-{
-	int ret = 0;
-
-	comp_info(dev, "file_prepare()");
-
-	ret = comp_set_state(dev, COMP_TRIGGER_PREPARE);
-	if (ret < 0)
-		return ret;
-
-	if (ret == COMP_STATUS_STATE_ALREADY_SET)
-		return PPL_STATUS_PATH_STOP;
-
-	dev->state = COMP_STATE_PREPARE;
-	return ret;
-}
-
-static int file_reset(struct comp_dev *dev)
-{
-	comp_info(dev, "file_reset()");
-	comp_set_state(dev, COMP_TRIGGER_RESET);
-	return 0;
-}
-
 static int file_get_hw_params(struct comp_dev *dev,
 			      struct sof_ipc_stream_params *params, int dir)
 {
-	struct dai_data *dd = comp_get_drvdata(dev);
-	struct file_comp_data *cd = comp_get_drvdata(dd->dai);
+	struct processing_module *mod = comp_get_drvdata(dev);
+	struct file_comp_data *cd = module_get_private_data(mod);
 
-	comp_info(dev, "file_hw_params()");
+	debug_print("file_hw_params()\n");
 	params->direction = dir;
 	params->rate = cd->rate;
 	params->channels = cd->channels;
@@ -905,50 +779,20 @@ static int file_get_hw_params(struct comp_dev *dev,
 	return 0;
 }
 
-static const struct comp_driver comp_file_host = {
-	.type = SOF_COMP_HOST,
-	.uid = SOF_RT_UUID(file_uuid),
-	.tctx	= &file_tr,
-	.ops = {
-		.create = file_new,
-		.free = file_free,
-		.params = file_params,
-		.cmd = file_cmd,
-		.trigger = file_trigger,
-		.copy = file_copy,
-		.prepare = file_prepare,
-		.reset = file_reset,
-	},
-
-};
-
-static const struct comp_driver comp_file_dai = {
-	.type = SOF_COMP_DAI,
-	.uid = SOF_RT_UUID(file_uuid),
-	.tctx	= &file_tr,
-	.ops = {
-		.create = file_new,
-		.free = file_free,
-		.params = file_params,
-		.cmd = file_cmd,
-		.trigger = file_trigger,
-		.copy = file_copy,
-		.prepare = file_prepare,
-		.reset = file_reset,
-		.dai_get_hw_params = file_get_hw_params,
-	},
+/* Needed for SOF_COMP_DAI */
+static struct module_endpoint_ops file_endpoint_ops = {
+	.dai_get_hw_params = file_get_hw_params,
+	.trigger = file_trigger,
 };
 
-static struct comp_driver_info comp_file_host_info = {
-	.drv = &comp_file_host,
+static const struct module_interface file_interface = {
+	.init = file_init,
+	.prepare = file_prepare,
+	.process_audio_stream = file_process,
+	.reset = file_reset,
+	.free = file_free,
+	.endpoint_ops = &file_endpoint_ops,
 };
 
-static struct comp_driver_info comp_file_dai_info = {
-	.drv = &comp_file_dai,
-};
-
-void sys_comp_file_init(void)
-{
-	comp_register(&comp_file_host_info);
-	comp_register(&comp_file_dai_info);
-}
+DECLARE_MODULE_ADAPTER(file_interface, file_uuid, file_tr);
+SOF_MODULE_INIT(file, sys_comp_module_file_interface_init);
diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h
index dd43c29ab0eb..084195e17f04 100644
--- a/tools/testbench/include/testbench/common_test.h
+++ b/tools/testbench/include/testbench/common_test.h
@@ -12,10 +12,10 @@
 #include <time.h>
 #include <stdio.h>
 #include <rtos/sof.h>
+#include <tplg_parser/topology.h>
 #include <sof/audio/component_ext.h>
 #include <sof/math/numbers.h>
 #include <sof/audio/format.h>
-
 #include <sof/lib/uuid.h>
 
 #define DEBUG_MSG_LEN		1024
@@ -23,11 +23,40 @@
 
 #define MAX_INPUT_FILE_NUM	16
 #define MAX_OUTPUT_FILE_NUM	16
+#define MAX_PIPELINES_NUM	16
+
 
 /* number of widgets types supported in testbench */
 #define NUM_WIDGETS_SUPPORTED	16
 
-struct tplg_context;
+#define TB_NAME_SIZE	256
+
+#define TB_MAX_CONFIG	128
+
+struct tb_mq_desc {
+	/* IPC message queue */
+	//mqd_t mq;
+	//struct mq_attr attr;
+	char queue_name[TB_NAME_SIZE];
+};
+
+struct tb_config {
+	char name[44];
+	unsigned long buffer_frames;
+	unsigned long buffer_time;
+	unsigned long period_frames;
+	unsigned long period_time;
+	int rate;
+	int channels;
+	unsigned long format;
+};
+
+struct file_comp_lookup {
+	int id;
+	int instance_id;
+	int pipeline_id;
+	struct file_state *state;
+};
 
 /*
  * Global testbench data.
@@ -37,20 +66,16 @@ struct tplg_context;
  */
 struct testbench_prm {
 	long long total_cycles;
-	char *tplg_file; /* topology file to use */
+	int pipelines[MAX_PIPELINES_NUM];
+	struct file_comp_lookup fr[MAX_INPUT_FILE_NUM];
+	struct file_comp_lookup fw[MAX_OUTPUT_FILE_NUM];
 	char *input_file[MAX_INPUT_FILE_NUM]; /* input file names */
 	char *output_file[MAX_OUTPUT_FILE_NUM]; /* output file names */
+	char *tplg_file; /* topology file to use */
+	char *bits_in; /* input bit format */
 	int input_file_num; /* number of input files */
 	int output_file_num; /* number of output files */
-	char *bits_in; /* input bit format */
-	int pipelines[MAX_OUTPUT_FILE_NUM]; /* output file names */
 	int pipeline_num;
-	struct tplg_context *ctx;
-
-	int fr_id;
-	int fw_id;
-
-	int max_pipeline_id;
 	int copy_iterations;
 	bool copy_check;
 	bool quiet;
@@ -59,12 +84,12 @@ struct testbench_prm {
 	int tick_period_us;
 	int pipeline_duration_ms;
 	int real_time;
-	FILE *file;
+	//FILE *file;
 	char *pipeline_string;
 	int output_file_index;
 	int input_file_index;
 
-	struct tplg_comp_info *info;
+	//struct tplg_comp_info *info;
 	int info_index;
 	int info_elems;
 
@@ -79,25 +104,39 @@ struct testbench_prm {
 	uint32_t channels_in;
 	uint32_t channels_out;
 	enum sof_ipc_frame frame_fmt;
+	int ipc_version;
+
+	/* topology */
+	struct tplg_context tplg;
+	struct list_item widget_list;
+	struct list_item route_list;
+	struct list_item pcm_list;
+	struct list_item pipeline_list;
+	int instance_ids[SND_SOC_TPLG_DAPM_LAST];
+	struct tb_mq_desc ipc_tx;
+	struct tb_mq_desc ipc_rx;
+
+	int pcm_id;	// TODO: This needs to be cleaned up
+	struct tplg_pcm_info *pcm_info;
+
+	struct tb_config config[TB_MAX_CONFIG];
+	int num_configs;
+
+	size_t period_size;
 };
 
 extern int debug;
 
-int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx);
+int tb_parse_topology(struct testbench_prm *tb);
 
 int edf_scheduler_init(void);
 
-void sys_comp_file_init(void);
-
-void sys_comp_filewrite_init(void);
-
 int tb_setup(struct sof *sof, struct testbench_prm *tp);
 void tb_free(struct sof *sof);
 
 int tb_pipeline_start(struct ipc *ipc, struct pipeline *p);
 
-int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p,
-		       struct tplg_context *ctx);
+int tb_pipeline_params(struct testbench_prm *tp, struct ipc *ipc, struct pipeline *p);
 
 int tb_pipeline_stop(struct ipc *ipc, struct pipeline *p);
 
@@ -109,4 +148,16 @@ void tb_gettime(struct timespec *td);
 
 void tb_getcycles(uint64_t *cycles);
 
+int tb_load_topology(struct testbench_prm *tb);
+
+int tb_set_up_all_pipelines(struct testbench_prm *tb);
+
+void tb_show_file_stats(struct testbench_prm *tp, int pipeline_id);
+
+bool tb_schedule_pipeline_check_state(struct testbench_prm *tp);
+
+bool tb_is_pipeline_enabled(struct testbench_prm *tb, int pipeline_id);
+
+int tb_find_file_components(struct testbench_prm *tb);
+
 #endif
diff --git a/tools/testbench/include/testbench/file.h b/tools/testbench/include/testbench/file.h
index d18f97972985..83ee9a222723 100644
--- a/tools/testbench/include/testbench/file.h
+++ b/tools/testbench/include/testbench/file.h
@@ -8,11 +8,13 @@
  *         Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
  */
 
-#ifndef _FILE_H
-#define _FILE_H
+#ifndef _TESTBENCH_FILE_H
+#define _TESTBENCH_FILE_H
 
 #include <stdint.h>
 
+#define FILE_MAX_COPIES_TIMEOUT	3
+
 /**< Convert with right shift a bytes count to samples count */
 #define FILE_BYTES_TO_S16_SAMPLES(s)	((s) >> 1)
 #define FILE_BYTES_TO_S32_SAMPLES(s)	((s) >> 2)
@@ -40,8 +42,11 @@ struct file_state {
 	enum file_format f_format;
 	bool reached_eof;
 	bool write_failed;
+	bool copy_timeout;
 };
 
+struct file_comp_data;
+
 /* file comp data */
 struct file_comp_data {
 	struct file_state fs;
@@ -49,12 +54,16 @@ struct file_comp_data {
 	uint32_t channels;
 	uint32_t rate;
 	int sample_container_bytes;
-	int (*file_func)(struct comp_dev *dev, struct audio_stream *sink,
+	int (*file_func)(struct file_comp_data *cd, struct audio_stream *sink,
 			 struct audio_stream *source, uint32_t frames);
 
 	/* maximum limits */
 	int max_samples;
 	int max_copies;
+	int max_frames;
+	int copies_timeout;
 };
 
-#endif
+void sys_comp_module_file_interface_init(void);
+
+#endif /* _TESTBENCH_FILE */
diff --git a/tools/testbench/include/testbench/file_ipc4.h b/tools/testbench/include/testbench/file_ipc4.h
new file mode 100644
index 000000000000..30ebd05f775d
--- /dev/null
+++ b/tools/testbench/include/testbench/file_ipc4.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright(c) 2024 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __TESTBENCH_FILE_IPC4_H_
+#define __TESTBENCH_FILE_IPC4_H_
+
+#include <ipc/topology.h>
+#include <ipc4/base-config.h>
+
+struct ipc4_file_config {
+	uint32_t rate;
+	uint32_t channels;
+	char *fn;
+	uint32_t mode;
+	uint32_t frame_fmt;
+	uint32_t direction;	/**< SOF_IPC_STREAM_ */
+};
+
+struct ipc4_file_module_cfg {
+	struct ipc4_base_module_cfg base_cfg;
+	struct ipc4_file_config config;
+} __packed __aligned(8);
+
+#endif /*  __TESTBENCH_FILE_IPC4_H_ */
diff --git a/tools/testbench/include/testbench/topology.h b/tools/testbench/include/testbench/topology.h
new file mode 100644
index 000000000000..3091950cb236
--- /dev/null
+++ b/tools/testbench/include/testbench/topology.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright(c) 2024 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __TESTBENCH_TOPOLOGY_H_
+#define __TESTBENCH_TOPOLOGY_H_
+
+#include "testbench/common_test.h"
+
+#define MAX_TPLG_OBJECT_SIZE	4096
+#define IPC3_MAX_MSG_SIZE	384
+#define IPC4_MAX_MSG_SIZE	384
+
+int tb_new_aif_in_out(struct testbench_prm *tb, int dir);
+
+int tb_new_dai_in_out(struct testbench_prm *tb, int dir);
+
+int tb_new_pga(struct testbench_prm *tb);
+
+int tb_new_process(struct testbench_prm *tb);
+
+int tb_free_pipelines(struct testbench_prm *tb, int dir);
+
+void tb_free_topology(struct testbench_prm *tb);
+
+int tb_get_instance_id_from_pipeline_id(struct testbench_prm *tp, int id);
+
+int tb_set_up_pipelines(struct testbench_prm *tb, int dir);
+
+int tb_free_all_pipelines(struct testbench_prm *tb);
+
+#endif /*  __TESTBENCH_TOPOLOGY_H_ */
diff --git a/tools/testbench/include/testbench/topology_ipc4.h b/tools/testbench/include/testbench/topology_ipc4.h
new file mode 100644
index 000000000000..866750dccd75
--- /dev/null
+++ b/tools/testbench/include/testbench/topology_ipc4.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright(c) 2024 Intel Corporation. All rights reserved.
+ */
+
+#ifndef _TESTBENCH_TOPOLOGY_IPC4_H
+#define _TESTBENCH_TOPOLOGY_IPC4_H
+
+#include <module/ipc4/base-config.h>
+#include "testbench/common_test.h"
+
+#define TB_FAKE_IPC	0
+
+int tb_parse_ipc4_comp_tokens(struct testbench_prm *tp, struct ipc4_base_module_cfg *base_cfg);
+
+void tb_setup_widget_ipc_msg(struct tplg_comp_info *comp_info);
+
+int tb_set_up_widget_ipc(struct testbench_prm *tb, struct tplg_comp_info *comp_info);
+
+int tb_set_up_route(struct testbench_prm *tb, struct tplg_route_info *route_info);
+
+int tb_set_up_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info);
+
+void tb_pipeline_update_resource_usage(struct testbench_prm *tb,
+					      struct tplg_comp_info *comp_info);
+
+int tb_is_single_format(struct sof_ipc4_pin_format *fmts, int num_formats);
+
+int tb_match_audio_format(struct testbench_prm *tb, struct tplg_comp_info *comp_info,
+				 struct tb_config *config);
+
+int tb_set_up_widget_base_config(struct testbench_prm *tb, struct tplg_comp_info *comp_info);
+
+int tb_pipelines_set_state(struct testbench_prm *tb, int state, int dir);
+
+int tb_delete_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info);
+
+int tb_free_route(struct testbench_prm *tb, struct tplg_route_info *route_info);
+
+int tb_set_running_state(struct testbench_prm *tb);
+
+int tb_set_reset_state(struct testbench_prm *tb);
+
+#endif /* _TESTBENCH_TOPOLOGY_IPC4_H */
diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c
index d4b03538792c..a3f26bc00db1 100644
--- a/tools/testbench/testbench.c
+++ b/tools/testbench/testbench.c
@@ -1,17 +1,19 @@
 // SPDX-License-Identifier: BSD-3-Clause
 //
-// Copyright(c) 2018 Intel Corporation. All rights reserved.
+// Copyright(c) 2018-2024 Intel Corporation. All rights reserved.
 //
 // Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
 //         Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
 
+#include <sof/audio/module_adapter/module/generic.h>
 #include <sof/ipc/driver.h>
 #include <sof/ipc/topology.h>
-#include <platform/lib/ll_schedule.h>
 #include <sof/list.h>
 #include <getopt.h>
 #include "testbench/common_test.h"
 #include <tplg_parser/topology.h>
+#include "testbench/topology_ipc4.h"
+#include "testbench/topology.h"
 #include "testbench/trace.h"
 #include "testbench/file.h"
 #include <limits.h>
@@ -141,140 +143,12 @@ static void print_usage(char *executable)
 	printf("-b S16_LE -a volume=libsof_volume.so\n");
 }
 
-/* free components */
-static void test_pipeline_free_comps(int pipeline_id)
-{
-	struct list_item *clist;
-	struct list_item *temp;
-	struct ipc_comp_dev *icd = NULL;
-	int err;
-
-	/* remove the components for this pipeline */
-	list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) {
-		icd = container_of(clist, struct ipc_comp_dev, list);
-
-		switch (icd->type) {
-		case COMP_TYPE_COMPONENT:
-			if (icd->cd->pipeline->pipeline_id != pipeline_id)
-				break;
-			err = ipc_comp_free(sof_get()->ipc, icd->id);
-			if (err)
-				fprintf(stderr, "failed to free comp %d\n",
-					icd->id);
-			break;
-		case COMP_TYPE_BUFFER:
-			if (buffer_pipeline_id(icd->cb) != pipeline_id)
-				break;
-			err = ipc_buffer_free(sof_get()->ipc, icd->id);
-			if (err)
-				fprintf(stderr, "failed to free buffer %d\n",
-					icd->id);
-			break;
-		default:
-			if (icd->pipeline->pipeline_id != pipeline_id)
-				break;
-			err = ipc_pipeline_free(sof_get()->ipc, icd->id);
-			if (err)
-				fprintf(stderr, "failed to free pipeline %d\n",
-					icd->id);
-			break;
-		}
-	}
-}
-
-static void test_pipeline_set_test_limits(int pipeline_id, int max_copies,
-					  int max_samples)
-{
-	struct list_item *clist;
-	struct list_item *temp;
-	struct ipc_comp_dev *icd = NULL;
-	struct comp_dev *cd;
-	struct dai_data *dd;
-	struct file_comp_data *fcd;
-
-	/* set the test limits for this pipeline */
-	list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) {
-		icd = container_of(clist, struct ipc_comp_dev, list);
-
-		switch (icd->type) {
-		case COMP_TYPE_COMPONENT:
-			cd = icd->cd;
-			if (cd->pipeline->pipeline_id != pipeline_id)
-				break;
-
-			switch (cd->drv->type) {
-			case SOF_COMP_HOST:
-			case SOF_COMP_DAI:
-			case SOF_COMP_FILEREAD:
-			case SOF_COMP_FILEWRITE:
-				/* only file limits supported today. TODO: add others */
-				dd = comp_get_drvdata(cd);
-				fcd = comp_get_drvdata(dd->dai);
-				fcd->max_samples = max_samples;
-				fcd->max_copies = max_copies;
-				break;
-			default:
-				break;
-			}
-			break;
-		case COMP_TYPE_BUFFER:
-		default:
-			break;
-		}
-	}
-}
-
-static void test_pipeline_get_file_stats(int pipeline_id)
-{
-	struct list_item *clist;
-	struct list_item *temp;
-	struct ipc_comp_dev *icd;
-	struct comp_dev *cd;
-	struct dai_data *dd;
-	struct file_comp_data *fcd;
-	unsigned long time;
-
-	/* get the file IO status for each file in pipeline */
-	list_for_item_safe(clist, temp, &sof_get()->ipc->comp_list) {
-		icd = container_of(clist, struct ipc_comp_dev, list);
-
-		switch (icd->type) {
-		case COMP_TYPE_COMPONENT:
-			cd = icd->cd;
-			if (cd->pipeline->pipeline_id != pipeline_id)
-				break;
-			switch (cd->drv->type) {
-			case SOF_COMP_HOST:
-			case SOF_COMP_DAI:
-			case SOF_COMP_FILEREAD:
-			case SOF_COMP_FILEWRITE:
-				dd = comp_get_drvdata(cd);
-				fcd = comp_get_drvdata(dd->dai);
-
-				time = cd->pipeline->pipe_task->start;
-				if (fcd->fs.copy_count == 0)
-					fcd->fs.copy_count = 1;
-				printf("file %s: id %d: type %d: samples %d copies %d total time %lu uS avg time %lu uS\n",
-				       fcd->fs.fn, cd->ipc_config.id, cd->drv->type, fcd->fs.n,
-				       fcd->fs.copy_count, time, time / fcd->fs.copy_count);
-				break;
-			default:
-				break;
-			}
-			break;
-		case COMP_TYPE_BUFFER:
-		default:
-			break;
-		}
-	}
-}
-
 static int parse_input_args(int argc, char **argv, struct testbench_prm *tp)
 {
 	int option = 0;
 	int ret = 0;
 
-	while ((option = getopt(argc, argv, "hdqi:o:t:b:a:r:R:c:n:C:P:Vp:T:D:")) != -1) {
+	while ((option = getopt(argc, argv, "hd:qi:o:t:b:r:R:c:n:C:P:p:T:D:")) != -1) {
 		switch (option) {
 		/* input sample file */
 		case 'i':
@@ -370,232 +244,47 @@ static int parse_input_args(int argc, char **argv, struct testbench_prm *tp)
 	return ret;
 }
 
-static struct pipeline *get_pipeline_by_id(int id)
-{
-	struct ipc_comp_dev *pcm_dev;
-	struct ipc *ipc = sof_get()->ipc;
-
-	pcm_dev = ipc_get_ppl_src_comp(ipc, id);
-	return pcm_dev->cd->pipeline;
-}
-
-static int test_pipeline_stop(struct testbench_prm *tp)
-{
-	struct pipeline *p;
-	struct ipc *ipc = sof_get()->ipc;
-	int ret = 0;
-	int i;
-
-	for (i = 0; i < tp->pipeline_num; i++) {
-		p = get_pipeline_by_id(tp->pipelines[i]);
-		ret = tb_pipeline_stop(ipc, p);
-		if (ret < 0)
-			break;
-	}
-
-	return ret;
-}
-
-static int test_pipeline_reset(struct testbench_prm *tp)
-{
-	struct pipeline *p;
-	struct ipc *ipc = sof_get()->ipc;
-	int ret = 0;
-	int i;
-
-	for (i = 0; i < tp->pipeline_num; i++) {
-		p = get_pipeline_by_id(tp->pipelines[i]);
-		ret = tb_pipeline_reset(ipc, p);
-		if (ret < 0)
-			break;
-	}
-
-	return ret;
-}
-
-static void test_pipeline_free(struct testbench_prm *tp)
-{
-	int i;
-
-	for (i = 0; i < tp->pipeline_num; i++)
-		test_pipeline_free_comps(tp->pipelines[i]);
-}
-
-static int test_pipeline_params(struct testbench_prm *tp, struct tplg_context *ctx)
-{
-	struct ipc_comp_dev *pcm_dev;
-	struct pipeline *p;
-	struct ipc *ipc = sof_get()->ipc;
-	int ret = 0;
-	int i;
-
-	/* Run pipeline until EOF from fileread */
-
-	for (i = 0; i < tp->pipeline_num; i++) {
-		pcm_dev = ipc_get_ppl_src_comp(ipc, tp->pipelines[i]);
-		if (!pcm_dev) {
-			fprintf(stderr, "error: pipeline %d has no source component\n",
-				tp->pipelines[i]);
-			return -EINVAL;
-		}
-
-		/* set up pipeline params */
-		p = pcm_dev->cd->pipeline;
-
-		/* input and output sample rate */
-		if (!tp->fs_in)
-			tp->fs_in = p->period * p->frames_per_sched;
-
-		if (!tp->fs_out)
-			tp->fs_out = p->period * p->frames_per_sched;
-
-		ret = tb_pipeline_params(tp, ipc, p, ctx);
-		if (ret < 0) {
-			fprintf(stderr, "error: pipeline params failed: %s\n",
-				strerror(ret));
-			return ret;
-		}
-	}
-
-
-	return 0;
-}
-
-static int test_pipeline_start(struct testbench_prm *tp)
-{
-	struct pipeline *p;
-	struct ipc *ipc = sof_get()->ipc;
-	int i;
-
-	/* Run pipeline until EOF from fileread */
-	for (i = 0; i < tp->pipeline_num; i++) {
-		p = get_pipeline_by_id(tp->pipelines[i]);
-
-		/* do we need to apply copy count limit ? */
-		if (tp->copy_check)
-			test_pipeline_set_test_limits(tp->pipelines[i], tp->copy_iterations, 0);
-
-		/* set pipeline params and trigger start */
-		if (tb_pipeline_start(ipc, p) < 0) {
-			fprintf(stderr, "error: pipeline params\n");
-			return -EINVAL;
-		}
-	}
-
-	return 0;
-}
-
-static bool test_pipeline_check_state(struct testbench_prm *tp, int state)
-{
-	struct pipeline *p;
-	uint64_t cycles0, cycles1;
-	int i;
-
-	tb_getcycles(&cycles0);
-
-	schedule_ll_run_tasks();
-
-	tb_getcycles(&cycles1);
-	tp->total_cycles += cycles1 - cycles0;
-
-	/* Run pipeline until EOF from fileread */
-	for (i = 0; i < tp->pipeline_num; i++) {
-		p = get_pipeline_by_id(tp->pipelines[i]);
-		if (p->pipe_task->state	== state)
-			return true;
-	}
-
-	return false;
-}
-
-static int test_pipeline_load(struct testbench_prm *tp, struct tplg_context *ctx)
-{
-	int ret;
-
-	/* setup the thread virtual core config */
-	memset(ctx, 0, sizeof(*ctx));
-	ctx->comp_id = 1;
-	ctx->core_id = 0;
-	ctx->sof = sof_get();
-	ctx->tplg_file = tp->tplg_file;
-	ctx->ipc_major = 3;
-
-	/* parse topology file and create pipeline */
-	ret = tb_parse_topology(tp, ctx);
-	if (ret < 0)
-		fprintf(stderr, "error: parsing topology\n");
-
-	return ret;
-}
-
-static void test_pipeline_stats(struct testbench_prm *tp,
-				struct tplg_context *ctx, long long delta_t)
+static void test_pipeline_stats(struct testbench_prm *tp, long long delta_t)
 {
-	int count = 1;
-	struct ipc_comp_dev *icd;
-	struct comp_dev *cd;
-	struct dai_data *dd;
-	struct pipeline *p;
-	struct file_comp_data *frcd, *fwcd;
 	long long file_cycles, pipeline_cycles;
 	float pipeline_mcps;
 	int n_in, n_out, frames_out;
 	int i;
+	int count = 1;
 
-	/* Get pointer to filewrite */
-	icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fw_id);
-	if (!icd) {
-		fprintf(stderr, "error: failed to get pointers to filewrite\n");
-		exit(EXIT_FAILURE);
-	}
-	cd = icd->cd;
-	dd = comp_get_drvdata(cd);
-	fwcd = comp_get_drvdata(dd->dai);
-
-	/* Get pointer to fileread */
-	icd = ipc_get_comp_by_id(sof_get()->ipc, tp->fr_id);
-	if (!icd) {
-		fprintf(stderr, "error: failed to get pointers to fileread\n");
-		exit(EXIT_FAILURE);
-	}
-	cd = icd->cd;
-	dd = comp_get_drvdata(cd);
-	frcd = comp_get_drvdata(dd->dai);
-
-	/* Run pipeline until EOF from fileread */
-	icd = ipc_get_comp_by_id(sof_get()->ipc, ctx->sched_id);
-	p = icd->cd->pipeline;
+	n_out = 0;
+	n_in = 0;
+	file_cycles = 0;
+	for (i = 0; i < tp->input_file_num; i++) {
+		if (tp->fr[i].id < 0)
+			continue;
 
-	/* input and output sample rate */
-	if (!tp->fs_in)
-		tp->fs_in = p->period * p->frames_per_sched;
+		n_in += tp->fr[i].state->n;
+		file_cycles += tp->fr[i].state->cycles_count;
+	}
 
-	if (!tp->fs_out)
-		tp->fs_out = p->period * p->frames_per_sched;
+	for (i = 0; i < tp->output_file_num; i++) {
+		if (tp->fw[i].id < 0)
+			continue;
 
-	n_in = frcd->fs.n;
-	n_out = fwcd->fs.n;
-	file_cycles = frcd->fs.cycles_count + fwcd->fs.cycles_count;
+		n_out += tp->fw[i].state->n;
+		file_cycles += tp->fw[i].state->cycles_count;
+	}
 
 	/* print test summary */
 	printf("==========================================================\n");
 	printf("		           Test Summary %d\n", count);
 	printf("==========================================================\n");
-	printf("Test Pipeline:\n");
-	printf("%s\n", tp->pipeline_string);
-	test_pipeline_get_file_stats(ctx->pipeline_id);
+
+	for (i = 0; i < tp->pipeline_num; i++) {
+		printf("pipeline %d\n", tp->pipelines[i]);
+		tb_show_file_stats(tp, tp->pipelines[i]);
+	}
 
 	printf("Input bit format: %s\n", tp->bits_in);
 	printf("Input sample rate: %d\n", tp->fs_in);
 	printf("Output sample rate: %d\n", tp->fs_out);
-	for (i = 0; i < tp->input_file_num; i++) {
-		printf("Input[%d] read from file: \"%s\"\n",
-		       i, tp->input_file[i]);
-	}
-	for (i = 0; i < tp->output_file_num; i++) {
-		printf("Output[%d] written to file: \"%s\"\n",
-		       i, tp->output_file[i]);
-	}
+
 	frames_out = n_out / tp->channels_out;
 	printf("Input sample (frame) count: %d (%d)\n", n_in, n_in / tp->channels_in);
 	printf("Output sample (frame) count: %d (%d)\n", n_out, frames_out);
@@ -624,7 +313,6 @@ static void test_pipeline_stats(struct testbench_prm *tp,
 static int pipline_test(struct testbench_prm *tp)
 {
 	int dp_count = 0;
-	struct tplg_context ctx;
 	struct timespec ts;
 	struct timespec td0, td1;
 	long long delta_t;
@@ -642,24 +330,28 @@ static int pipline_test(struct testbench_prm *tp)
 		printf("		           Test Start %d\n", dp_count);
 		printf("==========================================================\n");
 
-		err = test_pipeline_load(tp, &ctx);
+		err = tb_load_topology(tp);
 		if (err < 0) {
-			fprintf(stderr, "error: pipeline load %d failed %d\n",
-				dp_count, err);
+			fprintf(stderr, "error: topology load %d failed %d\n", dp_count, err);
 			break;
 		}
 
-		err = test_pipeline_params(tp, &ctx);
+		err = tb_set_up_all_pipelines(tp);
 		if (err < 0) {
-			fprintf(stderr, "error: pipeline params %d failed %d\n",
-				dp_count, err);
+			fprintf(stderr, "error: pipelines set up %d failed %d\n", dp_count, err);
 			break;
 		}
 
-		err = test_pipeline_start(tp);
+		err = tb_set_running_state(tp);
 		if (err < 0) {
-			fprintf(stderr, "error: pipeline run %d failed %d\n",
-				dp_count, err);
+			fprintf(stderr, "error: pipelines state set %d failed %d\n", dp_count, err);
+			break;
+		}
+
+
+		err = tb_find_file_components(tp); /* Track file comp status during copying */
+		if (err < 0) {
+			fprintf(stderr, "error: file component find failed %d\n", err);
 			break;
 		}
 
@@ -686,10 +378,8 @@ static int pipline_test(struct testbench_prm *tp)
 #endif
 			if (err == 0) {
 				nsleep_time += tp->tick_period_us; /* sleep fully completed */
-				if (test_pipeline_check_state(tp, SOF_TASK_STATE_CANCEL)) {
-					fprintf(stdout, "pipeline cancelled !\n");
+				if (tb_schedule_pipeline_check_state(tp))
 					break;
-				}
 			} else {
 				if (err == EINTR) {
 					continue; /* interrupted - keep going */
@@ -700,38 +390,40 @@ static int pipline_test(struct testbench_prm *tp)
 			}
 		}
 
+		tb_schedule_pipeline_check_state(tp); /* Once more to flush out remaining data */
+
 		tb_gettime(&td1);
 
-		err = test_pipeline_stop(tp);
+		err = tb_set_reset_state(tp);
 		if (err < 0) {
-			fprintf(stderr, "error: pipeline stop %d failed %d\n",
+			fprintf(stderr, "error: pipeline reset %d failed %d\n",
 				dp_count, err);
 			break;
 		}
 
+		/* TODO: This should be printed after reset and free to get cleaner output
+		 * but the file internal status would be lost there.
+		 */
 		delta_t = (td1.tv_sec - td0.tv_sec) * 1000000;
 		delta_t += (td1.tv_nsec - td0.tv_nsec) / 1000;
-		test_pipeline_stats(tp, &ctx, delta_t);
+		test_pipeline_stats(tp, delta_t);
 
-		err = test_pipeline_reset(tp);
+		err = tb_free_all_pipelines(tp);
 		if (err < 0) {
-			fprintf(stderr, "error: pipeline stop %d failed %d\n",
-				dp_count, err);
+			fprintf(stderr, "error: free pipelines %d failed %d\n", dp_count, err);
 			break;
 		}
 
-		test_pipeline_free(tp);
-
+		tb_free_topology(tp);
 		dp_count++;
 	}
 
 	return 0;
 }
 
-static struct testbench_prm tp;
-
 int main(int argc, char **argv)
 {
+	struct testbench_prm tp;
 	int i, err;
 
 	/* initialize input and output sample rates, files, etc. */
@@ -742,6 +434,8 @@ int main(int argc, char **argv)
 	tp.tplg_file = NULL;
 	tp.input_file_num = 0;
 	tp.output_file_num = 0;
+	tp.input_file_index = 0;
+	tp.output_file_index = 0;
 	for (i = 0; i < MAX_OUTPUT_FILE_NUM; i++)
 		tp.output_file[i] = NULL;
 
@@ -750,7 +444,6 @@ int main(int argc, char **argv)
 
 	tp.channels_in = TESTBENCH_NCH;
 	tp.channels_out = 0;
-	tp.max_pipeline_id = 0;
 	tp.copy_check = false;
 	tp.quiet = 0;
 	tp.dynamic_pipeline_iterations = 1;
@@ -760,6 +453,21 @@ int main(int argc, char **argv)
 	tp.tick_period_us = 0; /* Execute fast non-real time, for 1 ms tick use -T 1000 */
 	tp.pipeline_duration_ms = 5000;
 	tp.copy_iterations = 1;
+	tp.ipc_version = 4;	// FIXME from IPC_CONVFIG_MAJOR
+	tp.period_size = 96;	// FIXME becomes somehow obs in tb_match_audio_format()
+
+	// TODO move somewhere else and integrate with command line
+	tp.num_configs = 1;
+	strcpy(tp.config[0].name, "48k2c32b");
+	tp.config[0].buffer_frames = 24000;
+	tp.config[0].buffer_time = 0;
+	tp.config[0].period_frames = 6000;
+	tp.config[0].period_time = 0;
+	tp.config[0].rate = 48000;
+	tp.config[0].channels = 2;
+	tp.config[0].format = SOF_IPC_FRAME_S32_LE;
+
+	tp.pcm_id = 0; // TODO: Fix this
 
 	/* command line arguments*/
 	err = parse_input_args(argc, argv, &tp);
diff --git a/tools/testbench/topology.c b/tools/testbench/topology.c
index 95e1450df693..2e69ef329f1e 100644
--- a/tools/testbench/topology.c
+++ b/tools/testbench/topology.c
@@ -1,554 +1,321 @@
 // SPDX-License-Identifier: BSD-3-Clause
 //
-// Copyright(c) 2018 Intel Corporation. All rights reserved.
+// Copyright(c) 2018-2024 Intel Corporation. All rights reserved.
 //
 // Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
 //         Liam Girdwood <liam.r.girdwood@linux.intel.com>
 
 /* Topology loader to set up components and pipeline */
 
-#include <errno.h>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sof/common.h>
-#include <rtos/string.h>
 #include <sof/audio/component.h>
 #include <sof/ipc/driver.h>
 #include <sof/ipc/topology.h>
-#include <tplg_parser/topology.h>
+#include <rtos/string.h>
+#include <sof/common.h>
+#include <sof/lib/uuid.h>
 #include <tplg_parser/tokens.h>
+#include <tplg_parser/topology.h>
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include "testbench/common_test.h"
 #include "testbench/file.h"
+#include "testbench/topology.h"
+#include "testbench/topology_ipc4.h"
 
-#define MAX_TPLG_OBJECT_SIZE	4096
-
-/* load asrc dapm widget */
-static int tb_register_asrc(struct testbench_prm *tp, struct tplg_context *ctx)
+static int tb_new_src(struct testbench_prm *tb)
 {
+	struct tplg_context *ctx = &tb->tplg;
 	char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0};
-	struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object;
-	struct sof *sof = ctx->sof;
-	struct sof_ipc_comp_asrc *asrc;
-	int ret = 0;
-
-	ret = tplg_new_asrc(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0);
-	if (ret < 0)
-		return ret;
-
-	asrc = (struct sof_ipc_comp_asrc *)comp;
-
-	/* set testbench input and output sample rate from topology */
-	if (!tp->fs_out)
-		tp->fs_out = asrc->sink_rate;
-	else
-		asrc->sink_rate = tp->fs_out;
-
-	if (!tp->fs_in)
-		tp->fs_in = asrc->source_rate;
-	else
-		asrc->source_rate = tp->fs_in;
-
-	/* load asrc component */
-	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(asrc)) < 0) {
-		fprintf(stderr, "error: new asrc comp\n");
-		return -EINVAL;
-	}
-
-	return ret;
-}
-
-/* load buffer DAPM widget */
-static int tb_register_buffer(struct testbench_prm *tp, struct tplg_context *ctx)
-{
-	struct sof *sof = ctx->sof;
-	struct sof_ipc_buffer buffer = {{{0}}};
+	struct sof_ipc_comp_src *src = (struct sof_ipc_comp_src *)tplg_object;
+	struct snd_soc_tplg_ctl_hdr *tplg_ctl;
 	int ret;
 
-	ret = tplg_new_buffer(ctx, &buffer, sizeof(buffer),
-			      NULL, 0);
-	if (ret < 0)
-		return ret;
-
-	/* create buffer component */
-	if (ipc_buffer_new(sof->ipc, &buffer) < 0) {
-		fprintf(stderr, "error: buffer new\n");
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-/* load pipeline graph DAPM widget*/
-static int tb_register_graph(struct tplg_context *ctx, struct tplg_comp_info *temp_comp_list,
-			     char *pipeline_string,
-			     int num_comps, int num_connections,
-			     int pipeline_id)
-{
-	struct sof_ipc_pipe_comp_connect connection;
-	struct sof *sof = ctx->sof;
-	int ret = 0;
-	int i;
-
-	for (i = 0; i < num_connections; i++) {
-		ret = tplg_create_graph(ctx, num_comps, pipeline_id, temp_comp_list,
-					pipeline_string, &connection, i);
-		if (ret < 0)
-			return ret;
-
-		/* connect source and sink */
-		if (ipc_comp_connect(sof->ipc, ipc_to_pipe_connect(&connection)) < 0) {
-			fprintf(stderr, "error: comp connect\n");
-			return -EINVAL;
-		}
-	}
+	tplg_ctl = calloc(ctx->hdr->payload_size, 1);
+	if (!tplg_ctl)
+		return -ENOMEM;
 
-	/* pipeline complete after pipeline connections are established */
-	for (i = 0; i < num_comps; i++) {
-		if (temp_comp_list[i].pipeline_id == pipeline_id &&
-		    temp_comp_list[i].type == SND_SOC_TPLG_DAPM_SCHEDULER)
-			ipc_pipeline_complete(sof->ipc, temp_comp_list[i].id);
+	ret = tplg_new_src(ctx, &src->comp, MAX_TPLG_OBJECT_SIZE, tplg_ctl, ctx->hdr->payload_size);
+	if (ret < 0) {
+		fprintf(stderr, "error: failed to create SRC\n");
+		goto out;
 	}
 
+out:
+	free(tplg_ctl);
 	return ret;
 }
 
-/* load mixer dapm widget */
-static int tb_register_mixer(struct testbench_prm *tp, struct tplg_context *ctx)
+static int tb_new_asrc(struct testbench_prm *tb)
 {
+	struct tplg_context *ctx = &tb->tplg;
 	char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0};
-	struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object;
-	struct sof *sof = ctx->sof;
-	int ret = 0;
+	struct sof_ipc_comp_asrc *asrc = (struct sof_ipc_comp_asrc *)tplg_object;
+	struct snd_soc_tplg_ctl_hdr *tplg_ctl;
+	int ret;
 
-	ret = tplg_new_mixer(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0);
-	if (ret < 0)
-		return ret;
+	tplg_ctl = calloc(ctx->hdr->payload_size, 1);
+	if (!tplg_ctl)
+		return -ENOMEM;
 
-	/* load mixer component */
-	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(comp)) < 0) {
-		fprintf(stderr, "error: new mixer comp\n");
-		ret = -EINVAL;
+	ret = tplg_new_asrc(ctx, &asrc->comp, MAX_TPLG_OBJECT_SIZE,
+			    tplg_ctl, ctx->hdr->payload_size);
+	if (ret < 0) {
+		fprintf(stderr, "error: failed to create ASRC\n");
+		goto out;
 	}
 
+out:
+	free(tplg_ctl);
 	return ret;
 }
 
-static int tb_register_pga(struct testbench_prm *tp, struct tplg_context *ctx)
+static int tb_new_mixer(struct testbench_prm *tb)
 {
+	struct tplg_context *ctx = &tb->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
 	char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0};
-	struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object;
-	struct sof *sof = ctx->sof;
+	struct sof_ipc_comp_mixer *mixer = (struct sof_ipc_comp_mixer *)tplg_object;
+	struct snd_soc_tplg_ctl_hdr *tplg_ctl;
 	int ret;
 
-	ret = tplg_new_pga(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0);
+	tplg_ctl = calloc(ctx->hdr->payload_size, 1);
+	if (!tplg_ctl)
+		return -ENOMEM;
+
+	comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_MIXER]++;
+	comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg);
+	comp_info->ipc_payload = calloc(comp_info->ipc_size, 1);
+	if (!comp_info->ipc_payload)
+		return -ENOMEM;
+
+	ret = tplg_new_mixer(ctx, &mixer->comp, MAX_TPLG_OBJECT_SIZE,
+			     tplg_ctl, ctx->hdr->payload_size);
 	if (ret < 0) {
-		fprintf(stderr, "error: failed to create PGA\n");
-		return ret;
+		fprintf(stderr, "error: failed to create mixer\n");
+		goto out;
 	}
 
-	/* load volume component */
-	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(comp)) < 0) {
-		fprintf(stderr, "error: new pga comp\n");
-		ret = -EINVAL;
+	if (strstr(comp_info->name, "mixin")) {
+		comp_info->module_id = 0x2;
+		tb_setup_widget_ipc_msg(comp_info);
+	} else {
+		comp_info->module_id = 0x3;
+		tb_setup_widget_ipc_msg(comp_info);
 	}
-
+out:
+	free(tplg_ctl);
 	return ret;
 }
 
-/* load scheduler dapm widget */
-static int tb_register_pipeline(struct testbench_prm *tp, struct tplg_context *ctx)
+static int tb_new_pipeline(struct testbench_prm *tb)
 {
-	struct sof *sof = ctx->sof;
-	struct sof_ipc_pipe_new pipeline = {{0}};
+	struct tplg_pipeline_info *pipe_info;
+	struct sof_ipc_pipe_new pipeline = {0};
+	struct snd_soc_tplg_ctl_hdr *tplg_ctl;
+	struct tplg_context *ctx = &tb->tplg;
 	int ret;
 
-	ret = tplg_new_pipeline(ctx, &pipeline, sizeof(pipeline), NULL);
-	if (ret < 0)
-		return ret;
-
-	pipeline.sched_id = ctx->sched_id;
+	tplg_ctl = calloc(ctx->hdr->payload_size, 1);
+	if (!tplg_ctl)
+		return -ENOMEM;
 
-	/* Create pipeline */
-	if (ipc_pipeline_new(sof->ipc, (ipc_pipe_new *)&pipeline) < 0) {
-		fprintf(stderr, "error: pipeline new\n");
-		return -EINVAL;
+	pipe_info = calloc(sizeof(struct tplg_pipeline_info), 1);
+	if (!pipe_info) {
+		ret = -ENOMEM;
+		goto out;
 	}
 
-	return 0;
-}
-
-/* load process dapm widget */
-static int tb_register_process(struct testbench_prm *tp, struct tplg_context *ctx)
-{
-	struct sof *sof = ctx->sof;
-	struct sof_ipc_comp_process *process;
-	int ret = 0;
+	pipe_info->name = strdup(ctx->widget->name);
+	if (!pipe_info->name) {
+		free(pipe_info);
+		goto out;
+	}
 
-	process = calloc(1, MAX_TPLG_OBJECT_SIZE);
-	if (!process)
-		return -ENOMEM;
+	pipe_info->id = ctx->pipeline_id;
 
-	ret = tplg_new_process(ctx, process, MAX_TPLG_OBJECT_SIZE, NULL, 0);
-	if (ret < 0)
+	ret = tplg_new_pipeline(ctx, &pipeline, sizeof(pipeline), tplg_ctl);
+	if (ret < 0) {
+		fprintf(stderr, "error: failed to create pipeline\n");
+		free(pipe_info->name);
+		free(pipe_info);
 		goto out;
+	}
 
-	/* Instantiate */
-	ret = ipc_comp_new(sof->ipc, ipc_to_comp_new(process));
-
+	list_item_append(&pipe_info->item, &tb->pipeline_list);
+	tplg_debug("loading pipeline %s\n", pipe_info->name);
 out:
-	free(process);
-	if (ret < 0)
-		fprintf(stderr, "error: new process comp\n");
-
+	free(tplg_ctl);
 	return ret;
 }
 
-/* load src dapm widget */
-static int tb_register_src(struct testbench_prm *tp, struct tplg_context *ctx)
+static int tb_new_buffer(struct testbench_prm *tb)
 {
-	struct sof *sof = ctx->sof;
-	char tplg_object[MAX_TPLG_OBJECT_SIZE] = {0};
-	struct sof_ipc_comp *comp = (struct sof_ipc_comp *)tplg_object;
-	struct sof_ipc_comp_src *src;
-	int ret = 0;
-
-	ret = tplg_new_src(ctx, comp, MAX_TPLG_OBJECT_SIZE, NULL, 0);
-	if (ret < 0)
-		return ret;
-
-	src = (struct sof_ipc_comp_src *)comp;
+	struct ipc4_copier_module_cfg *copier = calloc(sizeof(struct ipc4_copier_module_cfg), 1);
+	struct tplg_context *ctx = &tb->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
+	int ret;
 
-	/* set testbench input and output sample rate from topology */
-	if (!tp->fs_out)
-		tp->fs_out = src->sink_rate;
-	else
-		src->sink_rate = tp->fs_out;
+	if (!copier)
+		return -ENOMEM;
 
-	if (!tp->fs_in)
-		tp->fs_in = src->source_rate;
-	else
-		src->source_rate = tp->fs_in;
+	comp_info->ipc_payload = copier;
 
-	/* load src component */
-	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(comp)) < 0) {
-		fprintf(stderr, "error: new src comp\n");
-		return -EINVAL;
+	ret = tplg_new_buffer(ctx, copier, sizeof(copier), NULL, 0);
+	if (ret < 0) {
+		fprintf(stderr, "error: failed to create pipeline\n");
+		free(copier);
 	}
 
 	return ret;
 }
 
-/* load fileread component */
-static int tb_new_fileread(struct tplg_context *ctx,
-			   struct sof_ipc_comp_file *fileread)
+static int tb_register_graph(struct testbench_prm *tb, int count)
 {
-	struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0];
-	size_t total_array_size = 0;
-	int size = ctx->widget->priv.size;
-	int comp_id = ctx->comp_id;
-	char uuid[UUID_SIZE];
-	int ret;
-
-	/* read vendor tokens */
-	while (total_array_size < size) {
-		if (!tplg_is_valid_priv_size(total_array_size, size, array)) {
-			fprintf(stderr, "error: filewrite array size mismatch for widget size %d\n",
-				size);
-			return -EINVAL;
-		}
-
-		/* parse comp tokens */
-		ret = sof_parse_tokens(&fileread->config, comp_tokens,
-				       ARRAY_SIZE(comp_tokens), array,
-				       array->size);
-		if (ret != 0) {
-			fprintf(stderr, "error: parse comp tokens %d\n",
-				size);
-			return -EINVAL;
-		}
-
-		/* parse uuid token */
-		ret = sof_parse_tokens(uuid, comp_ext_tokens,
-				       ARRAY_SIZE(comp_ext_tokens), array,
-				       array->size);
-		if (ret != 0) {
-			fprintf(stderr, "error: parse mixer uuid token %d\n", size);
-			return -EINVAL;
-		}
+	struct tplg_context *ctx = &tb->tplg;
+	int ret = 0;
+	int i;
 
-		total_array_size += array->size;
-		array = MOVE_POINTER_BY_BYTES(array, array->size);
+	for (i = 0; i < count; i++) {
+		ret = tplg_parse_graph(ctx, &tb->widget_list, &tb->route_list);
+		if (ret < 0)
+			return ret;
 	}
 
-	/* configure fileread */
-	fileread->mode = FILE_READ;
-	fileread->comp.id = comp_id;
-
-	/* use fileread comp as scheduling comp */
-	fileread->comp.core = ctx->core_id;
-	fileread->comp.hdr.size = sizeof(struct sof_ipc_comp_file);
-	fileread->comp.type = SOF_COMP_FILEREAD;
-	fileread->comp.pipeline_id = ctx->pipeline_id;
-	fileread->config.hdr.size = sizeof(struct sof_ipc_comp_config);
-	return 0;
+	return ret;
 }
 
-/* load filewrite component */
-static int tb_new_filewrite(struct tplg_context *ctx,
-			    struct sof_ipc_comp_file *filewrite)
+static int tb_parse_pcm(struct testbench_prm *tb, int count)
 {
-	struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0];
-	size_t total_array_size = 0;
-	int size = ctx->widget->priv.size;
-	int comp_id = ctx->comp_id;
-	char uuid[UUID_SIZE];
-	int ret;
-
-	/* read vendor tokens */
-	while (total_array_size < size) {
-		if (!tplg_is_valid_priv_size(total_array_size, size, array)) {
-			fprintf(stderr, "error: filewrite array size mismatch\n");
-			return -EINVAL;
-		}
+	struct tplg_context *ctx = &tb->tplg;
+	int ret, i;
 
-		ret = sof_parse_tokens(&filewrite->config, comp_tokens,
-				       ARRAY_SIZE(comp_tokens), array,
-				       array->size);
-		if (ret != 0) {
-			fprintf(stderr, "error: parse filewrite tokens %d\n",
-				size);
-			return -EINVAL;
-		}
-
-		/* parse uuid token */
-		ret = sof_parse_tokens(uuid, comp_ext_tokens,
-				       ARRAY_SIZE(comp_ext_tokens), array,
-				       array->size);
-		if (ret != 0) {
-			fprintf(stderr, "error: parse mixer uuid token %d\n", size);
-			return -EINVAL;
-		}
-
-		total_array_size += array->size;
-		array = MOVE_POINTER_BY_BYTES(array, array->size);
+	for (i = 0; i < count; i++) {
+		ret = tplg_parse_pcm(ctx, &tb->widget_list, &tb->pcm_list);
+		if (ret < 0)
+			return ret;
 	}
 
-	/* configure filewrite */
-	filewrite->comp.core = ctx->core_id;
-	filewrite->comp.id = comp_id;
-	filewrite->mode = FILE_WRITE;
-	filewrite->comp.hdr.size = sizeof(struct sof_ipc_comp_file);
-	filewrite->comp.type = SOF_COMP_FILEWRITE;
-	filewrite->comp.pipeline_id = ctx->pipeline_id;
-	filewrite->config.hdr.size = sizeof(struct sof_ipc_comp_config);
 	return 0;
 }
 
-/* load fileread component */
-static int tb_register_fileread(struct testbench_prm *tp,
-				struct tplg_context *ctx, int dir)
+/*
+ * create a list with all widget info
+ * containing mapping between component names and ids
+ * which will be used for setting up component connections
+ */
+static inline int tb_insert_comp(struct testbench_prm *tp)
 {
-	struct sof *sof = ctx->sof;
-	struct sof_ipc_comp_file fileread = {{{0}}};
+	struct tplg_context *ctx = &tp->tplg;
+	struct tplg_comp_info *comp_info;
+	int comp_id = ctx->comp_id;
 	int ret;
 
-	fileread.config.frame_fmt = tplg_find_format(tp->bits_in);
+	if (ctx->widget->id == SND_SOC_TPLG_DAPM_SCHEDULER)
+		return 0;
 
-	ret = tb_new_fileread(ctx, &fileread);
-	if (ret < 0)
-		return ret;
+	comp_info = calloc(sizeof(struct tplg_comp_info), 1);
+	if (!comp_info)
+		return -ENOMEM;
 
-	/* configure fileread */
-	fileread.fn = strdup(tp->input_file[tp->input_file_index]);
-	if (tp->input_file_index == 0)
-		tp->fr_id = ctx->comp_id;
-
-	/* use fileread comp as scheduling comp */
-	ctx->sched_id = ctx->comp_id;
-	tp->input_file_index++;
-
-	/* Set format from testbench command line*/
-	fileread.rate = tp->fs_in;
-	fileread.channels = tp->channels_in;
-	fileread.frame_fmt = tp->frame_fmt;
-	fileread.direction = dir;
-
-	/* Set type depending on direction */
-	fileread.comp.type = (dir == SOF_IPC_STREAM_PLAYBACK) ?
-		SOF_COMP_HOST : SOF_COMP_DAI;
-
-	/* create fileread component */
-	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(&fileread)) < 0) {
-		fprintf(stderr, "error: file read\n");
-		free(fileread.fn);
-		return -EINVAL;
+	comp_info->name = strdup(ctx->widget->name);
+	if (!comp_info->name) {
+		ret = -ENOMEM;
+		goto err;
 	}
 
-	free(fileread.fn);
-	return 0;
-}
-
-/* load filewrite component */
-static int tb_register_filewrite(struct testbench_prm *tp,
-				 struct tplg_context *ctx, int dir)
-{
-	struct sof *sof = ctx->sof;
-	struct sof_ipc_comp_file filewrite = {{{0}}};
-	int ret;
-
-	ret = tb_new_filewrite(ctx, &filewrite);
-	if (ret < 0)
-		return ret;
-
-	/* configure filewrite (multiple output files are supported.) */
-	if (!tp->output_file[tp->output_file_index]) {
-		fprintf(stderr, "error: output[%d] file name is null\n",
-			tp->output_file_index);
-		return -EINVAL;
-	}
-	filewrite.fn = strdup(tp->output_file[tp->output_file_index]);
-	if (tp->output_file_index == 0)
-		tp->fw_id = ctx->comp_id;
-	tp->output_file_index++;
-
-	/* Set format from testbench command line*/
-	filewrite.rate = tp->fs_out;
-	filewrite.channels = tp->channels_out;
-	filewrite.frame_fmt = tp->frame_fmt;
-	filewrite.direction = dir;
-
-	/* Set type depending on direction */
-	filewrite.comp.type = (dir == SOF_IPC_STREAM_PLAYBACK) ?
-		SOF_COMP_DAI : SOF_COMP_HOST;
-
-	/* create filewrite component */
-	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(&filewrite)) < 0) {
-		fprintf(stderr, "error: new file write\n");
-		free(filewrite.fn);
-		return -EINVAL;
+	comp_info->stream_name = strdup(ctx->widget->sname);
+	if (!comp_info->stream_name) {
+		ret = -ENOMEM;
+		goto sname_err;
 	}
 
-	free(filewrite.fn);
-	return 0;
-}
-
-static int tb_register_aif_in_out(struct testbench_prm *tb,
-				  struct tplg_context *ctx, int dir)
-{
-	if (dir == SOF_IPC_STREAM_PLAYBACK)
-		return tb_register_fileread(tb, ctx, dir);
-	else
-		return tb_register_filewrite(tb, ctx, dir);
-}
-
-static int tb_register_dai_in_out(struct testbench_prm *tb,
-				  struct tplg_context *ctx, int dir)
-{
-	if (dir == SOF_IPC_STREAM_PLAYBACK)
-		return tb_register_filewrite(tb, ctx, dir);
-	else
-		return tb_register_fileread(tb, ctx, dir);
-}
-
-/*
- * create a list with all widget info
- * containing mapping between component names and ids
- * which will be used for setting up component connections
- */
-static inline int tb_insert_comp(struct testbench_prm *tb, struct tplg_context *ctx)
-{
-	struct tplg_comp_info *temp_comp_list = tb->info;
-	int comp_index = tb->info_index;
-	int comp_id = ctx->comp_id;
+	comp_info->id = comp_id;
+	comp_info->type = ctx->widget->id;
+	comp_info->pipeline_id = ctx->pipeline_id;
+	ctx->current_comp_info = comp_info;
 
-	/* mapping should be empty */
-	if (temp_comp_list[comp_index].name) {
-		fprintf(stderr, "comp index %d already in use with %d:%s cant insert %d:%s\n",
-			comp_index,
-			temp_comp_list[comp_index].id, temp_comp_list[comp_index].name,
-			ctx->widget->id, ctx->widget->name);
-		return -EINVAL;
+	// TODO IPC3
+	if (ctx->ipc_major == 4) {
+		ret = tb_parse_ipc4_comp_tokens(tp, &comp_info->basecfg);
+		if (ret < 0)
+			goto sname_err;
 	}
 
-	temp_comp_list[comp_index].id = comp_id;
-	temp_comp_list[comp_index].name = ctx->widget->name;
-	temp_comp_list[comp_index].type = ctx->widget->id;
-	temp_comp_list[comp_index].pipeline_id = ctx->pipeline_id;
+	list_item_append(&comp_info->item, &tp->widget_list);
 
-	printf("debug: loading idx %d comp_id %d: widget %s type %d size %d at offset %ld\n",
-	       comp_index, comp_id, ctx->widget->name, ctx->widget->id, ctx->widget->size,
-	       ctx->tplg_offset);
+	printf("debug: loading comp_id %d: widget %s type %d size %d at offset %ld is_pages %d\n",
+	       comp_id, ctx->widget->name, ctx->widget->id, ctx->widget->size,
+	       ctx->tplg_offset, comp_info->basecfg.is_pages);
 
 	return 0;
+
+sname_err:
+	free(comp_info->name);
+
+err:
+	free(comp_info);
+	return ret;
 }
 
 /* load dapm widget */
-static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx)
+static int tb_load_widget(struct testbench_prm *tb)
 {
-	struct tplg_comp_info *temp_comp_list = tb->info;
-	int comp_id = ctx->comp_id;
+	struct tplg_context *ctx = &tb->tplg;
 	int ret = 0;
 
 	/* get next widget */
 	ctx->widget = tplg_get_widget(ctx);
 	ctx->widget_size = ctx->widget->size;
 
-	if (!temp_comp_list) {
-		fprintf(stderr, "load_widget: temp_comp_list argument NULL\n");
-		return -EINVAL;
-	}
-
 	/* insert widget into mapping */
-	ret = tb_insert_comp(tb, ctx);
+	ret = tb_insert_comp(tb);
 	if (ret < 0) {
-		fprintf(stderr, "plug_load_widget: invalid widget index\n");
+		fprintf(stderr, "tb_load_widget: invalid widget index\n");
 		return ret;
 	}
 
-	printf("debug: loading comp_id %d: widget %s id %d\n",
-	       comp_id, ctx->widget->name, ctx->widget->id);
-
 	/* load widget based on type */
-	switch (ctx->widget->id) {
+	switch (tb->tplg.widget->id) {
 	/* load pga widget */
 	case SND_SOC_TPLG_DAPM_PGA:
-		if (tb_register_pga(tb, ctx) < 0) {
+		if (tb_new_pga(tb) < 0) {
 			fprintf(stderr, "error: load pga\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_AIF_IN:
-		if (tb_register_aif_in_out(tb, ctx, SOF_IPC_STREAM_PLAYBACK) < 0) {
+		if (tb_new_aif_in_out(tb, SOF_IPC_STREAM_PLAYBACK) < 0) {
 			fprintf(stderr, "error: load AIF IN failed\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_AIF_OUT:
-		if (tb_register_aif_in_out(tb, ctx, SOF_IPC_STREAM_CAPTURE) < 0) {
+		if (tb_new_aif_in_out(tb, SOF_IPC_STREAM_CAPTURE) < 0) {
 			fprintf(stderr, "error: load AIF OUT failed\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_DAI_IN:
-		if (tb_register_dai_in_out(tb, ctx, SOF_IPC_STREAM_PLAYBACK) < 0) {
+		if (tb_new_dai_in_out(tb, SOF_IPC_STREAM_PLAYBACK) < 0) {
 			fprintf(stderr, "error: load filewrite\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_DAI_OUT:
-		if (tb_register_dai_in_out(tb, ctx, SOF_IPC_STREAM_CAPTURE) < 0) {
+		if (tb_new_dai_in_out(tb, SOF_IPC_STREAM_CAPTURE) < 0) {
 			fprintf(stderr, "error: load filewrite\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_BUFFER:
-		if (tb_register_buffer(tb, ctx) < 0) {
+		if (tb_new_buffer(tb) < 0) {
 			fprintf(stderr, "error: load buffer\n");
 			ret = -EINVAL;
 			goto exit;
@@ -556,7 +323,7 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx)
 		break;
 
 	case SND_SOC_TPLG_DAPM_SCHEDULER:
-		if (tb_register_pipeline(tb, ctx) < 0) {
+		if (tb_new_pipeline(tb) < 0) {
 			fprintf(stderr, "error: load pipeline\n");
 			ret = -EINVAL;
 			goto exit;
@@ -564,33 +331,34 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx)
 		break;
 
 	case SND_SOC_TPLG_DAPM_SRC:
-		if (tb_register_src(tb, ctx) < 0) {
+		if (tb_new_src(tb) < 0) {
 			fprintf(stderr, "error: load src\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_ASRC:
-		if (tb_register_asrc(tb, ctx) < 0) {
+		if (tb_new_asrc(tb) < 0) {
 			fprintf(stderr, "error: load src\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_MIXER:
-		if (tb_register_mixer(tb, ctx) < 0) {
+		if (tb_new_mixer(tb) < 0) {
 			fprintf(stderr, "error: load mixer\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
 	case SND_SOC_TPLG_DAPM_EFFECT:
-		if (tb_register_process(tb, ctx) < 0) {
+		if (tb_new_process(tb) < 0) {
 			fprintf(stderr, "error: load effect\n");
 			ret = -EINVAL;
 			goto exit;
 		}
 		break;
+
 	/* unsupported widgets */
 	default:
 		printf("info: Widget %s id %d unsupported and skipped: size %d priv size %d\n",
@@ -606,16 +374,15 @@ static int tb_load_widget(struct testbench_prm *tb, struct tplg_context *ctx)
 }
 
 /* parse topology file and set up pipeline */
-int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx)
+int tb_parse_topology(struct testbench_prm *tb)
 
 {
+	struct tplg_context *ctx = &tb->tplg;
 	struct snd_soc_tplg_hdr *hdr;
-	struct tplg_comp_info *comp_list_realloc = NULL;
-	char pipeline_string[256] = {0};
+	struct list_item *item;
 	int i;
 	int ret = 0;
 	FILE *file;
-	size_t size;
 
 	/* open topology file */
 	file = fopen(ctx->tplg_file, "rb");
@@ -650,21 +417,26 @@ int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx)
 	}
 	ret = fread(ctx->tplg_base, ctx->tplg_size, 1, file);
 	if (ret != 1) {
-		fprintf(stderr, "error: can't read topology: %s\n",
-			strerror(errno));
+		fprintf(stderr, "error: can't read topology: %s\n", strerror(errno));
 		free(ctx->tplg_base);
 		fclose(file);
 		return -errno;
 	}
 	fclose(file);
 
+	/* initialize widget, route, pipeline and pcm lists */
+	list_init(&tb->widget_list);
+	list_init(&tb->route_list);
+	list_init(&tb->pcm_list);
+	list_init(&tb->pipeline_list);
+
 	while (ctx->tplg_offset < ctx->tplg_size) {
 
 		/* read next topology header */
 		hdr = tplg_get_hdr(ctx);
 
-		fprintf(stdout, "type: %x, size: 0x%x count: %d index: %d\n",
-			hdr->type, hdr->payload_size, hdr->count, hdr->index);
+		tplg_debug("type: %x, size: 0x%x count: %d index: %d\n",
+			   hdr->type, hdr->payload_size, hdr->count, hdr->index);
 
 		ctx->hdr = hdr;
 
@@ -673,62 +445,513 @@ int tb_parse_topology(struct testbench_prm *tb, struct tplg_context *ctx)
 		/* load dapm widget */
 		case SND_SOC_TPLG_TYPE_DAPM_WIDGET:
 
-			fprintf(stdout, "number of DAPM widgets %d\n",
-				hdr->count);
+			tplg_debug("number of DAPM widgets %d\n", hdr->count);
 
 			/* update max pipeline_id */
 			ctx->pipeline_id = hdr->index;
 
-			tb->info_elems += hdr->count;
-			size = sizeof(struct tplg_comp_info) * tb->info_elems;
-			comp_list_realloc = (struct tplg_comp_info *)
-					 realloc(tb->info, size);
-
-			if (!comp_list_realloc && size) {
-				fprintf(stderr, "error: mem realloc\n");
-				ret = -errno;
-				goto out;
-			}
-			tb->info = comp_list_realloc;
-
-			for (i = (tb->info_elems - hdr->count); i < tb->info_elems; i++)
-				tb->info[i].name = NULL;
-
-			for (tb->info_index = (tb->info_elems - hdr->count);
-			     tb->info_index < tb->info_elems;
-			     tb->info_index++) {
-				ret = tb_load_widget(tb, ctx);
+			for (i = 0; i < hdr->count; i++) {
+				ret = tb_load_widget(tb);
 				if (ret < 0) {
-					printf("error: loading widget\n");
-					goto out;
-				} else if (ret > 0)
-					ctx->comp_id++;
+					fprintf(stderr, "error: loading widget\n");
+					return ret;
+				}
+				ctx->comp_id++;
 			}
 			break;
 
 		/* set up component connections from pipeline graph */
 		case SND_SOC_TPLG_TYPE_DAPM_GRAPH:
-			if (tb_register_graph(ctx, tb->info,
-					      pipeline_string,
-					      tb->info_elems,
-					      hdr->count,
-					      hdr->index) < 0) {
+			if (tb_register_graph(tb, hdr->count) < 0) {
 				fprintf(stderr, "error: pipeline graph\n");
 				ret = -EINVAL;
 				goto out;
 			}
 			break;
 
+		case SND_SOC_TPLG_TYPE_PCM:
+			ret = tb_parse_pcm(tb, hdr->count);
+			if (ret < 0)
+				goto out;
+			break;
+
 		default:
 			tplg_skip_hdr_payload(ctx);
 			break;
 		}
 	}
 
+	/* assign pipeline to every widget in the widget list */
+	list_for_item(item, &tb->widget_list) {
+		struct tplg_comp_info *comp_info = container_of(item, struct tplg_comp_info, item);
+		struct list_item *pipe_item;
+
+		list_for_item(pipe_item, &tb->pipeline_list) {
+			struct tplg_pipeline_info *pipe_info;
+
+			pipe_info = container_of(pipe_item, struct tplg_pipeline_info, item);
+			if (pipe_info->id == comp_info->pipeline_id) {
+				comp_info->pipe_info = pipe_info;
+				break;
+			}
+		}
+
+		if (!comp_info->pipe_info) {
+			fprintf(stderr, "warning: failed  assigning pipeline for %s\n",
+				comp_info->name);
+		}
+	}
+
 out:
 	/* free all data */
-	free(tb->info);
-	free(ctx->tplg_base);
+
+	// TODO: Check this
+	// free(ctx->tplg_base);
 	return ret;
 }
 
+static int tb_prepare_widget(struct testbench_prm *tb, struct tplg_pcm_info *pcm_info,
+			     struct tplg_comp_info *comp_info, int dir)
+{
+	struct tplg_pipeline_list *pipeline_list;
+	int ret, i;
+
+	if (dir)
+		pipeline_list = &pcm_info->capture_pipeline_list;
+	else
+		pipeline_list = &pcm_info->playback_pipeline_list;
+
+	/* populate base config */
+	ret = tb_set_up_widget_base_config(tb, comp_info);
+	if (ret < 0)
+		return ret;
+
+	tb_pipeline_update_resource_usage(tb, comp_info);
+
+	/* add pipeline to pcm pipeline_list if needed */
+	for (i = 0; i < pipeline_list->count; i++) {
+		struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i];
+
+		if (pipe_info == comp_info->pipe_info)
+			break;
+	}
+
+	if (i == pipeline_list->count) {
+		pipeline_list->pipelines[pipeline_list->count] = comp_info->pipe_info;
+		pipeline_list->count++;
+	}
+
+	tplg_debug("widget %s prepared\n", comp_info->name);
+	return 0;
+}
+
+static int tb_prepare_widgets(struct testbench_prm *tb, struct tplg_pcm_info *pcm_info,
+			      struct tplg_comp_info *starting_comp_info,
+			      struct tplg_comp_info *current_comp_info)
+{
+	struct list_item *item;
+	int ret;
+
+	/* for playback */
+	list_for_item(item, &tb->route_list) {
+		struct tplg_route_info *route_info = container_of(item, struct tplg_route_info,
+								  item);
+
+		if (route_info->source != current_comp_info)
+			continue;
+
+		/* set up source widget if it is the starting widget */
+		if (starting_comp_info == current_comp_info) {
+			ret = tb_prepare_widget(tb, pcm_info, current_comp_info, 0);
+			if (ret < 0)
+				return ret;
+		}
+
+		/* set up the sink widget */
+		ret = tb_prepare_widget(tb, pcm_info, route_info->sink, 0);
+		if (ret < 0)
+			return ret;
+
+		/* and then continue down the path */
+		if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN ||
+		    route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) {
+			ret = tb_prepare_widgets(tb, pcm_info, starting_comp_info,
+						 route_info->sink);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int tb_prepare_widgets_capture(struct testbench_prm *tb, struct tplg_pcm_info *pcm_info,
+				      struct tplg_comp_info *starting_comp_info,
+				      struct tplg_comp_info *current_comp_info)
+{
+	struct list_item *item;
+	int ret;
+
+	/* for capture */
+	list_for_item(item, &tb->route_list) {
+		struct tplg_route_info *route_info = container_of(item, struct tplg_route_info,
+								  item);
+
+		if (route_info->sink != current_comp_info)
+			continue;
+
+		/* set up sink widget if it is the starting widget */
+		if (starting_comp_info == current_comp_info) {
+			ret = tb_prepare_widget(tb, pcm_info, current_comp_info, 1);
+			if (ret < 0)
+				return ret;
+		}
+
+		/* set up the source widget */
+		ret = tb_prepare_widget(tb, pcm_info, route_info->source, 1);
+		if (ret < 0)
+			return ret;
+
+		/* and then continue up the path */
+		if (route_info->source->type != SND_SOC_TPLG_DAPM_DAI_IN &&
+		    route_info->source->type != SND_SOC_TPLG_DAPM_DAI_OUT) {
+			ret = tb_prepare_widgets_capture(tb, pcm_info, starting_comp_info,
+							 route_info->source);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+
+static int tb_set_up_widget(struct testbench_prm *tb, struct tplg_comp_info *comp_info)
+{
+	struct tplg_pipeline_info *pipe_info = comp_info->pipe_info;
+	int ret;
+
+	pipe_info->usage_count++;
+
+	/* first set up pipeline if needed, only done once for the first pipeline widget */
+	if (pipe_info->usage_count == 1) {
+		ret = tb_set_up_pipeline(tb, pipe_info);
+		if (ret < 0) {
+			pipe_info->usage_count--;
+			return ret;
+		}
+	}
+
+	/* now set up the widget */
+	ret = tb_set_up_widget_ipc(tb, comp_info);
+	if (ret < 0)
+		return ret;
+
+	tplg_debug("widget %s set up\n", comp_info->name);
+
+	return 0;
+}
+
+static int tb_set_up_widgets(struct testbench_prm *tb, struct tplg_comp_info *starting_comp_info,
+			     struct tplg_comp_info *current_comp_info)
+{
+	struct list_item *item;
+	int ret;
+
+	/* for playback */
+	list_for_item(item, &tb->route_list) {
+		struct tplg_route_info *route_info = container_of(item, struct tplg_route_info,
+								  item);
+
+		if (route_info->source != current_comp_info)
+			continue;
+
+		/* set up source widget if it is the starting widget */
+		if (starting_comp_info == current_comp_info) {
+			ret = tb_set_up_widget(tb, current_comp_info);
+			if (ret < 0)
+				return ret;
+		}
+
+		/* set up the sink widget */
+		ret = tb_set_up_widget(tb, route_info->sink);
+		if (ret < 0)
+			return ret;
+
+		/* source and sink widgets are up, so set up route now */
+		ret = tb_set_up_route(tb, route_info);
+		if (ret < 0)
+			return ret;
+
+		/* and then continue down the path */
+		if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN ||
+		    route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) {
+			ret = tb_set_up_widgets(tb, starting_comp_info, route_info->sink);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int tb_set_up_widgets_capture(struct testbench_prm *tb,
+				     struct tplg_comp_info *starting_comp_info,
+				     struct tplg_comp_info *current_comp_info)
+{
+	struct list_item *item;
+	int ret;
+
+	/* for playback */
+	list_for_item(item, &tb->route_list) {
+		struct tplg_route_info *route_info = container_of(item, struct tplg_route_info,
+								  item);
+
+		if (route_info->sink != current_comp_info)
+			continue;
+
+		/* set up source widget if it is the starting widget */
+		if (starting_comp_info == current_comp_info) {
+			ret = tb_set_up_widget(tb, current_comp_info);
+			if (ret < 0)
+				return ret;
+		}
+
+		/* set up the sink widget */
+		ret = tb_set_up_widget(tb, route_info->source);
+		if (ret < 0)
+			return ret;
+
+		/* source and sink widgets are up, so set up route now */
+		ret = tb_set_up_route(tb, route_info);
+		if (ret < 0)
+			return ret;
+
+		/* and then continue down the path */
+		if (route_info->source->type != SND_SOC_TPLG_DAPM_DAI_IN &&
+		    route_info->source->type != SND_SOC_TPLG_DAPM_DAI_OUT) {
+			ret = tb_set_up_widgets_capture(tb, starting_comp_info, route_info->source);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+int tb_set_up_pipelines(struct testbench_prm *tb, int dir)
+{
+	struct tplg_comp_info *host = NULL;
+	struct tplg_pcm_info *pcm_info;
+	struct list_item *item;
+	int ret;
+
+	// TODO tb->pcm_id is not defined?
+	list_for_item(item, &tb->pcm_list) {
+		pcm_info = container_of(item, struct tplg_pcm_info, item);
+
+		if (pcm_info->id == tb->pcm_id) {
+			if (dir)
+				host = pcm_info->capture_host;
+			else
+				host = pcm_info->playback_host;
+			break;
+		}
+	}
+
+	if (!host) {
+		fprintf(stderr, "No host component found for PCM ID: %d\n", tb->pcm_id);
+		return -EINVAL;
+	}
+
+	if (!tb_is_pipeline_enabled(tb, host->pipeline_id))
+		return 0;
+
+	tb->pcm_info = pcm_info; //  TODO must be an array
+
+	if (dir) {
+		ret = tb_prepare_widgets_capture(tb, pcm_info, host, host);
+		if (ret < 0)
+			return ret;
+
+		ret = tb_set_up_widgets_capture(tb, host, host);
+		if (ret < 0)
+			return ret;
+
+		tplg_debug("Setting up capture pipelines complete\n");
+
+		return 0;
+	}
+
+	ret = tb_prepare_widgets(tb, pcm_info, host, host);
+	if (ret < 0)
+		return ret;
+
+	ret = tb_set_up_widgets(tb, host, host);
+	if (ret < 0)
+		return ret;
+
+	tplg_debug("Setting up playback pipelines complete\n");
+
+	return 0;
+}
+
+static int tb_free_widgets(struct testbench_prm *tb, struct tplg_comp_info *starting_comp_info,
+			   struct tplg_comp_info *current_comp_info)
+{
+	struct tplg_route_info *route_info;
+	struct list_item *item;
+	int ret;
+
+	/* for playback */
+	list_for_item(item, &tb->route_list) {
+		route_info = container_of(item, struct tplg_route_info, item);
+		if (route_info->source != current_comp_info)
+			continue;
+
+		/* Widgets will be freed when the pipeline is deleted, so just unbind modules */
+		ret = tb_free_route(tb, route_info);
+		if (ret < 0)
+			return ret;
+
+		/* and then continue down the path */
+		if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN ||
+		    route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) {
+			ret = tb_free_widgets(tb, starting_comp_info, route_info->sink);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int tb_free_widgets_capture(struct testbench_prm *tb,
+				   struct tplg_comp_info *starting_comp_info,
+				   struct tplg_comp_info *current_comp_info)
+{
+	struct tplg_route_info *route_info;
+	struct list_item *item;
+	int ret;
+
+	/* for playback */
+	list_for_item(item, &tb->route_list) {
+		route_info = container_of(item, struct tplg_route_info, item);
+		if (route_info->sink != current_comp_info)
+			continue;
+
+		/* Widgets will be freed when the pipeline is deleted, so just unbind modules */
+		ret = tb_free_route(tb, route_info);
+		if (ret < 0)
+			return ret;
+
+		/* and then continue down the path */
+		if (route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_IN &&
+		    route_info->sink->type != SND_SOC_TPLG_DAPM_DAI_OUT) {
+			ret = tb_free_widgets_capture(tb, starting_comp_info, route_info->source);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+int tb_free_pipelines(struct testbench_prm *tb, int dir)
+{
+	struct tplg_pipeline_list *pipeline_list;
+	struct tplg_pcm_info *pcm_info;
+	struct list_item *item;
+	struct tplg_comp_info *host = NULL;
+	int ret, i;
+
+	list_for_item(item, &tb->pcm_list) {
+		pcm_info = container_of(item, struct tplg_pcm_info, item);
+		if (dir)
+			host = pcm_info->capture_host;
+		else
+			host = pcm_info->playback_host;
+
+		if (!host || !tb_is_pipeline_enabled(tb, host->pipeline_id))
+			continue;
+
+		if (dir) {
+			pipeline_list = &tb->pcm_info->capture_pipeline_list;
+			ret = tb_free_widgets_capture(tb, host, host);
+			if (ret < 0) {
+				fprintf(stderr, "failed to free widgets for capture PCM\n");
+				return ret;
+			}
+		} else {
+			pipeline_list = &tb->pcm_info->playback_pipeline_list;
+			ret = tb_free_widgets(tb, host, host);
+			if (ret < 0) {
+				fprintf(stderr, "failed to free widgets for playback PCM\n");
+				return ret;
+			}
+		}
+		for (i = 0; i < pipeline_list->count; i++) {
+			struct tplg_pipeline_info *pipe_info = pipeline_list->pipelines[i];
+
+			ret = tb_delete_pipeline(tb, pipe_info);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	tb->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER] = 0;
+	return 0;
+}
+
+int tb_free_all_pipelines(struct testbench_prm *tb)
+{
+	debug_print("freeing playback direction\n");
+	tb_free_pipelines(tb, SOF_IPC_STREAM_PLAYBACK);
+
+	debug_print("freeing capture direction\n");
+	tb_free_pipelines(tb, SOF_IPC_STREAM_CAPTURE);
+	return 0;
+}
+
+void tb_free_topology(struct testbench_prm *tb)
+{
+	struct tplg_pcm_info *pcm_info;
+	struct tplg_comp_info *comp_info;
+	struct tplg_route_info *route_info;
+	struct tplg_pipeline_info *pipe_info;
+	struct tplg_context *ctx = &tb->tplg;
+	struct sof_ipc4_available_audio_format *available_fmts;
+	struct list_item *item, *_item;
+
+	list_for_item_safe(item, _item, &tb->pcm_list) {
+		pcm_info = container_of(item, struct tplg_pcm_info, item);
+		free(pcm_info->name);
+		free(pcm_info);
+	}
+
+	list_for_item_safe(item, _item, &tb->widget_list) {
+		comp_info = container_of(item, struct tplg_comp_info, item);
+		available_fmts = &comp_info->available_fmt;
+		free(available_fmts->output_pin_fmts);
+		free(available_fmts->input_pin_fmts);
+		free(comp_info->name);
+		free(comp_info->stream_name);
+		free(comp_info->ipc_payload);
+		free(comp_info);
+	}
+
+	list_for_item_safe(item, _item, &tb->route_list) {
+		route_info = container_of(item, struct tplg_route_info, item);
+		free(route_info);
+	}
+
+	list_for_item_safe(item, _item, &tb->pipeline_list) {
+		pipe_info = container_of(item, struct tplg_pipeline_info, item);
+		free(pipe_info->name);
+		free(pipe_info);
+	}
+
+	// TODO: Do here or earlier?
+	free(ctx->tplg_base);
+	tplg_debug("freed all pipelines, widgets, routes and pcms\n");
+}
diff --git a/tools/testbench/topology_ipc3.c b/tools/testbench/topology_ipc3.c
new file mode 100644
index 000000000000..5f80b696aea8
--- /dev/null
+++ b/tools/testbench/topology_ipc3.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// Copyright(c) 2018-2024 Intel Corporation. All rights reserved.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//         Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//	   Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
+
+/* load buffer DAPM widget */
+static int tb_register_buffer(struct testbench_prm *tp)
+{
+	struct tplg_context *ctx = &tp->tplg;
+	struct sof *sof = ctx->sof;
+	struct sof_ipc_buffer buffer = {{{0}}};
+	int ret;
+
+	ret = tplg_new_buffer(ctx, &buffer, sizeof(buffer),
+			      NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	/* create buffer component */
+	if (ipc_buffer_new(sof->ipc, &buffer) < 0) {
+		fprintf(stderr, "error: buffer new\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* load fileread component */
+static int tb_new_fileread(struct testbench_prm *tp, struct sof_ipc_comp_file *fileread)
+{
+	struct tplg_context *ctx = &tp->tplg;
+	struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0];
+	size_t total_array_size = 0;
+	int size = ctx->widget->priv.size;
+	int comp_id = ctx->comp_id;
+	char uuid[UUID_SIZE];
+	int ret;
+
+	/* read vendor tokens */
+	while (total_array_size < size) {
+		if (!tplg_is_valid_priv_size(total_array_size, size, array)) {
+			fprintf(stderr, "error: filewrite array size mismatch for widget size %d\n",
+				size);
+			return -EINVAL;
+		}
+
+		/* parse comp tokens */
+		ret = sof_parse_tokens(&fileread->config, comp_tokens,
+				       ARRAY_SIZE(comp_tokens), array,
+				       array->size);
+		if (ret != 0) {
+			fprintf(stderr, "error: parse comp tokens %d\n",
+				size);
+			return -EINVAL;
+		}
+
+		/* parse uuid token */
+		ret = sof_parse_tokens(uuid, comp_ext_tokens,
+				       ARRAY_SIZE(comp_ext_tokens), array,
+				       array->size);
+		if (ret != 0) {
+			fprintf(stderr, "error: parse mixer uuid token %d\n", size);
+			return -EINVAL;
+		}
+
+		total_array_size += array->size;
+		array = MOVE_POINTER_BY_BYTES(array, array->size);
+	}
+
+	/* configure fileread */
+	fileread->mode = FILE_READ;
+	fileread->comp.id = comp_id;
+
+	/* use fileread comp as scheduling comp */
+	fileread->size = sizeof(struct ipc_comp_file);
+	fileread->comp.core = ctx->core_id;
+	fileread->comp.hdr.size = sizeof(struct sof_ipc_comp_file) + UUID_SIZE;
+	fileread->comp.type = SOF_COMP_FILEREAD;
+	fileread->comp.pipeline_id = ctx->pipeline_id;
+	fileread->config.hdr.size = sizeof(struct sof_ipc_comp_config);
+	fileread->comp.ext_data_length = UUID_SIZE;
+	return 0;
+}
+
+/* load filewrite component */
+static int tb_new_filewrite(struct testbench_prm *tp, struct sof_ipc_comp_file *filewrite)
+{
+	struct tplg_context *ctx = &tp->tplg;
+	struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0];
+	size_t total_array_size = 0;
+	int size = ctx->widget->priv.size;
+	int comp_id = ctx->comp_id;
+	char uuid[UUID_SIZE];
+	int ret;
+
+	/* read vendor tokens */
+	while (total_array_size < size) {
+		if (!tplg_is_valid_priv_size(total_array_size, size, array)) {
+			fprintf(stderr, "error: filewrite array size mismatch\n");
+			return -EINVAL;
+		}
+
+		ret = sof_parse_tokens(&filewrite->config, comp_tokens,
+				       ARRAY_SIZE(comp_tokens), array,
+				       array->size);
+		if (ret != 0) {
+			fprintf(stderr, "error: parse filewrite tokens %d\n",
+				size);
+			return -EINVAL;
+		}
+
+		/* parse uuid token */
+		ret = sof_parse_tokens(uuid, comp_ext_tokens,
+				       ARRAY_SIZE(comp_ext_tokens), array,
+				       array->size);
+		if (ret != 0) {
+			fprintf(stderr, "error: parse mixer uuid token %d\n", size);
+			return -EINVAL;
+		}
+
+		total_array_size += array->size;
+		array = MOVE_POINTER_BY_BYTES(array, array->size);
+	}
+
+	/* configure filewrite */
+	filewrite->comp.core = ctx->core_id;
+	filewrite->comp.id = comp_id;
+	filewrite->mode = FILE_WRITE;
+	filewrite->size = sizeof(struct ipc_comp_file);
+	filewrite->comp.hdr.size = sizeof(struct sof_ipc_comp_file) + UUID_SIZE;
+	filewrite->comp.type = SOF_COMP_FILEWRITE;
+	filewrite->comp.pipeline_id = ctx->pipeline_id;
+	filewrite->config.hdr.size = sizeof(struct sof_ipc_comp_config);
+	filewrite->comp.ext_data_length = UUID_SIZE;
+	return 0;
+}
+
+/* load fileread component */
+static int tb_register_fileread(struct testbench_prm *tp, int dir)
+{
+	struct tplg_context *ctx = &tp->tplg;
+	struct sof *sof = ctx->sof;
+	struct sof_ipc_comp_file *fileread;
+	struct sof_uuid *file_uuid;
+	int ret;
+
+	fileread = calloc(MAX_TPLG_OBJECT_SIZE, 1);
+	if (!fileread)
+		return -ENOMEM;
+
+	fileread->config.frame_fmt = tplg_find_format(tp->bits_in);
+
+	ret = tb_new_fileread(tp, &fileread);
+	if (ret < 0)
+		return ret;
+
+	/* configure fileread */
+	if (!tp->input_file[tp->input_file_index]) {
+		fprintf(stderr,
+			"error: input file [%d] is not defined, add filename to -i f1,f2,...\n",
+			tp->input_file_index);
+		return -EINVAL;
+	}
+
+	fileread.fn = strdup(tp->input_file[tp->input_file_index]);
+	if (tp->input_file_index == 0)
+		tp->fr_id = ctx->comp_id;
+
+	/* use fileread comp as scheduling comp */
+	ctx->sched_id = ctx->comp_id;
+	tp->input_file_index++;
+
+	/* Set format from testbench command line*/
+	fileread->rate = tp->fs_in;
+	fileread->channels = tp->channels_in;
+	fileread->frame_fmt = tp->frame_fmt;
+	fileread->direction = dir;
+
+	file_uuid = (struct sof_uuid *)((uint8_t *)fileread + sizeof(struct sof_ipc_comp_file));
+	file_uuid->a = 0xbfc7488c;
+	file_uuid->b = 0x75aa;
+	file_uuid->c = 0x4ce8;
+	file_uuid->d[0] = 0x9d;
+	file_uuid->d[1] = 0xbe;
+	file_uuid->d[2] = 0xd8;
+	file_uuid->d[3] = 0xda;
+	file_uuid->d[4] = 0x08;
+	file_uuid->d[5] = 0xa6;
+	file_uuid->d[6] = 0x98;
+	file_uuid->d[7] = 0xc2;
+
+	/* create fileread component */
+	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(fileread)) < 0) {
+		fprintf(stderr, "error: file read\n");
+		free(fileread->fn);
+		return -EINVAL;
+	}
+
+	free(fileread->fn);
+	free(fileread);
+	return 0;
+}
+
+/* load filewrite component */
+static int tb_register_filewrite(struct testbench_prm *tp, int dir)
+{
+	struct tplg_context *ctx = &tp->tplg;
+	struct sof *sof = ctx->sof;
+	struct sof_ipc_comp_file *filewrite;
+	struct sof_uuid *file_uuid;
+	int ret;
+
+	filewrite = calloc(MAX_TPLG_OBJECT_SIZE, 1);
+	if (!filewrite)
+		return -ENOMEM;
+
+	ret = tb_new_filewrite(tp, filewrite);
+	if (ret < 0)
+		return ret;
+
+	/* configure filewrite (multiple output files are supported.) */
+	if (!tp->output_file[tp->output_file_index]) {
+		fprintf(stderr,
+			"error: output file [%d] is not defined, add filename to -o f1,f2,..\n",
+			tp->output_file_index);
+		return -EINVAL;
+	}
+	filewrite->fn = strdup(tp->output_file[tp->output_file_index]);
+	if (tp->output_file_index == 0)
+		tp->fw_id = ctx->comp_id;
+	tp->output_file_index++;
+
+	/* Set format from testbench command line*/
+	filewrite->rate = tp->fs_out;
+	filewrite->channels = tp->channels_out;
+	filewrite->frame_fmt = tp->frame_fmt;
+	filewrite->direction = dir;
+
+	file_uuid = (struct sof_uuid *)((uint8_t *)filewrite + sizeof(struct sof_ipc_comp_file));
+	file_uuid->a = 0xbfc7488c;
+	file_uuid->b = 0x75aa;
+	file_uuid->c = 0x4ce8;
+	file_uuid->d[0] = 0x9d;
+	file_uuid->d[1] = 0xbe;
+	file_uuid->d[2] = 0xd8;
+	file_uuid->d[3] = 0xda;
+	file_uuid->d[4] = 0x08;
+	file_uuid->d[5] = 0xa6;
+	file_uuid->d[6] = 0x98;
+	file_uuid->d[7] = 0xc2;
+
+	/* create filewrite component */
+	if (ipc_comp_new(sof->ipc, ipc_to_comp_new(filewrite)) < 0) {
+		fprintf(stderr, "error: new file write\n");
+		free(filewrite->fn);
+		return -EINVAL;
+	}
+
+	free(filewrite->fn);
+	free(filewrite);
+	return 0;
+}
+
diff --git a/tools/testbench/topology_ipc4.c b/tools/testbench/topology_ipc4.c
new file mode 100644
index 000000000000..47680284b610
--- /dev/null
+++ b/tools/testbench/topology_ipc4.c
@@ -0,0 +1,812 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// Copyright(c) 2018-2024 Intel Corporation. All rights reserved.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//         Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//	   Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
+
+#include <ipc4/header.h>
+#include <tplg_parser/tokens.h>
+#include <tplg_parser/topology.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "testbench/common_test.h"
+#include "testbench/topology.h"
+#include "testbench/topology_ipc4.h"
+#include "testbench/file.h"
+#include "testbench/file_ipc4.h"
+#include "ipc4/pipeline.h"
+
+#define SOF_IPC4_FW_PAGE(x) ((((x) + BIT(12) - 1) & ~(BIT(12) - 1)) >> 12)
+#define SOF_IPC4_FW_ROUNDUP(x) (((x) + BIT(6) - 1) & (~(BIT(6) - 1)))
+#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12
+#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448
+#define SOF_IPC4_DATA_QUEUE_OBJECT_SIZE 128
+#define SOF_IPC4_LL_TASK_OBJECT_SIZE 72
+#define SOF_IPC4_LL_TASK_LIST_ITEM_SIZE 12
+#define SOF_IPC4_FW_MAX_QUEUE_COUNT 8
+
+static const struct sof_topology_token ipc4_comp_tokens[] = {
+	{SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, tplg_token_get_uint32_t,
+		offsetof(struct ipc4_base_module_cfg, is_pages)},
+};
+
+/*
+ * IPC
+ */
+
+static int tb_ipc_message(void *mailbox, size_t bytes)
+{
+	struct ipc *ipc = ipc_get();
+
+	/* reply is copied back to mailbox */
+	memcpy(ipc->comp_data, mailbox, bytes);
+	ipc_cmd(mailbox);
+	memcpy(mailbox, ipc->comp_data, bytes);
+
+	return 0;
+}
+
+static int tb_mq_cmd_tx_rx(struct tb_mq_desc *ipc_tx, struct tb_mq_desc *ipc_rx,
+			   void *msg, size_t len, void *reply, size_t rlen)
+{
+	char mailbox[IPC4_MAX_MSG_SIZE];
+	struct ipc4_message_reply *reply_msg = reply;
+
+
+	if (len > IPC4_MAX_MSG_SIZE || rlen > IPC4_MAX_MSG_SIZE) {
+		fprintf(stderr, "ipc: message too big len=%ld rlen=%ld\n", len, rlen);
+		return -EINVAL;
+	}
+
+#if TB_FAKE_IPC
+	reply_msg->primary.r.status = IPC4_SUCCESS;
+#else
+
+	memset(mailbox, 0, IPC4_MAX_MSG_SIZE);
+	memcpy(mailbox, msg, len);
+	tb_ipc_message(mailbox, len);
+
+	memcpy(reply, mailbox, rlen);
+#endif
+
+	if (reply_msg->primary.r.status != IPC4_SUCCESS)
+		return -EINVAL;
+
+	return 0;
+}
+
+int tb_parse_ipc4_comp_tokens(struct testbench_prm *tp,
+				     struct ipc4_base_module_cfg *base_cfg)
+{
+	struct tplg_context *ctx = &tp->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
+	struct snd_soc_tplg_vendor_array *array = &ctx->widget->priv.array[0];
+	int size = ctx->widget->priv.size;
+	int ret;
+
+	ret = sof_parse_token_sets(base_cfg, ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens),
+				   array, size, 1, 0);
+	if (ret < 0)
+		return ret;
+
+	return sof_parse_tokens(&comp_info->uuid, comp_ext_tokens,
+			       ARRAY_SIZE(comp_ext_tokens), array, size);
+}
+
+void tb_setup_widget_ipc_msg(struct tplg_comp_info *comp_info)
+{
+	struct ipc4_module_init_instance *module_init = &comp_info->module_init;
+
+	module_init->primary.r.type = SOF_IPC4_MOD_INIT_INSTANCE;
+	module_init->primary.r.module_id = comp_info->module_id;
+	module_init->primary.r.instance_id = comp_info->instance_id;
+	module_init->primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG;
+	module_init->primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
+}
+
+int tb_set_up_widget_ipc(struct testbench_prm *tb, struct tplg_comp_info *comp_info)
+{
+	struct ipc4_module_init_instance *module_init = &comp_info->module_init;
+	struct ipc4_message_reply reply;
+	void *msg;
+	int size;
+	int ret = 0;
+
+	module_init->extension.r.param_block_size = comp_info->ipc_size >> 2;
+	module_init->extension.r.ppl_instance_id = comp_info->pipe_info->instance_id;
+
+	size = sizeof(*module_init) + comp_info->ipc_size;
+	msg = calloc(size, 1);
+	if (!msg)
+		return -ENOMEM;
+
+	memcpy(msg, module_init, sizeof(*module_init));
+	memcpy(msg + sizeof(*module_init), comp_info->ipc_payload, comp_info->ipc_size);
+
+	// TODO
+	ret = tb_mq_cmd_tx_rx(&tb->ipc_tx, &tb->ipc_rx, msg, size, &reply, sizeof(reply));
+
+	free(msg);
+	if (ret < 0) {
+		fprintf(stderr, "error: can't set up widget %s\n", comp_info->name);
+		return ret;
+	}
+
+	if (reply.primary.r.status != IPC4_SUCCESS) {
+		fprintf(stderr, "widget %s set up failed with status %d\n",
+			comp_info->name, reply.primary.r.status);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int tb_set_up_route(struct testbench_prm *tb, struct tplg_route_info *route_info)
+{
+	struct tplg_comp_info *src_comp_info = route_info->source;
+	struct tplg_comp_info *sink_comp_info = route_info->sink;
+	struct ipc4_module_bind_unbind bu;
+	struct ipc4_message_reply reply;
+	int ret;
+
+	bu.primary.r.module_id = src_comp_info->module_id;
+	bu.primary.r.instance_id = src_comp_info->instance_id;
+	bu.primary.r.type = SOF_IPC4_MOD_BIND;
+	bu.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG;
+	bu.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
+
+	bu.extension.r.dst_module_id = sink_comp_info->module_id;
+	bu.extension.r.dst_instance_id = sink_comp_info->instance_id;
+
+	/* FIXME: assign queue ID for components with multiple inputs/outputs */
+	bu.extension.r.dst_queue = 0;
+	bu.extension.r.src_queue = 0;
+
+	ret = tb_mq_cmd_tx_rx(&tb->ipc_tx, &tb->ipc_rx, &bu, sizeof(bu), &reply, sizeof(reply));
+	if (ret < 0) {
+		fprintf(stderr, "error: can't set up route %s -> %s\n", src_comp_info->name,
+			sink_comp_info->name);
+		return ret;
+	}
+
+	if (reply.primary.r.status != IPC4_SUCCESS) {
+		fprintf(stderr, "route %s -> %s ID set up failed with status %d\n",
+			src_comp_info->name, sink_comp_info->name, reply.primary.r.status);
+		return -EINVAL;
+	}
+
+	tplg_debug("route %s -> %s set up\n", src_comp_info->name, sink_comp_info->name);
+
+	return 0;
+}
+
+int tb_set_up_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info)
+{
+	struct ipc4_pipeline_create msg = {.primary.dat = 0, .extension.dat = 0};
+	struct ipc4_message_reply reply;
+	int ret;
+
+	msg.primary.r.type = SOF_IPC4_GLB_CREATE_PIPELINE;
+	msg.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG;
+	msg.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
+	pipe_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_SCHEDULER]++;
+	msg.primary.r.instance_id = pipe_info->instance_id;
+	msg.primary.r.ppl_mem_size = pipe_info->mem_usage;
+
+	// TODO
+	ret = tb_mq_cmd_tx_rx(&tb->ipc_tx, &tb->ipc_rx, &msg, sizeof(msg), &reply, sizeof(reply));
+	if (ret < 0) {
+		fprintf(stderr, "error: can't set up pipeline %s\n", pipe_info->name);
+		return ret;
+	}
+
+	if (reply.primary.r.status != IPC4_SUCCESS) {
+		fprintf(stderr, "pipeline %s instance ID %d set up failed with status %d\n",
+			pipe_info->name, pipe_info->instance_id, reply.primary.r.status);
+		return -EINVAL;
+	}
+
+	tplg_debug("pipeline %s instance_id %d mem_usage %d set up\n", pipe_info->name,
+		   pipe_info->instance_id, pipe_info->mem_usage);
+
+	return 0;
+}
+
+void tb_pipeline_update_resource_usage(struct testbench_prm *tb,
+					      struct tplg_comp_info *comp_info)
+{
+	struct ipc4_base_module_cfg *base_config = &comp_info->basecfg;
+	struct tplg_pipeline_info *pipe_info = comp_info->pipe_info;
+	int task_mem, queue_mem;
+	int ibs, bss, total;
+
+	ibs = base_config->ibs;
+	bss = base_config->is_pages;
+
+	task_mem = SOF_IPC4_PIPELINE_OBJECT_SIZE;
+	task_mem += SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE + bss;
+
+	/* LL modules */
+	task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_LL_TASK_OBJECT_SIZE);
+	task_mem += SOF_IPC4_FW_MAX_QUEUE_COUNT * SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE;
+	task_mem += SOF_IPC4_LL_TASK_LIST_ITEM_SIZE;
+
+	ibs = SOF_IPC4_FW_ROUNDUP(ibs);
+	queue_mem = SOF_IPC4_FW_MAX_QUEUE_COUNT * (SOF_IPC4_DATA_QUEUE_OBJECT_SIZE +  ibs);
+
+	total = SOF_IPC4_FW_PAGE(task_mem + queue_mem);
+
+	pipe_info->mem_usage += total;
+}
+
+/*
+ * IPC
+ */
+
+int tb_is_single_format(struct sof_ipc4_pin_format *fmts, int num_formats)
+{
+	struct sof_ipc4_pin_format *fmt = &fmts[0];
+	uint32_t _rate, _channels, _valid_bits;
+	int i;
+
+	if (!fmt) {
+		fprintf(stderr, "Error: Null fmt\n");
+		return false;
+	}
+
+	_rate = fmt->audio_fmt.sampling_frequency;
+	_channels = fmt->audio_fmt.fmt_cfg & MASK(7, 0);
+	_valid_bits = (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8;
+	for (i = 1; i < num_formats; i++) {
+		struct sof_ipc4_pin_format *fmt = &fmts[i];
+		uint32_t rate, channels, valid_bits;
+
+		rate = fmt->audio_fmt.sampling_frequency;
+		channels = fmt->audio_fmt.fmt_cfg & MASK(7, 0);
+		valid_bits = (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8;
+		if (rate != _rate || channels != _channels || valid_bits != _valid_bits)
+			return false;
+	}
+
+	return true;
+}
+
+int tb_match_audio_format(struct testbench_prm *tb, struct tplg_comp_info *comp_info,
+				 struct tb_config *config)
+{
+	struct sof_ipc4_available_audio_format *available_fmt = &comp_info->available_fmt;
+	struct ipc4_base_module_cfg *base_cfg = &comp_info->basecfg;
+	struct sof_ipc4_pin_format *fmt;
+	int config_valid_bits;
+	int i;
+
+	switch (config->format) {
+	case SOF_IPC_FRAME_S16_LE:
+		config_valid_bits = 16;
+		break;
+	case SOF_IPC_FRAME_S32_LE:
+		config_valid_bits = 32;
+		break;
+	case SOF_IPC_FRAME_S24_4LE:
+		config_valid_bits = 24;
+		break;
+	default:
+		break;
+	}
+
+	if (tb_is_single_format(available_fmt->input_pin_fmts,
+				available_fmt->num_input_formats)) {
+		fmt = &available_fmt->input_pin_fmts[0];
+		goto out;
+	}
+
+	for (i = 0; i < available_fmt->num_input_formats; i++) {
+		uint32_t rate, channels, valid_bits;
+
+		fmt = &available_fmt->input_pin_fmts[i];
+
+		rate = fmt->audio_fmt.sampling_frequency;
+		channels = fmt->audio_fmt.fmt_cfg & MASK(7, 0);
+		valid_bits = (fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8;
+
+		if (rate == config->rate && channels == config->channels &&
+		    valid_bits == config_valid_bits)
+			break;
+	}
+
+	if (i == available_fmt->num_input_formats) {
+		fprintf(stderr,
+			"Cannot find matching format for rate %d channels %d valid_bits %d for %s\n",
+		       config->rate, config->channels, config_valid_bits, comp_info->name);
+		return -EINVAL;
+	}
+out:
+
+	base_cfg->audio_fmt.sampling_frequency = fmt->audio_fmt.sampling_frequency;
+	base_cfg->audio_fmt.depth = fmt->audio_fmt.bit_depth;
+	base_cfg->audio_fmt.ch_map = fmt->audio_fmt.ch_map;
+	base_cfg->audio_fmt.ch_cfg = fmt->audio_fmt.ch_cfg;
+	base_cfg->audio_fmt.interleaving_style = fmt->audio_fmt.interleaving_style;
+	base_cfg->audio_fmt.channels_count = fmt->audio_fmt.fmt_cfg & MASK(7, 0);
+	base_cfg->audio_fmt.valid_bit_depth =
+		(fmt->audio_fmt.fmt_cfg & MASK(15, 8)) >> 8;
+	base_cfg->audio_fmt.s_type =
+		(fmt->audio_fmt.fmt_cfg & MASK(23, 16)) >> 16;
+	base_cfg->ibs = tb->period_size * 2;
+	base_cfg->obs = tb->period_size * 2;
+
+	return 0;
+}
+
+int tb_set_up_widget_base_config(struct testbench_prm *tb, struct tplg_comp_info *comp_info)
+{
+	char *config_name = "48k2c32b"; // TODO
+	int num_configs = 1; // TODO
+	struct tb_config *config;
+	bool config_found = false;
+	int ret, i;
+
+	for (i = 0; i < num_configs; i++) {
+		config = &tb->config[i];
+
+		if (!strcmp(config->name, config_name)) {
+			config_found = true;
+			break;
+		}
+	}
+
+	if (!config_found) {
+		fprintf(stderr, "unsupported config requested %s\n", config_name);
+		return -ENOTSUP;
+	}
+
+	/* match audio formats and populate base config */
+	ret = tb_match_audio_format(tb, comp_info, config);
+	if (ret < 0)
+		return ret;
+
+	/* copy the basecfg into the ipc payload */
+	memcpy(comp_info->ipc_payload, &comp_info->basecfg, sizeof(struct ipc4_base_module_cfg));
+
+	return 0;
+}
+
+static int tb_pipeline_set_state(struct testbench_prm *tb, int state,
+				 struct ipc4_pipeline_set_state *pipe_state,
+				 struct tplg_pipeline_info *pipe_info,
+				 struct tb_mq_desc *ipc_tx, struct tb_mq_desc *ipc_rx)
+{
+	struct ipc4_message_reply reply = {{ 0 }};
+	int ret;
+
+	pipe_state->primary.r.ppl_id = pipe_info->instance_id;
+
+	ret = tb_mq_cmd_tx_rx(ipc_tx, ipc_rx, pipe_state, sizeof(*pipe_state),
+			      &reply, sizeof(reply));
+	if (ret < 0)
+		fprintf(stderr, "failed pipeline %d set state %d\n", pipe_info->instance_id, state);
+
+	return ret;
+}
+
+int tb_pipelines_set_state(struct testbench_prm *tb, int state, int dir)
+{
+	struct ipc4_pipeline_set_state pipe_state = {{ 0 }};
+	struct tplg_pipeline_list *pipeline_list;
+	struct tplg_pipeline_info *pipe_info;
+	int ret;
+	int i;
+
+	if (dir == SOF_IPC_STREAM_CAPTURE)
+		pipeline_list = &tb->pcm_info->capture_pipeline_list;
+	else
+		pipeline_list = &tb->pcm_info->playback_pipeline_list;
+
+	pipe_state.primary.r.ppl_state = state;
+	pipe_state.primary.r.type = SOF_IPC4_GLB_SET_PIPELINE_STATE;
+	pipe_state.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG;
+	pipe_state.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
+
+	/*
+	 * pipeline list is populated starting from the host to DAI. So traverse the list in
+	 * the reverse order for capture to start the source pipeline first.
+	 */
+	if (dir == SOF_IPC_STREAM_CAPTURE) {
+		for (i = pipeline_list->count - 1; i >= 0; i--) {
+			pipe_info = pipeline_list->pipelines[i];
+			ret = tb_pipeline_set_state(tb, state, &pipe_state, pipe_info,
+						    &tb->ipc_tx, &tb->ipc_rx);
+			if (ret < 0)
+				return ret;
+		}
+
+		return 0;
+	}
+
+	for (i = 0; i < pipeline_list->count; i++) {
+		pipe_info = pipeline_list->pipelines[i];
+		ret = tb_pipeline_set_state(tb, state, &pipe_state, pipe_info,
+					    &tb->ipc_tx, &tb->ipc_rx);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Topology widgets
+ */
+
+int tb_new_aif_in_out(struct testbench_prm *tb, int dir)
+{
+	struct tplg_context *ctx = &tb->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
+	struct ipc4_file_module_cfg *file;
+	int ret;
+
+	ret = tplg_parse_widget_audio_formats(ctx);
+	if (ret < 0)
+		return ret;
+
+	comp_info->ipc_payload =  calloc(sizeof(struct ipc4_file_module_cfg), 1);
+	if (!comp_info->ipc_payload)
+		return -ENOMEM;
+
+	comp_info->ipc_size = sizeof(struct ipc4_file_module_cfg);
+
+	if (dir == SOF_IPC_STREAM_PLAYBACK) {
+		/* Set from testbench command line*/
+		file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload;
+		file->config.mode = FILE_READ;
+		file->config.rate = tb->fs_in;
+		file->config.channels = tb->channels_in;
+		file->config.frame_fmt = tb->frame_fmt;
+		file->config.direction = dir;
+		if (tb->input_file_index >= tb->input_file_num) {
+			fprintf(stderr, "error: not enough input files\n");
+			return -EINVAL;
+		}
+
+		file->config.fn = tb->input_file[tb->input_file_index];
+		comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_AIF_IN]++;
+		comp_info->module_id = 0x9a;
+		tb->fr[tb->input_file_index].id = comp_info->module_id;
+		tb->fr[tb->input_file_index].instance_id = comp_info->instance_id;
+		tb->fr[tb->input_file_index].pipeline_id = ctx->pipeline_id;
+		tb->input_file_index++;
+		tb_setup_widget_ipc_msg(comp_info);
+	} else {
+		file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload;
+		file->config.mode = FILE_WRITE;
+		file->config.rate = tb->fs_out;
+		file->config.channels = tb->channels_out;
+		file->config.frame_fmt = tb->frame_fmt;
+		file->config.direction = dir;
+		if (tb->output_file_index >= tb->output_file_num) {
+			fprintf(stderr, "error: not enough output files\n");
+			return -EINVAL;
+		}
+
+		file->config.fn = tb->output_file[tb->output_file_index];
+		comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_AIF_OUT]++;
+		comp_info->module_id = 0x9b;
+		tb->fw[tb->output_file_index].id = comp_info->module_id;
+		tb->fw[tb->output_file_index].instance_id = comp_info->instance_id;
+		tb->fw[tb->output_file_index].pipeline_id = ctx->pipeline_id;
+		tb->output_file_index++;
+		tb_setup_widget_ipc_msg(comp_info);
+	}
+
+	return 0;
+}
+
+int tb_new_dai_in_out(struct testbench_prm *tb, int dir)
+{
+	struct tplg_context *ctx = &tb->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
+	struct ipc4_file_module_cfg *file;
+	int ret;
+
+	ret = tplg_parse_widget_audio_formats(ctx);
+	if (ret < 0)
+		return ret;
+
+	comp_info->ipc_payload =  calloc(sizeof(struct ipc4_file_module_cfg), 1);
+	if (!comp_info->ipc_payload)
+		return -ENOMEM;
+
+	comp_info->ipc_size = sizeof(struct ipc4_file_module_cfg);
+
+	if (dir == SOF_IPC_STREAM_PLAYBACK) {
+		/* Set from testbench command line*/
+		file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload;
+		file->config.mode = FILE_WRITE;
+		file->config.rate = tb->fs_out;
+		file->config.channels = tb->channels_out;
+		file->config.frame_fmt = tb->frame_fmt;
+		file->config.direction = dir;
+		if (tb->output_file_index >= tb->output_file_num) {
+			fprintf(stderr, "error: not enough output files\n");
+			return -EINVAL;
+		}
+
+		file->config.fn = tb->output_file[tb->output_file_index];
+		comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_DAI_OUT]++;
+		comp_info->module_id = 0x9c;
+		tb->fw[tb->output_file_index].id = comp_info->module_id;
+		tb->fw[tb->output_file_index].instance_id = comp_info->instance_id;
+		tb->fw[tb->output_file_index].pipeline_id = ctx->pipeline_id;
+		tb->output_file_index++;
+		tb_setup_widget_ipc_msg(comp_info);
+	} else {
+		file = (struct ipc4_file_module_cfg *)comp_info->ipc_payload;
+		file->config.mode = FILE_READ;
+		file->config.rate = tb->fs_in;
+		file->config.channels = tb->channels_in;
+		file->config.frame_fmt = tb->frame_fmt;
+		file->config.direction = dir;
+		if (tb->input_file_index >= tb->input_file_num) {
+			fprintf(stderr, "error: not enough input files\n");
+			return -EINVAL;
+		}
+
+		file->config.fn = tb->input_file[tb->input_file_index];
+		comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_DAI_IN]++;
+		comp_info->module_id = 0x9d;
+		tb->fr[tb->input_file_index].id = comp_info->module_id;
+		tb->fr[tb->input_file_index].instance_id = comp_info->instance_id;
+		tb->fr[tb->input_file_index].pipeline_id = ctx->pipeline_id;
+		tb->input_file_index++;
+		tb_setup_widget_ipc_msg(comp_info);
+	}
+
+	return 0;
+}
+
+int tb_new_pga(struct testbench_prm *tb)
+{
+	struct tplg_context *ctx = &tb->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
+	struct ipc4_peak_volume_config volume;
+	struct snd_soc_tplg_ctl_hdr *tplg_ctl;
+	int ret;
+
+	comp_info->ipc_size =
+		sizeof(struct ipc4_peak_volume_config) + sizeof(struct ipc4_base_module_cfg);
+	comp_info->ipc_payload = calloc(comp_info->ipc_size, 1);
+	if (!comp_info->ipc_payload)
+		return -ENOMEM;
+
+	/* FIXME: move this to when the widget is actually set up */
+	comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_PGA]++;
+	comp_info->module_id = 0x6;
+
+	tplg_ctl = calloc(ctx->hdr->payload_size, 1);
+	if (!tplg_ctl) {
+		free(comp_info->ipc_payload);
+		return -ENOMEM;
+	}
+
+	ret = tplg_new_pga(ctx, &volume, sizeof(struct ipc4_peak_volume_config),
+			   tplg_ctl, ctx->hdr->payload_size);
+	if (ret < 0) {
+		fprintf(stderr, "error: failed to create PGA\n");
+		goto out;
+	}
+
+	/* copy volume data to ipc_payload */
+	memcpy(comp_info->ipc_payload + sizeof(struct ipc4_base_module_cfg),
+	       &volume, sizeof(struct ipc4_peak_volume_config));
+
+	/* skip kcontrols for now */
+	ret = tplg_create_controls(ctx, ctx->widget->num_kcontrols, tplg_ctl,
+				   ctx->hdr->payload_size, &volume);
+	if (ret < 0) {
+		fprintf(stderr, "error: loading controls\n");
+		goto out;
+	}
+
+	tb_setup_widget_ipc_msg(comp_info);
+	free(tplg_ctl);
+	return ret;
+
+out:
+	free(tplg_ctl);
+	free(comp_info->ipc_payload);
+	return ret;
+}
+
+int tb_new_process(struct testbench_prm *tb)
+{
+	struct tplg_context *ctx = &tb->tplg;
+	struct tplg_comp_info *comp_info = ctx->current_comp_info;
+	struct snd_soc_tplg_ctl_hdr *tplg_ctl;
+	int ret;
+
+	ret = tplg_parse_widget_audio_formats(ctx);
+	if (ret < 0)
+		return ret;
+
+	tplg_ctl = calloc(ctx->hdr->payload_size, 1);
+	if (!tplg_ctl) {
+		free(comp_info->ipc_payload);
+		return -ENOMEM;
+	}
+
+	/* only base config supported for now. extn support will be added later */
+	comp_info->ipc_size = sizeof(struct ipc4_base_module_cfg);
+	comp_info->ipc_payload = calloc(comp_info->ipc_size, 1);
+	if (!comp_info->ipc_payload)
+		return -ENOMEM;
+
+	/* FIXME: move this to when the widget is actually set up */
+	comp_info->instance_id = tb->instance_ids[SND_SOC_TPLG_DAPM_EFFECT]++;
+	comp_info->module_id = 0x9e; /* dcblock */
+
+	/* skip kcontrols for now, set object to NULL */
+	ret = tplg_create_controls(ctx, ctx->widget->num_kcontrols, tplg_ctl,
+				   ctx->hdr->payload_size, NULL);
+	if (ret < 0) {
+		fprintf(stderr, "error: loading controls\n");
+		goto out;
+	}
+
+	tb_setup_widget_ipc_msg(comp_info);
+	return 0;
+
+out:
+	free(tplg_ctl);
+	free(comp_info->ipc_payload);
+	return ret;
+
+}
+
+/*
+ * To run
+ */
+
+int tb_set_running_state(struct testbench_prm *tb)
+{
+	int ret;
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_PLAYBACK);
+	if (ret) {
+		fprintf(stderr, "error: failed to set state to paused\n");
+		return ret;
+	}
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_CAPTURE);
+	if (ret) {
+		fprintf(stderr, "error: failed to set state to paused\n");
+		return ret;
+	}
+
+	fprintf(stdout, "pipelines are set to paused state\n");
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_RUNNING, SOF_IPC_STREAM_PLAYBACK);
+	if (ret) {
+		fprintf(stderr, "error: failed to set state to running\n");
+		return ret;
+	}
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_RUNNING, SOF_IPC_STREAM_CAPTURE);
+	if (ret)
+		fprintf(stderr, "error: failed to set state to running\n");
+
+	fprintf(stdout, "pipelines are set to running state\n");
+	return ret;
+}
+
+/*
+ * To stop
+ */
+
+int tb_set_reset_state(struct testbench_prm *tb)
+{
+	int ret;
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_PLAYBACK);
+	if (ret) {
+		fprintf(stderr, "error: failed to set state to paused\n");
+		return ret;
+	}
+
+	tb_schedule_pipeline_check_state(tb);
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_RESET, SOF_IPC_STREAM_PLAYBACK);
+	if (ret) {
+		fprintf(stderr, "error: failed to set state to reset\n");
+		return ret;
+	}
+
+	tb_schedule_pipeline_check_state(tb);
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_PAUSED, SOF_IPC_STREAM_CAPTURE);
+	if (ret) {
+		fprintf(stderr, "error: failed to set state to paused\n");
+		return ret;
+	}
+
+	tb_schedule_pipeline_check_state(tb);
+
+	ret = tb_pipelines_set_state(tb, SOF_IPC4_PIPELINE_STATE_RESET, SOF_IPC_STREAM_CAPTURE);
+	if (ret)
+		fprintf(stderr, "error: failed to set state to reset\n");
+
+	tb_schedule_pipeline_check_state(tb);
+	return ret;
+}
+
+int tb_delete_pipeline(struct testbench_prm *tb, struct tplg_pipeline_info *pipe_info)
+{
+	struct ipc4_pipeline_delete msg;
+	struct ipc4_message_reply reply;
+	int ret;
+
+	msg.primary.r.type = SOF_IPC4_GLB_DELETE_PIPELINE;
+	msg.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG;
+	msg.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
+	msg.primary.r.instance_id = pipe_info->instance_id;
+
+	ret = tb_mq_cmd_tx_rx(&tb->ipc_tx, &tb->ipc_rx, &msg, sizeof(msg), &reply, sizeof(reply));
+	if (ret < 0) {
+		fprintf(stderr, "error: can't delete pipeline %s\n", pipe_info->name);
+		return ret;
+	}
+
+	if (reply.primary.r.status != IPC4_SUCCESS) {
+		fprintf(stderr, "pipeline %s instance ID %d delete failed with status %d\n",
+			pipe_info->name, pipe_info->instance_id, reply.primary.r.status);
+		return -EINVAL;
+	}
+
+	tplg_debug("pipeline %s instance_id %d freed\n", pipe_info->name,
+		   pipe_info->instance_id);
+
+	return 0;
+}
+
+int tb_free_route(struct testbench_prm *tb, struct tplg_route_info *route_info)
+{
+	struct tplg_comp_info *src_comp_info = route_info->source;
+	struct tplg_comp_info *sink_comp_info = route_info->sink;
+	struct ipc4_module_bind_unbind bu;
+	struct ipc4_message_reply reply;
+	int ret;
+
+	/* only unbind when widgets belong to separate pipelines */
+	if (src_comp_info->pipeline_id == sink_comp_info->pipeline_id)
+		return 0;
+
+	bu.primary.r.module_id = src_comp_info->module_id;
+	bu.primary.r.instance_id = src_comp_info->instance_id;
+	bu.primary.r.type = SOF_IPC4_MOD_UNBIND;
+	bu.primary.r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_MODULE_MSG;
+	bu.primary.r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
+
+	bu.extension.r.dst_module_id = sink_comp_info->module_id;
+	bu.extension.r.dst_instance_id = sink_comp_info->instance_id;
+
+	/* FIXME: assign queue ID for components with multiple inputs/outputs */
+	bu.extension.r.dst_queue = 0;
+	bu.extension.r.src_queue = 0;
+
+	ret = tb_mq_cmd_tx_rx(&tb->ipc_tx, &tb->ipc_rx, &bu, sizeof(bu), &reply, sizeof(reply));
+	if (ret < 0) {
+		fprintf(stderr, "error: can't set up route %s -> %s\n", src_comp_info->name,
+			sink_comp_info->name);
+		return ret;
+	}
+
+	if (reply.primary.r.status != IPC4_SUCCESS) {
+		fprintf(stderr, "route %s -> %s ID set up failed with status %d\n",
+			src_comp_info->name, sink_comp_info->name, reply.primary.r.status);
+		return -EINVAL;
+	}
+
+	tplg_debug("route %s -> %s freed\n", src_comp_info->name, sink_comp_info->name);
+
+	return 0;
+}
diff --git a/tools/tplg_parser/CMakeLists.txt b/tools/tplg_parser/CMakeLists.txt
index 1569284b097d..3500f661245c 100644
--- a/tools/tplg_parser/CMakeLists.txt
+++ b/tools/tplg_parser/CMakeLists.txt
@@ -64,7 +64,7 @@ endif()
 
 # TODO: add IPC4 option when it's ready.
 target_compile_options(sof_tplg_parser PRIVATE
-	-g -O -Wall -Werror -fPIC -DPIC
+	-g -Wall -Werror -fPIC -DPIC
 	-Wmissing-prototypes ${implicit_fallthrough}
 	-DCONFIG_LIBRARY -D${tplg_ipc})
 
diff --git a/uuid-registry.txt b/uuid-registry.txt
index b597e27dcc0e..bbf92ecd2ec2 100644
--- a/uuid-registry.txt
+++ b/uuid-registry.txt
@@ -145,6 +145,7 @@ c1c5326d-8390-46b4-aa4795c3beca6550 src
 e61bb28d-149a-4c1f-b70946823ef5f5ae src4
 33441051-44cd-466a-83a3178478708aea src_lite
 eb0bd14b-7d5e-4dfa-bbe27762adb279f0 swaudiodai
+37c196ae-3532-4282-8a78dd9d50cc7123 testbench
 dd511749-d9fa-455c-b3a713585693f1af tdfb
 04e3f894-2c5c-4f2e-8dc1694eeaab53fa tone
 42f8060c-832f-4dbf-b24751e961997b34 up_down_mixer