[binutils-gdb] gas: pru: Add TSEN and MVI instructions

Dimitar Dimitrov dimitar@sourceware.org
Fri Mar 6 18:08:11 GMT 2026


https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=bd2f485e24365d6a5717efeeb3c6d4edb5231796

commit bd2f485e24365d6a5717efeeb3c6d4edb5231796
Author: Dimitar Dimitrov <dimitar@dinux.eu>
Date:   Fri Feb 20 22:43:20 2026 +0200

    gas: pru: Add TSEN and MVI instructions
    
    Add support for TSEN and MVI instructions to GAS and libopcodes.
    TSEN is available in newer PRU core revisions, and can be used to
    implement multitasking.  MVI allows indirectly addressing CPU registers
    using a pointer in R1 register.
    
    References:
     - https://www.ti.com/lit/ug/spruij2/spruij2.pdf
       Chapter "Move Register File Indirect (MVIx)"
     - https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/1426480/am2431-pru-assembly-instruction-user-guide
    
    Signed-off-by: Dimitar Dimitrov <dimitar@dinux.eu>

Diff:
---
 gas/config/tc-pru.c          | 109 +++++++++++++++++++++++++++++++++++++++++++
 gas/testsuite/gas/pru/mvi.d  |  17 +++++++
 gas/testsuite/gas/pru/mvi.s  |  12 +++++
 gas/testsuite/gas/pru/tsen.d |  10 ++++
 gas/testsuite/gas/pru/tsen.s |   5 ++
 include/opcode/pru.h         |  39 +++++++++++++++-
 opcodes/pru-dis.c            |  40 ++++++++++++++--
 opcodes/pru-opc.c            |   9 ++++
 8 files changed, 236 insertions(+), 5 deletions(-)

diff --git a/gas/config/tc-pru.c b/gas/config/tc-pru.c
index ee86511fafc..fe096f8e93c 100644
--- a/gas/config/tc-pru.c
+++ b/gas/config/tc-pru.c
@@ -1253,6 +1253,92 @@ pru_assemble_arg_n (pru_insn_infoS *insn_info, const char *argstr)
     SET_INSN_FIELD (XFR_LENGTH, insn_info->insn_code, burstlen);
 }
 
+static void
+pru_parse_mvi_operand (const char *argstr,
+		       struct pru_reg **blreg,
+		       unsigned int *mode)
+{
+  char *regstr;
+
+  *mode = MVI_OP_MODE_DIRECT;
+
+  if (*argstr == '*')
+    {
+      *mode = MVI_OP_MODE_INDIRECT;
+      argstr++;
+
+      if (argstr[0] == '-' && argstr[1] == '-')
+	{
+	  argstr += 2;
+	  *mode = MVI_OP_MODE_INDIRECT_PREDEC;
+	}
+    }
+  /* Decouple register string from the post increment operator.  */
+  regstr = strdup (argstr);
+  *strchrnul (regstr, '+') = '\0';
+  *blreg = pru_reg_lookup (regstr);
+  if (*blreg == NULL)
+    as_bad (_("unknown register %s"), regstr);
+  free (regstr);
+  regstr = NULL;
+  argstr += strlen ((*blreg)->name);
+
+  if (argstr[0] == '+' && argstr[1] == '+')
+    {
+      argstr += 2;
+      if (*mode == MVI_OP_MODE_DIRECT)
+	as_bad (_("missing indirect operator for post-increment operand"));
+      if (*mode == MVI_OP_MODE_INDIRECT_PREDEC)
+	as_bad (_("cannot both pre-decrement and post-increment an operand"));
+      *mode = MVI_OP_MODE_INDIRECT_POSTINC;
+    }
+  if (argstr[0] != '\0')
+      as_bad (_("unexpected statements at and of instruction"));
+
+  if (*mode != MVI_OP_MODE_DIRECT)
+    {
+      if ((*blreg)->index != 1)
+	as_bad (_("only R1 can be used for indirect addressing"));
+
+      if ((*blreg)->regsel != RSEL_7_0
+	  && (*blreg)->regsel != RSEL_15_8
+	  && (*blreg)->regsel != RSEL_23_16
+	  && (*blreg)->regsel != RSEL_31_24)
+	as_bad (_("only byte mode can be used for R1"));
+    }
+}
+
+static void
+pru_assemble_arg_M (pru_insn_infoS *insn_info, const char *argstr)
+{
+  unsigned int mode, rdmode;
+  struct pru_reg *blreg;
+
+  pru_parse_mvi_operand (argstr, &blreg, &mode);
+
+  SET_INSN_FIELD (MVI_RS1_MODE, insn_info->insn_code, mode);
+  SET_INSN_FIELD (RS1, insn_info->insn_code, blreg->index);
+  SET_INSN_FIELD (RS1SEL, insn_info->insn_code, blreg->regsel);
+
+  /* Assume source operand would be parsed after destination one.  */
+  rdmode = GET_INSN_FIELD (MVI_RD_MODE, insn_info->insn_code);
+  if (rdmode == MVI_OP_MODE_DIRECT && mode == MVI_OP_MODE_DIRECT)
+    as_bad (_("at least one MVI operand must be indirect"));
+}
+
+static void
+pru_assemble_arg_m (pru_insn_infoS *insn_info, const char *argstr)
+{
+  unsigned int mode;
+  struct pru_reg *blreg;
+
+  pru_parse_mvi_operand (argstr, &blreg, &mode);
+
+  SET_INSN_FIELD (MVI_RD_MODE, insn_info->insn_code, mode);
+  SET_INSN_FIELD (RD, insn_info->insn_code, blreg->index);
+  SET_INSN_FIELD (RDSEL, insn_info->insn_code, blreg->regsel);
+}
+
 static void
 pru_assemble_arg_c (pru_insn_infoS *insn_info, const char *argstr)
 {
@@ -1264,6 +1350,17 @@ pru_assemble_arg_c (pru_insn_infoS *insn_info, const char *argstr)
     SET_INSN_FIELD (CB, insn_info->insn_code, cb);
 }
 
