mirror of
https://github.com/verilator/verilator.git
synced 2025-04-28 11:36:56 +00:00
Add support for expressions in event controls (#3550)
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
This commit is contained in:
parent
1404319b28
commit
10cf492946
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
116
src/V3Sched.cpp
116
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<AstNodeStmt*> m_updates; // Update assignments
|
||||
std::vector<AstVar*> m_locals; // Trigger eval local variables
|
||||
std::vector<AstNodeStmt*> m_preUpdates; // Pre update assignments
|
||||
std::vector<AstNodeStmt*> m_postUpdates; // Post update assignments
|
||||
|
||||
std::unordered_map<VNRef<AstNode>, AstVarScope*> m_prev; // The 'previous value' signals
|
||||
std::unordered_set<VNRef<AstNode>> m_hasUpdate; // Whether the given sen expression already
|
||||
// has an update statement in m_updates
|
||||
std::unordered_map<VNRef<AstNode>, AstVarScope*> m_curr; // The 'current value' signals
|
||||
std::unordered_set<VNRef<AstNode>> m_hasPreUpdate; // Whether the given sen expression already
|
||||
// has an update statement in m_preUpdates
|
||||
std::unordered_set<VNRef<AstNode>> 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<AstNode>([](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<AstNodeStmt*> getAndClearUpdates() {
|
||||
m_hasUpdate.clear();
|
||||
return std::move(m_updates);
|
||||
std::vector<AstVar*> getAndClearLocals() { return std::move(m_locals); }
|
||||
|
||||
std::vector<AstNodeStmt*> getAndClearPreUpdates() {
|
||||
m_hasPreUpdate.clear();
|
||||
return std::move(m_preUpdates);
|
||||
}
|
||||
|
||||
std::vector<AstNodeStmt*> 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) {
|
||||
|
@ -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); }
|
||||
|
||||
|
@ -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
|
||||
|
@ -3027,15 +3027,8 @@ event_expression<senItemp>: // IEEE: event_expression - split over several
|
||||
|
||||
senitem<senItemp>: // 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{$<fl>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<senItemp>:
|
||||
@ -3043,19 +3036,11 @@ senitemVar<senItemp>:
|
||||
;
|
||||
|
||||
senitemEdge<senItemp>: // 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 }
|
||||
;
|
||||
|
||||
|
33
test_regress/t/t_event_control_expr.pl
Executable file
33
test_regress/t/t_event_control_expr.pl
Executable file
@ -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;
|
174
test_regress/t/t_event_control_expr.v
Normal file
174
test_regress/t/t_event_control_expr.v
Normal file
@ -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<j)
|
||||
`EXPR_TEST(array, 0, (input int t[5]), t[4])
|
||||
`EXPR_TEST(array_complex, 0, (input int t[5], int cyc), t[cyc / 4])
|
||||
`EXPR_TEST(queue, 0, (input int q[$]), q[0])
|
||||
`EXPR_TEST(queue_mul, 0, (input int q[$], int i), q[0]*i)
|
||||
|
||||
`ifdef UNSUP
|
||||
function int id(int x); return x; endfunction
|
||||
`EXPR_TEST(func, 0, (input int cyc), id(cyc))
|
||||
`endif
|
||||
|
||||
//========================================================================
|
||||
// Class tests (special case as V3Width doesn't always properly handle
|
||||
// out-of-module classes
|
||||
//
|
||||
`ifndef NO_CLASS
|
||||
`define CLASS_TEST(name, expr) \
|
||||
module t_``name(input int k); \
|
||||
class Cls; \
|
||||
int k; \
|
||||
function int get_k(); return k; endfunction \
|
||||
endclass \
|
||||
Cls obj = new; \
|
||||
assign obj.k = k; \
|
||||
int last = 0; \
|
||||
always @(expr) begin \
|
||||
`WRITE_VERBOSE(("[%0t] %s [changed] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \
|
||||
if ($time > 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
|
9
test_regress/t/t_event_control_expr_unsup.out
Normal file
9
test_regress/t/t_event_control_expr_unsup.out
Normal file
@ -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
|
22
test_regress/t/t_event_control_expr_unsup.pl
Executable file
22
test_regress/t/t_event_control_expr_unsup.pl
Executable file
@ -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;
|
Loading…
Reference in New Issue
Block a user