]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gas: pru: Add flag to select core revision
authorDimitar Dimitrov <dimitar@dinux.eu>
Fri, 6 Mar 2026 21:40:23 +0000 (23:40 +0200)
committerDimitar Dimitrov <dimitar@dinux.eu>
Sun, 15 Mar 2026 07:44:02 +0000 (09:44 +0200)
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/blob/3cffcaae73c708ce15337a9880ec91dc9b03aaa5/docs/PRU%20Assembly%20Instruction%20Cheat%20Sheet.md#core-revision-differences
    Core Revision Differences

Signed-off-by: Dimitar Dimitrov <dimitar@dinux.eu>
gas/config/tc-pru.c
gas/doc/c-pru.texi
gas/testsuite/gas/pru/core-rev-1.l [new file with mode: 0644]
gas/testsuite/gas/pru/core-rev-1.s [new file with mode: 0644]
gas/testsuite/gas/pru/core-rev-2.l [new file with mode: 0644]
gas/testsuite/gas/pru/core-rev-2.s [new file with mode: 0644]
gas/testsuite/gas/pru/pru.exp
gas/testsuite/gas/pru/tsen.d
include/opcode/pru.h
opcodes/pru-opc.c

index fe096f8e93c43128f163fc19b5c2cf14c3371710..74b3df95ab0e7c8e07fe7f1430476ee89b86170f 100644 (file)
@@ -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.  */
index 5c17e874f2d4b588f827c6d31f2860fdc509c06e..8cfb896a4440cb9a315a3f214689910395b6c53e 100644 (file)
@@ -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 (file)
index 0000000..0aee8e3
--- /dev/null
@@ -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 (file)
index 0000000..068bfa9
--- /dev/null
@@ -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 (file)
index 0000000..278b7f2
--- /dev/null
@@ -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 (file)
index 0000000..2d5d78b
--- /dev/null
@@ -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
index ecd5b74b33c74c7a2c72957ea7502bb0fe3f775b..8378af376a802835f5ab53d63121f7f5b2e058b2 100644 (file)
@@ -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"
 }
index 5c4779bb015d388986d4773725d03846d77c23d3..25448ad87d52bf014a5475288104857cc9577ca8 100644 (file)
@@ -1,5 +1,6 @@
 #objdump: -dr --prefix-addresses --show-raw-insn
 #name: PRU tsen
+#as: -mcore-revision=V4
 
 # Test the TSEN instruction
 
index 379b11e56a72346313f9c8db6d3dd34e7fc4f9a6..dccd0c8305a2c94fdbe55e467cc70b188f970be3 100644 (file)
  * 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
index aeef5224b298b2856ba3802b602d0aa0307fb04d..cbd25b816542946759a7b7e2303d877129f9e397 100644 (file)
@@ -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},
 };