[PATCH] gas: pru: Add flag to select core revision

Dimitar Dimitrov dimitar@dinux.eu
Sun Mar 8 20:15:25 GMT 2026


Add new command line option to select PRU core revision to assemble for.
This new option is used to limit the available opcodes to what the
selected revision supports.

References:
  - https://www.ti.com/lit/ug/spruij2/spruij2.pdf
    Table 3. PRU Core Revision Comparison
  - https://github.com/TexasInstruments/open-pru/pull/117
    docs/PRU Assembly Instruction Cheat Sheet.md
    Core Revision Differences

Signed-off-by: Dimitar Dimitrov <dimitar@dinux.eu>
---
 gas/config/tc-pru.c                | 49 ++++++++++++++++-
 gas/doc/c-pru.texi                 | 13 ++++-
 gas/testsuite/gas/pru/core-rev-1.l |  2 +
 gas/testsuite/gas/pru/core-rev-1.s |  7 +++
 gas/testsuite/gas/pru/core-rev-2.l |  2 +
 gas/testsuite/gas/pru/core-rev-2.s |  8 +++
 gas/testsuite/gas/pru/pru.exp      |  2 +
 gas/testsuite/gas/pru/tsen.d       |  1 +
 include/opcode/pru.h               | 11 ++++
 opcodes/pru-opc.c                  | 84 +++++++++++++++---------------
 10 files changed, 134 insertions(+), 45 deletions(-)
 create mode 100644 gas/testsuite/gas/pru/core-rev-1.l
 create mode 100644 gas/testsuite/gas/pru/core-rev-1.s
 create mode 100644 gas/testsuite/gas/pru/core-rev-2.l
 create mode 100644 gas/testsuite/gas/pru/core-rev-2.s

diff --git a/gas/config/tc-pru.c b/gas/config/tc-pru.c
index fe096f8e93c..74b3df95ab0 100644
--- a/gas/config/tc-pru.c
+++ b/gas/config/tc-pru.c
@@ -71,9 +71,13 @@ struct pru_opt_s
   /* -mno-warn-regname-label: do not output a warning that a label name
      matches a register name.  */
   bool warn_regname_label;
+
+  /* -mcore-revision: Select PRU core revision, to determine which
+     opcodes are supported.  */
+  enum pru_core_revision core_rev;
 };
 
-static struct pru_opt_s pru_opt = { true, true };
+static struct pru_opt_s pru_opt = { true, true, REV_V3 };
 
 const char md_shortopts[] = "r";
 
@@ -82,6 +86,7 @@ enum options
   OPTION_LINK_RELAX = OPTION_MD_BASE + 1,
   OPTION_NO_LINK_RELAX,
   OPTION_NO_WARN_REGNAME_LABEL,
