Repository: verilator/verilator
Pull request #5884: Add support for setuphold
Author: login: ksychla
Created: 2025-03-26T10:14:16Z | Merged: 2025-04-02T10:08:51Z

This PR adds support for $setuphold system task explicit delayed signals assignment. It does not implement the timing check. The setuphold calls are replaced in the AST with wire assignments for the delayed signals, like so:
``` 
specify
    $setuphold (posedge clk, negedge d, 0, 0, notifier,,, delayed_CLK, delayed_D);
endspecify
```
Replaced with:
```
assign delayed_CLK = clk;
assign delayed_D = d;
```
Along with the UDP changes (https://github.com/verilator/verilator/pull/5807), this will allow for post-synthesis simulation with PDKs such as ASAP7 (tested with OpenROAD-flow-scripts `aes` example).


Diff:
diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS
index 87d0c7cc455..f9717895643 100644
--- a/docs/CONTRIBUTORS
+++ b/docs/CONTRIBUTORS
@@ -134,6 +134,7 @@ Krzysztof Boronski
 Krzysztof Boroński
 Krzysztof Obłonczek
 Krzysztof Starecki
+Krzysztof Sychla
 Kuba Ober
 Larry Doolittle
 Liam Braun
diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h
index dd56e2ce752..a99bf7e0f53 100644
--- a/src/V3AstNodeOther.h
+++ b/src/V3AstNodeOther.h
@@ -3339,6 +3339,24 @@ class AstSFormat final : public AstNodeStmt {
     int instrCount() const override { return INSTR_COUNT_PLI; }
     bool sameNode(const AstNode* /*samep*/) const override { return true; }
 };
+class AstSetuphold final : public AstNodeStmt {
+    // Verilog $setuphold
+    // @astgen op1 := refevp : AstNodeExpr
+    // @astgen op2 := dataevp : AstNodeExpr
+    // @astgen op3 := delrefp : Optional[AstNodeExpr]
+    // @astgen op4 := deldatap : Optional[AstNodeExpr]
+public:
+    AstSetuphold(FileLine* fl, AstNodeExpr* refevp, AstNodeExpr* dataevp,
+                 AstNodeExpr* delrefp = nullptr, AstNodeExpr* deldatap = nullptr)
+        : ASTGEN_SUPER_Setuphold(fl) {
+        this->refevp(refevp);
+        this->dataevp(dataevp);
+        this->delrefp(delrefp);
+        this->deldatap(deldatap);
+    }
+    ASTGEN_MEMBERS_AstSetuphold;
+    bool sameNode(const AstNode* /*samep*/) const override { return true; }
+};
 class AstStackTraceT final : public AstNodeStmt {
     // $stacktrace used as task
 public:
diff --git a/src/V3Width.cpp b/src/V3Width.cpp
index 43b0715ebc0..cc6575e3b8a 100644
--- a/src/V3Width.cpp
+++ b/src/V3Width.cpp
@@ -1377,6 +1377,46 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+        AstAssignW* newp = nullptr;
+        if (nodep->delrefp()) {
+            newp = convertSetupholdToAssign(flp, nodep->refevp(), nodep->delrefp());
+        }
+        if (nodep->deldatap()) {
+            if (!newp) {
+                newp = convertSetupholdToAssign(flp, nodep->dataevp(), nodep->deldatap());
+            } else {
+                newp->addNextHere(
+                    convertSetupholdToAssign(flp, nodep->dataevp(), nodep->deldatap()));
+            }
+        }
+        if (!newp) {
+            pushDeletep(nodep->unlinkFrBack());
+            return;
+        }
+        nodep->replaceWith(newp);
+    }
+
+    AstAssignW* convertSetupholdToAssign(FileLine* const flp, AstNodeExpr* const evp,
+                                         AstNodeExpr* const delp) {
+        AstNodeExpr* const lhsp = delp->cloneTreePure(false);
+        AstNodeExpr* const rhsp = evp->cloneTreePure(false);
+        UASSERT_OBJ(VN_IS(lhsp, NodeVarRef) || VN_IS(lhsp, NodePreSel), lhsp,
+                    "Incorrect reference in a timing check");
+        if (AstNodeVarRef* varRefp = VN_CAST(lhsp, NodeVarRef)) {
+            if (varRefp->varp()->direction() == VDirection::INPUT) { return nullptr; }
+            varRefp->access(VAccess::WRITE);
+        }
+        if (AstNodePreSel* selp = VN_CAST(lhsp, NodePreSel)) {
+            if (AstNodeVarRef* varRefp = VN_CAST(selp->fromp(), NodeVarRef)) {
+                if (varRefp->varp()->direction() == VDirection::INPUT) { return nullptr; }
+                varRefp->access(VAccess::WRITE);
+            }
+        }
+        return new AstAssignW{flp, lhsp, rhsp};
+    }
+
     void visit(AstStable* nodep) override {
         if (m_vup->prelim()) {
             iterateCheckSizedSelf(nodep, "LHS", nodep->exprp(), SELF, BOTH);
diff --git a/src/verilog.l b/src/verilog.l
index 11b60630713..fc6b1a17f65 100644
--- a/src/verilog.l
+++ b/src/verilog.l
@@ -79,7 +79,7 @@ static double lexParseDouble(FileLine* fl, const char* textp, size_t length) {
 %o 25000
 
 %s V95 V01NC V01C V05 S05 S09 S12 S17 S23
-%s ATTRMODE QQQ STRING TABLE
+%s ATTRMODE QQQ STRING TABLE EDGEDESC
 %s VA5 SAX VLT
 %s SYSCHDR SYSCINT SYSCIMP SYSCIMPH SYSCCTOR SYSCDTOR
 
@@ -271,7 +271,7 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "$rtoi"               { FL; return yD_RTOI; }
   "$sampled"            { FL; return yD_SAMPLED; }
   "$setup"              { FL; return yaTIMINGSPEC; }
-  "$setuphold"          { FL; return yaTIMINGSPEC; }
+  "$setuphold"          { FL; return yD_SETUPHOLD; }
   "$sformat"            { FL; return yD_SFORMAT; }
   "$sformatf"           { FL; return yD_SFORMATF; }
   "$shortrealtobits"    { FL; return yD_SHORTREALTOBITS; }
@@ -328,6 +328,7 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "default"             { FL; return yDEFAULT; }
   "defparam"            { FL; return yDEFPARAM; }
   "disable"             { FL; return yDISABLE; }
+  "edge"/{ws}*"["       { FL; yy_push_state(EDGEDESC); return yEDGE; }
   "edge"                { FL; return yEDGE; }
   "else"                { FL; return yELSE; }
   "end"                 { FL; return yEND; }
@@ -1023,6 +1024,13 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
 <TABLE><<EOF>>          { FL; yylval.fl->v3error("EOF in 'table'");
                           yyleng = 0; yy_pop_state(); FL_BRK; yyterminate(); }
 
+<EDGEDESC>{
+  01|10|[01][zZxX]|[zZxX][01]  { FL; return yaEDGEDESC; }
+}
+<EDGEDESC>","|"["         { FL; return yytext[0]; }
+<EDGEDESC>{ws}            { FL_FWD; FL_BRK; }  /* otherwise ignore white-space */
+<EDGEDESC>"]"             { FL; yy_pop_state(); return yytext[0]; }
+
   /************************************************************************/
   /* Preprocessor */
   /* Common for all SYSC header states */
diff --git a/src/verilog.y b/src/verilog.y
index 543492cbef9..36bf03f0486 100644
--- a/src/verilog.y
+++ b/src/verilog.y
@@ -443,6 +443,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
 // IEEE: string_literal
 %token<strp>            yaSTRING        "STRING"
 %token<strp>            yaSTRING__IGNORE "STRING-ignored"       // Used when expr:string not allowed
+// IEEE: edge_descriptor
+%token<nump>            yaEDGEDESC        "EDGE DESCRIPTOR"
 
 %token<fl>              yaTIMINGSPEC    "TIMING SPEC ELEMENT"
 
@@ -916,6 +918,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
 %token<fl>              yD_ROSE_GCLK    "$rose_gclk"
 %token<fl>              yD_RTOI         "$rtoi"
 %token<fl>              yD_SAMPLED      "$sampled"
+%token<fl>              yD_SETUPHOLD    "$setuphold"
 %token<fl>              yD_SFORMAT      "$sformat"
 %token<fl>              yD_SFORMATF     "$sformatf"
 %token<fl>              yD_SHORTREALTOBITS "$shortrealtobits"
@@ -3110,6 +3113,11 @@ minTypMax<nodeExprp>:           // IEEE: mintypmax_expression and constant_minty
         |       delayExpr ':' delayExpr ':' delayExpr   { $$ = $3; MINTYPMAXDLYUNSUP($3); DEL($1); DEL($5); }
         ;
 
+minTypMaxE<nodeExprp>:
+                /*empty*/                               { $$ = nullptr; }
+        |       minTypMax                               { $$ = $1; }
+        ;
+
 netSigList<varp>:               // IEEE: list_of_port_identifiers
                 netSig                                  { $$ = $1; }
         |       netSigList ',' netSig                   { $$ = $1; $1->addNext($3); }
@@ -5758,33 +5766,82 @@ tableEntry<udpTableLinep>:      // IEEE: combinational_entry + sequential_entry
 //************************************************
 // Specify
 
-specify_block<nodep>:           // ==IEEE: specify_block
-                ySPECIFY specifyJunkList yENDSPECIFY    { $$ = nullptr; }
+specify_block<nodep>:               // ==IEEE: specify_block
+                ySPECIFY specify_itemList yENDSPECIFY   { $$ = $2; }
         |       ySPECIFY yENDSPECIFY                    { $$ = nullptr; }
         ;
 
-specifyJunkList:
-                specifyJunk                             { } /* ignored */
-        |       specifyJunkList specifyJunk             { } /* ignored */
+specify_itemList<nodep>:            // IEEE: { specify_item }
+                specify_item                            { $$ = $1; }
+        |       specify_itemList specify_item           { $$ = addNextNull($1, $2); }
         ;
 
-specifyJunk:
-                BISONPRE_NOT(ySPECIFY,yENDSPECIFY)      { }
-        |       ySPECIFY specifyJunk yENDSPECIFY        { }
-        |       error {}
+specify_item<nodep>:                // ==IEEE: specify_item
+                system_timing_check                     { $$ = $1; }
+        |       junkToSemiList ';'                      { $$ = nullptr; }
         ;
 
-specparam_declaration<nodep>:           // ==IEEE: specparam_declaration
+specparam_declaration<nodep>:       // ==IEEE: specparam_declaration
                 ySPECPARAM junkToSemiList ';'           { $$ = nullptr; }
         ;
 
+system_timing_check<nodep>:         // ==IEEE: system_timing_check
+                setuphold_timing_check                      { $$ = $1; }
+        ;
+
+setuphold_timing_check<nodep>:      // ==IEEE: $setuphold_timing_check
+                yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' timing_check_limit ',' timing_check_limit ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' timing_check_limit ',' timing_check_limit ',' idAnyE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' timing_check_limit ',' timing_check_limit ',' idAnyE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' timing_check_limit ',' timing_check_limit ',' idAnyE ',' minTypMaxE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' timing_check_limit ',' timing_check_limit ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' delayed_referenceE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17}; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' timing_check_limit ',' timing_check_limit ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' delayed_referenceE ',' delayed_referenceE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17, $19}; }
+        ;
+
+timing_check_event<nodeExprp>:      // ==IEEE: $timing_check_event
+                terminal_identifier                                                         { $$ = $1; }
+        |       yPOSEDGE terminal_identifier                                                { $$ = $2; }
+        |       yNEGEDGE terminal_identifier                                                { $$ = $2; }
+        |       yEDGE terminal_identifier                                                   { $$ = $2; }
+        |       yEDGE '[' edge_descriptor_list ']' terminal_identifier                      { $$ = $5; }
+        |       terminal_identifier yP_ANDANDAND expr                                       { $$ = $1; }
+        |       yPOSEDGE terminal_identifier yP_ANDANDAND expr                              { $$ = $2; }
+        |       yNEGEDGE terminal_identifier yP_ANDANDAND expr                              { $$ = $2; }
+        |       yEDGE terminal_identifier yP_ANDANDAND expr                                 { $$ = $2; }
+        |       yEDGE '[' edge_descriptor_list ']' terminal_identifier yP_ANDANDAND expr    { $$ = $5; }
+        ;
+
+edge_descriptor_list:
+                yaEDGEDESC                            {  }
+        |       edge_descriptor_list ',' yaEDGEDESC   {  }
+        ;
+
+timing_check_limit<nodeExprp>:
+                expr                      { $$ = $1; }
+        |       expr ':' expr ':' expr    { $$ = $3; }
+        ;
+
+delayed_referenceE<nodeExprp>:
+                /*empty*/                               { $$ = nullptr; }
+        |       terminal_identifier                     { $$ = $1; }
+        ;
+
+terminal_identifier<nodeExprp>:
+                idArrayed     { $$ = $1; }
+        ;
+
+idAnyE<strp>:
+                /*empty*/                               { $$ = nullptr; }
+        |       idAny                                   { $$ = $1; }
+        ;
+
 junkToSemiList:
                 junkToSemi                              { } /* ignored */
         |       junkToSemiList junkToSemi               { } /* ignored */
         ;
 
 junkToSemi:
-                BISONPRE_NOT(';',yENDSPECIFY,yENDMODULE)        { }
+                BISONPRE_NOT(';',yENDSPECIFY,yENDMODULE,yD_SETUPHOLD)        { }
         |       error {}
         ;
 
diff --git a/test_regress/t/t_gate_basic.v b/test_regress/t/t_gate_basic.v
index 8ac9c0dab6a..d7c9b269070 100644
--- a/test_regress/t/t_gate_basic.v
+++ b/test_regress/t/t_gate_basic.v
@@ -77,7 +77,6 @@ module t (/*AUTOARG*/
     $recrem();
     $removal();
     $setup();
-    $setuphold();
     $skew();
     $timeskew();
     $width();
diff --git a/test_regress/t/t_setuphold.py b/test_regress/t/t_setuphold.py
new file mode 100755
index 00000000000..f989a35fba4
--- /dev/null
+++ b/test_regress/t/t_setuphold.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# DESCRIPTION: Verilator: Verilog Test driver/expect definition
+#
+# Copyright 2025 by Wilson Snyder. This program is free software; you
+# can redistribute it and/or modify it under the terms of either the GNU
+# Lesser General Public License Version 3 or the Perl Artistic License
+# Version 2.0.
+# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
+
+import vltest_bootstrap
+
+test.scenarios('simulator')
+
+test.compile()
+
+test.execute()
+
+test.passes()
diff --git a/test_regress/t/t_setuphold.v b/test_regress/t/t_setuphold.v
new file mode 100644
index 00000000000..3085c7d70cb
--- /dev/null
+++ b/test_regress/t/t_setuphold.v
@@ -0,0 +1,84 @@
+// DESCRIPTION: Verilator: Verilog Test module
+//
+// This file ONLY is placed under the Creative Commons Public Domain, for
+// any use, without warranty, 2025 by Antmicro.
+// SPDX-License-Identifier: CC0-1.0
+
+module t (/*AUTOARG*/
+   // Inputs
+   clk,
+   d,
+   t_in
+   );
+
+   input clk;
+   input d;
+   input t_in;
+   wire delayed_CLK;
+   wire delayed_D;
+   reg notifier;
+   wire [1:0] BL_X = 2'b11;
+   wire [5:0] BL_X2;
+   wire BL_0;
+   wire [3:0] BL_1 = 4'b1100;
+   wire fake_CLK;
+   wire fake_D;
+
+   logic[3:0] sh1 = 1;
+   logic[3:0] sh2 = 2;
+   logic[3:0] sh3 = 3;
+   logic[3:0] sh4 = 4;
+   logic[3:0] sh5 = 5;
+   logic[3:0] sh6 = 6;
+
+   int cyc = 0;
+
+   specify
+      $setuphold (posedge clk, negedge d, 0, 0, notifier, (0:0:0), 0, delayed_CLK, delayed_D);
+      $setuphold (posedge sh1, negedge sh3, 0, 0, notifier,,, sh2, sh4);
+      $setuphold (posedge sh5, negedge d, 0, 0, notifier,,, sh6);
+      $setuphold (posedge clk, negedge d, 0, 0, notifier, (1:2:3), (0:0:0));
+      $setuphold (posedge clk, negedge d, 0, 0, notifier, (1:2:3));
+      $setuphold (posedge clk, negedge d, 0, 0, notifier);
+      $setuphold (posedge clk, negedge d, 0, 0);
+      $setuphold (posedge clk, negedge d, 0, 0);
+      $setuphold (posedge clk, negedge d, (0:0:0), (0:0:0));
+      $setuphold (posedge clk, negedge d, 0:0:0, 0:0:0);
+      $setuphold (posedge clk, negedge d, 0, 0,,,,,);
+
+      $setuphold (posedge clk &&& sh1, BL_X[0], 0, 0, ,,,delayed_CLK, BL_0);
+      $setuphold (posedge clk &&& sh1, BL_1, 0, 0, ,,,delayed_CLK, BL_X2[4:1]);
+
+      $setuphold (fake_CLK, fake_D &&& sh1, 0, 0);
+      $setuphold (posedge fake_CLK, posedge fake_D &&& sh1, 0, 0);
+      $setuphold (negedge fake_CLK, negedge fake_D &&& sh1, 0, 0);
+      $setuphold (edge fake_CLK, edge fake_D &&& sh1, 0, 0);
+      $setuphold (edge [0Z, z1, 10] fake_CLK, edge [01, x0, 0X] fake_CLK &&& sh1, 0, 0);
+
+      $setuphold (posedge clk, negedge d, 0, 0, notifier, (0:0:0), 0, t_in);
+   endspecify
+
+   initial begin
+      if (sh1 != sh2 || sh3 != sh4) begin
+         $stop;
+      end
+      if (sh5 != sh6) begin
+         $stop;
+      end
+      if (BL_0 != BL_X[0] || BL_1 != BL_X2[4:1]) begin
+         $stop;
+      end
+   end
+
+   always @(posedge clk) begin
+      cyc <= cyc + 1;
+      $display("%d %d", clk, delayed_CLK);
+      if (delayed_CLK != clk || delayed_D != d) begin
+         $stop;
+      end
+      if (cyc == 10) begin
+         $display("*-* All Finished *-*");
+         $finish;
+      end
+   end
+endmodule


Reviews:
user: login: wsnyder
body: Great! Always impressive to get a large patch as a first contribution from you.

Please run sv-tests suite with this change, there's several new syntax errors that need to be fixed for example:

```
%Error: tests/third_party/tools/icarus/ivtest/ivltests/pr1520314.v:16:32: syntax error, unexpected '[', expecting ','
   16 |  $setuphold(posedge A &&& B, BL[0], 0, 0, C,,,D, BL_X[0]);
      |                                ^
```
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T11:48:18Z
user: login: wsnyder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T11:49:49Z
user: login: wsnyder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-30T14:07:47Z
user: login: ksychla
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T13:30:29Z
user: login: ksychla
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T13:31:14Z
user: login: ksychla
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T13:31:27Z
user: login: wsnyder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T13:38:06Z
user: login: ksychla
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T08:18:26Z

Review comments:
diff_hunk: @@ -1377,6 +1377,38 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+
+        AstAssignW* newp = nullptr;
+
+        if (nodep->delrefp() != nullptr) {
+            AstNodeVarRef* lhsp = VN_AS(nodep->delrefp()->cloneTreePure(false), VarRef);
+            lhsp->access(VAccess::WRITE);
+            AstNodeVarRef* rhsp = VN_AS(nodep->refevp()->cloneTreePure(false), VarRef);
path: src/V3Width.cpp
user: login: wsnyder
body: Need error if the user didn't put a varref as one of the arguments, and need a test for that error.  Similar below (and nullptr/const cleanup too).

```suggestion
        if (nodep->delrefp()) {
            AstNodeVarRef* const lhsp = VN_AS(nodep->delrefp()->cloneTreePure(false), VarRef);
            lhsp->access(VAccess::WRITE);
            AstNodeVarRef* const rhsp = VN_AS(nodep->refevp()->cloneTreePure(false), VarRef);
```
created_at: 2025-03-26T11:44:53Z
author_association: MEMBER
diff_hunk: @@ -1377,6 +1377,38 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+
+        AstAssignW* newp = nullptr;
+
+        if (nodep->delrefp() != nullptr) {
+            AstNodeVarRef* lhsp = VN_AS(nodep->delrefp()->cloneTreePure(false), VarRef);
+            lhsp->access(VAccess::WRITE);
+            AstNodeVarRef* rhsp = VN_AS(nodep->refevp()->cloneTreePure(false), VarRef);
+            newp = new AstAssignW{flp, lhsp, rhsp};
+        }
+
+        if (nodep->deldatap() != nullptr) {
+            AstNodeVarRef* lhsp = VN_AS(nodep->deldatap()->cloneTreePure(false), VarRef);
+            lhsp->access(VAccess::WRITE);
+            AstNodeVarRef* rhsp = VN_AS(nodep->dataevp()->cloneTreePure(false), VarRef);
+
+            if (newp == nullptr) {
+                newp = new AstAssignW{flp, lhsp, rhsp};
+            } else {
+                newp->addNextHere(new AstAssignW{flp, lhsp, rhsp});
+            }
+        }
+
+        if (newp == nullptr) {
+            nodep->unlinkFrBack()->deleteTree();
+            return;
+        }
+
+        nodep->replaceWith(newp);
path: src/V3Width.cpp
user: login: wsnyder
body: ```suggestion
        if (!newp) {
            nodep->unlinkFrBack()->deleteTree();
            return;
        }
        nodep->replaceWith(newp);
```
created_at: 2025-03-26T11:46:20Z
author_association: MEMBER
diff_hunk: @@ -5758,33 +5764,70 @@ tableEntry<udpTableLinep>:      // IEEE: combinational_entry + sequential_entry
 //************************************************
 // Specify
 
-specify_block<nodep>:           // ==IEEE: specify_block
-                ySPECIFY specifyJunkList yENDSPECIFY    { $$ = nullptr; }
+specify_block<nodep>:               // ==IEEE: specify_block
+                ySPECIFY specify_itemList yENDSPECIFY   { $$ = $2; }
         |       ySPECIFY yENDSPECIFY                    { $$ = nullptr; }
         ;
 
-specifyJunkList:
-                specifyJunk                             { } /* ignored */
-        |       specifyJunkList specifyJunk             { } /* ignored */
+specify_itemList<nodep>:            // IEEE: { specify_item }
+                specify_item                            { $$ = $1; }
+        |       specify_itemList specify_item           { $$ = addNextNull($1, $2); }
         ;
 
-specifyJunk:
-                BISONPRE_NOT(ySPECIFY,yENDSPECIFY)      { }
-        |       ySPECIFY specifyJunk yENDSPECIFY        { }
-        |       error {}
+specify_item<nodep>:                // ==IEEE: specify_item
+                system_timing_check                     { $$ = $1; }
+        |       junkToSemiList ';'                      { $$ = nullptr; }
         ;
 
-specparam_declaration<nodep>:           // ==IEEE: specparam_declaration
+specparam_declaration<nodep>:       // ==IEEE: specparam_declaration
                 ySPECPARAM junkToSemiList ';'           { $$ = nullptr; }
         ;
 
+system_timing_check<nodep>:         // ==IEEE: system_timing_check
+                setuphold_timing_check                      { $$ = $1; }
+        ;
+
+setuphold_timing_check<nodep>:      // ==IEEE: $setuphold_timing_check
+                yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17}; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17, $19}; }
path: src/verilog.y
user: login: wsnyder
body: Please make sure the tests cover every line here.
created_at: 2025-03-26T11:49:14Z
author_association: MEMBER
diff_hunk: @@ -5758,33 +5764,70 @@ tableEntry<udpTableLinep>:      // IEEE: combinational_entry + sequential_entry
 //************************************************
 // Specify
 