+static void
+pru_assemble_arg_t (pru_insn_infoS *insn_info, const char *argstr)
+{
+  unsigned long val = pru_assemble_noreloc_expression (argstr);
+
+  if (val != 0 && val != 1)
+    as_bad (_("invalid task manager mode %ld"), val);
+  else
+    SET_INSN_FIELD (TSKMGR_MODE, insn_info->insn_code, val);
+}
+
 static void
 pru_assemble_arg_w (pru_insn_infoS *insn_info, const char *argstr)
 {
@@ -1354,7 +1451,10 @@ pru_consume_arg (char *argstr, const char *parsestr)
     case 'S':
     case 'l':
     case 'n':
+    case 'm':
+    case 'M':
     case 'R':
+    case 't':
     case 'w':
     case 'x':
       /* We can't have %pmem here.  */
@@ -1680,9 +1780,18 @@ md_assemble (char *op_str)
 	    case 'n':
 	      pru_assemble_arg_n (insn, *argtk++);
 	      continue;
+	    case 'm':
+	      pru_assemble_arg_m (insn, *argtk++);
+	      continue;
+	    case 'M':
+	      pru_assemble_arg_M (insn, *argtk++);
+	      continue;
 	    case 'c':
 	      pru_assemble_arg_c (insn, *argtk++);
 	      continue;
+	    case 't':
+	      pru_assemble_arg_t (insn, *argtk++);
+	      continue;
 	    case 'w':
 	      pru_assemble_arg_w (insn, *argtk++);
 	      continue;
diff --git a/gas/testsuite/gas/pru/mvi.d b/gas/testsuite/gas/pru/mvi.d
new file mode 100644
index 00000000000..626fc0cf0c4
--- /dev/null
+++ b/gas/testsuite/gas/pru/mvi.d
@@ -0,0 +1,17 @@
+#objdump: -dr --prefix-addresses --show-raw-insn
+#name: PRU mvi
+
+# Test the mvi instruction
+
+.*: +file format elf32-pru
+
+Disassembly of section .text:
+0+0000 <[^>]*> 2c204198 	mvib	r24.w0, \*r1.b2
+0+0004 <[^>]*> 2ca20101 	mvid	\*r1.b0, \*r1.b0
+0+0008 <[^>]*> 2ce20101 	mvid	\*r1.b0, \*--r1.b0
+0+000c <[^>]*> 2dc20101 	mvid	\*--r1.b0, \*r1.b0\+\+
+0+0010 <[^>]*> 2cc14101 	mviw	\*r1.b0, \*r1.b2\+\+
+0+0014 <[^>]*> 2cc14121 	mviw	\*r1.b1, \*r1.b2\+\+
+0+0018 <[^>]*> 2c4261f4 	mvid	r20, \*r1.b3\+\+
+0+001c <[^>]*> 2c6241f4 	mvid	r20, \*--r1.b2
+0+0020 <[^>]*> 2d82ff41 	mvid	\*--r1.b2, r31
diff --git a/gas/testsuite/gas/pru/mvi.s b/gas/testsuite/gas/pru/mvi.s
new file mode 100644
index 00000000000..cf23515668f
--- /dev/null
+++ b/gas/testsuite/gas/pru/mvi.s
@@ -0,0 +1,12 @@
+# Source file used to test the MVI instruction.
+
+foo:
+	mvib	r24.w0, *r1.b2
+	mvid	*r1.b0, *r1.b0
+	mvid	*r1.b0, *--r1.b0
+	mvid	*--r1.b0, *r1.b0++
+	mviw	*r1.b0, *r1.b2++
+	mviw	*r1.b1, *r1.b2++
+	mvid	r20, *r1.b3++
+	mvid	r20, *--r1.b2
+	mvid	*--r1.b2, r31
diff --git a/gas/testsuite/gas/pru/tsen.d b/gas/testsuite/gas/pru/tsen.d
new file mode 100644
index 00000000000..5c4779bb015
--- /dev/null
+++ b/gas/testsuite/gas/pru/tsen.d
@@ -0,0 +1,10 @@
+#objdump: -dr --prefix-addresses --show-raw-insn
+#name: PRU tsen
+
+# Test the TSEN instruction
+
+.*: +file format elf32-pru
+
+Disassembly of section .text:
+0+0000 <[^>]*> 32000000 	tsen	0
+0+0004 <[^>]*> 32800000 	tsen	1
diff --git a/gas/testsuite/gas/pru/tsen.s b/gas/testsuite/gas/pru/tsen.s
new file mode 100644
index 00000000000..716d7f476c5
--- /dev/null
+++ b/gas/testsuite/gas/pru/tsen.s
@@ -0,0 +1,5 @@
+# Source file used to test the TSEN instruction.
+
+foo:
+	tsen	0
+	tsen	1
diff --git a/include/opcode/pru.h b/include/opcode/pru.h
index f47860a681c..379b11e56a7 100644
--- a/include/opcode/pru.h
+++ b/include/opcode/pru.h
@@ -64,7 +64,8 @@ enum pru_instr_type
   prui_halt, prui_slp, prui_xin, prui_xout, prui_xchg, prui_sxin,
   prui_sxout, prui_sxchg, prui_loop, prui_iloop, prui_qbgt, prui_qbge,
   prui_qblt, prui_qble, prui_qbeq, prui_qbne, prui_qba, prui_qbbs,
-  prui_qbbc, prui_lbbo, prui_sbbo, prui_lbco, prui_sbco
+  prui_qbbc, prui_lbbo, prui_sbbo, prui_lbco, prui_sbco, prui_tsen,
+  prui_mvib, prui_mviw, prui_mvid
 };
 
 /* This structure holds information for a particular instruction.
@@ -81,11 +82,14 @@ enum pru_instr_type
      j - a 5.3-bit right source register index OR 18-bit PC address
      l - burst length (unsigned 7-bit immediate or r0.b[0-3]) for xLBCO
      n - burst length (unsigned 7-bit immediate or r0.b[0-3]) for XFR
+     m - MVI destination operand
+     M - MVI source operand
      o - a 10-bit signed PC-relative offset
      O - an 8-bit unsigned PC-relative offset for LOOP termination point
      R - a 5-bit destination register index
      s - a 5.3-bit left source register index
      S - a 5-bit left source register index
+     t - task manager mode (0: disabled, 1: enabled)
      w - a single bit for "WakeOnStatus"
      W - a 16-bit unsigned immediate with IO=0 field (LDI)
      x - an 8-bit XFR wide-bus address immediate
@@ -236,15 +240,19 @@ struct pru_reg
 #define SUBOP_LMBD			3u
 #define SUBOP_SCAN			4u
 #define SUBOP_HALT			5u
-#define SUBOP_RSVD_FOR_MVIx		6u
+#define SUBOP_MVIX			6u
 #define SUBOP_XFR			7u
 #define SUBOP_LOOP			8u
+#define SUBOP_TSEN			9u
 #define SUBOP_RSVD_FOR_RFI		14u
 #define SUBOP_SLP			15u
 
 #define OP_SH_WAKEONSTATUS		23
 #define OP_MASK_WAKEONSTATUS		(0x1u << 23)
 
+#define OP_SH_TSKMGR_MODE		23
+#define OP_MASK_TSKMGR_MODE		(0x1u << 23)
+
 /* Format 2 XFR specific fields.  */
 #define OP_SH_SUBOP_XFR			23
 #define OP_MASK_SUBOP_XFR		(3u << 23)
