diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 94827bcc4a..e1e4bb49fa 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -101,6 +101,7 @@ enum index {
 	QUEUE,
 	PUSH,
 	PULL,
+	HASH,
 
 	/* Flex arguments */
 	FLEX_ITEM_INIT,
@@ -206,6 +207,11 @@ enum index {
 	TABLE_PATTERN_TEMPLATE,
 	TABLE_ACTIONS_TEMPLATE,
 
+	/* Hash calculation arguments. */
+	HASH_CALC_TABLE,
+	HASH_CALC_PATTERN_INDEX,
+	HASH_CALC_PATTERN,
+
 	/* Tunnel arguments. */
 	TUNNEL_CREATE,
 	TUNNEL_CREATE_TYPE,
@@ -2678,6 +2684,9 @@ static int parse_push(struct context *, const struct token *,
 static int parse_pull(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);
@@ -3035,7 +3044,8 @@ static const struct token token_list[] = {
 			      FLEX,
 			      QUEUE,
 			      PUSH,
-			      PULL)),
+			      PULL,
+			      HASH)),
 		.call = parse_init,
 	},
 	/* Top-level command. */
@@ -3680,6 +3690,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} [...]]",
@@ -10449,6 +10486,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_flex(struct context *ctx, const struct token *token,
 	     const char *str, unsigned int len,
@@ -12351,6 +12430,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 11f3a22048..c244f1a071 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 f1df6a8faf..4637a47c06 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -1009,6 +1009,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/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 271d854f78..5f7dcdfdad 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -2431,3 +2431,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 23addb4382..b8c28394a1 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -6624,6 +6624,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 f9fb01b8a2..aa8635b1a8 100644
--- a/lib/ethdev/rte_flow_driver.h
+++ b/lib/ethdev/rte_flow_driver.h
@@ -358,6 +358,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 b965d6aa52..1496d8caa6 100644
--- a/lib/ethdev/version.map
+++ b/lib/ethdev/version.map
@@ -312,6 +312,7 @@ EXPERIMENTAL {
 	rte_flow_async_action_list_handle_query_update;
 	rte_flow_async_actions_update;
 	rte_flow_restore_info_dynflag;
+	rte_flow_calc_table_hash;
 };
 
 INTERNAL {