-specify_block<nodep>:           // ==IEEE: specify_block
-                ySPECIFY specifyJunkList yENDSPECIFY    { $$ = nullptr; }
+specify_block<nodep>:               // ==IEEE: specify_block
+                ySPECIFY specify_itemList yENDSPECIFY   { $$ = $2; }
         |       ySPECIFY yENDSPECIFY                    { $$ = nullptr; }
         ;
 
-specifyJunkList:
-                specifyJunk                             { } /* ignored */
-        |       specifyJunkList specifyJunk             { } /* ignored */
+specify_itemList<nodep>:            // IEEE: { specify_item }
+                specify_item                            { $$ = $1; }
+        |       specify_itemList specify_item           { $$ = addNextNull($1, $2); }
         ;
 
-specifyJunk:
-                BISONPRE_NOT(ySPECIFY,yENDSPECIFY)      { }
-        |       ySPECIFY specifyJunk yENDSPECIFY        { }
-        |       error {}
+specify_item<nodep>:                // ==IEEE: specify_item
+                system_timing_check                     { $$ = $1; }
+        |       junkToSemiList ';'                      { $$ = nullptr; }
         ;
 
-specparam_declaration<nodep>:           // ==IEEE: specparam_declaration
+specparam_declaration<nodep>:       // ==IEEE: specparam_declaration
                 ySPECPARAM junkToSemiList ';'           { $$ = nullptr; }
         ;
 
+system_timing_check<nodep>:         // ==IEEE: system_timing_check
+                setuphold_timing_check                      { $$ = $1; }
+        ;
+
+setuphold_timing_check<nodep>:      // ==IEEE: $setuphold_timing_check
+                yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17}; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17, $19}; }
+        ;
+
+timing_check_event<nodeExprp>:      // ==IEEE: $timing_check_event
+                terminal_identifier                                   { $$ = $1; }
+        |       yPOSEDGE terminal_identifier                          { $$ = $2; }
+        |       yNEGEDGE terminal_identifier                          { $$ = $2; }
+        |       yEDGE terminal_identifier                             { $$ = $2; }
+        |       terminal_identifier yP_ANDANDAND expr                 { $$ = $1; }
+        |       yPOSEDGE terminal_identifier yP_ANDANDAND expr        { $$ = $2; }
+        |       yNEGEDGE terminal_identifier yP_ANDANDAND expr        { $$ = $2; }
+        |       yEDGE terminal_identifier yP_ANDANDAND expr           { $$ = $2; }
path: src/verilog.y
user: login: wsnyder
body: Please make sure the tests cover every line here.
created_at: 2025-03-26T11:49:28Z
author_association: MEMBER
diff_hunk: @@ -1377,6 +1377,38 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+
+        AstAssignW* newp = nullptr;
+
+        if (nodep->delrefp() != nullptr) {
+            AstNodeVarRef* lhsp = VN_AS(nodep->delrefp()->cloneTreePure(false), VarRef);
+            lhsp->access(VAccess::WRITE);
+            AstNodeVarRef* rhsp = VN_AS(nodep->refevp()->cloneTreePure(false), VarRef);
+            newp = new AstAssignW{flp, lhsp, rhsp};
+        }
+
+        if (nodep->deldatap() != nullptr) {
+            AstNodeVarRef* lhsp = VN_AS(nodep->deldatap()->cloneTreePure(false), VarRef);
+            lhsp->access(VAccess::WRITE);
+            AstNodeVarRef* rhsp = VN_AS(nodep->dataevp()->cloneTreePure(false), VarRef);
+
+            if (newp == nullptr) {
+                newp = new AstAssignW{flp, lhsp, rhsp};
+            } else {
+                newp->addNextHere(new AstAssignW{flp, lhsp, rhsp});
+            }
+        }
+
+        if (newp == nullptr) {
+            nodep->unlinkFrBack()->deleteTree();
path: src/V3Width.cpp
user: login: wsnyder
body: ```suggestion
            pushDeletep(nodep->unlinkFrBack());
```
created_at: 2025-03-30T14:07:47Z
author_association: MEMBER
diff_hunk: @@ -1377,6 +1377,38 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+
+        AstAssignW* newp = nullptr;
+
+        if (nodep->delrefp() != nullptr) {
+            AstNodeVarRef* lhsp = VN_AS(nodep->delrefp()->cloneTreePure(false), VarRef);
+            lhsp->access(VAccess::WRITE);
+            AstNodeVarRef* rhsp = VN_AS(nodep->refevp()->cloneTreePure(false), VarRef);
path: src/V3Width.cpp
user: login: ksychla
body: Added an internal error in case the type is incorrect. I couldn't think of a test for that though. The parser seems to handle most references that are incorrect, like function identifiers, numbers etc. Also this transformation happens very early, and it seems there are no Ref transformations that could affect this.
created_at: 2025-04-01T13:30:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5758,33 +5764,70 @@ tableEntry<udpTableLinep>:      // IEEE: combinational_entry + sequential_entry
 //************************************************
 // Specify
 
-specify_block<nodep>:           // ==IEEE: specify_block
-                ySPECIFY specifyJunkList yENDSPECIFY    { $$ = nullptr; }
+specify_block<nodep>:               // ==IEEE: specify_block
+                ySPECIFY specify_itemList yENDSPECIFY   { $$ = $2; }
         |       ySPECIFY yENDSPECIFY                    { $$ = nullptr; }
         ;
 
-specifyJunkList:
-                specifyJunk                             { } /* ignored */
-        |       specifyJunkList specifyJunk             { } /* ignored */
+specify_itemList<nodep>:            // IEEE: { specify_item }
+                specify_item                            { $$ = $1; }
+        |       specify_itemList specify_item           { $$ = addNextNull($1, $2); }
         ;
 
-specifyJunk:
-                BISONPRE_NOT(ySPECIFY,yENDSPECIFY)      { }
-        |       ySPECIFY specifyJunk yENDSPECIFY        { }
-        |       error {}
+specify_item<nodep>:                // ==IEEE: specify_item
+                system_timing_check                     { $$ = $1; }
+        |       junkToSemiList ';'                      { $$ = nullptr; }
         ;
 
-specparam_declaration<nodep>:           // ==IEEE: specparam_declaration
+specparam_declaration<nodep>:       // ==IEEE: specparam_declaration
                 ySPECPARAM junkToSemiList ';'           { $$ = nullptr; }
         ;
 
+system_timing_check<nodep>:         // ==IEEE: system_timing_check
+                setuphold_timing_check                      { $$ = $1; }
+        ;
+
+setuphold_timing_check<nodep>:      // ==IEEE: $setuphold_timing_check
+                yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17}; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17, $19}; }
path: src/verilog.y
user: login: ksychla
body: Added tests
created_at: 2025-04-01T13:31:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5758,33 +5764,70 @@ tableEntry<udpTableLinep>:      // IEEE: combinational_entry + sequential_entry
 //************************************************
 // Specify
 
