diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 12809f1042..dd66429554 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -102,6 +102,7 @@ enum index {
 	QUEUE,
 	PUSH,
 	PULL,
+	HASH,
 
 	/* Flex arguments */
 	FLEX_ITEM_INIT,
@@ -214,6 +215,11 @@ enum index {
 	GROUP_TRANSFER,
 	GROUP_SET_MISS_ACTIONS,
 
+	/* Hash calculation arguments. */
+	HASH_CALC_TABLE,
+	HASH_CALC_PATTERN_INDEX,
+	HASH_CALC_PATTERN,
+
 	/* Tunnel arguments. */
 	TUNNEL_CREATE,
 	TUNNEL_CREATE_TYPE,
@@ -2708,6 +2714,9 @@ static int parse_pull(struct context *, const struct token *,
 static int parse_group(struct context *, const struct token *,
 		       const char *, unsigned int,
 		       void *, unsigned int);
+static int parse_hash(struct context *, const struct token *,
+		      const char *, unsigned int,
+		      void *, unsigned int);
 static int parse_tunnel(struct context *, const struct token *,
 			const char *, unsigned int,
 			void *, unsigned int);
@@ -3066,7 +3075,8 @@ static const struct token token_list[] = {
 			      FLEX,
 			      QUEUE,
 			      PUSH,
-			      PULL)),
+			      PULL,
+			      HASH)),
 		.call = parse_init,
 	},
 	/* Top-level command. */
@@ -3751,6 +3761,33 @@ static const struct token token_list[] = {
 		.args = ARGS(ARGS_ENTRY(struct buffer, queue)),
 	},
 	/* Top-level command. */
