feat(interface): broaden interface port binding

This commit is contained in:
Jose Tejada 2026-05-10 16:34:21 +02:00
parent c963809709
commit 39072cd452
24 changed files with 374 additions and 19 deletions

View File

@ -51,6 +51,7 @@
# include "netscalar.h"
# include "netclass.h"
# include "netmisc.h"
# include "PModport.h"
# include "util.h"
# include "parse_api.h"
# include "compiler.h"
@ -1261,12 +1262,14 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod,
if (pins_[idx].name[0] == '*') {
for (unsigned j = 0 ; j < nexp ; j += 1) {
if (rmod->ports[j] && !pins[j] && !pins_is_explicitly_not_connected[j]) {
pins_fromwc[j] = true;
pform_name_t path_;
path_.push_back(name_component_t(rmod->ports[j]->name));
symbol_search_results sr;
symbol_search(this, des, scope, path_, UINT_MAX, &sr);
if (sr.net != 0) {
if (sr.net != 0 ||
(rmod->ports[j]->is_interface_port() &&
sr.scope != 0 && sr.scope->is_interface())) {
pins_fromwc[j] = true;
pins[j] = new PEIdent(rmod->ports[j]->name, UINT_MAX, true);
pins[j]->set_lineno(get_lineno());
pins[j]->set_file(get_file());
@ -1326,7 +1329,7 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod,
NetScope*parent_scope,
NetScope*instance_scope,
const vector<PExpr*>&pins,
const vector<bool>&pins_fromwc) const
const vector<bool>&) const
{
bool flag = true;
@ -1335,15 +1338,6 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod,
if (!port || !port->is_interface_port())
continue;
if (pins_fromwc[idx]) {
cerr << get_fileline() << ": sorry: Wildcard connection "
"to interface port `" << port->name
<< "' is not yet supported." << endl;
des->errors += 1;
flag = false;
continue;
}
if (!pins[idx]) {
cerr << get_fileline() << ": error: Interface port `"
<< port->name << "' of module " << rmod->mod_name()
@ -1367,8 +1361,15 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod,
perm_string actual_name = actual_ident->path().name.front().name;
NetScope*actual_scope = parent_scope->child(hname_t(actual_name));
if (!actual_scope)
actual_scope = parent_scope->find_interface_port_alias_scope(actual_name);
const PModport*actual_modport = 0;
if (!actual_scope) {
const NetScope::interface_port_alias_t*actual_alias =
parent_scope->find_interface_port_alias(actual_name);
if (actual_alias) {
actual_scope = actual_alias->actual_scope;
actual_modport = actual_alias->modport;
}
}
if (!actual_scope || !actual_scope->is_interface()) {
cerr << pins[idx]->get_fileline() << ": error: Actual for "
@ -1391,14 +1392,27 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod,
map<perm_string,Module*>::const_iterator mod =
pform_modules.find(port->interface_type);
const PModport*modport = 0;
if (mod != pform_modules.end()) {
const PModport*formal_modport = 0;
if (port->modport_name.str() && mod != pform_modules.end()) {
map<perm_string,PModport*>::const_iterator mp =
mod->second->modports.find(port->modport_name);
if (mp != mod->second->modports.end())
modport = mp->second;
formal_modport = mp->second;
}
if (actual_modport && formal_modport &&
actual_modport->name() != formal_modport->name()) {
cerr << pins[idx]->get_fileline() << ": error: Interface port `"
<< port->name << "' cannot forward actual `" << actual_name
<< "' restricted by modport `" << actual_modport->name()
<< "' to formal modport `" << formal_modport->name()
<< "'." << endl;
des->errors += 1;
flag = false;
continue;
}
const PModport*modport = formal_modport? formal_modport : actual_modport;
instance_scope->add_interface_port_alias(port->name, actual_scope,
modport);
}

View File

@ -0,0 +1,3 @@
ivltests/sv_interface_port_forwarding_restrict_fail.v:23: error: Interface member `hidden' is not listed in modport `consumer'.
ivltests/sv_interface_port_forwarding_restrict_fail.v:23: error: Unable to elaborate r-value: bus.hidden
2 error(s) during elaboration.

View File

@ -0,0 +1,2 @@
ivltests/sv_interface_port_positional_unconnected_fail.v:7: error: Interface port `bus' of module bus_user is not connected.
Elaboration failed

View File

@ -0,0 +1,2 @@
ivltests/sv_interface_port_unmodported_missing_type_fail.v:7: error: Interface port bus uses unknown interface type `missing_if'.
1 error(s) during elaboration.

View File

@ -0,0 +1,41 @@
// This tests forwarding an interface-typed formal port to a child
// module while preserving the parent-facing modport view.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
assign bus.value = 1'b1;
parent dut(.bus(bus));
initial begin
#1;
if (bus.mirror !== 1'b1) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
module parent(
bus_if.consumer bus
);
child u_child(.bus(bus));
endmodule
module child(
bus_if bus
);
assign bus.mirror = bus.value;
endmodule
interface bus_if ();
logic value;
logic mirror;
logic hidden;
modport consumer(input value, output mirror);
endinterface

View File

@ -0,0 +1,31 @@
// This tests that forwarding an interface formal cannot widen access
// beyond the parent-facing modport restriction.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
parent dut(.bus(bus));
endmodule
module parent(
bus_if.consumer bus
);
child u_child(.bus(bus));
endmodule
module child(
bus_if bus
);
logic sample;
assign sample = bus.hidden;
endmodule
interface bus_if ();
logic value;
logic hidden;
modport consumer(input value);
endinterface

View File

@ -0,0 +1,29 @@
// This protects ordinary partial ANSI port parsing near interface
// formal grammar.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
logic [3:0] value;
logic [3:0] result;
plain dut(value, result);
initial begin
value = 4'ha;
#1;
if (result !== 4'ha) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
module plain(
input logic [3:0] value,
output logic [3:0] result
);
assign result = value;
endmodule

View File

@ -0,0 +1,33 @@
// This tests positional binding of an interface-typed module port.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
assign bus.value = 1'b1;
bus_user dut(bus);
initial begin
#1;
if (bus.sample !== 1'b1) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
module bus_user(
bus_if.consumer bus
);
assign bus.sample = bus.value;
endmodule
interface bus_if ();
logic value;
logic sample;
modport consumer(input value, output sample);
endinterface

View File

@ -0,0 +1,19 @@
// This tests rejection of an unconnected positional interface port.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_user dut();
endmodule
module bus_user(
bus_if.consumer bus
);
endmodule
interface bus_if ();
logic value;
modport consumer(input value);
endinterface

View File

@ -0,0 +1,31 @@
// This protects typedef-based ANSI ports from being interpreted as
// interface-typed formals.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
typedef logic [3:0] nibble_t;
module test;
nibble_t value;
nibble_t result;
typed dut(value, result);
initial begin
value = 4'h6;
#1;
if (result !== 4'h6) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
module typed(
input nibble_t value,
output nibble_t result
);
assign result = value;
endmodule

View File

@ -0,0 +1,40 @@
// This tests a concrete interface-typed module port without an explicit
// modport restriction.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
logic [7:0] lhs;
logic [7:0] rhs;
bus_if bus();
add_if dut(.bus(bus));
assign bus.lhs = lhs;
assign bus.rhs = rhs;
initial begin
lhs = 8'd9;
rhs = 8'd4;
#1;
if (bus.sum !== 9'd13) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
module add_if(
bus_if bus
);
assign bus.sum = bus.lhs + bus.rhs;
endmodule
interface bus_if ();
logic [7:0] lhs;
logic [7:0] rhs;
logic [8:0] sum;
endinterface

View File

@ -0,0 +1,11 @@
// This tests the diagnostic path for an unmodported interface-typed
// module port whose interface type name is not declared.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module bus_user(
missing_if bus
);
initial $display("FAILED");
endmodule

View File

@ -0,0 +1,33 @@
// This tests wildcard binding of an interface-typed module port.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
assign bus.value = 1'b1;
bus_user dut(.*);
initial begin
#1;
if (bus.sample !== 1'b1) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
module bus_user(
bus_if.consumer bus
);
assign bus.sample = bus.value;
endmodule
interface bus_if ();
logic value;
logic sample;
modport consumer(input value, output sample);
endinterface

View File

@ -276,6 +276,15 @@ sv_interface_port_non_interface_actual_fail vvp_tests/sv_interface_port_non_inte
sv_interface_port_wrong_type_fail vvp_tests/sv_interface_port_wrong_type_fail.json
sv_interface_port_modport_input_write_fail vvp_tests/sv_interface_port_modport_input_write_fail.json
sv_interface_port_unlisted_member_fail vvp_tests/sv_interface_port_unlisted_member_fail.json
sv_interface_port_unmodported_basic vvp_tests/sv_interface_port_unmodported_basic.json
sv_interface_port_unmodported_missing_type_fail vvp_tests/sv_interface_port_unmodported_missing_type_fail.json
sv_interface_port_forwarding vvp_tests/sv_interface_port_forwarding.json
sv_interface_port_forwarding_restrict_fail vvp_tests/sv_interface_port_forwarding_restrict_fail.json
sv_interface_port_positional vvp_tests/sv_interface_port_positional.json
sv_interface_port_positional_unconnected_fail vvp_tests/sv_interface_port_positional_unconnected_fail.json
sv_interface_port_wildcard vvp_tests/sv_interface_port_wildcard.json
sv_interface_port_plain_ansi_regression vvp_tests/sv_interface_port_plain_ansi_regression.json
sv_interface_port_typedef_ansi_regression vvp_tests/sv_interface_port_typedef_ansi_regression.json
sv_literals vvp_tests/sv_literals.json
sv_mixed_assign1 vvp_tests/sv_mixed_assign1.json
sv_mixed_assign2 vvp_tests/sv_mixed_assign2.json

View File

@ -0,0 +1,5 @@
{
"type" : "normal",
"source" : "sv_interface_port_forwarding.v",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,6 @@
{
"type" : "CE",
"source" : "sv_interface_port_forwarding_restrict_fail.v",
"gold" : "sv_interface_port_forwarding_restrict_fail",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,5 @@
{
"type" : "normal",
"source" : "sv_interface_port_plain_ansi_regression.v",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,5 @@
{
"type" : "normal",
"source" : "sv_interface_port_positional.v",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,6 @@
{
"type" : "CE",
"source" : "sv_interface_port_positional_unconnected_fail.v",
"gold" : "sv_interface_port_positional_unconnected_fail",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,5 @@
{
"type" : "normal",
"source" : "sv_interface_port_typedef_ansi_regression.v",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,5 @@
{
"type" : "normal",
"source" : "sv_interface_port_unmodported_basic.v",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,6 @@
{
"type" : "CE",
"source" : "sv_interface_port_unmodported_missing_type_fail.v",
"gold" : "sv_interface_port_unmodported_missing_type_fail",
"iverilog-args" : [ "-g2012" ]
}

View File

@ -0,0 +1,5 @@
{
"type" : "normal",
"source" : "sv_interface_port_wildcard.v",
"iverilog-args" : [ "-g2012" ]
}

13
parse.y
View File

@ -468,11 +468,13 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
pform_requires_sv(loc, "Interface port declaration");
Module::port_t *port = pform_module_interface_port_reference(
loc, lex_strings.make(type), lex_strings.make(modport),
loc, lex_strings.make(type),
modport ? lex_strings.make(modport) : perm_string(),
lex_strings.make(id));
delete[] type;
delete[] modport;
if (modport)
delete[] modport;
delete[] id;
pform_module_define_interface_port(loc, port, attributes);
@ -4658,6 +4660,9 @@ port_declaration
| attribute_list_opt IDENTIFIER '.' IDENTIFIER IDENTIFIER
{ $$ = module_declare_interface_port(@5, $2, $4, $5, $1);
}
| attribute_list_opt IDENTIFIER IDENTIFIER
{ $$ = module_declare_interface_port(@3, $2, 0, $3, $1);
}
| attribute_list_opt net_type_or_var data_type_or_implicit IDENTIFIER dimensions_opt initializer_opt
{ pform_requires_sv(@4, "Partial ANSI port declaration");
$$ = module_declare_port(@4, $4, port_declaration_context.port_type,
@ -5755,6 +5760,10 @@ port
{ $$ = module_declare_interface_port(@4, $1, $3, $4, 0);
}
| IDENTIFIER IDENTIFIER
{ $$ = module_declare_interface_port(@2, $1, 0, $2, 0);
}
/* This syntax attaches an external name to the port reference so
that the caller can bind by name to non-trivial port
references. The port_t object gets its PWire from the