-specify_block<nodep>:           // ==IEEE: specify_block
-                ySPECIFY specifyJunkList yENDSPECIFY    { $$ = nullptr; }
+specify_block<nodep>:               // ==IEEE: specify_block
+                ySPECIFY specify_itemList yENDSPECIFY   { $$ = $2; }
         |       ySPECIFY yENDSPECIFY                    { $$ = nullptr; }
         ;
 
-specifyJunkList:
-                specifyJunk                             { } /* ignored */
-        |       specifyJunkList specifyJunk             { } /* ignored */
+specify_itemList<nodep>:            // IEEE: { specify_item }
+                specify_item                            { $$ = $1; }
+        |       specify_itemList specify_item           { $$ = addNextNull($1, $2); }
         ;
 
-specifyJunk:
-                BISONPRE_NOT(ySPECIFY,yENDSPECIFY)      { }
-        |       ySPECIFY specifyJunk yENDSPECIFY        { }
-        |       error {}
+specify_item<nodep>:                // ==IEEE: specify_item
+                system_timing_check                     { $$ = $1; }
+        |       junkToSemiList ';'                      { $$ = nullptr; }
         ;
 
-specparam_declaration<nodep>:           // ==IEEE: specparam_declaration
+specparam_declaration<nodep>:       // ==IEEE: specparam_declaration
                 ySPECPARAM junkToSemiList ';'           { $$ = nullptr; }
         ;
 
