feat(sv): support interface-typed module ports

This commit is contained in:
Jose Tejada 2026-05-10 14:45:33 +02:00
parent e212ea1a1c
commit c963809709
36 changed files with 710 additions and 102 deletions

View File

@ -28,6 +28,11 @@ using namespace std;
list<Module::named_expr_t> Module::user_defparms;
Module::port_t::port_t()
: port_kind(P_SIGNAL), default_value(0), lexical_pos(0)
{
}
/* n is a permallocated string. */
Module::Module(LexicalScope*parent, perm_string n)
: PScopeExtra(n, parent)
@ -63,12 +68,18 @@ const vector<PEIdent*>& Module::get_port(unsigned idx) const
ivl_assert(*this, idx < ports.size());
static const vector<PEIdent*> zero;
if (ports[idx])
if (ports[idx] && !ports[idx]->is_interface_port())
return ports[idx]->expr;
else
return zero;
}
const Module::port_t* Module::get_port_info(unsigned idx) const
{
ivl_assert(*this, idx < ports.size());
return ports[idx];
}
unsigned Module::find_port(const char*name) const
{
ivl_assert(*this, name != 0);

View File

@ -65,9 +65,24 @@ class Module : public PScopeExtra, public PNamedItem {
default value. */
public:
struct port_t {
enum port_kind_t { P_SIGNAL, P_INTERFACE };
port_t();
port_kind_t port_kind;
perm_string name;
std::vector<PEIdent*> expr;
PExpr*default_value;
/* Interface formal port metadata. For signal ports these
fields are empty/zero. The modport name is optional in the
representation, although the parser initially only accepts
the explicit interface_type.modport form. */
perm_string interface_type;
perm_string modport_name;
unsigned lexical_pos;
bool is_interface_port() const { return port_kind == P_INTERFACE; }
};
public:
@ -148,6 +163,7 @@ class Module : public PScopeExtra, public PNamedItem {
unsigned port_count() const;
const std::vector<PEIdent*>& get_port(unsigned idx) const;
const port_t* get_port_info(unsigned idx) const;
unsigned find_port(const char*name) const;
// Return port name ("" for undeclared port)

View File

@ -241,6 +241,15 @@ class PGModule : public PGate {
void elaborate_scope_mod_(Design*des, Module*mod, NetScope*sc) const;
void elaborate_scope_mod_instances_(Design*des, Module*mod, NetScope*sc) const;
bool elaborate_sig_mod_(Design*des, NetScope*scope, const Module*mod) const;
bool bind_interface_ports_(Design*des, const Module*mod,
NetScope*parent_scope, NetScope*instance_scope,
const std::vector<PExpr*>&pins,
const std::vector<bool>&pins_fromwc) const;
bool match_module_ports_(Design*des, const Module*mod,
NetScope*scope,
std::vector<PExpr*>&pins,
std::vector<bool>&pins_fromwc,
std::vector<bool>&pins_is_explicitly_not_connected) const;
// Not currently used.
#if 0
bool elaborate_sig_udp_(Design*des, NetScope*scope, PUdp*udp) const;

View File

@ -4747,6 +4747,9 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope,
// If the identifier names a signal (a variable or a net)
// then create a NetESignal node to handle it.
if (sr.net != 0) {
if (!check_interface_modport_access(this, des, sr, false))
return 0;
if (NEED_CONST & flags) {
cerr << get_fileline() << ": error: A reference to a net "
"or variable (`" << path_ << "') is not allowed in "

View File

@ -557,6 +557,9 @@ NetNet* PEIdent::elaborate_lnet_common_(Design*des, NetScope*scope,
return 0;
}
if (!check_interface_modport_access(this, des, sr, true))
return 0;
if (debug_elaborate) {
cerr << get_fileline() << ": " << __func__ << ": "
<< "Found l-value path_=" << path_

View File

@ -44,6 +44,7 @@
# include "netqueue.h"
# include "netscalar.h"
# include "util.h"
# include "parse_api.h"
# include "ivl_assert.h"
using namespace std;
@ -298,6 +299,28 @@ bool Module::elaborate_sig(Design*des, NetScope*scope) const
if (pp == 0)
continue;
if (pp->is_interface_port()) {
map<perm_string,Module*>::const_iterator mod =
pform_modules.find(pp->interface_type);
if (mod == pform_modules.end() || !mod->second->is_interface) {
cerr << get_fileline() << ": error: Interface port "
<< pp->name << " uses unknown interface type `"
<< pp->interface_type << "'." << endl;
des->errors += 1;
continue;
}
if (pp->modport_name.str() &&
mod->second->modports.find(pp->modport_name) == mod->second->modports.end()) {
cerr << get_fileline() << ": error: Interface port "
<< pp->name << " uses unknown modport `"
<< pp->modport_name << "' of interface `"
<< pp->interface_type << "'." << endl;
des->errors += 1;
}
continue;
}
// The port has a name and an array of expressions. The
// expression are all identifiers that should reference
// wires within the scope.
@ -456,6 +479,12 @@ bool PGModule::elaborate_sig_mod_(Design*des, NetScope*scope,
NetScope::scope_vec_t instance = scope->instance_arrays[get_name()];
vector<PExpr*>pins (rmod->port_count());
vector<bool>pins_fromwc (rmod->port_count(), false);
vector<bool>pins_is_explicitly_not_connected (rmod->port_count(), false);
flag &= match_module_ports_(des, rmod, scope, pins, pins_fromwc,
pins_is_explicitly_not_connected);
for (unsigned idx = 0 ; idx < instance.size() ; idx += 1) {
// I know a priori that the elaborate_scope created the scope
// already, so just look it up as a child of the current scope.
@ -471,6 +500,9 @@ bool PGModule::elaborate_sig_mod_(Design*des, NetScope*scope,
}
ivl_assert(*this, my_scope->parent() == scope);
if (!bind_interface_ports_(des, rmod, scope, my_scope, pins, pins_fromwc))
flag = false;
if (! rmod->elaborate_sig(des, my_scope))
flag = false;

View File

@ -1248,6 +1248,164 @@ void elaborate_unpacked_port(Design *des, NetScope *scope, NetNet *port_net,
assign_unpacked_with_bufz(des, scope, port_net, port_net, expr_net);
}
bool PGModule::match_module_ports_(Design*des, const Module*rmod,
NetScope*scope,
vector<PExpr*>&pins,
vector<bool>&pins_fromwc,
vector<bool>&pins_is_explicitly_not_connected) const
{
if (pins_) {
unsigned nexp = rmod->port_count();
for (unsigned idx = 0 ; idx < npins_ ; idx += 1) {
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) {
pins[j] = new PEIdent(rmod->ports[j]->name, UINT_MAX, true);
pins[j]->set_lineno(get_lineno());
pins[j]->set_file(get_file());
}
}
}
continue;
}
unsigned pidx = rmod->find_port(pins_[idx].name);
if (pidx == nexp) {
cerr << get_fileline() << ": error: port ``" <<
pins_[idx].name << "'' is not a port of "
<< get_name() << "." << endl;
des->errors += 1;
continue;
}
if (pins_fromwc[pidx]) {
delete pins[pidx];
pins_fromwc[pidx] = false;
} else if (pins[pidx]) {
cerr << get_fileline() << ": error: port ``" <<
pins_[idx].name << "'' already bound." <<
endl;
des->errors += 1;
continue;
}
pins[pidx] = pins_[idx].parm;
if (!pins[pidx])
pins_is_explicitly_not_connected[pidx] = true;
}
} else if (pin_count() == 0) {
for (unsigned idx = 0 ; idx < rmod->port_count() ; idx += 1)
pins[idx] = 0;
} else {
if (pin_count() > rmod->port_count()) {
cerr << get_fileline() << ": error: Wrong number "
"of ports. Expecting at most " << rmod->port_count() <<
", got " << pin_count() << "."
<< endl;
des->errors += 1;
return false;
}
std::copy(get_pins().begin(), get_pins().end(), pins.begin());
}
return true;
}
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
{
bool flag = true;
for (unsigned idx = 0 ; idx < rmod->port_count() ; idx += 1) {
const Module::port_t*port = rmod->get_port_info(idx);
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()
<< " is not connected." << endl;
des->errors += 1;
flag = false;
continue;
}
const PEIdent*actual_ident = dynamic_cast<const PEIdent*>(pins[idx]);
if (!actual_ident || actual_ident->path().package ||
actual_ident->path().name.size() != 1 ||
!actual_ident->path().name.front().index.empty()) {
cerr << pins[idx]->get_fileline() << ": error: Interface port `"
<< port->name << "' must be connected to a simple "
"interface instance name." << endl;
des->errors += 1;
flag = false;
continue;
}
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);
if (!actual_scope || !actual_scope->is_interface()) {
cerr << pins[idx]->get_fileline() << ": error: Actual for "
"interface port `" << port->name
<< "' is not an interface instance." << endl;
des->errors += 1;
flag = false;
continue;
}
if (actual_scope->module_name() != port->interface_type) {
cerr << pins[idx]->get_fileline() << ": error: Interface port `"
<< port->name << "' expects interface type `"
<< port->interface_type << "' but actual `" << actual_name
<< "' has type `" << actual_scope->module_name() << "'." << endl;
des->errors += 1;
flag = false;
continue;
}
map<perm_string,Module*>::const_iterator mod =
pform_modules.find(port->interface_type);
const PModport*modport = 0;
if (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;
}
instance_scope->add_interface_port_alias(port->name, actual_scope,
modport);
}
return flag;
}
/*
* Instantiate a module by recursively elaborating it. Set the path of
* the recursive elaboration so that signal names get properly
@ -1274,103 +1432,9 @@ void PGModule::elaborate_mod_(Design*des, Module*rmod, NetScope*scope) const
vector<bool>pins_fromwc (rmod->port_count(), false);
vector<bool>pins_is_explicitly_not_connected (rmod->port_count(), false);
// If the instance has a pins_ member, then we know we are
// binding by name. Therefore, make up a pins array that
// reflects the positions of the named ports.
if (pins_) {
unsigned nexp = rmod->port_count();
// Scan the bindings, matching them with port names.
for (unsigned idx = 0 ; idx < npins_ ; idx += 1) {
// Handle wildcard named port
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) {
pins[j] = new PEIdent(rmod->ports[j]->name, UINT_MAX, true);
pins[j]->set_lineno(get_lineno());
pins[j]->set_file(get_file());
}
}
}
continue;
}
// Given a binding, look at the module port names
// for the position that matches the binding name.
unsigned pidx = rmod->find_port(pins_[idx].name);
// If the port name doesn't exist, the find_port
// method will return the port count. Detect that
// as an error.
if (pidx == nexp) {
cerr << get_fileline() << ": error: port ``" <<
pins_[idx].name << "'' is not a port of "
<< get_name() << "." << endl;
des->errors += 1;
continue;
}
// If I am overriding a wildcard port, delete and
// override it
if (pins_fromwc[pidx]) {
delete pins[pidx];
pins_fromwc[pidx] = false;
// If I already explicitly bound something to
// this port, then the pins array will already
// have a pointer value where I want to place this
// expression.
} else if (pins[pidx]) {
cerr << get_fileline() << ": error: port ``" <<
pins_[idx].name << "'' already bound." <<
endl;
des->errors += 1;
continue;
}
// OK, do the binding by placing the expression in
// the right place.
pins[pidx] = pins_[idx].parm;
if (!pins[pidx])
pins_is_explicitly_not_connected[pidx] = true;
}
} else if (pin_count() == 0) {
/* Handle the special case that no ports are
connected. It is possible that this is an empty
connect-by-name list, so we'll allow it and assume
that is the case. */
for (unsigned idx = 0 ; idx < rmod->port_count() ; idx += 1)
pins[idx] = 0;
} else {
/* Otherwise, this is a positional list of port
connections. Use as many ports as provided. Trailing
missing ports will be left unconnect or use the default
value if one is available */
if (pin_count() > rmod->port_count()) {
cerr << get_fileline() << ": error: Wrong number "
"of ports. Expecting at most " << rmod->port_count() <<
", got " << pin_count() << "."
<< endl;
des->errors += 1;
return;
}
std::copy(get_pins().begin(), get_pins().end(), pins.begin());
}
if (!match_module_ports_(des, rmod, scope, pins, pins_fromwc,
pins_is_explicitly_not_connected))
return;
// Elaborate these instances of the module. The recursive
// elaboration causes the module to generate a netlist with
@ -1403,6 +1467,13 @@ void PGModule::elaborate_mod_(Design*des, Module*rmod, NetScope*scope) const
bool using_default = false;
perm_string port_name = rmod->get_port_name(idx);
const Module::port_t*port_info = rmod->get_port_info(idx);
if (port_info && port_info->is_interface_port()) {
for (unsigned inst = 0 ; inst < instance.size() ; inst += 1)
instance[inst]->add_module_port_info(idx, port_name,
PortType::PIMPLICIT, 0);
continue;
}
// If the port is unconnected, substitute the default
// value. The parser ensures that a default value only

View File

@ -0,0 +1,2 @@
ivltests/sv_interface_port_missing_modport_fail.v:18: error: Interface port bus uses unknown modport `consumer' of interface `bus_if'.
1 error(s) during elaboration.

View File

@ -0,0 +1,2 @@
ivltests/sv_interface_port_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,2 @@
ivltests/sv_interface_port_modport_input_write_fail.v:21: error: Cannot assign to input modport member `value' through interface port `bus'.
1 error(s) during elaboration.

View File

@ -0,0 +1,2 @@
ivltests/sv_interface_port_non_interface_actual_fail.v:9: error: Actual for interface port `bus' is not an interface instance.
Elaboration failed

View File

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

View File

@ -0,0 +1,2 @@
ivltests/sv_interface_port_wrong_type_fail.v:9: error: Interface port `bus' expects interface type `bus_if' but actual `bus' has type `other_if'.
Elaboration failed

View File

@ -0,0 +1,47 @@
// This tests a SystemVerilog interface-typed module port with an
// explicit modport and a named actual interface instance connection.
//
// 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'd5;
rhs = 8'd7;
#1;
if (bus.sum !== 9'd12) begin
$display("FAILED");
$finish;
end
$display("PASSED");
end
endmodule
interface bus_if #(parameter WIDTH = 8) ();
logic [WIDTH-1:0] lhs;
logic [WIDTH-1:0] rhs;
logic [WIDTH:0] sum;
modport consumer(
input lhs,
input rhs,
output sum
);
endinterface
module add_if #(parameter WIDTH = 8) (
bus_if.consumer bus
);
assign bus.sum = bus.lhs + bus.rhs;
endmodule

View File

@ -0,0 +1,22 @@
// This tests the diagnostic path for an interface-typed module port
// that names a modport missing from the interface definition.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
bus_user dut(.bus(bus));
endmodule
interface bus_if ();
logic value;
modport producer(output value);
endinterface
module bus_user(
bus_if.consumer bus
);
initial $display("FAILED");
endmodule

View File

@ -0,0 +1,11 @@
// This tests the diagnostic path for an 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.consumer bus
);
initial $display("FAILED");
endmodule

View File

@ -0,0 +1,22 @@
// This tests rejection of an assignment through a member declared input
// by the selected interface modport.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
bus_user dut(.bus(bus));
endmodule
interface bus_if ();
logic value;
modport consumer(input value);
endinterface
module bus_user(
bus_if.consumer bus
);
assign bus.value = 1'b1;
endmodule

View File

@ -0,0 +1,22 @@
// This tests rejection of a non-interface actual connected to an
// interface-typed module port.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
logic value;
bus_user dut(.bus(value));
endmodule
interface bus_if ();
logic value;
modport consumer(input value);
endinterface
module bus_user(
bus_if.consumer bus
);
initial $display("FAILED");
endmodule

View File

@ -0,0 +1,25 @@
// This tests rejection of access to an interface member that is not
// listed in the selected modport.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
bus_if bus();
bus_user dut(.bus(bus));
endmodule
interface bus_if ();
logic visible;
logic hidden;
modport consumer(input visible);
endinterface
module bus_user(
bus_if.consumer bus
);
logic sample;
assign sample = bus.hidden;
endmodule

View File

@ -0,0 +1,28 @@
// This tests rejection of an actual interface instance whose type does
// not match the interface type of the formal module port.
//
// This file is placed into the Public Domain, for any use, without
// warranty.
module test;
other_if bus();
bus_user dut(.bus(bus));
endmodule
interface bus_if ();
logic value;
modport consumer(input value);
endinterface
interface other_if ();
logic value;
modport consumer(input value);
endinterface
module bus_user(
bus_if.consumer bus
);
initial $display("FAILED");
endmodule

View File

@ -269,6 +269,13 @@ sv_default_port_value3 vvp_tests/sv_default_port_value3.json
sv_foreach9 vvp_tests/sv_foreach9.json
sv_foreach10 vvp_tests/sv_foreach10.json
sv_interface vvp_tests/sv_interface.json
sv_interface_port_basic vvp_tests/sv_interface_port_basic.json
sv_interface_port_missing_type_fail vvp_tests/sv_interface_port_missing_type_fail.json
sv_interface_port_missing_modport_fail vvp_tests/sv_interface_port_missing_modport_fail.json
sv_interface_port_non_interface_actual_fail vvp_tests/sv_interface_port_non_interface_actual_fail.json
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_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_basic.v",
"iverilog-args" : [ "-g2012" ]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -812,6 +812,37 @@ const NetScope* NetScope::child(const hname_t&name) const
return cur->second;
}
void NetScope::add_interface_port_alias(perm_string formal_name,
NetScope*actual_scope,
const PModport*modport)
{
ivl_assert(*this, actual_scope);
interface_port_aliases_[formal_name] = interface_port_alias_t(actual_scope, modport);
}
const NetScope::interface_port_alias_t*
NetScope::find_interface_port_alias(perm_string formal_name) const
{
map<perm_string,interface_port_alias_t>::const_iterator cur;
cur = interface_port_aliases_.find(formal_name);
if (cur == interface_port_aliases_.end())
return 0;
return &cur->second;
}
NetScope* NetScope::find_interface_port_alias_scope(perm_string formal_name) const
{
const interface_port_alias_t*cur = find_interface_port_alias(formal_name);
return cur? cur->actual_scope : 0;
}
const PModport* NetScope::find_interface_port_modport(perm_string formal_name) const
{
const interface_port_alias_t*cur = find_interface_port_alias(formal_name);
return cur? cur->modport : 0;
}
/* Helper function to see if the given scope is defined in a class and if
* so return the class scope. */
const NetScope* NetScope::get_class_scope() const
@ -867,6 +898,8 @@ bool NetScope::symbol_exists(perm_string sym)
return true;
if (find_event(sym))
return true;
if (find_interface_port_alias(sym))
return true;
return false;
}