@@ -265,6 +273,23 @@ struct pru_reg
 #define OP_SH_LOOP_JMPOFFS		0
 #define OP_MASK_LOOP_JMPOFFS		(0xffu << 0)
 
+/* Format 2 MVI specific fields.  */
+#define OP_SH_MVI_RS1_MODE		21
+#define OP_MASK_MVI_RS1_MODE		(3u << 21)
+#define OP_SH_MVI_RD_MODE		23
+#define OP_MASK_MVI_RD_MODE		(3u << 23)
+#define OP_SH_MVI_LEN			16
+#define OP_MASK_MVI_LEN			(3u << 16)
+
+#define MVI_OP_MODE_DIRECT		0u
+#define MVI_OP_MODE_INDIRECT		1u
+#define MVI_OP_MODE_INDIRECT_POSTINC	2u
+#define MVI_OP_MODE_INDIRECT_PREDEC	3u
+
+#define MVI_LEN_BYTE			0u
+#define MVI_LEN_WORD			1u
+#define MVI_LEN_DWORD			2u
+
 /* Format 4 specific fields.  */
 #define OP_SH_BROFF98			25
 #define OP_MASK_BROFF98			(0x3u << 25)
@@ -331,6 +356,12 @@ struct pru_reg
 #define OP_MATCH_SCAN	(OP_MATCH_FMT2_OP | (SUBOP_SCAN << OP_SH_SUBOP))
 #define OP_MATCH_HALT	(OP_MATCH_FMT2_OP | (SUBOP_HALT << OP_SH_SUBOP))
 #define OP_MATCH_SLP	(OP_MATCH_FMT2_OP | (SUBOP_SLP << OP_SH_SUBOP))