+system_timing_check<nodep>:         // ==IEEE: system_timing_check
+                setuphold_timing_check                      { $$ = $1; }
+        ;
+
+setuphold_timing_check<nodep>:      // ==IEEE: $setuphold_timing_check
+                yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ')' ';' { $$ = nullptr; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17}; }
+        |       yD_SETUPHOLD '(' timing_check_event ',' timing_check_event ',' expr ',' expr ',' idAnyE ',' minTypMaxE ',' minTypMaxE ',' terminal_identifierE ',' terminal_identifierE ')' ';' { $$ = new AstSetuphold{$1, $3, $5, $17, $19}; }
+        ;
+
+timing_check_event<nodeExprp>:      // ==IEEE: $timing_check_event
+                terminal_identifier                                   { $$ = $1; }
+        |       yPOSEDGE terminal_identifier                          { $$ = $2; }
+        |       yNEGEDGE terminal_identifier                          { $$ = $2; }
+        |       yEDGE terminal_identifier                             { $$ = $2; }
+        |       terminal_identifier yP_ANDANDAND expr                 { $$ = $1; }
+        |       yPOSEDGE terminal_identifier yP_ANDANDAND expr        { $$ = $2; }
+        |       yNEGEDGE terminal_identifier yP_ANDANDAND expr        { $$ = $2; }
+        |       yEDGE terminal_identifier yP_ANDANDAND expr           { $$ = $2; }
path: src/verilog.y
user: login: ksychla
body: Added more tests
created_at: 2025-04-01T13:31:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1377,6 +1377,55 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+
path: src/V3Width.cpp
user: login: wsnyder
body: Nit, please remove all the blank lines in this and in the next function.  Ok to keep one or two if really needed for intent - but don't seem necessary. Thanks.

