Support modports referencing clocking blocks (#4555) (#6436)

This commit is contained in:
Ryszard Rozak 2025-09-16 19:25:40 +02:00 committed by GitHub
parent ef458be855
commit c856380fac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 141 additions and 10 deletions

View File

@ -38,6 +38,7 @@ class AssertPreVisitor final : public VNVisitor {
// We're not parsing the tree, or anything more complicated.
private:
// NODE STATE
// AstClockingItem::user1p() // AstVar*. varp() of ClockingItem after unlink
const VNUser1InUse m_inuser1;
// STATE
// Current context:
@ -156,19 +157,32 @@ private:
if (nodep->eventp()) nodep->addNextHere(nodep->eventp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstModportClockingRef* const nodep) override {
// It has to be converted to a list of ModportClockingVarRefs,
// because clocking blocks are removed in this pass
for (AstClockingItem* itemp = nodep->clockingp()->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), ClockingItem)) {
AstVar* const varp = itemp->varp() ? itemp->varp() : VN_AS(itemp->user1p(), Var);
if (varp) {
AstModportVarRef* const modVarp
= new AstModportVarRef{nodep->fileline(), varp->name(), itemp->direction()};
modVarp->varp(varp);
nodep->addNextHere(modVarp);
}
}
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstClockingItem* const nodep) override {
// Get a ref to the sampled/driven variable
AstVar* const varp = nodep->varp();
if (!varp) {
// Unused item
pushDeletep(nodep->unlinkFrBack());
return;
}
FileLine* const flp = nodep->fileline();
V3Const::constifyEdit(nodep->skewp());
if (!VN_IS(nodep->skewp(), Const)) {
nodep->skewp()->v3error("Skew must be constant (IEEE 1800-2023 14.4)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
AstConst* const skewp = VN_AS(nodep->skewp(), Const);
@ -176,6 +190,7 @@ private:
AstNodeExpr* const exprp = nodep->exprp();
varp->name(m_clockingp->name() + "__DOT__" + varp->name());
m_clockingp->addNextHere(varp->unlinkFrBack());
nodep->user1p(varp);
varp->user1p(nodep);
if (nodep->direction() == VDirection::OUTPUT) {
exprp->foreach([](const AstNodeVarRef* varrefp) {
@ -292,7 +307,6 @@ private:
} else {
nodep->v3fatalSrc("Invalid direction");
}
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstDelay* nodep) override {
// Only cycle delays are relevant in this stage; also only process once

View File

@ -826,6 +826,7 @@ public:
addItemsp(itemsp);
}
ASTGEN_MEMBERS_AstClocking;
bool maybePointedTo() const override VL_MT_SAFE { return true; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
std::string name() const override VL_MT_STABLE { return m_name; }
@ -1198,6 +1199,25 @@ public:
bool maybePointedTo() const override VL_MT_SAFE { return true; }
ASTGEN_MEMBERS_AstModport;
};
class AstModportClockingRef final : public AstNode {
// A clocking block referenced under a modport
// The storage for the clocking block itself is inside the interface, thus this is a reference
// PARENT: AstModport
//
// @astgen ptr := m_clockingp : Optional[AstClocking] // Link to the actual clocking block
string m_name; // Name of the clocking block referenced
public:
AstModportClockingRef(FileLine* fl, const string& name)
: ASTGEN_SUPER_ModportClockingRef(fl)
, m_name{name} {}
ASTGEN_MEMBERS_AstModportClockingRef;
void dump(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; }
AstClocking* clockingp() const VL_MT_STABLE {
return m_clockingp;
} // [After Link] Pointer to clocking block
void clockingp(AstClocking* clockingp) { m_clockingp = clockingp; }
};
class AstModportFTaskRef final : public AstNode {
// An import/export referenced under a modport
// The storage for the function itself is inside the

View File

@ -2082,6 +2082,15 @@ void AstMemberSel::dump(std::ostream& str) const {
}
}
void AstMemberSel::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstModportClockingRef::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (clockingp()) {
str << " -> ";
clockingp()->dump(str);
} else {
str << " -> UNLINKED";
}
}
void AstModportFTaskRef::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isExport()) str << " EXPORT";

View File

@ -2404,6 +2404,19 @@ class LinkDotIfaceVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstModportClockingRef* nodep) override { // IfaceVisitor::
UINFO(5, " fic: " << nodep);
iterateChildren(nodep);
VSymEnt* const symp = m_curSymp->findIdFallback(nodep->name());
if (!symp) {
nodep->v3error("Modport item not found: " << nodep->prettyNameQ());
} else if (AstClocking* const clockingp = VN_CAST(symp->nodep(), Clocking)) {
nodep->clockingp(clockingp);
m_statep->insertSym(m_curSymp, nodep->name(), nodep, nullptr /*package*/);
} else {
nodep->v3error("Modport item is not a clocking block: " << nodep->prettyNameQ());
}
}
void visit(AstNode* nodep) override { iterateChildren(nodep); } // IfaceVisitor::
public:
@ -3963,7 +3976,11 @@ class LinkDotResolveVisitor final : public VNVisitor {
dotSymp = m_statep->getNodeSym(ifaceRefp->ifacep());
}
}
} else if (const AstModportClockingRef* const clockingRefp
= VN_CAST(dotSymp->nodep(), ModportClockingRef)) {
dotSymp = m_statep->getNodeSym(clockingRefp->clockingp());
}
if (!m_statep->forScopeCreation()) {
VSymEnt* foundp = nullptr;
if (modport) {

View File

@ -1655,8 +1655,7 @@ modportPortsDecl<nodep>:
port_direction modportSimplePortOrTFPort { $$ = new AstModportVarRef{$<fl>2, *$2, GRAMMARP->m_varIO};
GRAMMARP->m_modportImpExpActive = false;}
// // IEEE: modport_clocking_declaration
| yCLOCKING idAny/*clocking_identifier*/
{ $$ = nullptr; BBUNSUP($<fl>1, "Unsupported: Modport clocking"); }
| yCLOCKING idAny/*clocking_identifier*/ { $$ = new AstModportClockingRef{$1, *$2}; }
// // IEEE: yIMPORT modport_tf_port
// // IEEE: yEXPORT modport_tf_port
// // modport_tf_port expanded here

View File

@ -51,7 +51,6 @@ for s in [
'Interface port declaration ',
'Modport item is not a function/task: ',
'Modport item is not a variable: ',
'Modport item not found: ',
'Modport not referenced as <interface>.',
'Modport not referenced from underneath an interface: ',
'Non-interface used as an interface: ',
@ -77,7 +76,6 @@ for s in [
'Unsupported: 4-state numbers in this context',
'Unsupported: Bind with instance list',
'Unsupported: Concatenation to form ',
'Unsupported: Modport clocking',
'Unsupported: Modport dotted port name',
'Unsupported: Modport export with prototype',
'Unsupported: Modport import with prototype',
@ -105,7 +103,6 @@ for s in [
'Unsupported: repeat event control',
'Unsupported: static cast to ',
'Unsupported: super',
'Unsupported: this.super',
'Unsupported: with[] stream expression',
]:
Suppressed[s] = True

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,51 @@
// 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
interface axi_if;
logic clk;
wire rlast;
wire rvalid;
clocking cb @(posedge clk);
inout rlast, rvalid;
endclocking
modport md1(clocking cb, inout clk, rlast, rvalid);
modport md2(clocking cb);
endinterface
module sub (
axi_if.md1 axi1,
axi_if.md2 axi2
);
initial begin
axi1.clk = 1'b0;
#1 axi1.clk = 1'b1;
#1 axi1.clk = 1'b0;
#1 axi1.clk = 1'b1;
end
initial begin
@(negedge axi1.rvalid);
$display("[%0t] rvalid==%b", $time, axi1.rvalid);
$display("[%0t] rlast is 1: ", $time, axi1.rlast === 1);
if (axi1.rlast === 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
initial begin
$display("[%0t] rvalid <= 1", $time);
axi1.cb.rvalid <= 1'b1;
@(posedge axi1.rvalid);
$display("[%0t] rvalid <= 0", $time);
axi1.cb.rvalid <= 1'b0;
@(negedge axi1.clk);
$display("[%0t] rlast <= 1", $time);
axi2.cb.rlast <= 1'b1;
end
endmodule
module t;
axi_if axi_vi ();
sub i_sub (.axi1(axi_vi), .axi2(axi_vi));
endmodule

View File

@ -1,5 +1,11 @@
%Error: t/t_mod_interface_clocking_bad.v:16:25: Modport item is not a clocking block: 'reset'
16 | modport mp(input clk, clocking reset, clocking cx);
| ^~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_mod_interface_clocking_bad.v:16:41: Modport item not found: 'cx'
16 | modport mp(input clk, clocking reset, clocking cx);
| ^~~~~~~~
%Error: t/t_mod_interface_clocking_bad.v:25:10: Can't find definition of 'cb'
25 | x.cb.reset <= 1;
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -13,7 +13,7 @@ interface mem_if (
output reset;
endclocking
modport mp(input clk);
modport mp(input clk, clocking reset, clocking cx);
endinterface