+#define OP_MATCH_MVIB	(OP_MATCH_FMT2_OP | (SUBOP_MVIX << OP_SH_SUBOP) \
+			 | (MVI_LEN_BYTE << OP_SH_MVI_LEN))
+#define OP_MATCH_MVIW	(OP_MATCH_FMT2_OP | (SUBOP_MVIX << OP_SH_SUBOP) \
+			 | (MVI_LEN_WORD << OP_SH_MVI_LEN))
+#define OP_MATCH_MVID	(OP_MATCH_FMT2_OP | (SUBOP_MVIX << OP_SH_SUBOP) \
+			 | (MVI_LEN_DWORD << OP_SH_MVI_LEN))
 #define OP_MATCH_XFR	(OP_MATCH_FMT2_OP | (SUBOP_XFR << OP_SH_SUBOP))
 #define OP_MATCH_SXFR	(OP_MATCH_XFR | OP_MASK_XFR_S)
 #define OP_MATCH_XIN	(OP_MATCH_XFR | (SUBOP_XFR_XIN << OP_SH_SUBOP_XFR))
@@ -342,6 +373,7 @@ struct pru_reg
 #define OP_MATCH_LOOP	(OP_MATCH_FMT2_OP | (SUBOP_LOOP << OP_SH_SUBOP))
 #define OP_MATCH_ILOOP	(OP_MATCH_FMT2_OP | (SUBOP_LOOP << OP_SH_SUBOP) \
 			 | OP_MASK_LOOP_INTERRUPTIBLE)
+#define OP_MATCH_TSEN	(OP_MATCH_FMT2_OP | (SUBOP_TSEN << OP_SH_SUBOP))
 
 #define OP_MATCH_QBGT	(OP_MATCH_FMT4_OP | OP_MASK_GT)
 #define OP_MATCH_QBGE	(OP_MATCH_FMT4_OP | OP_MASK_GT | OP_MASK_EQ)
@@ -394,6 +426,9 @@ struct pru_reg
 #define OP_MASK_LOOP_OP		(OP_MASK_FMT2_OP | OP_MASK_SUBOP \
 				 | OP_MASK_LOOP_INTERRUPTIBLE)
 
+#define OP_MASK_MVIX_OP		(OP_MASK_FMT2_OP | OP_MASK_SUBOP \
+				 | OP_MASK_MVI_LEN)
+
 /* These are the data structures we use to hold the instruction information.  */
 extern const struct pru_opcode pru_opcodes[];
 extern const int bfd_pru_num_opcodes;