I assume it's ok to merge once passes unless hear otherwise.
created_at: 2025-04-01T13:38:05Z
author_association: MEMBER
diff_hunk: @@ -1377,6 +1377,55 @@ class WidthVisitor final : public VNVisitor {
         }
     }
 
+    void visit(AstSetuphold* nodep) override {
+        FileLine* const flp = nodep->fileline();
+
path: src/V3Width.cpp
user: login: ksychla
body: Removed the blank lines
created_at: 2025-04-02T08:18:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: ksychla
created_at: 2025-04-01T13:32:06Z
body: I resolved issues with the sv-tests suite, though there is one remaining issue. In the `pr1520314.v` I'm now getting:
```
%Warning-MULTIDRIVEN: pr1520314.v:8:13: Bits [0:0] of signal 'BL_X[0]' have multiple combinational drivers
```
I ran the same test with a different proprietary simulator, which also complains about this test. So I'm not sure if it is correct, what would you like me to do in this situation?
author_association: CONTRIBUTOR
user: login: wsnyder
created_at: 2025-04-01T13:38:59Z
body: Please file a pull against sv-tests to blacklist that test, as it's invalid, thanks.
author_association: MEMBER
user: login: ksychla
created_at: 2025-04-02T08:18:57Z
body: I opened a PR in sv-tests: https://github.com/chipsalliance/sv-tests/pull/7078
author_association: CONTRIBUTOR