diff --git a/doc/openocd.texi b/doc/openocd.texi
index 107441d2d5cd2dd9f068ca5b6e869afd61190581..500faf9a590dd05c147ff1c610c2c5db5b33dd60 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -6096,6 +6096,8 @@ with handlers for that event.
 @deffn Command {pathmove} start_state [next_state ...]
 Start by moving to @var{start_state}, which
 must be one of the @emph{stable} states.
+Unless it is the only state given, this will often be the
+current state, so that no TCK transitions are needed.
 Then, in a series of single state transitions
 (conforming to the JTAG state machine) shift to
 each @var{next_state} in sequence, one per TCK cycle.
@@ -6130,8 +6132,8 @@ Default is enabled.
 
 The @var{tap_state} names used by OpenOCD in the @command{drscan},
 @command{irscan}, and @command{pathmove} commands are the same
-as those used in SVF boundary scan documents, except that some
-versions of SVF use @sc{idle} instead of @sc{run/idle}.
+as those used in SVF boundary scan documents, except that
+SVF uses @sc{idle} instead of @sc{run/idle}.
 
 @itemize @bullet
 @item @b{RESET} ... @emph{stable} (with TMS high);
@@ -6222,6 +6224,27 @@ Unless the @option{quiet} option is specified,
 messages are logged for comments and some retries.
 @end deffn
 
+The OpenOCD sources also include two utility scripts
+for working with XSVF; they are not currently installed
+after building the software.
+You may find them useful:
+
+@itemize
+@item @emph{svf2xsvf} ... converts SVF files into the extended XSVF
+syntax understood by the @command{xsvf} command; see notes below.
+@item @emph{xsvfdump} ... converts XSVF files into a text output format;
+understands the OpenOCD extensions.
+@end itemize
+
+The input format accepts a handful of non-standard extensions.
+These include three opcodes corresponding to SVF extensions
+from Lattice Semiconductor (LCOUNT, LDELAY, LDSR), and
+two opcodes supporting a more accurate translation of SVF
+(XTRST, XWAITSTATE).
+If @emph{xsvfdump} shows a file is using those opcodes, it
+probably will not be usable with other XSVF tools.
+
+
 @node TFTP
 @chapter TFTP
 @cindex TFTP
diff --git a/src/jtag/jtag.h b/src/jtag/jtag.h
index 5085445f3474755bb2688b7cfeac21b4de3d45ae..7253c3eaa7452d7f93906336607edcdfab484ffd 100644
--- a/src/jtag/jtag.h
+++ b/src/jtag/jtag.h
@@ -536,29 +536,7 @@ extern void jtag_add_pathmove(int num_states, const tap_state_t* path);
  * @return ERROR_OK on success, or an error code on failure.
  *
  * Moves from the current state to the goal \a state.
- *
- * This needs to be handled according to the xsvf spec, see the XSTATE
- * command description.  From the XSVF spec, pertaining to XSTATE:
- *
- * For special states known as stable states (Test-Logic-Reset,
- * Run-Test/Idle, Pause-DR, Pause- IR), an XSVF interpreter follows
- * predefined TAP state paths when the starting state is a stable state
- * and when the XSTATE specifies a new stable state.  See the STATE
- * command in the [Ref 5] for the TAP state paths between stable
- * states.
- *
- * For non-stable states, XSTATE should specify a state that is only one
- * TAP state transition distance from the current TAP state to avoid
- * undefined TAP state paths. A sequence of multiple XSTATE commands can
- * be issued to transition the TAP through a specific state path.
- *
- * @note Unless @c tms_bits holds a path that agrees with [Ref 5] in the
- * above spec, then this code is not fully conformant to the xsvf spec.
- * This puts a burden on tap_get_tms_path() function from the xsvf spec.
- * If in doubt, you should confirm that that burden is being met.
- *
- * Otherwise, @a goal_state must be immediately reachable in one clock
- * cycle, and does not need to be a stable state.
+ * Both states must be stable.
  */
 extern int jtag_add_statemove(tap_state_t goal_state);
 
diff --git a/src/svf/svf.c b/src/svf/svf.c
index ecb0ffa4ba1798a46c08062ab031df6212318c0f..dfbbde4bec03fa648518395dd801db43ba21b241 100644
--- a/src/svf/svf.c
+++ b/src/svf/svf.c
@@ -31,8 +31,8 @@
 #include "config.h"
 #endif
 
-#include "svf.h"
 #include "jtag.h"
+#include "svf.h"
 #include "time_support.h"
 
 
@@ -311,7 +311,7 @@ static const char* tap_state_svf_name(tap_state_t state)
 	return ret;
 }
 
-static int svf_add_statemove(tap_state_t state_to)
+int svf_add_statemove(tap_state_t state_to)
 {
 	tap_state_t state_from = cmd_queue_cur_state;
 	uint8_t index;
@@ -619,9 +619,10 @@ static int svf_parse_cmd_string(char *str, int len, char **argus, int *num_of_ar
 	return ERROR_OK;
 }
 
-static int svf_tap_state_is_stable(tap_state_t state)
+bool svf_tap_state_is_stable(tap_state_t state)
 {
-	return ((TAP_RESET == state) || (TAP_IDLE == state) || (TAP_DRPAUSE == state) || (TAP_IRPAUSE == state));
+	return (TAP_RESET == state) || (TAP_IDLE == state)
+			|| (TAP_DRPAUSE == state) || (TAP_IRPAUSE == state);
 }
 
 static int svf_tap_state_is_valid(tap_state_t state)
@@ -1082,6 +1083,7 @@ static int svf_run_command(struct command_context_s *cmd_ctx, char *cmd_str)
 			field.num_bits = i;
 			field.out_value = &svf_tdi_buffer[svf_buffer_index];
 			field.in_value = &svf_tdi_buffer[svf_buffer_index];
+			/* NOTE:  doesn't use SVF-specified state paths */
 			jtag_add_plain_dr_scan(1, &field, svf_para.dr_end_state);
 
 			svf_buffer_index += (i + 7) >> 3;
@@ -1177,6 +1179,7 @@ static int svf_run_command(struct command_context_s *cmd_ctx, char *cmd_str)
 			field.num_bits = i;
 			field.out_value = &svf_tdi_buffer[svf_buffer_index];
 			field.in_value = &svf_tdi_buffer[svf_buffer_index];
+			/* NOTE:  doesn't use SVF-specified state paths */
 			jtag_add_plain_ir_scan(1, &field, svf_para.ir_end_state);
 
 			svf_buffer_index += (i + 7) >> 3;
@@ -1278,10 +1281,13 @@ static int svf_run_command(struct command_context_s *cmd_ctx, char *cmd_str)
 				// run_state and end_state is checked to be stable state
 				// TODO: do runtest
 #if 1
+				/* FIXME handle statemove failures */
+				int retval;
+
 				// enter into run_state if necessary
 				if (cmd_queue_cur_state != svf_para.runtest_run_state)
 				{
-					svf_add_statemove(svf_para.runtest_run_state);
+					retval = svf_add_statemove(svf_para.runtest_run_state);
 				}
 
 				// call jtag_add_clocks
@@ -1290,7 +1296,7 @@ static int svf_run_command(struct command_context_s *cmd_ctx, char *cmd_str)
 				// move to end_state if necessary
 				if (svf_para.runtest_end_state != svf_para.runtest_run_state)
 				{
-					svf_add_statemove(svf_para.runtest_end_state);
+					retval = svf_add_statemove(svf_para.runtest_end_state);
 				}
 #else
 				if (svf_para.runtest_run_state != TAP_IDLE)
@@ -1337,8 +1343,10 @@ static int svf_run_command(struct command_context_s *cmd_ctx, char *cmd_str)
 					free(path);
 					return ERROR_FAIL;
 				}