+  OPTION_CORE_REVISION,
 };
 
 const struct option md_longopts[] = {
@@ -89,11 +94,25 @@ const struct option md_longopts[] = {
   { "mno-link-relax",  no_argument, NULL, OPTION_NO_LINK_RELAX  },
   { "mno-warn-regname-label",  no_argument, NULL,
     OPTION_NO_WARN_REGNAME_LABEL  },
+  { "mcore-revision",  required_argument, NULL, OPTION_CORE_REVISION  },
   { NULL, no_argument, NULL, 0 }
 };
 
 const size_t md_longopts_size = sizeof (md_longopts);
 
+struct pru_core_rev_str_entry
+{
+  enum pru_core_revision core_rev;
+  const char *str;
+};
+
+static const struct pru_core_rev_str_entry pru_core_rev_table[] = {
+  { REV_V1, "V1" },
+  { REV_V2, "V2" },
+  { REV_V3, "V3" },
+  { REV_V4, "V4" },
+};
+
 typedef struct pru_insn_reloc
 {
   /* Any expression in the instruction is parsed into this field,
@@ -1613,7 +1632,7 @@ output_insn_ldi32 (pru_insn_infoS *insn)
 /* The following functions are called by machine-independent parts of
    the assembler.  */
 int
-md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED)
+md_parse_option (int c, const char *arg)
 {
   switch (c)
     {
@@ -1630,6 +1649,20 @@ md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED)
     case OPTION_NO_WARN_REGNAME_LABEL:
       pru_opt.warn_regname_label = false;
       break;
+    case OPTION_CORE_REVISION:
+	{
+	  size_t i;
+	  for (i = 0; i < ARRAY_SIZE (pru_core_rev_table); i++)
+	    {
+	      if (!strcmp (arg, pru_core_rev_table[i].str))
+		break;
+	    }
+	  if (i == ARRAY_SIZE (pru_core_rev_table))
+	    as_bad (_("invalid core revision %s"), arg);
+	  else
+	    pru_opt.core_rev = pru_core_rev_table[i].core_rev;
+	}
+      break;
     default:
       return 0;
       break;
@@ -1720,6 +1753,18 @@ md_assemble (char *op_str)
       /* Set the opcode for the instruction.  */
       insn->insn_code = insn->insn_pru_opcode->match;
 
+      /* Opcodes for older core revisions are not proper subsets of
+	 newer core revisions, as specified by TI.  There are opcodes
+	 in old cores which are not present in newer ones (e.g. SCAN).
+	 But the currently implemented opcodes in assembler are proper
+	 subsets, so we can use a simple "linear" check here.  */
+      if (pru_opt.core_rev < insn->insn_pru_opcode->core_rev)
+	{
+	  as_bad (_("instruction %s is not valid for selected core revision"),
+		  insn->insn_tokens[0]);
+	  return;
+	}
+
       if (pru_mode == PRU_MODE_TEST)
 	{
 	  /* Add the "expected" instruction parameter used for validation.  */
diff --git a/gas/doc/c-pru.texi b/gas/doc/c-pru.texi
index 5c17e874f2d..8cfb896a444 100644
--- a/gas/doc/c-pru.texi
+++ b/gas/doc/c-pru.texi
@@ -43,6 +43,16 @@ Do not warn if a label name matches a register name. Usually assembler
 programmers will want this warning to be emitted. C compilers may want
 to turn this off.
 
+@cindex @code{mcore-revision=} command-line option, PRU
+@item -mcore-revision=@var{revision}
+This option specifies the PRU core revision to assemble for.
+The following revisions are recognized:
+@code{V1},
+@code{V2},
+@code{V3} and
+@code{V4}.
+The assembler defaults to @code{V3}.
+
 @end table
 @c man end
 
@@ -139,7 +149,8 @@ to passing the @code{-mno-warn-regname-label} command-line option.
 @cindex PRU opcodes
 @cindex opcodes for PRU
 @code{@value{AS}} implements all the standard PRU core V3 opcodes in the
-original pasm assembler.  Older cores are not supported by @code{@value{AS}}.
+original pasm assembler.  Opcodes unique to older cores are not
+supported by @code{@value{AS}}.
 
 GAS also implements the LDI32 pseudo instruction for loading a 32-bit
 immediate value into a register.
diff --git a/gas/testsuite/gas/pru/core-rev-1.l b/gas/testsuite/gas/pru/core-rev-1.l
new file mode 100644
index 00000000000..0aee8e35d97
--- /dev/null
+++ b/gas/testsuite/gas/pru/core-rev-1.l
@@ -0,0 +1,2 @@
+.*core-rev-1.s: Assembler messages:
+.*core-rev-1.s:7: Error: instruction xin is not valid for selected core revision
diff --git a/gas/testsuite/gas/pru/core-rev-1.s b/gas/testsuite/gas/pru/core-rev-1.s
new file mode 100644
index 00000000000..068bfa93fe8
--- /dev/null
+++ b/gas/testsuite/gas/pru/core-rev-1.s
@@ -0,0 +1,7 @@
+# Source file used to test illegal opcodes.
+
+foo:
+	add	r1, r2, 101
+	jmp	r2
+# Not available in V1
+	xin	0, r10, 1
diff --git a/gas/testsuite/gas/pru/core-rev-2.l b/gas/testsuite/gas/pru/core-rev-2.l
new file mode 100644
index 00000000000..278b7f2b383
--- /dev/null
+++ b/gas/testsuite/gas/pru/core-rev-2.l
@@ -0,0 +1,2 @@
+.*core-rev-2.s: Assembler messages:
+.*core-rev-2.s:8: Error: instruction sxin is not valid for selected core revision
diff --git a/gas/testsuite/gas/pru/core-rev-2.s b/gas/testsuite/gas/pru/core-rev-2.s
new file mode 100644
index 00000000000..2d5d78bc5cc
--- /dev/null
+++ b/gas/testsuite/gas/pru/core-rev-2.s
@@ -0,0 +1,8 @@
+# Source file used to test illegal opcodes.
+
+foo:
+	add	r1, r2, 101
+	jmp	r2
+	xin	0, r10, 1
+# Not available in V1
+	sxin	0, r10, 1
diff --git a/gas/testsuite/gas/pru/pru.exp b/gas/testsuite/gas/pru/pru.exp
index ecd5b74b33c..8378af376a8 100644
--- a/gas/testsuite/gas/pru/pru.exp
+++ b/gas/testsuite/gas/pru/pru.exp
@@ -24,4 +24,6 @@ if { [istarget pru-*-*] } {
     run_list_test "illegal" ""
     run_list_test "illegal2" ""
     run_list_test "warn_reglabel" ""
+    run_list_test "core-rev-1" "-mcore-revision=V1"
+    run_list_test "core-rev-2" "-mcore-revision=V2"
 }
diff --git a/gas/testsuite/gas/pru/tsen.d b/gas/testsuite/gas/pru/tsen.d
index 5c4779bb015..25448ad87d5 100644
--- a/gas/testsuite/gas/pru/tsen.d
+++ b/gas/testsuite/gas/pru/tsen.d
@@ -1,5 +1,6 @@
 #objdump: -dr --prefix-addresses --show-raw-insn
 #name: PRU tsen
+#as: -mcore-revision=V4
 
 # Test the TSEN instruction
 
diff --git a/include/opcode/pru.h b/include/opcode/pru.h
index 379b11e56a7..dccd0c8305a 100644
--- a/include/opcode/pru.h
+++ b/include/opcode/pru.h
@@ -30,6 +30,15 @@
  * access various opcode fields.
  ****************************************************************************/
 
+/* PRU core revision, as defined by TI.  */
+enum pru_core_revision
+{
+  REV_V1 = 1,
+  REV_V2,
+  REV_V3,
+  REV_V4,
+};
+
 /* Identify different overflow situations for error messages.  */
 enum overflow_type
 {
@@ -115,6 +124,8 @@ enum pru_instr_type
 struct pru_opcode
 {
   const char *name;		/* The name of the instruction.  */
+  enum pru_core_revision core_rev; /* First PRU core revision supporting
+				      this instruction.  */
   enum pru_instr_type type;	/* Instruction type. Used for fast indexing
 				   by the simulator.  */
   const char *args;		/* A string describing the arguments for this
diff --git a/opcodes/pru-opc.c b/opcodes/pru-opc.c
index aeef5224b29..cbd25b81654 100644
--- a/opcodes/pru-opc.c
+++ b/opcodes/pru-opc.c
@@ -91,7 +91,7 @@ const struct pru_opcode pru_opcodes[] =
   /* { name, args,
        match, mask, pinfo, overflow_msg } */
 #define DECLARE_FORMAT1_OPCODE(str, subop) \
-  { #str, prui_ ## str, "d,s,b", \
+  { #str, REV_V1, prui_ ## str, "d,s,b", \
     OP_MATCH_ ## subop, OP_MASK_FMT1_OP | OP_MASK_SUBOP, 0, \
     unsigned_immed8_overflow }
 
@@ -111,86 +111,86 @@ const struct pru_opcode pru_opcodes[] =
   DECLARE_FORMAT1_OPCODE (clr, CLR),
   DECLARE_FORMAT1_OPCODE (set, SET),
 
-  { "not", prui_not, "d,s",
+  { "not", REV_V1, prui_not, "d,s",
    OP_MATCH_NOT | OP_MASK_IO,
    OP_MASK_FMT1_OP | OP_MASK_SUBOP | OP_MASK_IO, 0, no_overflow},
 
-  { "jmp", prui_jmp, "j",
+  { "jmp", REV_V1, prui_jmp, "j",
    OP_MATCH_JMP, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, unsigned_immed16_overflow},
-  { "jal", prui_jal, "d,j",
+  { "jal", REV_V1, prui_jal, "d,j",
    OP_MATCH_JAL, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, unsigned_immed16_overflow},
-  { "ldi", prui_ldi, "d,W",
+  { "ldi", REV_V1, prui_ldi, "d,W",
    OP_MATCH_LDI, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, unsigned_immed16_overflow},
-  { "lmbd", prui_lmbd, "d,s,b",
+  { "lmbd", REV_V1, prui_lmbd, "d,s,b",
    OP_MATCH_LMBD, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, unsigned_immed8_overflow},
-  { "halt", prui_halt, "",
+  { "halt", REV_V1, prui_halt, "",
    OP_MATCH_HALT, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, no_overflow},
-  { "tsen", prui_tsen, "t",
+  { "tsen", REV_V4, prui_tsen, "t",
    OP_MATCH_TSEN, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, no_overflow},
-  { "slp", prui_slp, "w",
+  { "slp", REV_V3, prui_slp, "w",
    OP_MATCH_SLP, OP_MASK_FMT2_OP | OP_MASK_SUBOP, 0, no_overflow},
 
-  { "mvib", prui_mvib, "m,M",
+  { "mvib", REV_V2, prui_mvib, "m,M",
    OP_MATCH_MVIB, OP_MASK_MVIX_OP, 0, no_overflow},
-  { "mviw", prui_mviw, "m,M",
+  { "mviw", REV_V2, prui_mviw, "m,M",
    OP_MATCH_MVIW, OP_MASK_MVIX_OP, 0, no_overflow},
-  { "mvid", prui_mvid, "m,M",
+  { "mvid", REV_V2, prui_mvid, "m,M",
    OP_MATCH_MVID, OP_MASK_MVIX_OP, 0, no_overflow},
 
-  { "xin", prui_xin, "x,D,n",
+  { "xin", REV_V2, prui_xin, "x,D,n",
    OP_MATCH_XIN, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
-  { "xout", prui_xout, "x,D,n",
+  { "xout", REV_V2, prui_xout, "x,D,n",
    OP_MATCH_XOUT, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
-  { "xchg", prui_xchg, "x,D,n",
+  { "xchg", REV_V2, prui_xchg, "x,D,n",
    OP_MATCH_XCHG, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
-  { "sxin", prui_sxin, "x,D,n",
+  { "sxin", REV_V3, prui_sxin, "x,D,n",
    OP_MATCH_SXIN, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
-  { "sxout", prui_sxout, "x,D,n",
+  { "sxout", REV_V3, prui_sxout, "x,D,n",
    OP_MATCH_SXOUT, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
-  { "sxchg", prui_sxchg, "x,D,n",
+  { "sxchg", REV_V3, prui_sxchg, "x,D,n",
    OP_MATCH_SXCHG, OP_MASK_XFR_OP, 0, unsigned_immed8_overflow},
 
-  { "loop", prui_loop, "O,B",
+  { "loop", REV_V3, prui_loop, "O,B",
    OP_MATCH_LOOP, OP_MASK_LOOP_OP, 0, unsigned_immed8_overflow},
-  { "iloop", prui_loop, "O,B",
+  { "iloop", REV_V3, prui_loop, "O,B",
    OP_MATCH_ILOOP, OP_MASK_LOOP_OP, 0, unsigned_immed8_overflow},
 
-  { "qbgt", prui_qbgt, "o,s,b",
+  { "qbgt", REV_V1, prui_qbgt, "o,s,b",
    OP_MATCH_QBGT, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
-  { "qbge", prui_qbge, "o,s,b",
+  { "qbge", REV_V1, prui_qbge, "o,s,b",
    OP_MATCH_QBGE, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
-  { "qblt", prui_qblt, "o,s,b",
+  { "qblt", REV_V1, prui_qblt, "o,s,b",
    OP_MATCH_QBLT, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
-  { "qble", prui_qble, "o,s,b",
+  { "qble", REV_V1, prui_qble, "o,s,b",
    OP_MATCH_QBLE, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
-  { "qbeq", prui_qbeq, "o,s,b",
+  { "qbeq", REV_V1, prui_qbeq, "o,s,b",
    OP_MATCH_QBEQ, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
-  { "qbne", prui_qbne, "o,s,b",
+  { "qbne", REV_V1, prui_qbne, "o,s,b",
    OP_MATCH_QBNE, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
-  { "qba", prui_qba, "o",
+  { "qba", REV_V1, prui_qba, "o",
    OP_MATCH_QBA, OP_MASK_FMT4_OP | OP_MASK_CMP, 0, qbranch_target_overflow},
 
-  { "qbbs", prui_qbbs, "o,s,b",
+  { "qbbs", REV_V1, prui_qbbs, "o,s,b",
    OP_MATCH_QBBS, OP_MASK_FMT5_OP | OP_MASK_BCMP, 0, qbranch_target_overflow},
-  { "qbbc", prui_qbbc, "o,s,b",
+  { "qbbc", REV_V1, prui_qbbc, "o,s,b",
    OP_MATCH_QBBC, OP_MASK_FMT5_OP | OP_MASK_BCMP, 0, qbranch_target_overflow},
 
-  { "lbbo", prui_lbbo, "D,S,b,l",
+  { "lbbo", REV_V1, prui_lbbo, "D,S,b,l",
    OP_MATCH_LBBO, OP_MASK_FMT6AB_OP | OP_MASK_LOADSTORE, 0,
    unsigned_immed8_overflow},
-  { "sbbo", prui_sbbo, "D,S,b,l",
+  { "sbbo", REV_V1, prui_sbbo, "D,S,b,l",
    OP_MATCH_SBBO, OP_MASK_FMT6AB_OP | OP_MASK_LOADSTORE, 0,
    unsigned_immed8_overflow},
-  { "lbco", prui_lbco, "D,c,b,l",
+  { "lbco", REV_V1, prui_lbco, "D,c,b,l",
    OP_MATCH_LBCO, OP_MASK_FMT6CD_OP | OP_MASK_LOADSTORE, 0,
    unsigned_immed8_overflow},
-  { "sbco", prui_sbco, "D,c,b,l",
+  { "sbco", REV_V1, prui_sbco, "D,c,b,l",
    OP_MATCH_SBCO, OP_MASK_FMT6CD_OP | OP_MASK_LOADSTORE, 0,
    unsigned_immed8_overflow},
 
   /* Fill in the default values for the real-instruction arguments.
      The assembler will not do it!  */
-  { "nop", prui_or, "",
+  { "nop", REV_V1, prui_or, "",
    OP_MATCH_OR
      | (RSEL_31_0 << OP_SH_RS2SEL) | (0 << OP_SH_RS2)
      | (RSEL_31_0 << OP_SH_RS1SEL) | (0 << OP_SH_RS1)
@@ -199,42 +199,42 @@ const struct pru_opcode pru_opcodes[] =
      | OP_MASK_RS2SEL | OP_MASK_RS2 | OP_MASK_RS1SEL | OP_MASK_RS1
      | OP_MASK_RDSEL | OP_MASK_RD | OP_MASK_IO,
    PRU_INSN_MACRO, no_overflow},
-  { "mov", prui_or, "d,s",
+  { "mov", REV_V1, prui_or, "d,s",
    OP_MATCH_OR | (0 << OP_SH_IMM8) | OP_MASK_IO,
    OP_MASK_FMT1_OP | OP_MASK_SUBOP | OP_MASK_IMM8 | OP_MASK_IO,
    PRU_INSN_MACRO, no_overflow},
-  { "ret", prui_jmp, "",
+  { "ret", REV_V1, prui_jmp, "",
    OP_MATCH_JMP
      | (RSEL_31_16 << OP_SH_RS2SEL) | (3 << OP_SH_RS2),
    OP_MASK_FMT2_OP | OP_MASK_SUBOP
      | OP_MASK_RS2SEL | OP_MASK_RS2 | OP_MASK_IO,
    PRU_INSN_MACRO, unsigned_immed16_overflow},
-  { "call", prui_jal, "j",
+  { "call", REV_V1, prui_jal, "j",
    OP_MATCH_JAL
      | (RSEL_31_16 << OP_SH_RDSEL) | (3 << OP_SH_RD),
    OP_MASK_FMT2_OP | OP_MASK_SUBOP
      | OP_MASK_RDSEL | OP_MASK_RD,
    PRU_INSN_MACRO, unsigned_immed16_overflow},
 
-  { "wbc", prui_qbbs, "s,b",
+  { "wbc", REV_V1, prui_qbbs, "s,b",
    OP_MATCH_QBBS | (0 << OP_SH_BROFF98) | (0 << OP_SH_BROFF70),
    OP_MASK_FMT5_OP | OP_MASK_BCMP | OP_MASK_BROFF,
    PRU_INSN_MACRO, qbranch_target_overflow},
-  { "wbs", prui_qbbc, "s,b",
+  { "wbs", REV_V1, prui_qbbc, "s,b",
    OP_MATCH_QBBC | (0 << OP_SH_BROFF98) | (0 << OP_SH_BROFF70),
    OP_MASK_FMT5_OP | OP_MASK_BCMP | OP_MASK_BROFF,
    PRU_INSN_MACRO, qbranch_target_overflow},
 
-  { "fill", prui_xin, "D,n",
+  { "fill", REV_V3, prui_xin, "D,n",
    OP_MATCH_XIN | (254 << OP_SH_XFR_WBA),
    OP_MASK_XFR_OP | OP_MASK_XFR_WBA,
    PRU_INSN_MACRO, unsigned_immed8_overflow},
-  { "zero", prui_xin, "D,n",
+  { "zero", REV_V3, prui_xin, "D,n",
    OP_MATCH_XIN | (255 << OP_SH_XFR_WBA),
    OP_MASK_XFR_OP | OP_MASK_XFR_WBA,
    PRU_INSN_MACRO, unsigned_immed8_overflow},
 
-  { "ldi32", prui_ldi, "R,i",
+  { "ldi32", REV_V1, prui_ldi, "R,i",
    OP_MATCH_LDI, OP_MASK_FMT2_OP | OP_MASK_SUBOP,
    PRU_INSN_LDI32, unsigned_immed32_overflow},
 };
-- 
2.53.0



More information about the Binutils mailing list