View File

@ -79,6 +79,7 @@ class NetEvWait;
class PClass;
class PExpr;
class PFunction;
class PModport;
class PPackage;
class PTaskFunc;
class PWire;
@ -1043,6 +1044,26 @@ class NetScope : public Definitions, public Attrib {
const NetScope* parent() const { return up_; }
const NetScope* child(const hname_t&name) const;
struct interface_port_alias_t {
interface_port_alias_t() : actual_scope(0), modport(0) { }
interface_port_alias_t(NetScope*actual, const PModport*mp)
: actual_scope(actual), modport(mp) { }
NetScope*actual_scope;
const PModport*modport;
};
/* Interface-typed module formals are represented as aliases to
concrete interface instance scopes. These are deliberately kept
out of the real child-scope map; only alias-aware lookup paths
should traverse them. */
void add_interface_port_alias(perm_string formal_name,
NetScope*actual_scope,
const PModport*modport);
const interface_port_alias_t* find_interface_port_alias(perm_string formal_name) const;
NetScope* find_interface_port_alias_scope(perm_string formal_name) const;
const PModport* find_interface_port_modport(perm_string formal_name) const;
/* A helper function to find the enclosing class scope. */
const NetScope* get_class_scope() const;
@ -1347,6 +1368,7 @@ class NetScope : public Definitions, public Attrib {
NetScope*unit_;
NetScope*up_;
std::map<hname_t,NetScope*> children_;
std::map<perm_string,interface_port_alias_t> interface_port_aliases_;
unsigned lcounter_;
bool need_const_func_, is_const_func_, is_auto_, is_cell_, calls_stask_;

View File

@ -50,6 +50,9 @@ struct symbol_search_results {
type = 0;
eve = 0;
decl_after_use = 0;
interface_alias_scope = 0;
interface_alias_target = 0;
interface_alias_modport = 0;
}
inline bool is_scope() const {
@ -76,6 +79,10 @@ struct symbol_search_results {
return "nothing found";
}
inline bool through_interface_alias() const {
return interface_alias_target != 0;
}
// Scope where symbol was located. This is set in all cases,
// assuming the search succeeded.
NetScope*scope;
@ -93,6 +100,14 @@ struct symbol_search_results {
// one is retained.
const LineInfo*decl_after_use;
// If lookup traversed an interface-typed formal port alias, these
// fields describe the alias edge. The resolved object remains in the
// normal scope/net/parameter/event fields.
NetScope*interface_alias_scope;
perm_string interface_alias_name;
NetScope*interface_alias_target;
const PModport*interface_alias_modport;
// Store bread crumbs of the search here. The path_tail is the parts
// of the original path that were not found, or are after an object
// (and so are probably members or methods).
@ -128,6 +143,10 @@ extern bool symbol_search(const LineInfo *li, Design *des, NetScope *scope,
const pform_scoped_name_t &path, unsigned lexical_pos,
struct symbol_search_results*res);
extern bool check_interface_modport_access(const LineInfo *li, Design *des,
const symbol_search_results &res,
bool is_write);
/*
* This function transforms an expression by either zero or sign extending
* the high bits until the expression has the desired width. This may mean

48
parse.y
View File

@ -461,6 +461,29 @@ Module::port_t *module_declare_port(const YYLTYPE&loc, char *id,
return port;
}
Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
char *modport, char *id,
std::list<named_pexpr_t> *attributes)
{
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),
lex_strings.make(id));
delete[] type;
delete[] modport;
delete[] id;
pform_module_define_interface_port(loc, port, attributes);
port_declaration_context.port_type = NetNet::NOT_A_PORT;
port_declaration_context.port_net_type = NetNet::NONE;
port_declaration_context.data_type = nullptr;
return port;
}
%}
%union {
@ -4603,10 +4626,20 @@ list_of_port_declarations
{ std::vector<Module::port_t*> *ports = $1;
Module::port_t* port;
port = module_declare_port(@4, $4, port_declaration_context.port_type,
port_declaration_context.port_net_type,
port_declaration_context.data_type,
$5, $6, $3);
if (port_declaration_context.port_type == NetNet::NOT_A_PORT) {
yyerror(@4, "error: Incomplete interface port declaration.");
delete[]$4;
delete $5;
delete $6;
delete $3;
port = 0;
} else {
port = module_declare_port(@4, $4,
port_declaration_context.port_type,
port_declaration_context.port_net_type,
port_declaration_context.data_type,
$5, $6, $3);
}
ports->push_back(port);
$$ = ports;
}
@ -4622,6 +4655,9 @@ port_declaration
: attribute_list_opt port_direction net_type_or_var_opt data_type_or_implicit IDENTIFIER dimensions_opt initializer_opt
{ $$ = module_declare_port(@5, $5, $2, $3, $4, $6, $7, $1);
}
| attribute_list_opt IDENTIFIER '.' IDENTIFIER IDENTIFIER
{ $$ = module_declare_interface_port(@5, $2, $4, $5, $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,
@ -5715,6 +5751,10 @@ port
: port_reference
{ $$ = $1; }
| IDENTIFIER '.' IDENTIFIER IDENTIFIER
{ $$ = module_declare_interface_port(@4, $1, $3, $4, 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

View File

@ -1386,6 +1386,33 @@ Module::port_t* pform_module_port_reference(const struct vlltype&loc,
return ptmp;
}
Module::port_t* pform_module_interface_port_reference(
const struct vlltype&loc,
perm_string interface_type,
perm_string modport_name,
perm_string name)
{
Module::port_t*ptmp = new Module::port_t;
ptmp->port_kind = Module::port_t::P_INTERFACE;
ptmp->name = name;
ptmp->interface_type = interface_type;
ptmp->modport_name = modport_name;
ptmp->lexical_pos = loc.lexical_pos;
return ptmp;
}
void pform_module_define_interface_port(const struct vlltype&loc,
Module::port_t*port,
list<named_pexpr_t>*attr)
{
ivl_assert(loc, port);
ivl_assert(loc, port->is_interface_port());
delete attr;
}
void pform_module_set_ports(vector<Module::port_t*>*ports)
{
assert(! pform_cur_module.empty());

View File

@ -167,6 +167,14 @@ extern void pform_module_define_port(const struct vlltype&li,
extern Module::port_t* pform_module_port_reference(const struct vlltype&loc,
perm_string name);
extern Module::port_t* pform_module_interface_port_reference(
const struct vlltype&loc,
perm_string interface_type,
perm_string modport_name,
perm_string name);
extern void pform_module_define_interface_port(const struct vlltype&loc,
Module::port_t*port,
std::list<named_pexpr_t>*attr);
extern void pform_endmodule(const char*, bool inside_celldefine,
Module::UCDriveType uc_drive_def);

View File

@ -25,6 +25,7 @@
# include "compiler.h"
# include "PPackage.h"
# include "PWire.h"
# include "PModport.h"
# include "ivl_assert.h"
using namespace std;
@ -75,6 +76,25 @@ bool symbol_search(const LineInfo*li, Design*des, NetScope*scope,
if (! flag)
return false;
if (res->net && res->path_tail.empty() && !res->path_head.empty()) {
name_component_t prefix_tail = res->path_head.back();
if (prefix_tail.index.empty() &&
res->scope->child_byname(prefix_tail.name)) {
bool eval_flag = false;
hname_t path_item = eval_path_component(des, start_scope,
prefix_tail, eval_flag);
if (eval_flag) {
cerr << li->get_fileline() << ": XXXXX: Errors evaluating scope index" << endl;
} else if (NetScope*chld = res->scope->child(path_item)) {
if (chld->is_interface()) {
res->scope = chld;
res->net = 0;
res->type = 0;
}
}
}
}
// The prefix is found to be something besides a scope. Put the
// tail into the path_tail of the result, and return success. The
// caller needs to deal with that tail bit. Note that the
@ -292,6 +312,27 @@ bool symbol_search(const LineInfo*li, Design*des, NetScope*scope,
}
}
if (path_tail.index.empty()) {
if (const NetScope::interface_port_alias_t*alias =
scope->find_interface_port_alias(path_tail.name)) {
path.push_back(path_tail);
res->scope = alias->actual_scope;
res->path_head = path;
res->interface_alias_scope = scope;
res->interface_alias_name = path_tail.name;
res->interface_alias_target = alias->actual_scope;
res->interface_alias_modport = alias->modport;
if (debug_scopes || debug_elaborate) {
cerr << li->get_fileline() << ": symbol_search: "
<< "Interface alias " << path_tail.name
<< " -> " << scope_path(alias->actual_scope) << endl;
}
return true;
}
}
// Don't scan up if we are searching within a prefixed scope.
if (prefix_scope)
break;
@ -387,3 +428,34 @@ bool symbol_search(const LineInfo *li, Design *des, NetScope *scope,
return symbol_search(li, des, search_scope, path.name, lexical_pos,
res, search_scope, prefix_scope);
}
bool check_interface_modport_access(const LineInfo *li, Design *des,
const symbol_search_results &res,
bool is_write)
{
if (!res.through_interface_alias() || !res.interface_alias_modport || !res.net)
return true;
const PModport *modport = res.interface_alias_modport;
perm_string member = res.net->name();
map<perm_string,PModport::simple_port_t>::const_iterator cur =
modport->simple_ports.find(member);
if (cur == modport->simple_ports.end()) {
cerr << li->get_fileline() << ": error: Interface member `"
<< member << "' is not listed in modport `"
<< modport->name() << "'." << endl;
des->errors += 1;
return false;
}
if (is_write && cur->second.first == NetNet::PINPUT) {
cerr << li->get_fileline() << ": error: Cannot assign to input "
"modport member `" << member << "' through interface port `"
<< res.interface_alias_name << "'." << endl;
des->errors += 1;
return false;
}
return true;
}

View File

@ -135,6 +135,10 @@ Makefile: $(srcdir)/Makefile.in
dep:
mkdir dep
# Older dependency files may refer to ivl_dlfcn.h from before the
# shared dlopen wrapper was moved to the top-level source directory.
ivl_dlfcn.h: $(srcdir)/../ivl_dlfcn.h
ifeq (@LIBVVP@,yes)
CPPFLAGS+= -fpic