+				/* OpenOCD refuses paths containing TAP_RESET */
 				if (TAP_RESET == path[i])
 				{
+					/* FIXME last state MUST be stable! */
 					if (i > 0)
 					{
 						jtag_add_pathmove(i, path);
@@ -1378,10 +1386,10 @@ static int svf_run_command(struct command_context_s *cmd_ctx, char *cmd_str)
 			state = svf_find_string_in_array(argus[1], (char **)svf_tap_state_name, dimof(svf_tap_state_name));
 			if (svf_tap_state_is_stable(state))
 			{
-				// TODO: move to state
+				LOG_DEBUG("\tmove to %s by svf_add_statemove",
+						svf_tap_state_name[state]);
+				/* FIXME handle statemove failures */
 				svf_add_statemove(state);
-
-				LOG_DEBUG("\tmove to %s by svf_add_statemove", svf_tap_state_name[state]);
 			}
 			else
 			{
diff --git a/src/svf/svf.h b/src/svf/svf.h
index 822cad22e94eb303f9ea41e777c43801ee85800d..83123fc3b65ff1d5928733ea5bdb2a990f8d7373 100644
--- a/src/svf/svf.h
+++ b/src/svf/svf.h
@@ -24,4 +24,25 @@
 
 extern int svf_register_commands(struct command_context_s *cmd_ctx);
 
+/**
+ * svf_add_statemove() moves from the current state to @a goal_state.
+ *
+ * @param goal_state The final TAP state.
+ * @return ERROR_OK on success, or an error code on failure.
+ *
+ * The current and goal states must satisfy svf_tap_state_is_stable().
+ * State transition paths used by this routine are those given in the
+ * SVF specification for single-argument STATE commands (and also used
+ * for various other state transitions).
+ */
+extern int svf_add_statemove(tap_state_t goal_state);
+
+/**
+ * svf_tap_state_is_stable() returns true for stable non-SHIFT states
+ *
+ * @param state The TAP state in question
+ * @return true iff the state is stable and not a SHIFT state.
+ */
+extern bool svf_tap_state_is_stable(tap_state_t state);
+
 #endif /* SVF_H */
diff --git a/src/xsvf/Makefile.am b/src/xsvf/Makefile.am
index 847fb8070037396276e96e75ed8bc9cde46edcb7..fd9f8c3f7960c426c94c259f7e36b2c2e6329f8e 100644
--- a/src/xsvf/Makefile.am
+++ b/src/xsvf/Makefile.am
@@ -1,6 +1,7 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/helper \
+	-I$(top_srcdir)/src/svf \
 	-I$(top_srcdir)/src/jtag
 
 METASOURCES = AUTO
diff --git a/src/xsvf/xsvf.c b/src/xsvf/xsvf.c
index 083e6e3253343c5076d463190af67d71d65aef90..d00c47c6dd55d69b32fe27da2d1756bf8c64bfb8 100644
--- a/src/xsvf/xsvf.c
+++ b/src/xsvf/xsvf.c
@@ -42,6 +42,7 @@
 
 #include "xsvf.h"
 #include "jtag.h"
+#include "svf.h"
 
 
 /* XSVF commands, from appendix B of xapp503.pdf  */
@@ -432,7 +433,7 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 				/* See page 19 of XSVF spec regarding opcode "XSDR" */
 				if (xruntest)
 				{
-					jtag_add_statemove(TAP_IDLE);
+					result = svf_add_statemove(TAP_IDLE);
 
 					if (runtest_requires_tck)
 						jtag_add_clocks(xruntest);
@@ -440,7 +441,7 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 						jtag_add_sleep(xruntest);
 				}
 				else if (xendir != TAP_DRPAUSE)	/* we are already in TAP_DRPAUSE */
-					jtag_add_statemove(xenddr);
+					result = svf_add_statemove(xenddr);
 			}
 			break;
 
@@ -499,32 +500,37 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 
 				LOG_DEBUG("XSTATE 0x%02X %s", uc, tap_state_name(mystate));
 
-				/*	there is no need for the lookahead code that was here since we
-					queue up the jtag commands anyway.  This is a simple way to handle
-					the XSTATE.
-				*/
+				/* NOTE: the current state is SVF-stable! */
+
+				/* no change == NOP */
+				if (mystate == cmd_queue_cur_state
+						&& mystate != TAP_RESET)
+					break;
 
-				if (jtag_add_statemove(mystate) != ERROR_OK)
+				/* Hand off to SVF? */
+				if (svf_tap_state_is_stable(mystate))
 				{
-					/*	For special states known as stable states
-						(Test-Logic-Reset, Run-Test/Idle, Pause-DR, Pause- IR),
-						an XSVF interpreter follows predefined TAP state paths
-						when the starting state is a stable state and when the
-						XSTATE specifies a new stable state (see the STATE
-						command in the [Ref 5] for the TAP state paths between
-						stable states). For non-stable states, XSTATE should
-						specify a state that is only one TAP state transition
-						distance from the current TAP state to avoid undefined
-						TAP state paths. A sequence of multiple XSTATE commands
-						can be issued to transition the TAP through a specific
-						state path.
-					*/
-
-					LOG_ERROR("XSTATE %s is not reachable from current state %s in one clock cycle",
-						tap_state_name(mystate),
-						tap_state_name(cmd_queue_cur_state)
-);
+					result = svf_add_statemove(mystate);
+					if (result != ERROR_OK)
+						unsupported = 1;
+					break;
 				}
+
+				/*
+				 * A sequence of XSTATE transitions, each TAP
+				 * state adjacent to the previous one.
+				 *
+				 * NOTE: OpenOCD requires something that XSVF
+				 * doesn't:  the last TAP state in the path
+				 * must be stable.
+				 *
+				 * FIXME Implement path collection; submit via
+				 * jtag_add_pathmove() after teaching it to
+				 * report errors.
+				 */
+				LOG_ERROR("XSVF: 'XSTATE %s' ... NYET",
+						tap_state_name(mystate));
+				unsupported = 1;
 			}
 			break;
 
@@ -708,9 +714,10 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 				}
 				else
 				{
-					jtag_add_statemove(wait_state);
+					/* FIXME handle statemove errors ... */
+					result = svf_add_statemove(wait_state);
 					jtag_add_sleep(delay);
-					jtag_add_statemove(end_state);
+					result = svf_add_statemove(end_state);
 				}
 			}
 			break;
