mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
Fix cycle delay in synchronous drive not counting its clocking block (#8487)
This commit is contained in:
+159
-21
@@ -32,6 +32,36 @@
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
//######################################################################
|
||||
// Clocking blocks of clocking items
|
||||
|
||||
class AssertPreClockingVisitor final : public VNVisitorConst {
|
||||
// Captures the clocking block of each clocking item before AssertPreVisitor, which needs it
|
||||
// at synchronous drives, often visited before the clocking block, e.g. in a parent module
|
||||
// NODE STATE
|
||||
// AstClockingItem::user3p() // AstClocking*. Clocking block of the item, user3 claimed by
|
||||
// // AssertPreVisitor
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNetlist* nodep) override { iterateAndNextConstNull(nodep->modulesp()); }
|
||||
void visit(AstNodeModule* nodep) override { iterateAndNextConstNull(nodep->stmtsp()); }
|
||||
void visit(AstGenBlock* nodep) override { iterateAndNextConstNull(nodep->itemsp()); }
|
||||
void visit(AstClocking* nodep) override {
|
||||
for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) {
|
||||
if (AstClockingItem* const citemp = VN_CAST(itemp, ClockingItem)) {
|
||||
citemp->user3p(nodep);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clocking blocks are only in modules and their generate blocks
|
||||
void visit(AstNode*) override {}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit AssertPreClockingVisitor(AstNetlist* nodep) { iterateConst(nodep); }
|
||||
~AssertPreClockingVisitor() override = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Assert class functions
|
||||
|
||||
@@ -49,9 +79,12 @@ private:
|
||||
// NODE STATE
|
||||
// AstClockingItem::user1p() // AstVar*. varp() of ClockingItem after unlink
|
||||
// AstClockingItem::user2p() // AstVar*. Flag set by drives of output clockvar
|
||||
// AstClockingItem::user3p() // AstClocking*. Clocking block of the item, captured
|
||||
// // by AssertPreClockingVisitor
|
||||
// AstPExpr::user1() // bool. Created from AstUntil
|
||||
const VNUser1InUse m_inuser1;
|
||||
const VNUser2InUse m_inuser2;
|
||||
const VNUser3InUse m_inuser3;
|
||||
// STATE
|
||||
// Current context:
|
||||
AstNetlist* const m_netlistp = nullptr; // Current netlist
|
||||
@@ -83,6 +116,7 @@ private:
|
||||
bool m_inAssign = false; // True if in an AssignNode
|
||||
bool m_inAssignDlyLhs = false; // True if in AssignDly's LHS
|
||||
bool m_inSynchDrive = false; // True if in synchronous drive
|
||||
bool m_inConcatAssign = false; // True if in assignment to a concatenation
|
||||
std::vector<SynchDrive> m_drives; // Clockvars written by the synchronous drive
|
||||
bool m_hasCycleDelay = false; // True if node has cycle delay beneath
|
||||
std::vector<AstVarXRef*> m_xrefsp; // list of xrefs that need name fixup
|
||||
@@ -100,26 +134,97 @@ private:
|
||||
}
|
||||
return VN_AS(itemp->user2p(), Var);
|
||||
}
|
||||
// Assignment setting the drive flag of a driven clockvar, referenced like the clockvar
|
||||
AstAssign* newDrivenSetp(FileLine* flp, const SynchDrive& drive) {
|
||||
AstVar* const varp = getCreateDrivenVarp(drive.itemp);
|
||||
AstNodeExpr* refp;
|
||||
// Reference to a variable in the scope of a driven clockvar, made like the clockvar's
|
||||
static AstNodeExpr* newDriveRefp(FileLine* flp, const SynchDrive& drive, AstVar* varp,
|
||||
const VAccess& access) {
|
||||
if (const AstVarXRef* const xrefp = VN_CAST(drive.refp, VarXRef)) {
|
||||
refp = new AstVarXRef{flp, varp, xrefp->dotted(), VAccess::WRITE};
|
||||
return new AstVarXRef{flp, varp, xrefp->dotted(), access};
|
||||
} else if (const AstMemberSel* const selp = VN_CAST(drive.refp, MemberSel)) {
|
||||
// The interface expression is evaluated again for the flag, so it must not have side
|
||||
// effects; cloneTreePure warns if it has any
|
||||
// The interface expression is evaluated again, so it must not have side effects;
|
||||
// cloneTreePure warns if it has any
|
||||
AstMemberSel* const newSelp
|
||||
= new AstMemberSel{flp, selp->fromp()->cloneTreePure(false), varp};
|
||||
newSelp->access(VAccess::WRITE);
|
||||
refp = newSelp;
|
||||
} else {
|
||||
refp = new AstVarRef{flp, varp, VAccess::WRITE};
|
||||
newSelp->access(access);
|
||||
return newSelp;
|
||||
}
|
||||
AstAssign* const setp = new AstAssign{flp, refp, new AstConst{flp, AstConst::BitTrue{}}};
|
||||
return new AstVarRef{flp, varp, access};
|
||||
}
|
||||
// Assignment setting the drive flag of a driven clockvar
|
||||
AstAssign* newDrivenSetp(FileLine* flp, const SynchDrive& drive) {
|
||||
AstVar* const varp = getCreateDrivenVarp(drive.itemp);
|
||||
AstAssign* const setp = new AstAssign{flp, newDriveRefp(flp, drive, varp, VAccess::WRITE),
|
||||
new AstConst{flp, AstConst::BitTrue{}}};
|
||||
setp->user1(true);
|
||||
return setp;
|
||||
}
|
||||
// Clocking block of a clocking item
|
||||
static AstClocking* clockingOf(const AstClockingItem* itemp) {
|
||||
UASSERT_OBJ(itemp->user3p(), itemp, "Clocking item not captured in a clocking block");
|
||||
return VN_AS(itemp->user3p(), Clocking);
|
||||
}
|
||||
// Clocking event of a driven clockvar, referenced from the drive like the clockvar, or
|
||||
// nullptr if unsupported, which is reported on the drive's cycle delay
|
||||
AstSenItem* newDriveSensesp(AstDelay* delayp, const SynchDrive& drive) {
|
||||
AstSenItem* const origp = clockingOf(drive.itemp)->sensesp();
|
||||
if (VN_IS(drive.refp, VarRef)) return origp->cloneTree(false);
|
||||
if (origp->exists([](const AstVarXRef*) { return true; })) {
|
||||
// The reference is relative to the clocking block
|
||||
delayp->v3warn(E_UNSUPPORTED, "Unsupported: cycle delay in synchronous drive to"
|
||||
" clockvar in another scope, whose clocking event"
|
||||
" has a hierarchical reference");
|
||||
return nullptr;
|
||||
}
|
||||
const AstMemberSel* const selp = VN_CAST(drive.refp, MemberSel);
|
||||
// Processes outside classes evaluate their events also when not waiting on them,
|
||||
// possibly before the interface reference is set, so the event is guarded against a
|
||||
// null reference, which needs an integral value; scheduling does not guard it
|
||||
const bool guardNull = selp && !VN_IS(m_modp, Class);
|
||||
if (guardNull && !origp->sensp()->dtypep()->skipRefp()->isIntegralOrPacked()) {
|
||||
delayp->v3warn(E_UNSUPPORTED, "Unsupported: cycle delay in synchronous drive through"
|
||||
" an interface reference outside a class, to clocking"
|
||||
" block with non-integral clocking event");
|
||||
return nullptr;
|
||||
}
|
||||
// Interface declaring the clocking block, if driven through an interface reference
|
||||
AstIface* const ifacep
|
||||
= selp ? VN_AS(selp->fromp()->dtypep()->skipRefp(), IfaceRefDType)->ifaceViaCellp()
|
||||
: nullptr;
|
||||
AstSenItem* const sensesp = origp->cloneTree(false);
|
||||
std::vector<AstVarRef*> refps;
|
||||
sensesp->foreach([&](AstVarRef* refp) {
|
||||
if (!refp->classOrPackagep()) refps.push_back(refp);
|
||||
});
|
||||
for (AstVarRef* const refp : refps) {
|
||||
AstVar* const varp = refp->varp();
|
||||
if (ifacep) {
|
||||
// Mark the variable as read through an interface reference, as V3Width does for
|
||||
// a member select. The mark is the interface declaring the variable, whose
|
||||
// instances all share it, so drives through references to any instances agree.
|
||||
UASSERT_OBJ(!varp->sensIfacep() || varp->sensIfacep() == ifacep, refp,
|
||||
"Variable read through references to different interfaces");
|
||||
varp->sensIfacep(ifacep);
|
||||
}
|
||||
refp->replaceWith(newDriveRefp(refp->fileline(), drive, varp, refp->access()));
|
||||
VL_DO_DANGLING(pushDeletep(refp), refp);
|
||||
}
|
||||
if (guardNull) guardNullIface(sensesp, selp->fromp());
|
||||
return sensesp;
|
||||
}
|
||||
// Make an integral clocking event through an interface reference false while the reference
|
||||
// is null
|
||||
static void guardNullIface(AstSenItem* sensesp, AstNodeExpr* fromp) {
|
||||
FileLine* const flp = sensesp->fileline();
|
||||
AstNodeExpr* const sensp = sensesp->sensp()->unlinkFrBack();
|
||||
sensesp->sensp(new AstCond{flp, newNotNullp(flp, fromp), sensp,
|
||||
new AstConst{flp, AstConst::DTyped{}, sensp->dtypep()}});
|
||||
if (AstNodeExpr* const condp = sensesp->condp()) {
|
||||
condp->unlinkFrBack();
|
||||
sensesp->condp(new AstLogAnd{flp, newNotNullp(flp, fromp), condp});
|
||||
}
|
||||
}
|
||||
static AstNodeExpr* newNotNullp(FileLine* flp, AstNodeExpr* fromp) {
|
||||
return new AstNeq{flp, fromp->cloneTreePure(false), new AstConst{flp, AstConst::Null{}}};
|
||||
}
|
||||
|
||||
static void checkSamplingFuncDType(AstNodeExpr* nodep, const AstNode* exprp) {
|
||||
const AstNodeDType* const dtypep = exprp->dtypep()->skipRefp();
|
||||
@@ -493,12 +598,12 @@ private:
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
|
||||
const AstConst* const constp = VN_CAST(valuep, Const);
|
||||
if (!constp) {
|
||||
if (!constp && !m_inSynchDrive) {
|
||||
// V3AssertNfa handles non-const delays before this pass and
|
||||
// replaces the property; this branch should never be reached.
|
||||
nodep->v3fatalSrc("Non-constant cycle delay in assertion: "
|
||||
"should have been caught by V3AssertNfa");
|
||||
} else if (constp->isZero()) {
|
||||
} else if (constp && constp->isZero()) {
|
||||
VL_DO_DANGLING(pushDeletep(valuep), valuep);
|
||||
if (m_inSynchDrive) {
|
||||
// ##0 has no effect in synchronous drives (IEEE 1800-2023 14.11)
|
||||
@@ -535,20 +640,36 @@ private:
|
||||
return;
|
||||
}
|
||||
AstSenItem* sensesp = nullptr;
|
||||
if (!m_defaultClockingp) {
|
||||
if (!m_drives.empty()) {
|
||||
if (m_inConcatAssign) {
|
||||
// Also the clockvars may be of different clocking blocks
|
||||
nodep->v3error("Cycle delays cannot be used in synchronous drives to a"
|
||||
" concatenation (IEEE 1800-2023 14.16)");
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
VL_DO_DANGLING(pushDeletep(valuep), valuep);
|
||||
return;
|
||||
}
|
||||
// Count the cycles of the driven clockvar's clocking block (IEEE 1800-2023 14.16)
|
||||
sensesp = newDriveSensesp(nodep, m_drives.front());
|
||||
if (!sensesp) {
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
VL_DO_DANGLING(pushDeletep(valuep), valuep);
|
||||
return;
|
||||
}
|
||||
} else if (!m_defaultClockingp) {
|
||||
if (!m_pexprp) {
|
||||
nodep->v3error("Usage of cycle delays requires default clocking"
|
||||
" (IEEE 1800-2023 14.11)");
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
VL_DO_DANGLING(valuep->deleteTree(), valuep);
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
VL_DO_DANGLING(pushDeletep(valuep), valuep);
|
||||
return;
|
||||
}
|
||||
sensesp = m_senip;
|
||||
sensesp = m_senip->cloneTree(false);
|
||||
} else {
|
||||
sensesp = m_defaultClockingp->sensesp();
|
||||
sensesp = m_defaultClockingp->sensesp()->cloneTree(false);
|
||||
}
|
||||
AstEventControl* const controlp = new AstEventControl{
|
||||
nodep->fileline(), new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr};
|
||||
AstEventControl* const controlp
|
||||
= new AstEventControl{nodep->fileline(), new AstSenTree{flp, sensesp}, nullptr};
|
||||
const std::string delayName = m_cycleDlyNames.get(nodep);
|
||||
AstNodeExpr* throughoutp
|
||||
= nodep->throughoutp() ? nodep->throughoutp()->unlinkFrBack() : nullptr;
|
||||
@@ -557,6 +678,15 @@ private:
|
||||
nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
|
||||
cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
AstBegin* const beginp = new AstBegin{flp, delayName + "__block", cntVarp, true};
|
||||
// A non-constant delay of a synchronous drive is counted at runtime, waiting for none if
|
||||
// zero, like a constant '##0' in a synchronous drive. The count is integral of 64 bits,
|
||||
// as V3Width makes delays, or real, which is rounded (IEEE 1800-2023 6.12.1)
|
||||
if (valuep->isDouble()) {
|
||||
valuep = new AstRToIRoundS{flp, valuep};
|
||||
valuep->dtypeFrom(cntVarp);
|
||||
} else {
|
||||
valuep = new AstSel{flp, valuep, 0, cntVarp->width()};
|
||||
}
|
||||
beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, valuep});
|
||||
|
||||
// Throughout: create flag tracking whether condition held every tick
|
||||
@@ -703,9 +833,16 @@ private:
|
||||
if (nodep->user1()) return;
|
||||
VL_RESTORER(m_inAssign);
|
||||
VL_RESTORER(m_inSynchDrive);
|
||||
VL_RESTORER(m_inConcatAssign);
|
||||
VL_RESTORER_CLEAR(m_drives);
|
||||
m_inAssign = true;
|
||||
m_inSynchDrive = false;
|
||||
{
|
||||
// The braces of a concatenation are a replication, by one
|
||||
const AstNodeExpr* lhsp = nodep->lhsp();
|
||||
if (const AstReplicate* const repp = VN_CAST(lhsp, Replicate)) lhsp = repp->srcp();
|
||||
m_inConcatAssign = VN_IS(lhsp, Concat);
|
||||
}
|
||||
{
|
||||
VL_RESTORER(m_inAssignDlyLhs);
|
||||
m_inAssignDlyLhs = VN_IS(nodep, AssignDly);
|
||||
@@ -1686,6 +1823,7 @@ public:
|
||||
// CONSTRUCTORS
|
||||
explicit AssertPreVisitor(AstNetlist* nodep)
|
||||
: m_netlistp{nodep} {
|
||||
{ AssertPreClockingVisitor{nodep}; }
|
||||
// Process
|
||||
iterate(nodep);
|
||||
// Fix up varref names
|
||||
|
||||
@@ -43,4 +43,8 @@
|
||||
: ... note: In instance 't'
|
||||
39 | $display(cb1.out);
|
||||
| ^~~
|
||||
%Error: t/t_clocking_bad4.v:46:33: Cycle delays cannot be used in synchronous drives to a concatenation (IEEE 1800-2023 14.16)
|
||||
: ... note: In instance 't'
|
||||
46 | always {cb1.out, cb3.out3} <= ##1 2'b11;
|
||||
| ^~
|
||||
%Error: Exiting due to
|
||||
|
||||
@@ -38,4 +38,10 @@ module t (
|
||||
cb1.in = 1;
|
||||
$display(cb1.out);
|
||||
end
|
||||
|
||||
logic out3;
|
||||
clocking cb3 @(posedge clk);
|
||||
output out3;
|
||||
endclocking
|
||||
always {cb1.out, cb3.out3} <= ##1 2'b11;
|
||||
endmodule
|
||||
|
||||
@@ -35,12 +35,18 @@ module t;
|
||||
b = 0;
|
||||
@(pe);
|
||||
{pe.a, pe.b} <= 2'b11;
|
||||
#2;
|
||||
a = 0;
|
||||
b = 0;
|
||||
// '##0' has no effect, so is accepted, unlike other cycle delays (see t_clocking_bad4)
|
||||
@(pe);
|
||||
{pe.a, pe.b} <= ##0 2'b11;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#30;
|
||||
`checks(a_log, "1@5 0@7 1@15 ")
|
||||
`checks(b_log, "1@5 0@7 1@15 ")
|
||||
#40;
|
||||
`checks(a_log, "1@5 0@7 1@15 0@17 1@25 ")
|
||||
`checks(b_log, "1@5 0@7 1@15 0@17 1@25 ")
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Cycle delays of synchronous drives count the target clocking block
|
||||
#
|
||||
# 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-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,276 @@
|
||||
// DESCRIPTION: Verilator: Cycle delays of synchronous drives count the target clocking block
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=\"%s\" exp=\"%s\"\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
package pkg;
|
||||
bit pclk;
|
||||
endpackage
|
||||
|
||||
interface bus_if (
|
||||
input bit clk
|
||||
);
|
||||
bit w;
|
||||
bit ws;
|
||||
bit z;
|
||||
clocking cb @(posedge clk);
|
||||
output w, z;
|
||||
output #1 ws;
|
||||
endclocking
|
||||
modport tb(clocking cb);
|
||||
endinterface
|
||||
|
||||
interface en_if (
|
||||
input bit clk
|
||||
);
|
||||
bit en;
|
||||
bit y;
|
||||
clocking icb @(posedge clk iff en);
|
||||
output y;
|
||||
endclocking
|
||||
endinterface
|
||||
|
||||
interface ev_if;
|
||||
event ev;
|
||||
bit e;
|
||||
clocking ecb @(ev);
|
||||
output e;
|
||||
endclocking
|
||||
endinterface
|
||||
|
||||
// A class has no default clocking
|
||||
class Driver;
|
||||
virtual bus_if vif;
|
||||
virtual bus_if.tb tvif;
|
||||
virtual bus_if vifs[2];
|
||||
virtual ev_if vvif;
|
||||
task run();
|
||||
@(vif.cb);
|
||||
vif.cb.w <= ##2 1;
|
||||
tvif.cb.w <= ##2 1;
|
||||
tvif.cb.ws <= ##2 1;
|
||||
vifs[0].cb.w <= ##2 1;
|
||||
vvif.ecb.e <= ##2 1;
|
||||
vif.cb.z <= ##0 1;
|
||||
endtask
|
||||
endclass
|
||||
|
||||
// No default clocking is needed for a cycle delay in a drive
|
||||
module sub (
|
||||
input bit clk,
|
||||
output bit [1:0] q
|
||||
);
|
||||
bit [6:0] s;
|
||||
bit n;
|
||||
bit p;
|
||||
clocking cb @(posedge clk);
|
||||
output q, s;
|
||||
endclocking
|
||||
clocking ncb @(negedge clk);
|
||||
output n;
|
||||
endclocking
|
||||
clocking pcb @(posedge pkg::pclk);
|
||||
output p;
|
||||
endclocking
|
||||
for (genvar i = 0; i < 2; ++i) begin : g
|
||||
bit x;
|
||||
clocking cb @(posedge clk);
|
||||
output x;
|
||||
endclocking
|
||||
end
|
||||
initial begin
|
||||
@(cb);
|
||||
cb.q <= ##1 2;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module port_drv (
|
||||
bus_if b
|
||||
);
|
||||
initial begin
|
||||
@(b.cb);
|
||||
b.cb.w <= ##2 1;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
bit clk;
|
||||
bit tclk;
|
||||
bit slow_clk;
|
||||
bit [1:0] v;
|
||||
bit [1:0] q;
|
||||
bit c;
|
||||
bit c0;
|
||||
bit c7;
|
||||
bit cr;
|
||||
bit cr5;
|
||||
bit d;
|
||||
int cycles = 2;
|
||||
int nocycles = 0;
|
||||
bit [6:0] cycles7 = 2;
|
||||
real rcycles = 1.6;
|
||||
string v_log;
|
||||
string q_log;
|
||||
string s_log;
|
||||
string n_log;
|
||||
string p_log;
|
||||
string x_log;
|
||||
string x0_log;
|
||||
string c_log;
|
||||
string c0_log;
|
||||
string c7_log;
|
||||
string cr_log;
|
||||
string cr5_log;
|
||||
string proc_time;
|
||||
string d_log;
|
||||
string z_log;
|
||||
string w_log;
|
||||
string tw_log;
|
||||
string tws_log;
|
||||
string w0_log;
|
||||
string w1_log;
|
||||
string mw_log;
|
||||
string pw_log;
|
||||
string y_log;
|
||||
string e_log;
|
||||
Driver drv = new;
|
||||
|
||||
always #5 clk = ~clk;
|
||||
always #7 tclk = ~tclk;
|
||||
always #20 slow_clk = ~slow_clk;
|
||||
always @(clk) pkg::pclk = clk;
|
||||
|
||||
default clocking slow @(posedge slow_clk);
|
||||
output d;
|
||||
endclocking
|
||||
|
||||
clocking fast @(posedge clk);
|
||||
output v, c, c0, c7, cr, cr5;
|
||||
endclocking
|
||||
|
||||
sub sub (
|
||||
.clk,
|
||||
.q
|
||||
);
|
||||
bus_if bus (.clk);
|
||||
// Another instance of the interface, with another clock
|
||||
bus_if tbus (.clk(tclk));
|
||||
bus_if buses[2] (.clk);
|
||||
bus_if mbus (.clk);
|
||||
bus_if pbus (.clk);
|
||||
en_if ebus (.clk);
|
||||
ev_if vbus ();
|
||||
port_drv port_drv (.b(pbus));
|
||||
|
||||
virtual bus_if mvif;
|
||||
virtual en_if evif;
|
||||
|
||||
initial forever #10->vbus.ev;
|
||||
|
||||
always @(v) if ($time != 0) v_log = {v_log, $sformatf("%0d@%0d ", v, $time)};
|
||||
always @(q) if ($time != 0) q_log = {q_log, $sformatf("%0d@%0d ", q, $time)};
|
||||
always @(sub.s) if ($time != 0) s_log = {s_log, $sformatf("%0d@%0d ", sub.s, $time)};
|
||||
always @(sub.n) if ($time != 0) n_log = {n_log, $sformatf("%0d@%0d ", sub.n, $time)};
|
||||
always @(sub.p) if ($time != 0) p_log = {p_log, $sformatf("%0d@%0d ", sub.p, $time)};
|
||||
always @(sub.g[1].x) if ($time != 0) x_log = {x_log, $sformatf("%0d@%0d ", sub.g[1].x, $time)};
|
||||
always @(sub.g[0].x) if ($time != 0) x0_log = {x0_log, $sformatf("%0d@%0d ", sub.g[0].x, $time)};
|
||||
always @(c) if ($time != 0) c_log = {c_log, $sformatf("%0d@%0d ", c, $time)};
|
||||
always @(c0) if ($time != 0) c0_log = {c0_log, $sformatf("%0d@%0d ", c0, $time)};
|
||||
always @(c7) if ($time != 0) c7_log = {c7_log, $sformatf("%0d@%0d ", c7, $time)};
|
||||
always @(cr) if ($time != 0) cr_log = {cr_log, $sformatf("%0d@%0d ", cr, $time)};
|
||||
always @(cr5) if ($time != 0) cr5_log = {cr5_log, $sformatf("%0d@%0d ", cr5, $time)};
|
||||
always @(d) if ($time != 0) d_log = {d_log, $sformatf("%0d@%0d ", d, $time)};
|
||||
always @(bus.z) if ($time != 0) z_log = {z_log, $sformatf("%0d@%0d ", bus.z, $time)};
|
||||
always @(bus.w) if ($time != 0) w_log = {w_log, $sformatf("%0d@%0d ", bus.w, $time)};
|
||||
always @(tbus.w) if ($time != 0) tw_log = {tw_log, $sformatf("%0d@%0d ", tbus.w, $time)};
|
||||
always @(tbus.ws) if ($time != 0) tws_log = {tws_log, $sformatf("%0d@%0d ", tbus.ws, $time)};
|
||||
always @(buses[0].w) if ($time != 0) w0_log = {w0_log, $sformatf("%0d@%0d ", buses[0].w, $time)};
|
||||
always @(buses[1].w) if ($time != 0) w1_log = {w1_log, $sformatf("%0d@%0d ", buses[1].w, $time)};
|
||||
always @(mbus.w) if ($time != 0) mw_log = {mw_log, $sformatf("%0d@%0d ", mbus.w, $time)};
|
||||
always @(pbus.w) if ($time != 0) pw_log = {pw_log, $sformatf("%0d@%0d ", pbus.w, $time)};
|
||||
always @(ebus.y) if ($time != 0) y_log = {y_log, $sformatf("%0d@%0d ", ebus.y, $time)};
|
||||
always @(vbus.e) if ($time != 0) e_log = {e_log, $sformatf("%0d@%0d ", vbus.e, $time)};
|
||||
|
||||
initial begin
|
||||
mvif = mbus;
|
||||
evif = ebus;
|
||||
drv.vif = bus;
|
||||
drv.tvif = tbus;
|
||||
drv.vifs[0] = buses[0];
|
||||
drv.vifs[1] = buses[1];
|
||||
drv.vvif = vbus;
|
||||
drv.run();
|
||||
end
|
||||
|
||||
// Each drive counts the cycles of the target clockvar's clocking block, not of the default
|
||||
// clocking (IEEE 1800-2023 14.16)
|
||||
initial begin
|
||||
@(fast);
|
||||
fast.v <= ##2 1;
|
||||
sub.cb.s <= ##2 7'h55;
|
||||
sub.ncb.n <= ##1 1;
|
||||
sub.pcb.p <= ##1 1;
|
||||
sub.g[1].cb.x <= ##2 1;
|
||||
// The default clocking counts its cycles as before
|
||||
slow.d <= ##1 1;
|
||||
// A non-constant cycle delay is evaluated by the drive, and counted at runtime
|
||||
fast.c <= ##cycles 1;
|
||||
fast.c0 <= ##nocycles 1;
|
||||
fast.c7 <= ##cycles7 1;
|
||||
// A real cycle count is rounded (IEEE 1800-2023 6.12.1)
|
||||
fast.cr <= ##rcycles 1;
|
||||
fast.cr5 <= ##(2.5) 1;
|
||||
sub.g[0].cb.x <= ##cycles 1;
|
||||
buses[1].cb.w <= ##2 1;
|
||||
mvif.cb.w <= ##2 1;
|
||||
end
|
||||
|
||||
// The clocking event of icb only occurs while en is set
|
||||
initial begin
|
||||
#1 evif.icb.y <= ##1 1;
|
||||
#20 ebus.en = 1;
|
||||
end
|
||||
|
||||
// Also in a procedural cycle delay, of the default clocking
|
||||
initial begin
|
||||
#2;
|
||||
##(1.5);
|
||||
proc_time = $sformatf("%0d", $time);
|
||||
end
|
||||
|
||||
initial begin
|
||||
#100;
|
||||
`checks(v_log, "1@25 ")
|
||||
`checks(q_log, "2@15 ")
|
||||
`checks(s_log, "85@25 ")
|
||||
`checks(n_log, "1@10 ")
|
||||
`checks(p_log, "1@15 ")
|
||||
`checks(x_log, "1@25 ")
|
||||
`checks(x0_log, "1@25 ")
|
||||
`checks(c_log, "1@25 ")
|
||||
`checks(c0_log, "1@5 ")
|
||||
`checks(c7_log, "1@25 ")
|
||||
`checks(cr_log, "1@25 ")
|
||||
`checks(cr5_log, "1@35 ")
|
||||
`checks(d_log, "1@20 ")
|
||||
`checks(proc_time, "60")
|
||||
`checks(z_log, "1@5 ")
|
||||
`checks(w_log, "1@25 ")
|
||||
`checks(tw_log, "1@21 ")
|
||||
`checks(tws_log, "1@22 ")
|
||||
`checks(w0_log, "1@25 ")
|
||||
`checks(w1_log, "1@25 ")
|
||||
`checks(mw_log, "1@25 ")
|
||||
`checks(pw_log, "1@25 ")
|
||||
`checks(y_log, "1@25 ")
|
||||
`checks(e_log, "1@20 ")
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,10 @@
|
||||
%Error-UNSUPPORTED: t/t_clocking_drive_cycle_unsup.v:18:24: Unsupported: cycle delay in synchronous drive to clockvar in another scope, whose clocking event has a hierarchical reference
|
||||
: ... note: In instance 't'
|
||||
18 | initial sub.scb.s <= ##1 1;
|
||||
| ^~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_clocking_drive_cycle_unsup.v:24:19: Unsupported: cycle delay in synchronous drive through an interface reference outside a class, to clocking block with non-integral clocking event
|
||||
: ... note: In instance 't'
|
||||
24 | vvif.ecb.e <= ##1 1;
|
||||
| ^~
|
||||
%Error: Exiting due to
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,34 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module sub;
|
||||
bit s;
|
||||
clocking scb @(posedge t.clk);
|
||||
output s;
|
||||
endclocking
|
||||
initial scb.s <= ##1 1;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
bit clk;
|
||||
sub sub ();
|
||||
initial sub.scb.s <= ##1 1;
|
||||
|
||||
ev_if vbus ();
|
||||
virtual ev_if vvif;
|
||||
initial begin
|
||||
vvif = vbus;
|
||||
vvif.ecb.e <= ##1 1;
|
||||
end
|
||||
endmodule
|
||||
|
||||
interface ev_if;
|
||||
event ev;
|
||||
bit e;
|
||||
clocking ecb @(ev);
|
||||
output e;
|
||||
endclocking
|
||||
endinterface
|
||||
Reference in New Issue
Block a user