+	[HASH] = {
+		.name = "hash",
+		.help = "calculate hash for a given pattern in a given template table",
+		.next = NEXT(NEXT_ENTRY(HASH_CALC_TABLE), NEXT_ENTRY(COMMON_PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_hash,
+	},
+	/* Sub-level commands. */
+	[HASH_CALC_TABLE] = {
+		.name = "template_table",
+		.help = "specify table id",
+		.next = NEXT(NEXT_ENTRY(HASH_CALC_PATTERN_INDEX),
+			     NEXT_ENTRY(COMMON_TABLE_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer,
+					args.vc.table_id)),
+		.call = parse_hash,
+	},
+	[HASH_CALC_PATTERN_INDEX] = {
+		.name = "pattern_template",
+		.help = "specify pattern template id",
+		.next = NEXT(NEXT_ENTRY(ITEM_PATTERN),
+			     NEXT_ENTRY(COMMON_UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct buffer,
+					args.vc.pat_templ_id)),
+		.call = parse_hash,
+	},
+	/* Top-level command. */
 	[INDIRECT_ACTION] = {
 		.name = "indirect_action",
 		.type = "{command} {port_id} [{arg} [...]]",
@@ -10544,6 +10581,48 @@ parse_pull(struct context *ctx, const struct token *token,
 	return len;
 }
 
+/** Parse tokens for hash calculation commands. */
+static int
+parse_hash(struct context *ctx, const struct token *token,
+	 const char *str, unsigned int len,
+	 void *buf, unsigned int size)
+{
+	struct buffer *out = buf;
+
+	/* Token name must match. */
+	if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+		return -1;
+	/* Nothing else to do if there is no buffer. */
+	if (!out)
+		return len;
+	if (!out->command) {
+		if (ctx->curr != HASH)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+		out->args.vc.data = (uint8_t *)out + size;
+		return len;
+	}
+	switch (ctx->curr) {
+	case HASH_CALC_TABLE:
+	case HASH_CALC_PATTERN_INDEX:
+		return len;
+	case ITEM_PATTERN:
+		out->args.vc.pattern =
+			(void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
+					       sizeof(double));
+		ctx->object = out->args.vc.pattern;
+		ctx->objmask = NULL;
+		return len;
+	default:
+		return -1;
+	}
+}
+
 static int
 parse_group(struct context *ctx, const struct token *token,
 	    const char *str, unsigned int len,
@@ -12498,6 +12577,11 @@ cmd_flow_parsed(const struct buffer *in)
 	case PULL:
 		port_queue_flow_pull(in->port, in->queue);
 		break;
+	case HASH:
+		port_flow_hash_calc(in->port, in->args.vc.table_id,
+				    in->args.vc.pat_templ_id,
+				    in->args.vc.pattern);
+		break;
 	case QUEUE_AGED:
 		port_queue_flow_aged(in->port, in->queue,
 				     in->args.aged.destroy);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 709864bb44..35586e0ad2 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6,6 +6,7 @@
 #include <ctype.h>
 #include <stdarg.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -3301,6 +3302,59 @@ port_queue_flow_push(portid_t port_id, queueid_t queue_id)
 	return ret;
 }
 
+/** Calculate the hash result for a given pattern in a given table. */
+int
+port_flow_hash_calc(portid_t port_id, uint32_t table_id,
+		    uint8_t pattern_template_index, const struct rte_flow_item pattern[])
+{
+	uint32_t hash;
+	bool found;
+	struct port_table *pt;
+	struct rte_port *port;
+	struct rte_flow_error error;
+	int ret = 0;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+	port = &ports[port_id];
+
+	found = false;
+	pt = port->table_list;
+	while (pt) {
+		if (table_id == pt->id) {
+			found = true;
+			break;
+		}
+		pt = pt->next;
+	}
+	if (!found) {
+		printf("Table #%u is invalid\n", table_id);
+		return -EINVAL;
+	}
+
+	memset(&error, 0x55, sizeof(error));
+	ret = rte_flow_calc_table_hash(port_id, pt->table, pattern,
+				       pattern_template_index, &hash, &error);
+	if (ret < 0) {
+		printf("Failed to calculate hash ");
+		switch (abs(ret)) {
+		case ENODEV:
+			printf("no such device\n");
+			break;
+		case ENOTSUP:
+			printf("device doesn't support this operation\n");
+			break;
+		default:
+			printf("\n");
+			break;
+		}
+		return ret;
+	}
+	printf("Hash results 0x%x\n", hash);
+	return 0;
+}
+
 /** Pull queue operation results from the queue. */
 static int
 port_queue_aged_flow_destroy(portid_t port_id, queueid_t queue_id,
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index e236845a81..dec333a8fa 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -1014,6 +1014,8 @@ port_queue_action_handle_query_update(portid_t port_id,
 				      const struct rte_flow_action *action);
 int port_queue_flow_push(portid_t port_id, queueid_t queue_id);
 int port_queue_flow_pull(portid_t port_id, queueid_t queue_id);
+int port_flow_hash_calc(portid_t port_id, uint32_t table_id,
+			uint8_t pattern_template_index, const struct rte_flow_item pattern[]);
 void port_queue_flow_aged(portid_t port_id, uint32_t queue_id, uint8_t destroy);
 int port_flow_validate(portid_t port_id,
 		       const struct rte_flow_attr *attr,
diff --git a/doc/guides/prog_guide/rte_flow.rst b/doc/guides/prog_guide/rte_flow.rst
index 860e42025f..a11eb2977a 100644
--- a/doc/guides/prog_guide/rte_flow.rst
+++ b/doc/guides/prog_guide/rte_flow.rst
@@ -4173,6 +4173,23 @@ User data may be provided during a flow creation/destruction in order
 to distinguish between multiple operations. User data is returned as part
 of the result to provide a method to detect which operation is completed.
 
+Calculate hash
+~~~~~~~~~~~~~~
+
+Calculating hash of a packet in SW as it would be calculated in HW.
+
+The application can use this function to calculate the hash of a given packet
+as it would be calculated in the HW.
+
+.. code-block:: c
+
+   int
+   rte_flow_calc_table_hash(uint16_t port_id,
+                            const struct rte_flow_template_table *table,
+			                   const struct rte_flow_item pattern[],
+                            uint8_t pattern_template_index,
+			                   uint32_t *hash, struct rte_flow_error *error);
+
 .. _flow_isolated_mode:
 
 Flow isolated mode
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index e6d218caaa..1006239d86 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -3276,6 +3276,28 @@ The usual error message is shown when operations results cannot be pulled::
 
    Caught error type [...] ([...]): [...]
 
+Calculating hash
+~~~~~~~~~~~~~~~~
+
+``flow hash`` calculates the hash for a given pattern.
+It is bound to ``rte_flow_calc_table_hash()``::
+
+   flow hash {port_id} template_table {table_id}
+       pattern_template {pattern_template_index}
+       actions_template {actions_template_index}
+       pattern {item} [/ {item} [...]] / end
+
+If successful, it will show the calculated hash result as seen below::
+
+   Hash results 0x[...]
+
+Otherwise, it will show an error message of the form::
+
+   Caught error type [...] ([...]): [...]
+
+This command uses the same pattern items as ``flow create``,
+their format is described in `Creating flow rules`_.
+
 Creating a tunnel stub for offload
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index c9d23da702..6917fbd262 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -2454,3 +2454,24 @@ rte_flow_async_action_list_handle_query_update(uint16_t port_id, uint32_t queue_
 							     ret);
 	return ret;
 }
+
+int
+rte_flow_calc_table_hash(uint16_t port_id, const struct rte_flow_template_table *table,
+			 const struct rte_flow_item pattern[], uint8_t pattern_template_index,
+			 uint32_t *hash, struct rte_flow_error *error)
+{
+	int ret;
+	struct rte_eth_dev *dev;
+	const struct rte_flow_ops *ops;
+
+	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);
+	ops = rte_flow_ops_get(port_id, error);
+	if (!ops || !ops->flow_calc_table_hash)
+		return rte_flow_error_set(error, ENOTSUP,
+					  RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+					  "action_list async query_update not supported");
+	dev = &rte_eth_devices[port_id];
+	ret = ops->flow_calc_table_hash(dev, table, pattern, pattern_template_index,
+					hash, error);
+	return flow_err(port_id, ret, error);
+}
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index e576b15e3d..00e472b65b 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -6717,6 +6717,38 @@ rte_flow_async_action_list_handle_query_update(uint16_t port_id, uint32_t queue_
 					  void *user_data,
 					  struct rte_flow_error *error);
 
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Calculate the hash for a given pattern in a given table as
+ * calculated by the HW.
+ *
+ * @param port_id
+ *   Port identifier of Ethernet device.
+ * @param table
+ *   The table the SW wishes to simulate.
+ * @param pattern
+ *   The values to be used in the hash calculation.
+ * @param pattern_template_index
+ *   The pattern index in the table to be used for the calculation.
+ * @param hash
+ *   Used to return the calculated hash.
+ * @param error
+ *   Perform verbose error reporting if not NULL.
+ *   PMDs initialize this structure in case of error only.
+ *
+ * @return
+ *   - (0) if success.
+ *   - (-ENODEV) if *port_id* invalid.
+ *   - (-ENOTSUP) if underlying device does not support this functionality.
+ */
+__rte_experimental
+int
+rte_flow_calc_table_hash(uint16_t port_id, const struct rte_flow_template_table *table,
+			 const struct rte_flow_item pattern[], uint8_t pattern_template_index,
+			 uint32_t *hash, struct rte_flow_error *error);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/ethdev/rte_flow_driver.h b/lib/ethdev/rte_flow_driver.h
index 3ced086c47..f35f659503 100644
--- a/lib/ethdev/rte_flow_driver.h
+++ b/lib/ethdev/rte_flow_driver.h
@@ -365,6 +365,11 @@ struct rte_flow_ops {
 		 const void **update, void **query,
 		 enum rte_flow_query_update_mode mode,
 		 void *user_data, struct rte_flow_error *error);
+	/** @see rte_flow_calc_table_hash() */
+	int (*flow_calc_table_hash)
+		(struct rte_eth_dev *dev, const struct rte_flow_template_table *table,
+		 const struct rte_flow_item pattern[], uint8_t pattern_template_index,
+		 uint32_t *hash, struct rte_flow_error *error);
 };
 
 /**
diff --git a/lib/ethdev/version.map b/lib/ethdev/version.map
index 1e51478405..919ba5b8e6 100644
--- a/lib/ethdev/version.map
+++ b/lib/ethdev/version.map
@@ -316,6 +316,7 @@ EXPERIMENTAL {
 	# added in 23.11
 	rte_eth_recycle_rx_queue_info_get;
 	rte_flow_group_set_miss_actions;
+	rte_flow_calc_table_hash;
 };
 
 INTERNAL {