diff --git a/opcodes/pru-dis.c b/opcodes/pru-dis.c
index 5465c26a138..5cda4e9d6b4 100644
--- a/opcodes/pru-dis.c
+++ b/opcodes/pru-dis.c
@@ -66,13 +66,31 @@ pru_find_opcode (unsigned long opcode)
 #define NUMREGNAMES (32 * 8)
 
 static void
-pru_print_insn_arg_reg (unsigned int r, unsigned int sel,
-			disassemble_info *info)
+pru_print_insn_arg_indreg (unsigned int r, unsigned int sel,
+			   unsigned int mode,
+			   disassemble_info *info)
 {
+  const char *fmtstr;
   unsigned int i = r * RSEL_NUM_ITEMS + sel;
   assert (i < (unsigned int)pru_num_regs);
   assert (i < NUMREGNAMES);
-  (*info->fprintf_func) (info->stream, "%s", pru_regs[i].name);
+
+  switch (mode)
+    {
+    case MVI_OP_MODE_DIRECT:		fmtstr = "%s"; break;
+    case MVI_OP_MODE_INDIRECT:		fmtstr = "*%s"; break;
+    case MVI_OP_MODE_INDIRECT_POSTINC:	fmtstr = "*%s++"; break;
+    case MVI_OP_MODE_INDIRECT_PREDEC:	fmtstr = "*--%s"; break;
+    default:				fmtstr = "<invalid>%s"; break;
+    }
+  (*info->fprintf_func) (info->stream, fmtstr, pru_regs[i].name);
+}
+
+static void
+pru_print_insn_arg_reg (unsigned int r, unsigned int sel,
+			disassemble_info *info)
+{
+  pru_print_insn_arg_indreg (r, sel, MVI_OP_MODE_DIRECT, info);
 }
 
 /* The function pru_print_insn_arg uses the character pointed
@@ -186,6 +204,18 @@ pru_print_insn_arg (const char *argptr,
 	  (*info->fprintf_func) (info->stream, "r0.b%ld", i);
 	}
       break;
+    case 'm':
+      pru_print_insn_arg_indreg (GET_INSN_FIELD (RD, opcode),
+				 GET_INSN_FIELD (RDSEL, opcode),
+				 GET_INSN_FIELD (MVI_RD_MODE, opcode),
+				 info);
+      break;
+    case 'M':
+      pru_print_insn_arg_indreg (GET_INSN_FIELD (RS1, opcode),
+				 GET_INSN_FIELD (RS1SEL, opcode),
+				 GET_INSN_FIELD (MVI_RS1_MODE, opcode),
+				 info);
+      break;
     case 'n':
       i = GET_INSN_FIELD (XFR_LENGTH, opcode);
       if (i < LSSBBO_BYTECOUNT_R0_BITS7_0)
@@ -200,6 +230,10 @@ pru_print_insn_arg (const char *argptr,
       i = GET_INSN_FIELD (CB, opcode);
       (*info->fprintf_func) (info->stream, "%ld", i);
       break;
+    case 't':
+      i = GET_INSN_FIELD (TSKMGR_MODE, opcode);
+      (*info->fprintf_func) (info->stream, "%ld", i);
+      break;
     case 'w':
       i = GET_INSN_FIELD (WAKEONSTATUS, opcode);
       (*info->fprintf_func) (info->stream, "%ld", i);
diff --git a/opcodes/pru-opc.c b/opcodes/pru-opc.c
index ee7fb16d8d4..aeef5224b29 100644
--- a/opcodes/pru-opc.c
+++ b/opcodes/pru-opc.c
@@ -125,9 +125,18 @@ const struct pru_opcode pru_opcodes[] =
    OP_MATCH_LMBD, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, unsigned_immed8_overflow},
   { "halt", prui_halt, "",
    OP_MATCH_HALT, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, no_overflow},
+  { "tsen", prui_tsen, "t",
+   OP_MATCH_TSEN, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, no_overflow},
   { "slp", prui_slp, "w",
    OP_MATCH_SLP, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, no_overflow},
 
+  { "mvib", prui_mvib, "m,M",
+   OP_MATCH_MVIB, OP_MASK_MVIX_OP, 0, no_overflow},
+  { "mviw", prui_mviw, "m,M",
+   OP_MATCH_MVIW, OP_MASK_MVIX_OP, 0, no_overflow},
+  { "mvid", prui_mvid, "m,M",
+   OP_MATCH_MVID, OP_MASK_MVIX_OP, 0, no_overflow},
+
   { "xin", prui_xin, "x,D,n",
    OP_MATCH_XIN, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
   { "xout", prui_xout, "x,D,n",


More information about the Binutils-cvs mailing list