From 10cf492946a79354e4791f00dc85700b9189cd63 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 19 Aug 2022 20:18:38 +0200 Subject: [PATCH] Add support for expressions in event controls (#3550) Signed-off-by: Krzysztof Bieganski --- src/V3Const.cpp | 4 +- src/V3LinkResolve.cpp | 36 ---- src/V3Sched.cpp | 116 +++++++++--- src/V3Task.cpp | 12 ++ src/V3Width.cpp | 17 +- src/verilog.y | 23 +-- test_regress/t/t_event_control_expr.pl | 33 ++++ test_regress/t/t_event_control_expr.v | 174 ++++++++++++++++++ test_regress/t/t_event_control_expr_unsup.out | 9 + test_regress/t/t_event_control_expr_unsup.pl | 22 +++ 10 files changed, 361 insertions(+), 85 deletions(-) create mode 100755 test_regress/t/t_event_control_expr.pl create mode 100644 test_regress/t/t_event_control_expr.v create mode 100644 test_regress/t/t_event_control_expr_unsup.out create mode 100755 test_regress/t/t_event_control_expr_unsup.pl diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 05f673851..52334947a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2717,9 +2717,7 @@ private: } UINFO(8, "senItem(NOT...) " << nodep << " " << invert << endl); if (invert) nodep->edgeType(nodep->edgeType().invert()); - AstNodeVarRef* const senvarp = VN_AS(lastSensp->unlinkFrBack(), NodeVarRef); - UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable"); - sensp->replaceWith(senvarp); + sensp->replaceWith(lastSensp->unlinkFrBack()); VL_DO_DANGLING(sensp->deleteTree(), sensp); } } diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index a215b5dc0..073ffda44 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -145,42 +145,6 @@ private: nodep->scopeNamep(new AstScopeName{nodep->fileline(), false}); } } - - virtual void visit(AstSenItem* nodep) override { - // Remove bit selects, and bark if it's not a simple variable - iterateChildren(nodep); - if (!nodep->isClocked()) { - // Old V1995 sensitivity list; we'll probably mostly ignore - bool did = true; - while (did) { - did = false; - if (AstNodeSel* const selp = VN_CAST(nodep->sensp(), NodeSel)) { - AstNode* const fromp = selp->fromp()->unlinkFrBack(); - selp->replaceWith(fromp); - VL_DO_DANGLING(selp->deleteTree(), selp); - did = true; - } - // NodeSel doesn't include AstSel.... - if (AstSel* const selp = VN_CAST(nodep->sensp(), Sel)) { - AstNode* const fromp = selp->fromp()->unlinkFrBack(); - selp->replaceWith(fromp); - VL_DO_DANGLING(selp->deleteTree(), selp); - did = true; - } - if (AstNodePreSel* const selp = VN_CAST(nodep->sensp(), NodePreSel)) { - AstNode* const fromp = selp->fromp()->unlinkFrBack(); - selp->replaceWith(fromp); - VL_DO_DANGLING(selp->deleteTree(), selp); - did = true; - } - } - } - if (nodep->isIllegal()) { - if (debug()) nodep->dumpTree(cout, "-tree: "); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list"); - } - } - virtual void visit(AstNodePreSel* nodep) override { if (!nodep->attrp()) { iterateChildren(nodep); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index f1085acc6..e3f875327 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -268,13 +268,19 @@ class SenExprBuilder final { AstCFunc* const m_initp; // The initialization function AstScope* const m_scopeTopp; // Top level scope - std::vector m_updates; // Update assignments + std::vector m_locals; // Trigger eval local variables + std::vector m_preUpdates; // Pre update assignments + std::vector m_postUpdates; // Post update assignments std::unordered_map, AstVarScope*> m_prev; // The 'previous value' signals - std::unordered_set> m_hasUpdate; // Whether the given sen expression already - // has an update statement in m_updates + std::unordered_map, AstVarScope*> m_curr; // The 'current value' signals + std::unordered_set> m_hasPreUpdate; // Whether the given sen expression already + // has an update statement in m_preUpdates + std::unordered_set> m_hasPostUpdate; // Likewis for m_postUpdates - V3UniqueNames m_uniqueNames{"__Vtrigprev__expression"}; // For generating unique signal names + V3UniqueNames m_currNames{"__Vtrigcurr__expression"}; // For generating unique current value + // signal names + V3UniqueNames m_prevNames{"__Vtrigprev__expression"}; // Likewise for previous values static bool isSupportedDType(AstNodeDType* dtypep) { dtypep = dtypep->skipRefp(); @@ -285,29 +291,62 @@ class SenExprBuilder final { return false; } + static bool isSimpleExpr(const AstNode* const exprp) { + return exprp->forall([](const AstNode* const nodep) { + return VN_IS(nodep, Const) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, Sel) + || VN_IS(nodep, NodeSel) || VN_IS(nodep, MemberSel) + || VN_IS(nodep, CMethodHard); + }); + } + // METHODS - AstVarScope* getPrev(AstNode* currp) { - FileLine* const flp = currp->fileline(); - const auto rdCurr = [=]() { return currp->cloneTree(false); }; + AstNode* getCurr(AstNode* exprp) { + // For simple expressions like varrefs or selects, just use them directly + if (isSimpleExpr(exprp)) return exprp->cloneTree(false); + + // Create the 'current value' variable + FileLine* const flp = exprp->fileline(); + auto result = m_curr.emplace(*exprp, nullptr); + if (result.second) { + AstVar* const varp + = new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()}; + varp->funcLocal(true); + m_locals.push_back(varp); + AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp}; + m_scopeTopp->addVarp(vscp); + result.first->second = vscp; + } + AstVarScope* const currp = result.first->second; + + // Add pre update if it does not exist yet in this round + if (m_hasPreUpdate.emplace(*currp).second) { + m_preUpdates.push_back(new AstAssign{flp, new AstVarRef{flp, currp, VAccess::WRITE}, + exprp->cloneTree(false)}); + } + return new AstVarRef{flp, currp, VAccess::READ}; + } + AstVarScope* getPrev(AstNode* exprp) { + FileLine* const flp = exprp->fileline(); + const auto rdCurr = [=]() { return getCurr(exprp); }; // Create the 'previous value' variable - auto it = m_prev.find(*currp); + auto it = m_prev.find(*exprp); if (it == m_prev.end()) { // For readability, use the scoped signal name if the trigger is a simple AstVarRef string name; - if (AstVarRef* const refp = VN_CAST(currp, VarRef)) { + if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) { AstVarScope* vscp = refp->varScopep(); name = "__Vtrigrprev__" + vscp->scopep()->nameDotless() + "__" + vscp->varp()->name(); } else { - name = m_uniqueNames.get(currp); + name = m_prevNames.get(exprp); } - AstVarScope* const prevp = m_scopeTopp->createTemp(name, currp->dtypep()); - it = m_prev.emplace(*currp, prevp).first; + AstVarScope* const prevp = m_scopeTopp->createTemp(name, exprp->dtypep()); + it = m_prev.emplace(*exprp, prevp).first; // Add the initializer init - AstNode* const initp = rdCurr(); + AstNode* const initp = exprp->cloneTree(false); m_initp->addStmtsp( new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp}); } @@ -316,22 +355,22 @@ class SenExprBuilder final { const auto wrPrev = [=]() { return new AstVarRef{flp, prevp, VAccess::WRITE}; }; - // Add update if it does not exist yet - if (m_hasUpdate.emplace(*currp).second) { - if (!isSupportedDType(currp->dtypep())) { - currp->v3warn(E_UNSUPPORTED, + // Add post update if it does not exist yet + if (m_hasPostUpdate.emplace(*exprp).second) { + if (!isSupportedDType(exprp->dtypep())) { + exprp->v3warn(E_UNSUPPORTED, "Unsupported: Cannot detect changes on expression of complex type" " (see combinational cycles reported by UNOPTFLAT)"); return prevp; } - if (AstUnpackArrayDType* const dtypep = VN_CAST(currp->dtypep(), UnpackArrayDType)) { + if (AstUnpackArrayDType* const dtypep = VN_CAST(exprp->dtypep(), UnpackArrayDType)) { AstCMethodHard* const cmhp = new AstCMethodHard{flp, wrPrev(), "assign", rdCurr()}; cmhp->dtypeSetVoid(); cmhp->statement(true); - m_updates.push_back(cmhp); + m_postUpdates.push_back(cmhp); } else { - m_updates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); + m_postUpdates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); } } @@ -342,7 +381,7 @@ class SenExprBuilder final { FileLine* const flp = senItemp->fileline(); AstNode* const senp = senItemp->sensp(); - const auto currp = [=]() { return senp->cloneTree(false); }; + const auto currp = [=]() { return getCurr(senp); }; const auto prevp = [=]() { return new AstVarRef{flp, getPrev(senp), VAccess::READ}; }; const auto lsb = [=](AstNodeMath* opp) { return new AstSel{flp, opp, 0, 1}; }; @@ -371,7 +410,7 @@ class SenExprBuilder final { AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; callp->dtypeSetBit(); AstIf* const ifp = new AstIf{flp, callp}; - m_updates.push_back(ifp); + m_postUpdates.push_back(ifp); // Clear 'fired' state when done AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"}; @@ -417,9 +456,16 @@ public: return {resultp, firedAtInitialization}; } - std::vector getAndClearUpdates() { - m_hasUpdate.clear(); - return std::move(m_updates); + std::vector getAndClearLocals() { return std::move(m_locals); } + + std::vector getAndClearPreUpdates() { + m_hasPreUpdate.clear(); + return std::move(m_preUpdates); + } + + std::vector getAndClearPostUpdates() { + m_hasPostUpdate.clear(); + return std::move(m_postUpdates); } // CONSTRUCTOR @@ -606,7 +652,25 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui ++triggerNumber; } // Add the update statements - for (AstNodeStmt* const nodep : senExprBuilder.getAndClearUpdates()) funcp->addStmtsp(nodep); + for (AstNodeStmt* const nodep : senExprBuilder.getAndClearPostUpdates()) { + funcp->addStmtsp(nodep); + } + const auto& preUpdates = senExprBuilder.getAndClearPreUpdates(); + if (!preUpdates.empty()) { + for (AstNodeStmt* const nodep : vlstd::reverse_view(preUpdates)) { + UASSERT_OBJ(funcp->stmtsp(), funcp, + "No statements in trigger eval function, but there are pre updates"); + funcp->stmtsp()->addHereThisAsNext(nodep); + } + } + const auto& locals = senExprBuilder.getAndClearLocals(); + if (!locals.empty()) { + UASSERT_OBJ(funcp->stmtsp(), funcp, + "No statements in trigger eval function, but there are locals"); + for (AstVar* const nodep : vlstd::reverse_view(locals)) { + funcp->stmtsp()->addHereThisAsNext(nodep); + } + } // Add the initialization statements if (initialTrigsp) { diff --git a/src/V3Task.cpp b/src/V3Task.cpp index fee06a242..42c995865 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -360,6 +360,7 @@ private: AstScope* m_scopep = nullptr; // Current scope InsertMode m_insMode = IM_BEFORE; // How to insert AstNode* m_insStmtp = nullptr; // Where to insert statement + bool m_inSensesp = false; // Are we under a senitem? int m_modNCalls = 0; // Incrementing func # for making symbols DpiCFuncs m_dpiNames; // Map of all created DPI functions @@ -1369,6 +1370,11 @@ private: m_scopep = nullptr; } virtual void visit(AstNodeFTaskRef* nodep) override { + if (m_inSensesp) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: function calls in sensitivity lists"); + nodep->taskp(nullptr); // So V3Broken doesn't complain + return; + } // Includes handling AstMethodCall, AstNew UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked?"); iterateIntoFTask(nodep->taskp()); // First, do hierarchical funcs @@ -1521,6 +1527,12 @@ private: iterateChildren(nodep); m_insStmtp = nullptr; // Next thing should be new statement } + virtual void visit(AstSenItem* nodep) override { + UASSERT_OBJ(!m_inSensesp, nodep, "Senitem under senitem?"); + VL_RESTORER(m_inSensesp); + m_inSensesp = true; + iterateChildren(nodep); + } //-------------------- virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index ba22697b5..f938f95d7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5050,7 +5050,22 @@ private: m_procedurep = nullptr; } virtual void visit(AstSenItem* nodep) override { - userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + UASSERT_OBJ(nodep->isClocked(), nodep, "Invalid edge"); + // Optimize concat/replicate senitems; this has to be done here at the latest, otherwise we + // emit WIDTHCONCAT if there are unsized constants + if (VN_IS(nodep->sensp(), Concat) || VN_IS(nodep->sensp(), Replicate)) { + auto* const concatOrReplp = VN_CAST(nodep->sensp(), NodeBiop); + auto* const rhsp = concatOrReplp->rhsp()->unlinkFrBack(); + if (nodep->edgeType() == VEdgeType::ET_CHANGED) { + // If it's ET_CHANGED, split concatenations into multiple senitems + auto* const lhsp = concatOrReplp->lhsp()->unlinkFrBack(); + nodep->addNextHere(new AstSenItem{lhsp->fileline(), nodep->edgeType(), lhsp}); + } // Else only use the RHS + nodep->replaceWith(new AstSenItem{rhsp->fileline(), nodep->edgeType(), rhsp}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } else { + userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + } } virtual void visit(AstWith* nodep) override { // Should otherwise be underneath a method call diff --git a/src/verilog.y b/src/verilog.y index b3baf5ac3..71e97120c 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3027,15 +3027,8 @@ event_expression: // IEEE: event_expression - split over several senitem: // IEEE: part of event_expression, non-'OR' ',' terms senitemEdge { $$ = $1; } - | senitemVar { $$ = $1; } - | '(' senitem ')' { $$ = $2; } - //UNSUP expr { UNSUP } - | '{' event_expression '}' { $$ = $2; } - | senitem yP_ANDAND senitem { $$ = new AstSenItem($2, AstSenItem::Illegal()); } + | expr { $$ = new AstSenItem{$1, VEdgeType::ET_CHANGED, $1}; } //UNSUP expr yIFF expr { UNSUP } - // Since expr is unsupported we allow and ignore constants (removed in V3Const) - | yaINTNUM { $$ = nullptr; } - | yaFLOATNUM { $$ = nullptr; } ; senitemVar: @@ -3043,19 +3036,11 @@ senitemVar: ; senitemEdge: // IEEE: part of event_expression - //UNSUP // Below are all removed - yPOSEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_POSEDGE, $2); } - | yNEGEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_NEGEDGE, $2); } - | yEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_BOTHEDGE, $2); } - | yPOSEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_POSEDGE, $3); } - | yNEGEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_NEGEDGE, $3); } - | yEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_BOTHEDGE, $3); } - //UNSUP // Above are all removed, replace with: - //UNSUP yPOSEDGE expr { UNSUP } + yPOSEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_POSEDGE, $2}; } + | yNEGEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_NEGEDGE, $2}; } + | yEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_BOTHEDGE, $2}; } //UNSUP yPOSEDGE expr yIFF expr { UNSUP } - //UNSUP yNEGEDGE expr { UNSUP } //UNSUP yNEGEDGE expr yIFF expr { UNSUP } - //UNSUP yEDGE expr { UNSUP } //UNSUP yEDGE expr yIFF expr { UNSUP } ; diff --git a/test_regress/t/t_event_control_expr.pl b/test_regress/t/t_event_control_expr.pl new file mode 100755 index 000000000..a50ea7293 --- /dev/null +++ b/test_regress/t/t_event_control_expr.pl @@ -0,0 +1,33 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(simulator => 1); + +compile( + # do not test classes for multithreaded, as V3InstrCount doesn't handle MemberSel + verilator_flags2 => $Self->{vltmt} ? ['-DNO_CLASS'] : [], + ); + +execute( + check_finished => 1, + ); + +for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.cpp")) { + # Check that these simple expressions are not stored in temp variables + file_grep_not($file, qr/__Vtrigcurr__expression_.* = vlSelf->clk;/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = vlSelf->t__DOT__q.at\(0U\);/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSelf->t__DOT____Vcellinp__u_array__t/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSymsp->TOP__t__DOT__u_class.__PVT__obj/); + # The line below should only be generated if concats/replicates aren't converted to separate senitems + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSelf->t__DOT__a/); +} + +ok(1); +1; diff --git a/test_regress/t/t_event_control_expr.v b/test_regress/t/t_event_control_expr.v new file mode 100644 index 000000000..23fc5bc55 --- /dev/null +++ b/test_regress/t/t_event_control_expr.v @@ -0,0 +1,174 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +`define STRINGIFY(text) `"text`" + +//======================================================================== +// Various expression tests. The macro generates a module with the desired +// input and tested expression. +// +`define EXPR_TEST(name, test_edges, inputs, expr) \ +module t_``name inputs; \ + logic[$bits(expr)-1:0] last = 0; \ + always @(expr) begin \ + if ($bits(expr) > 1) begin \ + `WRITE_VERBOSE(("[%0t] %s [changed] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + end \ + if ($time > 0 && (expr) == last) $stop; \ + last <= expr; \ + end \ + generate if (test_edges) begin \ + always @(posedge expr) begin \ + `WRITE_VERBOSE(("[%0t] %s [posedge] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + if ($time > 0 && ({1'b0, ~(expr)}[0] || last[0])) $stop; \ + end \ + always @(negedge expr) begin \ + `WRITE_VERBOSE(("[%0t] %s [negedge] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + if ($time > 0 && ({1'b0, expr}[0] || ~last[0])) $stop; \ + end \ + end endgenerate \ +endmodule + +`EXPR_TEST(xor, 1, (input a, b), b^a) +`EXPR_TEST(nand, 1, (input a, b, c), ~(c&b&a)) +`EXPR_TEST(concat1, 1, (input a, b, c), {{a, b},c,a,{2{a,b,c}}}) +`EXPR_TEST(reduce, 1, (input[3:0] v), v[0]^v[1]^v[2]^v[3]) +`EXPR_TEST(concat2, 1, (input[3:0] v), {{v[0]|v[1]},v[1]|v[2],{4{v[2]|v[3]}}}) +`EXPR_TEST(add, 0, (input int i, j), i+j) +`EXPR_TEST(lt, 1, (input int i, j), i 0 && expr == last) $stop; \ + last <= expr; \ + end \ +endmodule + +`CLASS_TEST(class, obj.k) + +`ifdef UNSUP +`CLASS_TEST(method, obj.get_k()) +`endif +`endif + +//======================================================================== +// $c test has to be written out explicitly as the STRINGIFY macro can't handle it +// +module t_cstmt; + logic last = 0; + always @($c("vlSelf->clk")) begin + if ($time > 0 && logic'($c("vlSelf->clk")) == last) $stop; + last <= logic'($c("vlSelf->clk")); + end + always @(posedge $c("vlSelf->clk")) begin + `WRITE_VERBOSE(("[%0t] cstmt [posedge] $c(\"vlSelf->clk\")=%0b, last=%b\n", $time, $c("vlSelf->clk"), last)); + if ($time > 0 && (~logic'($c("vlSelf->clk")) || last)) $stop; + end + always @(negedge $c("vlSelf->clk")) begin + `WRITE_VERBOSE(("[%0t] cstmt [negedge] $c(\"vlSelf->clk\")=%0b, last=%b\n", $time, $c("vlSelf->clk"), last)); + if ($time > 0 && (logic'($c("vlSelf->clk")) || !last)) $stop; + end +endmodule + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic a = 0, b = 0, c = 0; + t_xor u_xor(.*); + t_nand u_nand(.*); + t_concat1 u_concat1(.*); + + logic[3:0] v = '0; + t_reduce u_reduce(.*); + t_concat2 u_concat2(.*); + + int i = 0, j = 0; + t_add u_add(.*); + t_lt u_lt(.*); + + int t[5] = {0, 1, 2, 3, 4}; + t_array u_array(.*); + t_array_complex u_array_complex(.*); + + int q[$]; + t_queue u_queue(.*); + t_queue_mul u_queue_mul(.*); + +`ifdef UNSUP + t_func u_func(.*); +`endif + + int k; + assign k = i + j; + `ifndef NO_CLASS + t_class u_class(.*); +`ifdef UNSUP + t_method u_method(.*); +`endif + `endif + + t_cstmt u_cstmt; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + // a, b, c + a <= ~a; + if (cyc % 2 == 0) b <= ~b; + else c <= ~c; + // v + if (cyc % 3 == 0) v[0] <= 1; + else v <= v << 1; + // i, j + i <= i + 2; + if (cyc % 2 == 0) j <= j + 4; + // t + t[cyc % 5] <= t[cyc % 5] + cyc; + // q + q.push_front(cyc); + `WRITE_VERBOSE(("[%0t] values: clk=%b, cyc=%0d, a=%b, b=%b, v=%b, i=%0x, j=%0x, t=[%0x, %0x, %0x, %0x, %0x], obj.k=%0x\n", + $time, clk, cyc, a, b, v, i, j, t[0], t[1], t[2], t[3], t[4], k)); + `WRITE_VERBOSE((" q=%p\n", q)); + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_event_control_expr_unsup.out b/test_regress/t/t_event_control_expr_unsup.out new file mode 100644 index 000000000..8f8e2cb64 --- /dev/null +++ b/test_regress/t/t_event_control_expr_unsup.out @@ -0,0 +1,9 @@ +%Error-UNSUPPORTED: t/t_event_control_expr.v:55:13: Unsupported: function calls in sensitivity lists + 55 | always @(id(cyc)) begin + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_event_control_expr.v:82:17: Unsupported: function calls in sensitivity lists + : ... In instance t.u_method + 82 | always @(obj.get_k()) begin + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_expr_unsup.pl b/test_regress/t/t_event_control_expr_unsup.pl new file mode 100755 index 000000000..1bf0030aa --- /dev/null +++ b/test_regress/t/t_event_control_expr_unsup.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(vlt => 1); # no vltmt, as AstMemberSel is unhandled in V3InstrCount + +top_filename("t_event_control_expr.v"); + +compile( + verilator_flags2 => ['-DUNSUP'], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1;