Support modport export/import task prototypes and out-of-block definitions (#7277)
This commit is contained in:
parent
b8ca9292a4
commit
a0a684109f
|
|
@ -87,6 +87,7 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
|
|||
// @astgen op4 := scopeNamep : Optional[AstScopeName]
|
||||
string m_name; // Name of task
|
||||
string m_cname; // Name of task if DPI import
|
||||
string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8)
|
||||
uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees
|
||||
bool m_taskPublic : 1; // Public task
|
||||
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
|
||||
|
|
@ -179,6 +180,8 @@ public:
|
|||
void isExternProto(bool flag) { m_isExternProto = flag; }
|
||||
bool isExternDef() const { return m_isExternDef; }
|
||||
void isExternDef(bool flag) { m_isExternDef = flag; }
|
||||
const string& ifacePortName() const { return m_ifacePortName; }
|
||||
void ifacePortName(const string& name) { m_ifacePortName = name; }
|
||||
bool prototype() const { return m_prototype; }
|
||||
void prototype(bool flag) { m_prototype = flag; }
|
||||
bool dpiExport() const { return m_dpiExport; }
|
||||
|
|
|
|||
|
|
@ -1489,11 +1489,129 @@ class LinkDotFindVisitor final : public VNVisitor {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Handle out-of-block interface method definition (IEEE 1800-2023 25.7)
|
||||
// Move task body from module into the interface's prototype task,
|
||||
// rewriting port-prefixed references in the body.
|
||||
void moveIfaceExportBody(AstNodeFTask* nodep) {
|
||||
const string& portName = nodep->ifacePortName();
|
||||
UINFO(5, " moveIfaceExportBody: port=" << portName << " task=" << nodep->name() << endl);
|
||||
// Look up the interface port variable in the module's symbol table
|
||||
VSymEnt* const portSymp = m_modSymp->findIdFallback(portName);
|
||||
if (!portSymp) {
|
||||
nodep->v3error("Interface port not found for out-of-block definition: '"
|
||||
<< portName << "' (IEEE 1800-2023 25.7)");
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
return;
|
||||
}
|
||||
AstVar* const portp = VN_CAST(portSymp->nodep(), Var);
|
||||
if (!portp) {
|
||||
nodep->v3fatalSrc("Out-of-block definition port is not a variable: " << portName);
|
||||
return;
|
||||
}
|
||||
// Get the interface name from the port's type
|
||||
// At this stage dtypep() may not be set yet; check childDTypep() too
|
||||
AstIfaceRefDType* ifaceRefDtp = nullptr;
|
||||
if (portp->dtypep()) { ifaceRefDtp = VN_CAST(portp->dtypep(), IfaceRefDType); }
|
||||
if (!ifaceRefDtp && portp->childDTypep()) {
|
||||
ifaceRefDtp = VN_CAST(portp->childDTypep(), IfaceRefDType);
|
||||
}
|
||||
if (!ifaceRefDtp) {
|
||||
nodep->v3error("Out-of-block definition port is not an interface port: '"
|
||||
<< portName << "' (IEEE 1800-2023 25.7)");
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
return;
|
||||
}
|
||||
const string ifaceName = ifaceRefDtp->ifaceName();
|
||||
UINFO(5, " moveIfaceExportBody: iface=" << ifaceName << endl);
|
||||
// Find the interface module by name
|
||||
AstNodeModule* const ifaceModp = m_statep->findModuleSym(ifaceName);
|
||||
if (!ifaceModp) {
|
||||
nodep->v3fatalSrc("Interface not found for out-of-block definition: " << ifaceName);
|
||||
return;
|
||||
}
|
||||
// Find the prototype task in the interface
|
||||
AstNodeFTask* protoTaskp = nullptr;
|
||||
for (AstNode* itemp = ifaceModp->stmtsp(); itemp; itemp = itemp->nextp()) {
|
||||
if (AstNodeFTask* const ftaskp = VN_CAST(itemp, NodeFTask)) {
|
||||
if (ftaskp->name() == nodep->name() && ftaskp->prototype()) {
|
||||
protoTaskp = ftaskp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!protoTaskp) {
|
||||
nodep->v3error("No matching export prototype found in interface "
|
||||
<< ifaceName << " for task " << nodep->prettyNameQ()
|
||||
<< " (IEEE 1800-2023 25.7)");
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
return;
|
||||
}
|
||||
// Move body statements (non-port declarations) from the out-of-block task
|
||||
// to the prototype task. Rewrite port.X references to just X in the body.
|
||||
for (AstNode* stmtp = nodep->stmtsp(); stmtp;) {
|
||||
AstNode* const nextp = stmtp->nextp();
|
||||
// Skip port declarations -- they already exist in the prototype
|
||||
if (const AstVar* const varp = VN_CAST(stmtp, Var)) {
|
||||
if (varp->isIO()) {
|
||||
stmtp = nextp;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Clone this statement and rewrite port references
|
||||
AstNode* const newStmtp = stmtp->cloneTree(false);
|
||||
rewriteIfacePortRefsSingle(newStmtp, portName);
|
||||
protoTaskp->addStmtsp(newStmtp);
|
||||
stmtp = nextp;
|
||||
}
|
||||
// Mark prototype as no longer prototype (it has a body now)
|
||||
protoTaskp->prototype(false);
|
||||
// Delete the out-of-block task from the module
|
||||
nodep->unlinkFrBack();
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
|
||||
// Rewrite AstDot nodes where lhsp is AstParseRef with the port name
|
||||
// to just the rhsp (stripping the port prefix).
|
||||
static void rewriteIfacePortRefsSingle(AstNode* nodep, const string& portName) {
|
||||
if (!nodep) return;
|
||||
// Check if this is a non-colon Dot with lhs matching portName
|
||||
if (AstDot* const dotp = VN_CAST(nodep, Dot)) {
|
||||
if (!dotp->colon()) {
|
||||
const AstParseRef* const lhsRefp = VN_CAST(dotp->lhsp(), ParseRef);
|
||||
if (lhsRefp && lhsRefp->name() == portName) {
|
||||
UINFO(5,
|
||||
" rewriteIfacePortRefs: stripping port prefix from " << dotp << endl);
|
||||
AstNode* const rhsp = dotp->rhsp()->unlinkFrBack();
|
||||
dotp->replaceWith(rhsp);
|
||||
VL_DO_DANGLING(dotp->deleteTree(), dotp);
|
||||
rewriteIfacePortRefsSingle(rhsp, portName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Recurse into all children
|
||||
for (AstNode* childp : {nodep->op1p(), nodep->op2p(), nodep->op3p(), nodep->op4p()}) {
|
||||
while (childp) {
|
||||
AstNode* const nextp = childp->nextp();
|
||||
rewriteIfacePortRefsSingle(childp, portName);
|
||||
childp = nextp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstNodeFTask* nodep) override { // FindVisitor::
|
||||
// NodeTask: Remember its name for later resolution
|
||||
UINFO(5, " " << nodep);
|
||||
UASSERT_OBJ(m_curSymp && m_modSymp, nodep, "Function/Task not under module?");
|
||||
if (nodep->name() == "new") m_explicitNew = true;
|
||||
// Handle out-of-block interface method definition (IEEE 25.8)
|
||||
if (!nodep->ifacePortName().empty() && m_statep->forPrimary()) {
|
||||
moveIfaceExportBody(nodep);
|
||||
return; // Task has been deleted
|
||||
}
|
||||
// Remember the existing symbol table scope
|
||||
VL_RESTORER(m_classOrPackagep);
|
||||
VL_RESTORER(m_curSymp);
|
||||
|
|
@ -2659,7 +2777,6 @@ class LinkDotIfaceVisitor final : public VNVisitor {
|
|||
void visit(AstModportFTaskRef* nodep) override { // IfaceVisitor::
|
||||
UINFO(5, " fif: " << nodep);
|
||||
iterateChildren(nodep);
|
||||
if (nodep->isExport()) nodep->v3warn(E_UNSUPPORTED, "Unsupported: modport export");
|
||||
VSymEnt* const symp = m_curSymp->findIdFallback(nodep->name());
|
||||
if (!symp) {
|
||||
nodep->v3error("Modport item not found: " << nodep->prettyNameQ());
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ public:
|
|||
= false; // Standalone ID is a tf_identifier instead of port_identifier
|
||||
bool m_modportImpExpLastIsExport
|
||||
= false; // Last import_export statement in modportPortsDecl is an export
|
||||
AstNode* m_modportProtoTasksp
|
||||
= nullptr; // Prototype tasks from modport import/export with prototype
|
||||
|
||||
int m_pinNum = -1; // Pin number currently parsing
|
||||
std::stack<int> m_pinStack; // Queue of pin numbers being parsed
|
||||
|
|
|
|||
|
|
@ -1690,8 +1690,16 @@ modport_itemList<nodep>: // IEEE: part of modport_declaration
|
|||
|
||||
modport_item<nodep>: // ==IEEE: modport_item
|
||||
idAny/*new-modport*/ '('
|
||||
/*mid*/ { VARRESET_NONLIST(UNKNOWN); VARIO(INOUT); }
|
||||
/*cont*/ modportPortsDeclList ')' { $$ = new AstModport{$<fl>1, *$1, $4}; }
|
||||
/*mid*/ { VARRESET_NONLIST(UNKNOWN); VARIO(INOUT);
|
||||
GRAMMARP->m_modportProtoTasksp = nullptr; }
|
||||
/*cont*/ modportPortsDeclList ')'
|
||||
{ $$ = new AstModport{$<fl>1, *$1, $4};
|
||||
// Append any prototype tasks from import/export as interface siblings
|
||||
if (GRAMMARP->m_modportProtoTasksp) {
|
||||
$$ = AstNode::addNextNull(
|
||||
GRAMMARP->m_modportProtoTasksp, $$);
|
||||
GRAMMARP->m_modportProtoTasksp = nullptr;
|
||||
} }
|
||||
;
|
||||
|
||||
modportPortsDeclList<nodep>:
|
||||
|
|
@ -1720,9 +1728,19 @@ modportPortsDecl<nodep>:
|
|||
GRAMMARP->m_modportImpExpActive = true;
|
||||
GRAMMARP->m_modportImpExpLastIsExport = true; }
|
||||
| yIMPORT method_prototype
|
||||
{ $$ = nullptr; BBUNSUP($<fl>1, "Unsupported: Modport import with prototype"); DEL($2); }
|
||||
{ $$ = new AstModportFTaskRef{$<fl>2, $2->name(), false};
|
||||
GRAMMARP->m_modportImpExpActive = true;
|
||||
GRAMMARP->m_modportImpExpLastIsExport = false;
|
||||
// Import prototype: task should already exist in interface
|
||||
// (from export or direct declaration). Delete the prototype.
|
||||
DEL($2); }
|
||||
| yEXPORT method_prototype
|
||||
{ $$ = nullptr; BBUNSUP($<fl>1, "Unsupported: Modport export with prototype"); DEL($2); }
|
||||
{ $$ = new AstModportFTaskRef{$<fl>2, $2->name(), true};
|
||||
GRAMMARP->m_modportImpExpActive = true;
|
||||
GRAMMARP->m_modportImpExpLastIsExport = true;
|
||||
// Export: add prototype task to interface (as sibling of modport)
|
||||
GRAMMARP->m_modportProtoTasksp
|
||||
= AstNode::addNextNull(GRAMMARP->m_modportProtoTasksp, $2); }
|
||||
// Continuations of above after a comma.
|
||||
// // IEEE: modport_simple_ports_declaration
|
||||
| modportSimplePortOrTFPort { $$ = $1; }
|
||||
|
|
@ -4679,7 +4697,7 @@ taskId<nodeFTaskp>:
|
|||
| id/*interface_identifier*/ '.' idAny
|
||||
{ $$ = new AstTask{$<fl>$, *$3, nullptr};
|
||||
$$->verilogTask(true);
|
||||
BBUNSUP($2, "Unsupported: Out of block function declaration"); }
|
||||
$$->ifacePortName(*$1); }
|
||||
//
|
||||
| packageClassScope id
|
||||
{ $$ = new AstTask{$<fl>$, *$2, nullptr};
|
||||
|
|
@ -4745,9 +4763,9 @@ fIdScoped<funcp>: // IEEE: part of function_body_declaration/task_
|
|||
//
|
||||
| id/*interface_identifier*/ '.' idAny
|
||||
{ $<fl>$ = $<fl>1;
|
||||
$$ = new AstFunc{$<fl>$, *$1, nullptr, nullptr};
|
||||
$$ = new AstFunc{$<fl>$, *$3, nullptr, nullptr};
|
||||
$$->verilogFunction(true);
|
||||
BBUNSUP($2, "Unsupported: Out of block function declaration"); }
|
||||
$$->ifacePortName(*$1); }
|
||||
//
|
||||
| packageClassScope id
|
||||
{ $<fl>$ = $<fl>1;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,4 @@
|
|||
12 | extern function myfunc (input logic val);
|
||||
| ^~~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_interface_modport_export.v:48:30: Unsupported: Out of block function declaration
|
||||
48 | function automatic logic ie.myfunc (input logic val);
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
%Error: t/t_modport_export_bad.v:18:8: Out-of-block definition port is not an interface port: 'clk' (IEEE 1800-2023 25.7)
|
||||
18 | task clk.send(input logic [7:0] val);
|
||||
| ^~~
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: t/t_modport_export_bad.v:25:8: Interface port not found for out-of-block definition: 'nonexistent' (IEEE 1800-2023 25.7)
|
||||
25 | task nonexistent.send(input logic [7:0] val);
|
||||
| ^~~~~~~~~~~
|
||||
%Error: t/t_modport_export_bad.v:40:8: No matching export prototype found in interface bus_if_noexport for task 'send' (IEEE 1800-2023 25.7)
|
||||
40 | task port.send(input logic [7:0] val);
|
||||
| ^~~~
|
||||
%Error: Exiting due to
|
||||
|
|
@ -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('vlt')
|
||||
|
||||
test.compile(fails=test.vlt_all, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 PlanV GmbH
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
interface bus_if;
|
||||
logic [7:0] data;
|
||||
|
||||
modport provider(
|
||||
output data,
|
||||
export task send(input logic [7:0] val)
|
||||
);
|
||||
endinterface
|
||||
|
||||
// Error 1: 'clk' is a plain wire port, not an interface port
|
||||
module driver1(bus_if.provider port, input logic clk);
|
||||
task clk.send(input logic [7:0] val); // lint_off: UNUSED
|
||||
port.data = val;
|
||||
endtask
|
||||
endmodule
|
||||
|
||||
// Error 2: 'nonexistent' is not a port on this module
|
||||
module driver2(bus_if.provider port);
|
||||
task nonexistent.send(input logic [7:0] val);
|
||||
port.data = val;
|
||||
endtask
|
||||
endmodule
|
||||
|
||||
interface bus_if_noexport;
|
||||
logic [7:0] data;
|
||||
|
||||
modport provider(
|
||||
output data
|
||||
);
|
||||
endinterface
|
||||
|
||||
// Error 3: no export prototype for 'send' in interface
|
||||
module driver3(bus_if_noexport.provider port);
|
||||
task port.send(input logic [7:0] val);
|
||||
port.data = val;
|
||||
endtask
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
bus_if bif();
|
||||
logic clk;
|
||||
driver1 drv1(.port(bif.provider), .clk(clk));
|
||||
driver2 drv2(.port(bif.provider));
|
||||
bus_if_noexport bif2();
|
||||
driver3 drv3(.port(bif2.provider));
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/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('simulator')
|
||||
|
||||
test.compile()
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 PlanV GmbH
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0)
|
||||
|
||||
interface bus_if;
|
||||
logic [7:0] data;
|
||||
logic [7:0] result;
|
||||
|
||||
modport provider(
|
||||
output data,
|
||||
output result,
|
||||
export task send(input logic [7:0] val),
|
||||
export task accumulate(input logic [7:0] a, input logic [7:0] b)
|
||||
);
|
||||
|
||||
modport consumer(
|
||||
input data,
|
||||
input result,
|
||||
import task send(input logic [7:0] val),
|
||||
import task accumulate(input logic [7:0] a, input logic [7:0] b)
|
||||
);
|
||||
endinterface
|
||||
|
||||
module driver(bus_if.provider port);
|
||||
task port.send(input logic [7:0] val);
|
||||
port.data = val;
|
||||
port.result = val + 8'h01;
|
||||
endtask
|
||||
|
||||
task port.accumulate(input logic [7:0] a, input logic [7:0] b);
|
||||
port.data = a;
|
||||
port.result = a + b;
|
||||
endtask
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
bus_if bif();
|
||||
driver drv(.port(bif.provider));
|
||||
|
||||
initial begin
|
||||
// Test 1: send -- multiple statements in task body
|
||||
bif.consumer.send(8'hAB);
|
||||
`checkh(bif.data, 8'hAB);
|
||||
`checkh(bif.result, 8'hAC);
|
||||
|
||||
// Test 2: send again with different value
|
||||
bif.consumer.send(8'h42);
|
||||
`checkh(bif.data, 8'h42);
|
||||
`checkh(bif.result, 8'h43);
|
||||
|
||||
// Test 3: accumulate -- multiple statements + two arguments
|
||||
bif.consumer.accumulate(8'h10, 8'h20);
|
||||
`checkh(bif.data, 8'h10);
|
||||
`checkh(bif.result, 8'h30);
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue