diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS
index f84c3d6a9..7ee2c4306 100644
--- a/docs/CONTRIBUTORS
+++ b/docs/CONTRIBUTORS
@@ -135,6 +135,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 26c895c29..e25f1502b 100644
--- a/src/V3AstNodeOther.h
+++ b/src/V3AstNodeOther.h
@@ -3339,6 +3339,24 @@ public:
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 108f2de02..8da3687be 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 eb13dbaf8..9aa326993 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 SYSCHDRP 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}
<> { FL; yylval.fl->v3error("EOF in 'table'");
yyleng = 0; yy_pop_state(); FL_BRK; yyterminate(); }
+{
+ 01|10|[01][zZxX]|[zZxX][01] { FL; return yaEDGEDESC; }
+}
+","|"[" { FL; return yytext[0]; }
+{ws} { FL_FWD; FL_BRK; } /* otherwise ignore white-space */
+"]" { 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 c75848d4e..bc7c2586b 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 yaSTRING "STRING"
%token yaSTRING__IGNORE "STRING-ignored" // Used when expr:string not allowed
+// IEEE: edge_descriptor
+%token yaEDGEDESC "EDGE DESCRIPTOR"
%token yaTIMINGSPEC "TIMING SPEC ELEMENT"
@@ -917,6 +919,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token yD_ROSE_GCLK "$rose_gclk"
%token yD_RTOI "$rtoi"
%token yD_SAMPLED "$sampled"
+%token yD_SETUPHOLD "$setuphold"
%token yD_SFORMAT "$sformat"
%token yD_SFORMATF "$sformatf"
%token yD_SHORTREALTOBITS "$shortrealtobits"
@@ -3117,6 +3120,11 @@ minTypMax: // IEEE: mintypmax_expression and constant_minty
| delayExpr ':' delayExpr ':' delayExpr { $$ = $3; MINTYPMAXDLYUNSUP($3); DEL($1); DEL($5); }
;
+minTypMaxE:
+ /*empty*/ { $$ = nullptr; }
+ | minTypMax { $$ = $1; }
+ ;
+
netSigList: // IEEE: list_of_port_identifiers
netSig { $$ = $1; }
| netSigList ',' netSig { $$ = $1; $1->addNext($3); }
@@ -5765,33 +5773,82 @@ tableEntry: // IEEE: combinational_entry + sequential_entry
//************************************************
// Specify
-specify_block: // ==IEEE: specify_block
- ySPECIFY specifyJunkList yENDSPECIFY { $$ = nullptr; }
+specify_block: // ==IEEE: specify_block
+ ySPECIFY specify_itemList yENDSPECIFY { $$ = $2; }
| ySPECIFY yENDSPECIFY { $$ = nullptr; }
;
-specifyJunkList:
- specifyJunk { } /* ignored */
- | specifyJunkList specifyJunk { } /* ignored */
+specify_itemList: // IEEE: { specify_item }
+ specify_item { $$ = $1; }
+ | specify_itemList specify_item { $$ = addNextNull($1, $2); }
;
-specifyJunk:
- BISONPRE_NOT(ySPECIFY,yENDSPECIFY) { }
- | ySPECIFY specifyJunk yENDSPECIFY { }
- | error {}
+specify_item: // ==IEEE: specify_item
+ system_timing_check { $$ = $1; }
+ | junkToSemiList ';' { $$ = nullptr; }
;
-specparam_declaration: // ==IEEE: specparam_declaration
+specparam_declaration: // ==IEEE: specparam_declaration
ySPECPARAM junkToSemiList ';' { $$ = nullptr; }
;
+system_timing_check: // ==IEEE: system_timing_check
+ setuphold_timing_check { $$ = $1; }
+ ;
+
+setuphold_timing_check: // ==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: // ==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:
+ expr { $$ = $1; }
+ | expr ':' expr ':' expr { $$ = $3; }
+ ;
+
+delayed_referenceE:
+ /*empty*/ { $$ = nullptr; }
+ | terminal_identifier { $$ = $1; }
+ ;
+
+terminal_identifier:
+ idArrayed { $$ = $1; }
+ ;
+
+idAnyE:
+ /*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 8ac9c0dab..d7c9b2690 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 000000000..f989a35fb
--- /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 000000000..3085c7d70
--- /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