@@ -755,19 +762,22 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 				 * be issuing a number of clocks in this state.  This set of allowed states is also
 				 * determined by the SVF RUNTEST command's allowed states.
 				 */
-				if (wait_state != TAP_IRPAUSE && wait_state != TAP_DRPAUSE && wait_state != TAP_RESET && wait_state != TAP_IDLE)
+				if (!svf_tap_state_is_stable(wait_state))
 				{
-					LOG_ERROR("illegal XWAITSTATE wait_state: \"%s\"", tap_state_name(wait_state));
+					LOG_ERROR("illegal XWAITSTATE wait_state: \"%s\"",
+							tap_state_name(wait_state));
 					unsupported = 1;
+					/* REVISIT "break" so we won't run? */
 				}
 
-				jtag_add_statemove(wait_state);
+				/* FIXME handle statemove errors ... */
+				result = svf_add_statemove(wait_state);
 
 				jtag_add_clocks(clock_count);
 
 				jtag_add_sleep(usecs);
 
-				jtag_add_statemove(end_state);
+				result = svf_add_statemove(end_state);
 			}
 			break;
 
@@ -806,6 +816,7 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 					break;
 				}
 
+				/* NOTE:  loop_state must be stable! */
 				loop_state  = xsvf_to_tap(state);
 				loop_clocks = be_to_h_u32(clock_buf);
 				loop_usecs  = be_to_h_u32(usecs_buf);
@@ -839,7 +850,7 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 				{
 					scan_field_t field;
 
-					jtag_add_statemove(loop_state);
+					result = svf_add_statemove(loop_state);
 					jtag_add_clocks(loop_clocks);
 					jtag_add_sleep(loop_usecs);
 
@@ -917,8 +928,8 @@ static int handle_xsvf_command(struct command_context_s *cmd_ctx, char *cmd, cha
 			LOG_DEBUG("xsvf failed, setting taps to reasonable state");
 
 			/* upon error, return the TAPs to a reasonable state */
-			jtag_add_statemove(TAP_IDLE);
-			jtag_execute_queue();
+			result = svf_add_statemove(TAP_IDLE);
+			result = jtag_execute_queue();
 			break;
 		}
 	}