From c963809709acd4f0dfdfc9fa0f39dc52b644047f Mon Sep 17 00:00:00 2001 From: Jose Tejada Date: Sun, 10 May 2026 14:45:33 +0200 Subject: [PATCH 01/11] feat(sv): support interface-typed module ports --- Module.cc | 13 +- Module.h | 16 ++ PGate.h | 9 + elab_expr.cc | 3 + elab_net.cc | 3 + elab_sig.cc | 32 +++ elaborate.cc | 265 +++++++++++------- ..._missing_modport_fail-iverilog-stderr.gold | 2 + ...ort_missing_type_fail-iverilog-stderr.gold | 2 + ...port_input_write_fail-iverilog-stderr.gold | 2 + ...interface_actual_fail-iverilog-stderr.gold | 2 + ..._unlisted_member_fail-iverilog-stderr.gold | 3 + ..._port_wrong_type_fail-iverilog-stderr.gold | 2 + ivtest/ivltests/sv_interface_port_basic.v | 47 ++++ .../sv_interface_port_missing_modport_fail.v | 22 ++ .../sv_interface_port_missing_type_fail.v | 11 + ..._interface_port_modport_input_write_fail.v | 22 ++ ...interface_port_non_interface_actual_fail.v | 22 ++ .../sv_interface_port_unlisted_member_fail.v | 25 ++ .../sv_interface_port_wrong_type_fail.v | 28 ++ ivtest/regress-vvp.list | 7 + ivtest/vvp_tests/sv_interface_port_basic.json | 5 + ...v_interface_port_missing_modport_fail.json | 6 + .../sv_interface_port_missing_type_fail.json | 6 + ...terface_port_modport_input_write_fail.json | 6 + ...erface_port_non_interface_actual_fail.json | 6 + ...v_interface_port_unlisted_member_fail.json | 6 + .../sv_interface_port_wrong_type_fail.json | 6 + net_scope.cc | 33 +++ netlist.h | 22 ++ netmisc.h | 19 ++ parse.y | 48 +++- pform.cc | 27 ++ pform.h | 8 + symbol_search.cc | 72 +++++ vvp/Makefile.in | 4 + 36 files changed, 710 insertions(+), 102 deletions(-) create mode 100644 ivtest/gold/sv_interface_port_missing_modport_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_modport_input_write_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_non_interface_actual_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_unlisted_member_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_wrong_type_fail-iverilog-stderr.gold create mode 100644 ivtest/ivltests/sv_interface_port_basic.v create mode 100644 ivtest/ivltests/sv_interface_port_missing_modport_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_missing_type_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_modport_input_write_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_non_interface_actual_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_unlisted_member_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_wrong_type_fail.v create mode 100644 ivtest/vvp_tests/sv_interface_port_basic.json create mode 100644 ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_missing_type_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json diff --git a/Module.cc b/Module.cc index 6aefe59ab..5babfb973 100644 --- a/Module.cc +++ b/Module.cc @@ -28,6 +28,11 @@ using namespace std; list 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& Module::get_port(unsigned idx) const ivl_assert(*this, idx < ports.size()); static const vector 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); diff --git a/Module.h b/Module.h index eecfbe781..2713be544 100644 --- a/Module.h +++ b/Module.h @@ -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 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& 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) diff --git a/PGate.h b/PGate.h index 59c09954c..88e0d8c57 100644 --- a/PGate.h +++ b/PGate.h @@ -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&pins, + const std::vector&pins_fromwc) const; + bool match_module_ports_(Design*des, const Module*mod, + NetScope*scope, + std::vector&pins, + std::vector&pins_fromwc, + std::vector&pins_is_explicitly_not_connected) const; // Not currently used. #if 0 bool elaborate_sig_udp_(Design*des, NetScope*scope, PUdp*udp) const; diff --git a/elab_expr.cc b/elab_expr.cc index 0902f5be1..32751c0b4 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -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 " diff --git a/elab_net.cc b/elab_net.cc index 6ec75d067..dc9dc9782 100644 --- a/elab_net.cc +++ b/elab_net.cc @@ -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_ diff --git a/elab_sig.cc b/elab_sig.cc index 5b8ed476b..d0b202ae0 100644 --- a/elab_sig.cc +++ b/elab_sig.cc @@ -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::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()]; + vectorpins (rmod->port_count()); + vectorpins_fromwc (rmod->port_count(), false); + vectorpins_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; diff --git a/elaborate.cc b/elaborate.cc index 5216ad7b7..8c12940d1 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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&pins, + vector&pins_fromwc, + vector&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&pins, + const vector&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(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::const_iterator mod = + pform_modules.find(port->interface_type); + const PModport*modport = 0; + if (mod != pform_modules.end()) { + map::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 vectorpins_fromwc (rmod->port_count(), false); vectorpins_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 diff --git a/ivtest/gold/sv_interface_port_missing_modport_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_missing_modport_fail-iverilog-stderr.gold new file mode 100644 index 000000000..7b28ae5f5 --- /dev/null +++ b/ivtest/gold/sv_interface_port_missing_modport_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold new file mode 100644 index 000000000..601207b55 --- /dev/null +++ b/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/gold/sv_interface_port_modport_input_write_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_modport_input_write_fail-iverilog-stderr.gold new file mode 100644 index 000000000..f8f61b1f6 --- /dev/null +++ b/ivtest/gold/sv_interface_port_modport_input_write_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/gold/sv_interface_port_non_interface_actual_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_non_interface_actual_fail-iverilog-stderr.gold new file mode 100644 index 000000000..4c603b47c --- /dev/null +++ b/ivtest/gold/sv_interface_port_non_interface_actual_fail-iverilog-stderr.gold @@ -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 diff --git a/ivtest/gold/sv_interface_port_unlisted_member_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_unlisted_member_fail-iverilog-stderr.gold new file mode 100644 index 000000000..4295dc034 --- /dev/null +++ b/ivtest/gold/sv_interface_port_unlisted_member_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/gold/sv_interface_port_wrong_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_wrong_type_fail-iverilog-stderr.gold new file mode 100644 index 000000000..44e3c706c --- /dev/null +++ b/ivtest/gold/sv_interface_port_wrong_type_fail-iverilog-stderr.gold @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_basic.v b/ivtest/ivltests/sv_interface_port_basic.v new file mode 100644 index 000000000..a9054a523 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_basic.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_missing_modport_fail.v b/ivtest/ivltests/sv_interface_port_missing_modport_fail.v new file mode 100644 index 000000000..b1fa58f7b --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_missing_modport_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_missing_type_fail.v b/ivtest/ivltests/sv_interface_port_missing_type_fail.v new file mode 100644 index 000000000..df1a5a513 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_missing_type_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_modport_input_write_fail.v b/ivtest/ivltests/sv_interface_port_modport_input_write_fail.v new file mode 100644 index 000000000..d08f9016d --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_modport_input_write_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_non_interface_actual_fail.v b/ivtest/ivltests/sv_interface_port_non_interface_actual_fail.v new file mode 100644 index 000000000..018f14543 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_non_interface_actual_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_unlisted_member_fail.v b/ivtest/ivltests/sv_interface_port_unlisted_member_fail.v new file mode 100644 index 000000000..3457e330c --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_unlisted_member_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_wrong_type_fail.v b/ivtest/ivltests/sv_interface_port_wrong_type_fail.v new file mode 100644 index 000000000..33dfc40b1 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_wrong_type_fail.v @@ -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 diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index e9c9a1633..22f0917b6 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -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 diff --git a/ivtest/vvp_tests/sv_interface_port_basic.json b/ivtest/vvp_tests/sv_interface_port_basic.json new file mode 100644 index 000000000..33eef112d --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_basic.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_basic.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json b/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json new file mode 100644 index 000000000..a86412965 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_missing_modport_fail.v", + "gold" : "sv_interface_port_missing_modport_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json b/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json new file mode 100644 index 000000000..f43ed1e1c --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_missing_type_fail.v", + "gold" : "sv_interface_port_missing_type_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json b/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json new file mode 100644 index 000000000..10d17db33 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json @@ -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" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json b/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json new file mode 100644 index 000000000..786b3e11d --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json @@ -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" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json b/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json new file mode 100644 index 000000000..802f08a7e --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_unlisted_member_fail.v", + "gold" : "sv_interface_port_unlisted_member_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json b/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json new file mode 100644 index 000000000..d0d34c17b --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_wrong_type_fail.v", + "gold" : "sv_interface_port_wrong_type_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/net_scope.cc b/net_scope.cc index d90b49b82..6292817a2 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -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::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; } diff --git a/netlist.h b/netlist.h index d1eb80764..7011c5a9f 100644 --- a/netlist.h +++ b/netlist.h @@ -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 children_; + std::map interface_port_aliases_; unsigned lcounter_; bool need_const_func_, is_const_func_, is_auto_, is_cell_, calls_stask_; diff --git a/netmisc.h b/netmisc.h index be460ad04..ff4aa0e01 100644 --- a/netmisc.h +++ b/netmisc.h @@ -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 diff --git a/parse.y b/parse.y index 10184aeff..99949e7ee 100644 --- a/parse.y +++ b/parse.y @@ -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 *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 *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 diff --git a/pform.cc b/pform.cc index c7e337b63..affc5c775 100644 --- a/pform.cc +++ b/pform.cc @@ -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*attr) +{ + ivl_assert(loc, port); + ivl_assert(loc, port->is_interface_port()); + + delete attr; +} + void pform_module_set_ports(vector*ports) { assert(! pform_cur_module.empty()); diff --git a/pform.h b/pform.h index f7d5b491b..2f83c5541 100644 --- a/pform.h +++ b/pform.h @@ -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*attr); extern void pform_endmodule(const char*, bool inside_celldefine, Module::UCDriveType uc_drive_def); diff --git a/symbol_search.cc b/symbol_search.cc index 61a28d4ec..7b83dd664 100644 --- a/symbol_search.cc +++ b/symbol_search.cc @@ -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::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; +} diff --git a/vvp/Makefile.in b/vvp/Makefile.in index c924dda85..a9315c8b5 100644 --- a/vvp/Makefile.in +++ b/vvp/Makefile.in @@ -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 From 39072cd4523da6dc4536bfec1d599188cbc5b41b Mon Sep 17 00:00:00 2001 From: Jose Tejada Date: Sun, 10 May 2026 16:34:21 +0200 Subject: [PATCH 02/11] feat(interface): broaden interface port binding --- elaborate.cc | 48 ++++++++++++------- ...warding_restrict_fail-iverilog-stderr.gold | 3 ++ ...onal_unconnected_fail-iverilog-stderr.gold | 2 + ...ted_missing_type_fail-iverilog-stderr.gold | 2 + .../ivltests/sv_interface_port_forwarding.v | 41 ++++++++++++++++ ..._interface_port_forwarding_restrict_fail.v | 31 ++++++++++++ .../sv_interface_port_plain_ansi_regression.v | 29 +++++++++++ .../ivltests/sv_interface_port_positional.v | 33 +++++++++++++ ...terface_port_positional_unconnected_fail.v | 19 ++++++++ ...v_interface_port_typedef_ansi_regression.v | 31 ++++++++++++ .../sv_interface_port_unmodported_basic.v | 40 ++++++++++++++++ ...rface_port_unmodported_missing_type_fail.v | 11 +++++ ivtest/ivltests/sv_interface_port_wildcard.v | 33 +++++++++++++ ivtest/regress-vvp.list | 9 ++++ .../sv_interface_port_forwarding.json | 5 ++ ...terface_port_forwarding_restrict_fail.json | 6 +++ ..._interface_port_plain_ansi_regression.json | 5 ++ .../sv_interface_port_positional.json | 5 ++ ...face_port_positional_unconnected_fail.json | 6 +++ ...nterface_port_typedef_ansi_regression.json | 5 ++ .../sv_interface_port_unmodported_basic.json | 5 ++ ...ce_port_unmodported_missing_type_fail.json | 6 +++ .../vvp_tests/sv_interface_port_wildcard.json | 5 ++ parse.y | 13 ++++- 24 files changed, 374 insertions(+), 19 deletions(-) create mode 100644 ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold create mode 100644 ivtest/ivltests/sv_interface_port_forwarding.v create mode 100644 ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_plain_ansi_regression.v create mode 100644 ivtest/ivltests/sv_interface_port_positional.v create mode 100644 ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_typedef_ansi_regression.v create mode 100644 ivtest/ivltests/sv_interface_port_unmodported_basic.v create mode 100644 ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_wildcard.v create mode 100644 ivtest/vvp_tests/sv_interface_port_forwarding.json create mode 100644 ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json create mode 100644 ivtest/vvp_tests/sv_interface_port_positional.json create mode 100644 ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json create mode 100644 ivtest/vvp_tests/sv_interface_port_unmodported_basic.json create mode 100644 ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_wildcard.json diff --git a/elaborate.cc b/elaborate.cc index 8c12940d1..bd6354164 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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&pins, - const vector&pins_fromwc) const + const vector&) 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::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::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); } diff --git a/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold new file mode 100644 index 000000000..90758da05 --- /dev/null +++ b/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold new file mode 100644 index 000000000..8fc0533db --- /dev/null +++ b/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold @@ -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 diff --git a/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold new file mode 100644 index 000000000..24caf2d27 --- /dev/null +++ b/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/ivltests/sv_interface_port_forwarding.v b/ivtest/ivltests/sv_interface_port_forwarding.v new file mode 100644 index 000000000..9fb52a87b --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_forwarding.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v b/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v new file mode 100644 index 000000000..79a1ced9f --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_plain_ansi_regression.v b/ivtest/ivltests/sv_interface_port_plain_ansi_regression.v new file mode 100644 index 000000000..30ab169a2 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_plain_ansi_regression.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_positional.v b/ivtest/ivltests/sv_interface_port_positional.v new file mode 100644 index 000000000..d0cb7d057 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_positional.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v b/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v new file mode 100644 index 000000000..5b9627b52 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_typedef_ansi_regression.v b/ivtest/ivltests/sv_interface_port_typedef_ansi_regression.v new file mode 100644 index 000000000..0fc169015 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_typedef_ansi_regression.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_unmodported_basic.v b/ivtest/ivltests/sv_interface_port_unmodported_basic.v new file mode 100644 index 000000000..c8bad7778 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_unmodported_basic.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v b/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v new file mode 100644 index 000000000..b02bb3985 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_wildcard.v b/ivtest/ivltests/sv_interface_port_wildcard.v new file mode 100644 index 000000000..e13630fb6 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_wildcard.v @@ -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 diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index 22f0917b6..638d8877f 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -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 diff --git a/ivtest/vvp_tests/sv_interface_port_forwarding.json b/ivtest/vvp_tests/sv_interface_port_forwarding.json new file mode 100644 index 000000000..5983d5d82 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_forwarding.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_forwarding.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json b/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json new file mode 100644 index 000000000..4e26953b8 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_forwarding_restrict_fail.v", + "gold" : "sv_interface_port_forwarding_restrict_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json b/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json new file mode 100644 index 000000000..56b84fc13 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_plain_ansi_regression.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_positional.json b/ivtest/vvp_tests/sv_interface_port_positional.json new file mode 100644 index 000000000..66f253f1d --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_positional.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_positional.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json b/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json new file mode 100644 index 000000000..07880369e --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_positional_unconnected_fail.v", + "gold" : "sv_interface_port_positional_unconnected_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json b/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json new file mode 100644 index 000000000..7b7d719a6 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_typedef_ansi_regression.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json b/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json new file mode 100644 index 000000000..01423ed0a --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_unmodported_basic.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json b/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json new file mode 100644 index 000000000..d00e41cec --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json @@ -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" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_wildcard.json b/ivtest/vvp_tests/sv_interface_port_wildcard.json new file mode 100644 index 000000000..d34e83b0e --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_wildcard.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_wildcard.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/parse.y b/parse.y index 99949e7ee..3257e7761 100644 --- a/parse.y +++ b/parse.y @@ -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 From 2228e31a6a742ad6f3a89c7b263535437aedafcc Mon Sep 17 00:00:00 2001 From: Jose Tejada Date: Sun, 10 May 2026 17:08:35 +0200 Subject: [PATCH 03/11] refactor(interface): share port resolution paths --- Module.cc | 47 +++++++++++++++++++++++++ Module.h | 13 +++++++ elab_sig.cc | 20 ++--------- elaborate.cc | 97 ++++++++++++++++++++++++++++++++-------------------- net_scope.cc | 12 ------- netlist.h | 2 -- 6 files changed, 122 insertions(+), 69 deletions(-) diff --git a/Module.cc b/Module.cc index 5babfb973..212fe87ad 100644 --- a/Module.cc +++ b/Module.cc @@ -21,8 +21,11 @@ # include "Module.h" # include "PGate.h" +# include "PModport.h" # include "PWire.h" +# include "parse_api.h" # include "ivl_assert.h" +# include using namespace std; @@ -33,6 +36,50 @@ Module::port_t::port_t() { } +bool resolve_interface_formal_port(const LineInfo*li, Design*des, + const Module::port_t*port, + interface_formal_port_t&res, + bool emit_errors) +{ + ivl_assert(*li, port); + ivl_assert(*li, port->is_interface_port()); + + res = interface_formal_port_t(); + + map::const_iterator mod = + pform_modules.find(port->interface_type); + if (mod == pform_modules.end() || !mod->second->is_interface) { + if (emit_errors) { + cerr << li->get_fileline() << ": error: Interface port " + << port->name << " uses unknown interface type `" + << port->interface_type << "'." << endl; + des->errors += 1; + } + return false; + } + + res.module = mod->second; + + if (port->modport_name.str()) { + map::const_iterator mp = + mod->second->modports.find(port->modport_name); + if (mp == mod->second->modports.end()) { + if (emit_errors) { + cerr << li->get_fileline() << ": error: Interface port " + << port->name << " uses unknown modport `" + << port->modport_name << "' of interface `" + << port->interface_type << "'." << endl; + des->errors += 1; + } + return false; + } + + res.modport = mp->second; + } + + return true; +} + /* n is a permallocated string. */ Module::Module(LexicalScope*parent, perm_string n) : PScopeExtra(n, parent) diff --git a/Module.h b/Module.h index 2713be544..ac9f3da5d 100644 --- a/Module.h +++ b/Module.h @@ -43,6 +43,7 @@ class PFunction; class PWire; class PProcess; class Design; +class LineInfo; class NetScope; /* @@ -197,4 +198,16 @@ class Module : public PScopeExtra, public PNamedItem { Module& operator= (const Module&); }; +struct interface_formal_port_t { + interface_formal_port_t() : module(0), modport(0) { } + + const Module*module; + const PModport*modport; +}; + +extern bool resolve_interface_formal_port(const LineInfo*li, Design*des, + const Module::port_t*port, + interface_formal_port_t&res, + bool emit_errors); + #endif /* IVL_Module_H */ diff --git a/elab_sig.cc b/elab_sig.cc index d0b202ae0..1526e1426 100644 --- a/elab_sig.cc +++ b/elab_sig.cc @@ -300,24 +300,8 @@ bool Module::elaborate_sig(Design*des, NetScope*scope) const continue; if (pp->is_interface_port()) { - map::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; - } + interface_formal_port_t formal; + resolve_interface_formal_port(this, des, pp, formal, true); continue; } diff --git a/elaborate.cc b/elaborate.cc index bd6354164..e8de36fc3 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -1325,6 +1325,48 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, return true; } +struct interface_actual_scope_t { + interface_actual_scope_t() : scope(0), modport(0) { } + + NetScope*scope; + const PModport*modport; + perm_string display_name; +}; + +static bool resolve_interface_actual_scope(const PExpr*actual, + NetScope*parent_scope, + Design*des, + interface_actual_scope_t&res) +{ + res = interface_actual_scope_t(); + + const PEIdent*actual_ident = dynamic_cast(actual); + if (!actual_ident || actual_ident->path().package || + actual_ident->path().name.size() != 1 || + !actual_ident->path().name.front().index.empty()) { + return false; + } + + res.display_name = actual_ident->path().name.front().name; + + symbol_search_results sr; + symbol_search(actual, des, parent_scope, actual_ident->path(), + actual_ident->lexical_pos(), &sr); + + res.scope = sr.scope; + if (sr.through_interface_alias()) + res.modport = sr.interface_alias_modport; + else if (NetScope*child = parent_scope->child(hname_t(res.display_name))) + res.scope = child; + else if (const NetScope::interface_port_alias_t*alias = + parent_scope->find_interface_port_alias(res.display_name)) { + res.scope = alias->actual_scope; + res.modport = alias->modport; + } + + return true; +} + bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, NetScope*parent_scope, NetScope*instance_scope, @@ -1347,10 +1389,8 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, continue; } - const PEIdent*actual_ident = dynamic_cast(pins[idx]); - if (!actual_ident || actual_ident->path().package || - actual_ident->path().name.size() != 1 || - !actual_ident->path().name.front().index.empty()) { + interface_actual_scope_t actual; + if (!resolve_interface_actual_scope(pins[idx], parent_scope, des, actual)) { cerr << pins[idx]->get_fileline() << ": error: Interface port `" << port->name << "' must be connected to a simple " "interface instance name." << endl; @@ -1359,19 +1399,7 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, continue; } - perm_string actual_name = actual_ident->path().name.front().name; - NetScope*actual_scope = parent_scope->child(hname_t(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()) { + 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; @@ -1380,40 +1408,35 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, continue; } - if (actual_scope->module_name() != port->interface_type) { + interface_formal_port_t formal; + resolve_interface_formal_port(pins[idx], des, port, formal, false); + if (!formal.module) + continue; + + if (actual.scope->module_name() != formal.module->mod_name()) { 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; + << port->interface_type << "' but actual `" << actual.display_name + << "' has type `" << actual.scope->module_name() << "'." << endl; des->errors += 1; flag = false; continue; } - map::const_iterator mod = - pform_modules.find(port->interface_type); - const PModport*formal_modport = 0; - if (port->modport_name.str() && mod != pform_modules.end()) { - map::const_iterator mp = - mod->second->modports.find(port->modport_name); - if (mp != mod->second->modports.end()) - formal_modport = mp->second; - } - - if (actual_modport && formal_modport && - actual_modport->name() != formal_modport->name()) { + 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() + << port->name << "' cannot forward actual `" << actual.display_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, + const PModport*modport = formal.modport? formal.modport : actual.modport; + instance_scope->add_interface_port_alias(port->name, actual.scope, modport); } diff --git a/net_scope.cc b/net_scope.cc index 6292817a2..0719bcc6c 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -831,18 +831,6 @@ NetScope::find_interface_port_alias(perm_string formal_name) const 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 diff --git a/netlist.h b/netlist.h index 7011c5a9f..4b465fd13 100644 --- a/netlist.h +++ b/netlist.h @@ -1061,8 +1061,6 @@ class NetScope : public Definitions, public Attrib { 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; From 417ab5444574594dbfa7774416b6a346b5d5044a Mon Sep 17 00:00:00 2001 From: Jose Tejada Date: Sun, 10 May 2026 17:30:54 +0200 Subject: [PATCH 04/11] feat(interface): support interface port arrays --- Module.cc | 2 +- Module.h | 1 + elaborate.cc | 221 +++++++++++++++++- ...modport_restrict_fail-iverilog-stderr.gold | 2 + ...ay_size_mismatch_fail-iverilog-stderr.gold | 2 + .../ivltests/sv_interface_port_array_basic.v | 46 ++++ ...terface_port_array_modport_restrict_fail.v | 16 ++ ..._interface_port_array_size_mismatch_fail.v | 13 ++ .../sv_interface_port_indexed_actual.v | 33 +++ ...v_interface_port_indexed_actual_generate.v | 43 ++++ ivtest/regress-vvp.list | 5 + .../sv_interface_port_array_basic.json | 5 + ...face_port_array_modport_restrict_fail.json | 6 + ...terface_port_array_size_mismatch_fail.json | 6 + .../sv_interface_port_indexed_actual.json | 5 + ...nterface_port_indexed_actual_generate.json | 5 + net_scope.cc | 40 ++++ netlist.h | 8 + parse.y | 19 +- pform.cc | 4 +- pform.h | 3 +- symbol_search.cc | 25 ++ 22 files changed, 491 insertions(+), 19 deletions(-) create mode 100644 ivtest/gold/sv_interface_port_array_modport_restrict_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_array_size_mismatch_fail-iverilog-stderr.gold create mode 100644 ivtest/ivltests/sv_interface_port_array_basic.v create mode 100644 ivtest/ivltests/sv_interface_port_array_modport_restrict_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_array_size_mismatch_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_indexed_actual.v create mode 100644 ivtest/ivltests/sv_interface_port_indexed_actual_generate.v create mode 100644 ivtest/vvp_tests/sv_interface_port_array_basic.json create mode 100644 ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_indexed_actual.json create mode 100644 ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json diff --git a/Module.cc b/Module.cc index 212fe87ad..957822743 100644 --- a/Module.cc +++ b/Module.cc @@ -32,7 +32,7 @@ using namespace std; list Module::user_defparms; Module::port_t::port_t() -: port_kind(P_SIGNAL), default_value(0), lexical_pos(0) +: port_kind(P_SIGNAL), default_value(0), interface_unpacked_dimensions(0), lexical_pos(0) { } diff --git a/Module.h b/Module.h index ac9f3da5d..62082ab68 100644 --- a/Module.h +++ b/Module.h @@ -81,6 +81,7 @@ class Module : public PScopeExtra, public PNamedItem { the explicit interface_type.modport form. */ perm_string interface_type; perm_string modport_name; + std::list*interface_unpacked_dimensions; unsigned lexical_pos; bool is_interface_port() const { return port_kind == P_INTERFACE; } diff --git a/elaborate.cc b/elaborate.cc index e8de36fc3..5fa150c15 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -1333,6 +1333,48 @@ struct interface_actual_scope_t { perm_string display_name; }; +struct interface_actual_array_t { + std::map elements; + perm_string display_name; +}; + +static long interface_array_index(unsigned idx, long left, long right) +{ + long low = left < right ? left : right; + long high = left < right ? right : left; + return low < high ? low + idx : low - idx; +} + +static bool interface_formal_range(const Module::port_t*port, + Design*des, NetScope*scope, + const LineInfo*li, + long&left, long&right, + unsigned&count) +{ + if (!port->interface_unpacked_dimensions) { + left = 0; + right = 0; + count = 1; + return true; + } + + if (port->interface_unpacked_dimensions->size() != 1) { + cerr << li->get_fileline() << ": sorry: Interface port `" + << port->name << "' uses a multidimensional array, which " + "is not supported." << endl; + des->errors += 1; + return false; + } + + if (!evaluate_range(des, scope, li, + port->interface_unpacked_dimensions->front(), + left, right)) + return false; + + count = left > right ? left - right + 1 : right - left + 1; + return true; +} + static bool resolve_interface_actual_scope(const PExpr*actual, NetScope*parent_scope, Design*des, @@ -1343,11 +1385,50 @@ static bool resolve_interface_actual_scope(const PExpr*actual, const PEIdent*actual_ident = dynamic_cast(actual); if (!actual_ident || actual_ident->path().package || actual_ident->path().name.size() != 1 || - !actual_ident->path().name.front().index.empty()) { + actual_ident->path().name.front().index.size() > 1) { return false; } - res.display_name = actual_ident->path().name.front().name; + const name_component_t&comp = actual_ident->path().name.front(); + res.display_name = comp.name; + + if (!comp.index.empty()) { + if (comp.index.front().sel != index_component_t::SEL_BIT) { + cerr << actual->get_fileline() << ": sorry: Interface array " + << "slice actuals are not supported." << endl; + des->errors += 1; + return true; + } + + bool error_flag = false; + hname_t actual_name = eval_path_component(des, parent_scope, + comp, error_flag); + if (error_flag) + return true; + + if (NetScope*child = parent_scope->child(actual_name)) + res.scope = child; + else if (actual_name.has_numbers()) { + const NetScope::interface_port_alias_t*alias = + parent_scope->find_interface_port_alias_element( + comp.name, actual_name.peek_number(0)); + if (alias) { + res.scope = alias->actual_scope; + res.modport = alias->modport; + } + } + + if (!res.scope) { + symbol_search_results sr; + symbol_search(actual, des, parent_scope, actual_ident->path(), + actual_ident->lexical_pos(), &sr); + res.scope = sr.scope; + if (sr.through_interface_alias()) + res.modport = sr.interface_alias_modport; + } + + return true; + } symbol_search_results sr; symbol_search(actual, des, parent_scope, actual_ident->path(), @@ -1367,6 +1448,49 @@ static bool resolve_interface_actual_scope(const PExpr*actual, return true; } +static bool resolve_interface_actual_array(const PExpr*actual, + NetScope*parent_scope, + interface_actual_array_t&res) +{ + res = interface_actual_array_t(); + + const PEIdent*actual_ident = dynamic_cast(actual); + if (!actual_ident || actual_ident->path().package || + actual_ident->path().name.size() != 1 || + !actual_ident->path().name.front().index.empty()) + return false; + + perm_string name = actual_ident->path().name.front().name; + res.display_name = name; + + for (NetScope*scope = parent_scope ; scope ; scope = scope->parent()) { + map::const_iterator arr = + scope->instance_arrays.find(name); + if (arr != scope->instance_arrays.end()) { + for (unsigned idx = 0 ; idx < arr->second.size() ; idx += 1) { + NetScope*inst = arr->second[idx]; + if (!inst) + return false; + hname_t hname = inst->fullname(); + if (!hname.has_numbers()) + return false; + res.elements[hname.peek_number(0)] = + NetScope::interface_port_alias_t(inst, 0); + } + return true; + } + + const map*alias_arr = + scope->find_interface_port_alias_array(name); + if (alias_arr) { + res.elements = *alias_arr; + return true; + } + } + + return false; +} + bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, NetScope*parent_scope, NetScope*instance_scope, @@ -1389,6 +1513,94 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, continue; } + long formal_left = 0; + long formal_right = 0; + unsigned formal_count = 1; + if (!interface_formal_range(port, des, instance_scope, pins[idx], + formal_left, formal_right, formal_count)) { + flag = false; + continue; + } + bool formal_is_array = port->interface_unpacked_dimensions != 0; + + interface_formal_port_t formal; + resolve_interface_formal_port(pins[idx], des, port, formal, false); + if (!formal.module) + continue; + + if (formal_is_array) { + interface_actual_array_t actual_array; + if (!resolve_interface_actual_array(pins[idx], parent_scope, actual_array)) { + cerr << pins[idx]->get_fileline() << ": error: Interface " + << "array port `" << port->name << "' must be " + "connected to an interface instance array." << endl; + des->errors += 1; + flag = false; + continue; + } + + if (actual_array.elements.size() != formal_count) { + cerr << pins[idx]->get_fileline() << ": error: Interface " + << "array port `" << port->name << "' expects " + << formal_count << " element(s) but actual `" + << actual_array.display_name << "' has " + << actual_array.elements.size() << " element(s)." << endl; + des->errors += 1; + flag = false; + continue; + } + + unsigned pos = 0; + bool array_ok = true; + for (map::const_iterator cur = + actual_array.elements.begin() + ; cur != actual_array.elements.end() ; ++cur, ++pos) { + NetScope*actual_scope = cur->second.actual_scope; + if (!actual_scope || !actual_scope->is_interface()) { + cerr << pins[idx]->get_fileline() << ": error: Actual " + << "element for interface array port `" + << port->name << "' is not an interface instance." << endl; + des->errors += 1; + array_ok = false; + continue; + } + + if (actual_scope->module_name() != formal.module->mod_name()) { + cerr << pins[idx]->get_fileline() << ": error: Interface " + << "array port `" << port->name + << "' expects interface type `" << port->interface_type + << "' but actual `" << actual_array.display_name + << "' has element type `" << actual_scope->module_name() + << "'." << endl; + des->errors += 1; + array_ok = false; + continue; + } + + if (cur->second.modport && formal.modport && + cur->second.modport->name() != formal.modport->name()) { + cerr << pins[idx]->get_fileline() << ": error: Interface " + << "array port `" << port->name + << "' cannot forward actual `" << actual_array.display_name + << "' restricted by modport `" << cur->second.modport->name() + << "' to formal modport `" << formal.modport->name() + << "'." << endl; + des->errors += 1; + array_ok = false; + continue; + } + + const PModport*modport = formal.modport? + formal.modport : cur->second.modport; + long formal_index = interface_array_index(pos, formal_left, formal_right); + instance_scope->add_interface_port_alias_element( + port->name, formal_index, actual_scope, modport); + } + + flag = flag && array_ok; + continue; + } + interface_actual_scope_t actual; if (!resolve_interface_actual_scope(pins[idx], parent_scope, des, actual)) { cerr << pins[idx]->get_fileline() << ": error: Interface port `" @@ -1408,11 +1620,6 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, continue; } - interface_formal_port_t formal; - resolve_interface_formal_port(pins[idx], des, port, formal, false); - if (!formal.module) - continue; - if (actual.scope->module_name() != formal.module->mod_name()) { cerr << pins[idx]->get_fileline() << ": error: Interface port `" << port->name << "' expects interface type `" diff --git a/ivtest/gold/sv_interface_port_array_modport_restrict_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_array_modport_restrict_fail-iverilog-stderr.gold new file mode 100644 index 000000000..5228fdac6 --- /dev/null +++ b/ivtest/gold/sv_interface_port_array_modport_restrict_fail-iverilog-stderr.gold @@ -0,0 +1,2 @@ +ivltests/sv_interface_port_array_modport_restrict_fail.v:10: error: Cannot assign to input modport member `value' through interface port `bus'. +1 error(s) during elaboration. diff --git a/ivtest/gold/sv_interface_port_array_size_mismatch_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_array_size_mismatch_fail-iverilog-stderr.gold new file mode 100644 index 000000000..ded548edf --- /dev/null +++ b/ivtest/gold/sv_interface_port_array_size_mismatch_fail-iverilog-stderr.gold @@ -0,0 +1,2 @@ +ivltests/sv_interface_port_array_size_mismatch_fail.v:12: error: Interface array port `bus' expects 2 element(s) but actual `buses' has 1 element(s). +Elaboration failed diff --git a/ivtest/ivltests/sv_interface_port_array_basic.v b/ivtest/ivltests/sv_interface_port_array_basic.v new file mode 100644 index 000000000..6915b8c14 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_array_basic.v @@ -0,0 +1,46 @@ +// This tests a one-dimensional interface formal array connected to a +// whole interface instance array, then indexed inside the receiving +// module. + +interface bus_if (); + logic [7:0] value; + modport producer(output value); + modport consumer(input value); +endinterface + +module drive(input [7:0] val, bus_if.producer bus); + assign bus.value = val; +endmodule + +module sample(output [7:0] y, bus_if.consumer bus); + assign y = bus.value; +endmodule + +module child_array(output [7:0] y0, output [7:0] y1, + bus_if.consumer bus[2]); + sample c0(.y(y0), .bus(bus[0])); + sample c1(.y(y1), .bus(bus[1])); +endmodule + +module test; + bus_if buses[2](); + wire [7:0] y0; + wire [7:0] y1; + + drive d0(8'd21, buses[0]); + drive d1(8'd42, buses[1]); + child_array dut(.bus(buses), .y0(y0), .y1(y1)); + + initial begin + #1; + if (y0 !== 8'd21) begin + $display("FAILED: y0=%0d", y0); + $finish; + end + if (y1 !== 8'd42) begin + $display("FAILED: y1=%0d", y1); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/ivltests/sv_interface_port_array_modport_restrict_fail.v b/ivtest/ivltests/sv_interface_port_array_modport_restrict_fail.v new file mode 100644 index 000000000..5a5e96fc9 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_array_modport_restrict_fail.v @@ -0,0 +1,16 @@ +// This tests that modport restrictions apply through indexed interface +// formal array elements. + +interface bus_if (); + logic value; + modport consumer(input value); +endinterface + +module bad(bus_if.consumer bus[1]); + assign bus[0].value = 1'b1; +endmodule + +module test; + bus_if buses[1](); + bad dut(.bus(buses)); +endmodule diff --git a/ivtest/ivltests/sv_interface_port_array_size_mismatch_fail.v b/ivtest/ivltests/sv_interface_port_array_size_mismatch_fail.v new file mode 100644 index 000000000..af2338918 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_array_size_mismatch_fail.v @@ -0,0 +1,13 @@ +// This tests rejection of a whole interface array actual whose size +// does not match the formal interface array size. + +interface bus_if (); +endinterface + +module child(bus_if bus[2]); +endmodule + +module test; + bus_if buses[1](); + child dut(.bus(buses)); +endmodule diff --git a/ivtest/ivltests/sv_interface_port_indexed_actual.v b/ivtest/ivltests/sv_interface_port_indexed_actual.v new file mode 100644 index 000000000..979ef7fbb --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_indexed_actual.v @@ -0,0 +1,33 @@ +// This tests connecting a scalar interface-typed formal to one element +// of an interface instance array. + +interface bus_if (); + logic [7:0] value; + modport producer(output value); + modport consumer(input value); +endinterface + +module drive(input [7:0] val, bus_if.producer bus); + assign bus.value = val; +endmodule + +module sample(output [7:0] y, bus_if.consumer bus); + assign y = bus.value; +endmodule + +module test; + bus_if buses[2](); + wire [7:0] y; + + drive d0(8'd37, buses[0]); + sample s0(.bus(buses[0]), .y(y)); + + initial begin + #1; + if (y !== 8'd37) begin + $display("FAILED: y=%0d", y); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/ivltests/sv_interface_port_indexed_actual_generate.v b/ivtest/ivltests/sv_interface_port_indexed_actual_generate.v new file mode 100644 index 000000000..2ec7690b5 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_indexed_actual_generate.v @@ -0,0 +1,43 @@ +// This tests connecting scalar interface-typed formals to interface +// array elements selected by generated constant indices. + +interface bus_if (); + logic [7:0] value; + modport producer(output value); + modport consumer(input value); +endinterface + +module drive(input [7:0] val, bus_if.producer bus); + assign bus.value = val; +endmodule + +module sample(output [7:0] y, bus_if.consumer bus); + assign y = bus.value; +endmodule + +module test; + bus_if buses[2](); + wire [7:0] y[2]; + + genvar i; + generate + for (i = 0; i < 2; i = i + 1) begin : gen + localparam [7:0] VAL = 8'd11 + i; + drive d(VAL, buses[i]); + sample s(.y(y[i]), .bus(buses[i])); + end + endgenerate + + initial begin + #1; + if (y[0] !== 8'd11) begin + $display("FAILED: y[0]=%0d", y[0]); + $finish; + end + if (y[1] !== 8'd12) begin + $display("FAILED: y[1]=%0d", y[1]); + $finish; + end + $display("PASSED"); + end +endmodule diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index 638d8877f..d205e357e 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -283,6 +283,11 @@ sv_interface_port_forwarding_restrict_fail vvp_tests/sv_interface_port_forwardin 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_indexed_actual vvp_tests/sv_interface_port_indexed_actual.json +sv_interface_port_indexed_actual_generate vvp_tests/sv_interface_port_indexed_actual_generate.json +sv_interface_port_array_basic vvp_tests/sv_interface_port_array_basic.json +sv_interface_port_array_modport_restrict_fail vvp_tests/sv_interface_port_array_modport_restrict_fail.json +sv_interface_port_array_size_mismatch_fail vvp_tests/sv_interface_port_array_size_mismatch_fail.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 diff --git a/ivtest/vvp_tests/sv_interface_port_array_basic.json b/ivtest/vvp_tests/sv_interface_port_array_basic.json new file mode 100644 index 000000000..72ff9a89f --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_array_basic.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_array_basic.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json b/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json new file mode 100644 index 000000000..01f2e561c --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_array_modport_restrict_fail.v", + "gold" : "sv_interface_port_array_modport_restrict_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json b/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json new file mode 100644 index 000000000..1876f1bd4 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_array_size_mismatch_fail.v", + "gold" : "sv_interface_port_array_size_mismatch_fail", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_indexed_actual.json b/ivtest/vvp_tests/sv_interface_port_indexed_actual.json new file mode 100644 index 000000000..0d0064b63 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_indexed_actual.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_indexed_actual.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json b/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json new file mode 100644 index 000000000..f8532856c --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json @@ -0,0 +1,5 @@ +{ + "type" : "normal", + "source" : "sv_interface_port_indexed_actual_generate.v", + "iverilog-args" : [ "-g2012" ] +} diff --git a/net_scope.cc b/net_scope.cc index 0719bcc6c..3c9c5cd3d 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -831,6 +831,44 @@ NetScope::find_interface_port_alias(perm_string formal_name) const return &cur->second; } +void NetScope::add_interface_port_alias_element(perm_string formal_name, + long index, + NetScope*actual_scope, + const PModport*modport) +{ + ivl_assert(*this, actual_scope); + interface_port_alias_arrays_[formal_name][index] = + interface_port_alias_t(actual_scope, modport); +} + +const NetScope::interface_port_alias_t* +NetScope::find_interface_port_alias_element(perm_string formal_name, + long index) const +{ + map >::const_iterator arr; + arr = interface_port_alias_arrays_.find(formal_name); + if (arr == interface_port_alias_arrays_.end()) + return 0; + + map::const_iterator cur; + cur = arr->second.find(index); + if (cur == arr->second.end()) + return 0; + + return &cur->second; +} + +const map* +NetScope::find_interface_port_alias_array(perm_string formal_name) const +{ + map >::const_iterator cur; + cur = interface_port_alias_arrays_.find(formal_name); + if (cur == interface_port_alias_arrays_.end()) + return 0; + + return &cur->second; +} + /* 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 @@ -888,6 +926,8 @@ bool NetScope::symbol_exists(perm_string sym) return true; if (find_interface_port_alias(sym)) return true; + if (find_interface_port_alias_array(sym)) + return true; return false; } diff --git a/netlist.h b/netlist.h index 4b465fd13..b3baec96a 100644 --- a/netlist.h +++ b/netlist.h @@ -1061,6 +1061,13 @@ class NetScope : public Definitions, public Attrib { NetScope*actual_scope, const PModport*modport); const interface_port_alias_t* find_interface_port_alias(perm_string formal_name) const; + void add_interface_port_alias_element(perm_string formal_name, + long index, + NetScope*actual_scope, + const PModport*modport); + const interface_port_alias_t* find_interface_port_alias_element(perm_string formal_name, + long index) const; + const std::map* find_interface_port_alias_array(perm_string formal_name) const; /* A helper function to find the enclosing class scope. */ const NetScope* get_class_scope() const; @@ -1367,6 +1374,7 @@ class NetScope : public Definitions, public Attrib { NetScope*up_; std::map children_; std::map interface_port_aliases_; + std::map > interface_port_alias_arrays_; unsigned lcounter_; bool need_const_func_, is_const_func_, is_auto_, is_cell_, calls_stask_; diff --git a/parse.y b/parse.y index 3257e7761..96830851a 100644 --- a/parse.y +++ b/parse.y @@ -463,6 +463,7 @@ Module::port_t *module_declare_port(const YYLTYPE&loc, char *id, Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, char *modport, char *id, + std::list *udims, std::list *attributes) { pform_requires_sv(loc, "Interface port declaration"); @@ -470,7 +471,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, Module::port_t *port = pform_module_interface_port_reference( loc, lex_strings.make(type), modport ? lex_strings.make(modport) : perm_string(), - lex_strings.make(id)); + lex_strings.make(id), udims); delete[] type; if (modport) @@ -4657,11 +4658,11 @@ 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 IDENTIFIER '.' IDENTIFIER IDENTIFIER dimensions_opt + { $$ = module_declare_interface_port(@5, $2, $4, $5, $6, $1); } - | attribute_list_opt IDENTIFIER IDENTIFIER - { $$ = module_declare_interface_port(@3, $2, 0, $3, $1); + | attribute_list_opt IDENTIFIER IDENTIFIER dimensions_opt + { $$ = module_declare_interface_port(@3, $2, 0, $3, $4, $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"); @@ -5756,12 +5757,12 @@ port : port_reference { $$ = $1; } - | IDENTIFIER '.' IDENTIFIER IDENTIFIER - { $$ = module_declare_interface_port(@4, $1, $3, $4, 0); + | IDENTIFIER '.' IDENTIFIER IDENTIFIER dimensions_opt + { $$ = module_declare_interface_port(@4, $1, $3, $4, $5, 0); } - | IDENTIFIER IDENTIFIER - { $$ = module_declare_interface_port(@2, $1, 0, $2, 0); + | IDENTIFIER IDENTIFIER dimensions_opt + { $$ = module_declare_interface_port(@2, $1, 0, $2, $3, 0); } /* This syntax attaches an external name to the port reference so diff --git a/pform.cc b/pform.cc index affc5c775..e001914b7 100644 --- a/pform.cc +++ b/pform.cc @@ -1390,7 +1390,8 @@ Module::port_t* pform_module_interface_port_reference( const struct vlltype&loc, perm_string interface_type, perm_string modport_name, - perm_string name) + perm_string name, + list*udims) { Module::port_t*ptmp = new Module::port_t; @@ -1398,6 +1399,7 @@ Module::port_t* pform_module_interface_port_reference( ptmp->name = name; ptmp->interface_type = interface_type; ptmp->modport_name = modport_name; + ptmp->interface_unpacked_dimensions = udims; ptmp->lexical_pos = loc.lexical_pos; return ptmp; diff --git a/pform.h b/pform.h index 2f83c5541..95d60d458 100644 --- a/pform.h +++ b/pform.h @@ -171,7 +171,8 @@ extern Module::port_t* pform_module_interface_port_reference( const struct vlltype&loc, perm_string interface_type, perm_string modport_name, - perm_string name); + perm_string name, + std::list*udims = 0); extern void pform_module_define_interface_port(const struct vlltype&loc, Module::port_t*port, std::list*attr); diff --git a/symbol_search.cc b/symbol_search.cc index 7b83dd664..d05aa8ce9 100644 --- a/symbol_search.cc +++ b/symbol_search.cc @@ -331,6 +331,31 @@ bool symbol_search(const LineInfo*li, Design*des, NetScope*scope, return true; } + } else { + bool flag = false; + hname_t path_item = eval_path_component(des, start_scope, path_tail, flag); + if (!flag && path_item.has_numbers() == 1) { + if (const NetScope::interface_port_alias_t*alias = + scope->find_interface_port_alias_element(path_tail.name, + path_item.peek_number(0))) { + 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 + << "[" << path_item.peek_number(0) << "]" + << " -> " << scope_path(alias->actual_scope) << endl; + } + + return true; + } + } } // Don't scan up if we are searching within a prefixed scope. From 377881b72361370fa79c3a843ce7edcb51f28cc2 Mon Sep 17 00:00:00 2001 From: Jose Tejada Date: Mon, 11 May 2026 07:44:43 +0200 Subject: [PATCH 05/11] fix(interface): address port array review feedback --- elaborate.cc | 41 +++++++++++++++---- ...warding_restrict_fail-iverilog-stderr.gold | 4 +- ...ort_missing_type_fail-iverilog-stderr.gold | 2 - ...onal_unconnected_fail-iverilog-stderr.gold | 2 +- ...ted_missing_type_fail-iverilog-stderr.gold | 2 - .../ivltests/sv_interface_port_forwarding.v | 16 ++++---- ..._interface_port_forwarding_restrict_fail.v | 14 +++---- .../sv_interface_port_missing_type_fail.v | 11 ----- .../ivltests/sv_interface_port_positional.v | 14 +++---- ...terface_port_positional_unconnected_fail.v | 12 +++--- .../sv_interface_port_unmodported_basic.v | 12 +++--- ...rface_port_unmodported_missing_type_fail.v | 11 ----- ivtest/ivltests/sv_interface_port_wildcard.v | 14 +++---- ivtest/regress-vvp.list | 2 - .../sv_interface_port_array_basic.json | 2 +- ...face_port_array_modport_restrict_fail.json | 2 +- ...terface_port_array_size_mismatch_fail.json | 2 +- ivtest/vvp_tests/sv_interface_port_basic.json | 2 +- .../sv_interface_port_forwarding.json | 2 +- ...terface_port_forwarding_restrict_fail.json | 2 +- .../sv_interface_port_indexed_actual.json | 2 +- ...nterface_port_indexed_actual_generate.json | 2 +- ...v_interface_port_missing_modport_fail.json | 2 +- .../sv_interface_port_missing_type_fail.json | 6 --- ...terface_port_modport_input_write_fail.json | 2 +- ...erface_port_non_interface_actual_fail.json | 2 +- ..._interface_port_plain_ansi_regression.json | 2 +- .../sv_interface_port_positional.json | 2 +- ...face_port_positional_unconnected_fail.json | 2 +- ...nterface_port_typedef_ansi_regression.json | 2 +- ...v_interface_port_unlisted_member_fail.json | 2 +- .../sv_interface_port_unmodported_basic.json | 2 +- ...ce_port_unmodported_missing_type_fail.json | 6 --- .../vvp_tests/sv_interface_port_wildcard.json | 2 +- .../sv_interface_port_wrong_type_fail.json | 2 +- lexor.lex | 11 +++++ net_scope.cc | 9 ++-- netlist.h | 2 +- parse.y | 28 ++++++++----- parse_misc.h | 5 +++ pform.cc | 8 ++++ symbol_search.cc | 2 +- vvp/Makefile.in | 4 -- 43 files changed, 142 insertions(+), 134 deletions(-) delete mode 100644 ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold delete mode 100644 ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold delete mode 100644 ivtest/ivltests/sv_interface_port_missing_type_fail.v delete mode 100644 ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v delete mode 100644 ivtest/vvp_tests/sv_interface_port_missing_type_fail.json delete mode 100644 ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json diff --git a/elaborate.cc b/elaborate.cc index 5fa150c15..8efdfc8bd 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -1255,13 +1255,19 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, vector&pins_fromwc, vector&pins_is_explicitly_not_connected) const { + // 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; @@ -1269,7 +1275,6 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, 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()); @@ -1279,7 +1284,13 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, 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 " @@ -1288,10 +1299,16 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, 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." << @@ -1300,16 +1317,26 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, 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() << @@ -1326,7 +1353,7 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod, } struct interface_actual_scope_t { - interface_actual_scope_t() : scope(0), modport(0) { } + interface_actual_scope_t() : scope(nullptr), modport(nullptr) { } NetScope*scope; const PModport*modport; @@ -1464,8 +1491,7 @@ static bool resolve_interface_actual_array(const PExpr*actual, res.display_name = name; for (NetScope*scope = parent_scope ; scope ; scope = scope->parent()) { - map::const_iterator arr = - scope->instance_arrays.find(name); + auto arr = scope->instance_arrays.find(name); if (arr != scope->instance_arrays.end()) { for (unsigned idx = 0 ; idx < arr->second.size() ; idx += 1) { NetScope*inst = arr->second[idx]; @@ -1475,7 +1501,7 @@ static bool resolve_interface_actual_array(const PExpr*actual, if (!hname.has_numbers()) return false; res.elements[hname.peek_number(0)] = - NetScope::interface_port_alias_t(inst, 0); + NetScope::interface_port_alias_t(inst, nullptr); } return true; } @@ -1521,7 +1547,7 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, flag = false; continue; } - bool formal_is_array = port->interface_unpacked_dimensions != 0; + bool formal_is_array = port->interface_unpacked_dimensions != nullptr; interface_formal_port_t formal; resolve_interface_formal_port(pins[idx], des, port, formal, false); @@ -1552,8 +1578,7 @@ bool PGModule::bind_interface_ports_(Design*des, const Module*rmod, unsigned pos = 0; bool array_ok = true; - for (map::const_iterator cur = - actual_array.elements.begin() + for (auto cur = actual_array.elements.begin() ; cur != actual_array.elements.end() ; ++cur, ++pos) { NetScope*actual_scope = cur->second.actual_scope; if (!actual_scope || !actual_scope->is_interface()) { diff --git a/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold index 90758da05..05a2a50ec 100644 --- a/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold +++ b/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold @@ -1,3 +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 +ivltests/sv_interface_port_forwarding_restrict_fail.v:30: error: Interface member `hidden' is not listed in modport `consumer'. +ivltests/sv_interface_port_forwarding_restrict_fail.v:30: error: Unable to elaborate r-value: bus.hidden 2 error(s) during elaboration. diff --git a/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold deleted file mode 100644 index 601207b55..000000000 --- a/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold +++ /dev/null @@ -1,2 +0,0 @@ -ivltests/sv_interface_port_missing_type_fail.v:7: error: Interface port bus uses unknown interface type `missing_if'. -1 error(s) during elaboration. diff --git a/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold index 8fc0533db..4201c3c5e 100644 --- a/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold +++ b/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold @@ -1,2 +1,2 @@ -ivltests/sv_interface_port_positional_unconnected_fail.v:7: error: Interface port `bus' of module bus_user is not connected. +ivltests/sv_interface_port_positional_unconnected_fail.v:13: error: Interface port `bus' of module bus_user is not connected. Elaboration failed diff --git a/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold deleted file mode 100644 index 24caf2d27..000000000 --- a/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold +++ /dev/null @@ -1,2 +0,0 @@ -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. diff --git a/ivtest/ivltests/sv_interface_port_forwarding.v b/ivtest/ivltests/sv_interface_port_forwarding.v index 9fb52a87b..58cd1bc81 100644 --- a/ivtest/ivltests/sv_interface_port_forwarding.v +++ b/ivtest/ivltests/sv_interface_port_forwarding.v @@ -4,6 +4,14 @@ // This file is placed into the Public Domain, for any use, without // warranty. +interface bus_if (); + logic value; + logic mirror; + logic hidden; + + modport consumer(input value, output mirror); +endinterface + module test; bus_if bus(); @@ -31,11 +39,3 @@ module child( ); assign bus.mirror = bus.value; endmodule - -interface bus_if (); - logic value; - logic mirror; - logic hidden; - - modport consumer(input value, output mirror); -endinterface diff --git a/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v b/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v index 79a1ced9f..a0fb3ab79 100644 --- a/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v +++ b/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v @@ -4,6 +4,13 @@ // This file is placed into the Public Domain, for any use, without // warranty. +interface bus_if (); + logic value; + logic hidden; + + modport consumer(input value); +endinterface + module test; bus_if bus(); parent dut(.bus(bus)); @@ -22,10 +29,3 @@ module child( assign sample = bus.hidden; endmodule - -interface bus_if (); - logic value; - logic hidden; - - modport consumer(input value); -endinterface diff --git a/ivtest/ivltests/sv_interface_port_missing_type_fail.v b/ivtest/ivltests/sv_interface_port_missing_type_fail.v deleted file mode 100644 index df1a5a513..000000000 --- a/ivtest/ivltests/sv_interface_port_missing_type_fail.v +++ /dev/null @@ -1,11 +0,0 @@ -// 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 diff --git a/ivtest/ivltests/sv_interface_port_positional.v b/ivtest/ivltests/sv_interface_port_positional.v index d0cb7d057..8b6d47551 100644 --- a/ivtest/ivltests/sv_interface_port_positional.v +++ b/ivtest/ivltests/sv_interface_port_positional.v @@ -3,6 +3,13 @@ // This file is placed into the Public Domain, for any use, without // warranty. +interface bus_if (); + logic value; + logic sample; + + modport consumer(input value, output sample); +endinterface + module test; bus_if bus(); @@ -24,10 +31,3 @@ module bus_user( ); assign bus.sample = bus.value; endmodule - -interface bus_if (); - logic value; - logic sample; - - modport consumer(input value, output sample); -endinterface diff --git a/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v b/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v index 5b9627b52..90e17871a 100644 --- a/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v +++ b/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v @@ -3,6 +3,12 @@ // This file is placed into the Public Domain, for any use, without // warranty. +interface bus_if (); + logic value; + + modport consumer(input value); +endinterface + module test; bus_user dut(); endmodule @@ -11,9 +17,3 @@ module bus_user( bus_if.consumer bus ); endmodule - -interface bus_if (); - logic value; - - modport consumer(input value); -endinterface diff --git a/ivtest/ivltests/sv_interface_port_unmodported_basic.v b/ivtest/ivltests/sv_interface_port_unmodported_basic.v index c8bad7778..aa981cf2f 100644 --- a/ivtest/ivltests/sv_interface_port_unmodported_basic.v +++ b/ivtest/ivltests/sv_interface_port_unmodported_basic.v @@ -4,6 +4,12 @@ // This file is placed into the Public Domain, for any use, without // warranty. +interface bus_if (); + logic [7:0] lhs; + logic [7:0] rhs; + logic [8:0] sum; +endinterface + module test; logic [7:0] lhs; logic [7:0] rhs; @@ -32,9 +38,3 @@ module add_if( ); assign bus.sum = bus.lhs + bus.rhs; endmodule - -interface bus_if (); - logic [7:0] lhs; - logic [7:0] rhs; - logic [8:0] sum; -endinterface diff --git a/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v b/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v deleted file mode 100644 index b02bb3985..000000000 --- a/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v +++ /dev/null @@ -1,11 +0,0 @@ -// 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 diff --git a/ivtest/ivltests/sv_interface_port_wildcard.v b/ivtest/ivltests/sv_interface_port_wildcard.v index e13630fb6..693e702ed 100644 --- a/ivtest/ivltests/sv_interface_port_wildcard.v +++ b/ivtest/ivltests/sv_interface_port_wildcard.v @@ -3,6 +3,13 @@ // This file is placed into the Public Domain, for any use, without // warranty. +interface bus_if (); + logic value; + logic sample; + + modport consumer(input value, output sample); +endinterface + module test; bus_if bus(); @@ -24,10 +31,3 @@ module bus_user( ); assign bus.sample = bus.value; endmodule - -interface bus_if (); - logic value; - logic sample; - - modport consumer(input value, output sample); -endinterface diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index d205e357e..9fd43c39b 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -270,14 +270,12 @@ 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_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 diff --git a/ivtest/vvp_tests/sv_interface_port_array_basic.json b/ivtest/vvp_tests/sv_interface_port_array_basic.json index 72ff9a89f..e6cc98c0c 100644 --- a/ivtest/vvp_tests/sv_interface_port_array_basic.json +++ b/ivtest/vvp_tests/sv_interface_port_array_basic.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_array_basic.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json b/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json index 01f2e561c..262a5e80a 100644 --- a/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_array_modport_restrict_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_array_modport_restrict_fail.v", "gold" : "sv_interface_port_array_modport_restrict_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json b/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json index 1876f1bd4..2e9c815f8 100644 --- a/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_array_size_mismatch_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_array_size_mismatch_fail.v", "gold" : "sv_interface_port_array_size_mismatch_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_basic.json b/ivtest/vvp_tests/sv_interface_port_basic.json index 33eef112d..2c45baa13 100644 --- a/ivtest/vvp_tests/sv_interface_port_basic.json +++ b/ivtest/vvp_tests/sv_interface_port_basic.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_basic.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_forwarding.json b/ivtest/vvp_tests/sv_interface_port_forwarding.json index 5983d5d82..5642f3d7e 100644 --- a/ivtest/vvp_tests/sv_interface_port_forwarding.json +++ b/ivtest/vvp_tests/sv_interface_port_forwarding.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_forwarding.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json b/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json index 4e26953b8..96d4040ef 100644 --- a/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_forwarding_restrict_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_forwarding_restrict_fail.v", "gold" : "sv_interface_port_forwarding_restrict_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_indexed_actual.json b/ivtest/vvp_tests/sv_interface_port_indexed_actual.json index 0d0064b63..8ea7b2d4f 100644 --- a/ivtest/vvp_tests/sv_interface_port_indexed_actual.json +++ b/ivtest/vvp_tests/sv_interface_port_indexed_actual.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_indexed_actual.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json b/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json index f8532856c..7fb5875fa 100644 --- a/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json +++ b/ivtest/vvp_tests/sv_interface_port_indexed_actual_generate.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_indexed_actual_generate.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json b/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json index a86412965..86f475edc 100644 --- a/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_missing_modport_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_missing_modport_fail.v", "gold" : "sv_interface_port_missing_modport_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json b/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json deleted file mode 100644 index f43ed1e1c..000000000 --- a/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type" : "CE", - "source" : "sv_interface_port_missing_type_fail.v", - "gold" : "sv_interface_port_missing_type_fail", - "iverilog-args" : [ "-g2012" ] -} diff --git a/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json b/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json index 10d17db33..401281de9 100644 --- a/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_modport_input_write_fail.v", "gold" : "sv_interface_port_modport_input_write_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json b/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json index 786b3e11d..0fbfdea28 100644 --- a/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_non_interface_actual_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_non_interface_actual_fail.v", "gold" : "sv_interface_port_non_interface_actual_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json b/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json index 56b84fc13..89a8d8039 100644 --- a/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json +++ b/ivtest/vvp_tests/sv_interface_port_plain_ansi_regression.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_plain_ansi_regression.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_positional.json b/ivtest/vvp_tests/sv_interface_port_positional.json index 66f253f1d..bd2e7ad3a 100644 --- a/ivtest/vvp_tests/sv_interface_port_positional.json +++ b/ivtest/vvp_tests/sv_interface_port_positional.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_positional.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json b/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json index 07880369e..6403f0a87 100644 --- a/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_positional_unconnected_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_positional_unconnected_fail.v", "gold" : "sv_interface_port_positional_unconnected_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json b/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json index 7b7d719a6..ea864c616 100644 --- a/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json +++ b/ivtest/vvp_tests/sv_interface_port_typedef_ansi_regression.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_typedef_ansi_regression.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json b/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json index 802f08a7e..880858381 100644 --- a/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_unlisted_member_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_unlisted_member_fail.v", "gold" : "sv_interface_port_unlisted_member_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json b/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json index 01423ed0a..97611b589 100644 --- a/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json +++ b/ivtest/vvp_tests/sv_interface_port_unmodported_basic.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_unmodported_basic.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json b/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json deleted file mode 100644 index d00e41cec..000000000 --- a/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type" : "CE", - "source" : "sv_interface_port_unmodported_missing_type_fail.v", - "gold" : "sv_interface_port_unmodported_missing_type_fail", - "iverilog-args" : [ "-g2012" ] -} diff --git a/ivtest/vvp_tests/sv_interface_port_wildcard.json b/ivtest/vvp_tests/sv_interface_port_wildcard.json index d34e83b0e..11daa24f7 100644 --- a/ivtest/vvp_tests/sv_interface_port_wildcard.json +++ b/ivtest/vvp_tests/sv_interface_port_wildcard.json @@ -1,5 +1,5 @@ { "type" : "normal", "source" : "sv_interface_port_wildcard.v", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json b/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json index d0d34c17b..6c3f2096c 100644 --- a/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json +++ b/ivtest/vvp_tests/sv_interface_port_wrong_type_fail.json @@ -2,5 +2,5 @@ "type" : "CE", "source" : "sv_interface_port_wrong_type_fail.v", "gold" : "sv_interface_port_wrong_type_fail", - "iverilog-args" : [ "-g2012" ] + "iverilog-args" : [ "-g2005-sv" ] } diff --git a/lexor.lex b/lexor.lex index f48149b94..97e07560c 100644 --- a/lexor.lex +++ b/lexor.lex @@ -417,6 +417,13 @@ TU [munpf] } } + /* If this identifier names a previously declared interface, then + return this as an INTERFACE_IDENTIFIER instead. */ + if (rc == IDENTIFIER && gn_system_verilog()) { + if (pform_test_interface_identifier(yylval.text)) + rc = INTERFACE_IDENTIFIER; + } + /* If this identifier names a previously declared type, then return this as a TYPE_IDENTIFIER instead. */ if (rc == IDENTIFIER && gn_system_verilog()) { @@ -442,6 +449,10 @@ TU [munpf] return PACKAGE_IDENTIFIER; } } + if (gn_system_verilog()) { + if (pform_test_interface_identifier(yylval.text)) + return INTERFACE_IDENTIFIER; + } if (gn_system_verilog()) { if (typedef_t*type = pform_test_type_identifier(yylloc, yylval.text)) { yylval.type_identifier.text = yylval.text; diff --git a/net_scope.cc b/net_scope.cc index 3c9c5cd3d..ec9d6f912 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -845,13 +845,11 @@ const NetScope::interface_port_alias_t* NetScope::find_interface_port_alias_element(perm_string formal_name, long index) const { - map >::const_iterator arr; - arr = interface_port_alias_arrays_.find(formal_name); + auto arr = interface_port_alias_arrays_.find(formal_name); if (arr == interface_port_alias_arrays_.end()) return 0; - map::const_iterator cur; - cur = arr->second.find(index); + auto cur = arr->second.find(index); if (cur == arr->second.end()) return 0; @@ -861,8 +859,7 @@ NetScope::find_interface_port_alias_element(perm_string formal_name, const map* NetScope::find_interface_port_alias_array(perm_string formal_name) const { - map >::const_iterator cur; - cur = interface_port_alias_arrays_.find(formal_name); + auto cur = interface_port_alias_arrays_.find(formal_name); if (cur == interface_port_alias_arrays_.end()) return 0; diff --git a/netlist.h b/netlist.h index b3baec96a..cfaeba861 100644 --- a/netlist.h +++ b/netlist.h @@ -1045,7 +1045,7 @@ class NetScope : public Definitions, public Attrib { 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() : actual_scope(nullptr), modport(nullptr) { } interface_port_alias_t(NetScope*actual, const PModport*mp) : actual_scope(actual), modport(mp) { } diff --git a/parse.y b/parse.y index 96830851a..792454140 100644 --- a/parse.y +++ b/parse.y @@ -606,7 +606,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, enum typedef_t::basic_type typedef_basic_type; }; -%token IDENTIFIER SYSTEM_IDENTIFIER STRING TIME_LITERAL +%token IDENTIFIER INTERFACE_IDENTIFIER SYSTEM_IDENTIFIER STRING TIME_LITERAL %token TYPE_IDENTIFIER %token PACKAGE_IDENTIFIER %token DISCIPLINE_IDENTIFIER @@ -4658,10 +4658,10 @@ 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 dimensions_opt + | attribute_list_opt INTERFACE_IDENTIFIER '.' IDENTIFIER IDENTIFIER dimensions_opt { $$ = module_declare_interface_port(@5, $2, $4, $5, $6, $1); } - | attribute_list_opt IDENTIFIER IDENTIFIER dimensions_opt + | attribute_list_opt INTERFACE_IDENTIFIER IDENTIFIER dimensions_opt { $$ = module_declare_interface_port(@3, $2, 0, $3, $4, $1); } | attribute_list_opt net_type_or_var data_type_or_implicit IDENTIFIER dimensions_opt initializer_opt @@ -5207,6 +5207,13 @@ module_item delete[]$2; } + | attribute_list_opt + INTERFACE_IDENTIFIER parameter_value_opt gate_instance_list ';' + { perm_string tmp1 = lex_strings.make($2); + pform_make_modgates(@2, tmp1, $3, $4, $1); + delete[]$2; + } + | attribute_list_opt IDENTIFIER parameter_value_opt error ';' { yyerror(@2, "error: Invalid module instantiation"); @@ -5214,6 +5221,13 @@ module_item if ($1) delete $1; } + | attribute_list_opt + INTERFACE_IDENTIFIER parameter_value_opt error ';' + { yyerror(@2, "error: Invalid module instantiation"); + delete[]$2; + if ($1) delete $1; + } + /* Continuous assignment can have an optional drive strength, then an optional delay3 that applies to all the assignments in the cont_assign_list. */ @@ -5757,14 +5771,6 @@ port : port_reference { $$ = $1; } - | IDENTIFIER '.' IDENTIFIER IDENTIFIER dimensions_opt - { $$ = module_declare_interface_port(@4, $1, $3, $4, $5, 0); - } - - | IDENTIFIER IDENTIFIER dimensions_opt - { $$ = module_declare_interface_port(@2, $1, 0, $2, $3, 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 diff --git a/parse_misc.h b/parse_misc.h index 23547e85c..b669a9af0 100644 --- a/parse_misc.h +++ b/parse_misc.h @@ -92,6 +92,11 @@ extern void lex_in_package_scope(PPackage*pkg); extern typedef_t* pform_test_type_identifier(const YYLTYPE&loc, const char*txt); extern typedef_t* pform_test_type_identifier(PPackage*pkg, const char*txt); +/* + * Test if this identifier is a previously declared interface name. + */ +extern bool pform_test_interface_identifier(const char*txt); + /* * Test if this identifier is a package name. The pform needs to help * the lexor here because the parser detects packages and saves them. diff --git a/pform.cc b/pform.cc index e001914b7..0ef02563f 100644 --- a/pform.cc +++ b/pform.cc @@ -926,6 +926,14 @@ typedef_t* pform_test_type_identifier(const struct vlltype&loc, const char*txt) return 0; } +bool pform_test_interface_identifier(const char*txt) +{ + perm_string name = lex_strings.make(txt); + map::const_iterator cur = pform_modules.find(name); + + return cur != pform_modules.end() && cur->second->is_interface; +} + PECallFunction* pform_make_call_function(const struct vlltype&loc, const pform_name_t&name, const list &parms) diff --git a/symbol_search.cc b/symbol_search.cc index d05aa8ce9..3aaf06e1b 100644 --- a/symbol_search.cc +++ b/symbol_search.cc @@ -331,7 +331,7 @@ bool symbol_search(const LineInfo*li, Design*des, NetScope*scope, return true; } - } else { + } else if (scope->find_interface_port_alias_array(path_tail.name)) { bool flag = false; hname_t path_item = eval_path_component(des, start_scope, path_tail, flag); if (!flag && path_item.has_numbers() == 1) { diff --git a/vvp/Makefile.in b/vvp/Makefile.in index a9315c8b5..c924dda85 100644 --- a/vvp/Makefile.in +++ b/vvp/Makefile.in @@ -135,10 +135,6 @@ 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 From 56afcb6e75ed5b31d59af6f816c65f4e0a08d917 Mon Sep 17 00:00:00 2001 From: Jose Tejada Date: Mon, 11 May 2026 22:25:32 +0200 Subject: [PATCH 06/11] fix(interface): allow forward interface port types --- ...warding_restrict_fail-iverilog-stderr.gold | 4 +- ...ort_missing_type_fail-iverilog-stderr.gold | 2 + ...onal_unconnected_fail-iverilog-stderr.gold | 2 +- ...ted_missing_type_fail-iverilog-stderr.gold | 2 + .../ivltests/sv_interface_port_forwarding.v | 16 ++++---- ..._interface_port_forwarding_restrict_fail.v | 14 +++---- .../sv_interface_port_missing_type_fail.v | 11 ++++++ .../ivltests/sv_interface_port_positional.v | 14 +++---- ...terface_port_positional_unconnected_fail.v | 12 +++--- .../sv_interface_port_unmodported_basic.v | 12 +++--- ...rface_port_unmodported_missing_type_fail.v | 11 ++++++ ivtest/ivltests/sv_interface_port_wildcard.v | 14 +++---- ivtest/regress-vvp.list | 2 + .../sv_interface_port_missing_type_fail.json | 6 +++ ...ce_port_unmodported_missing_type_fail.json | 6 +++ lexor.lex | 38 ++++++++++++++++++- parse.y | 18 +++++---- parse_misc.h | 7 ++++ 18 files changed, 137 insertions(+), 54 deletions(-) create mode 100644 ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold create mode 100644 ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold create mode 100644 ivtest/ivltests/sv_interface_port_missing_type_fail.v create mode 100644 ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v create mode 100644 ivtest/vvp_tests/sv_interface_port_missing_type_fail.json create mode 100644 ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json diff --git a/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold index 05a2a50ec..90758da05 100644 --- a/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold +++ b/ivtest/gold/sv_interface_port_forwarding_restrict_fail-iverilog-stderr.gold @@ -1,3 +1,3 @@ -ivltests/sv_interface_port_forwarding_restrict_fail.v:30: error: Interface member `hidden' is not listed in modport `consumer'. -ivltests/sv_interface_port_forwarding_restrict_fail.v:30: error: Unable to elaborate r-value: bus.hidden +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. diff --git a/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold new file mode 100644 index 000000000..601207b55 --- /dev/null +++ b/ivtest/gold/sv_interface_port_missing_type_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold index 4201c3c5e..8fc0533db 100644 --- a/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold +++ b/ivtest/gold/sv_interface_port_positional_unconnected_fail-iverilog-stderr.gold @@ -1,2 +1,2 @@ -ivltests/sv_interface_port_positional_unconnected_fail.v:13: error: Interface port `bus' of module bus_user is not connected. +ivltests/sv_interface_port_positional_unconnected_fail.v:7: error: Interface port `bus' of module bus_user is not connected. Elaboration failed diff --git a/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold b/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold new file mode 100644 index 000000000..24caf2d27 --- /dev/null +++ b/ivtest/gold/sv_interface_port_unmodported_missing_type_fail-iverilog-stderr.gold @@ -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. diff --git a/ivtest/ivltests/sv_interface_port_forwarding.v b/ivtest/ivltests/sv_interface_port_forwarding.v index 58cd1bc81..9fb52a87b 100644 --- a/ivtest/ivltests/sv_interface_port_forwarding.v +++ b/ivtest/ivltests/sv_interface_port_forwarding.v @@ -4,14 +4,6 @@ // This file is placed into the Public Domain, for any use, without // warranty. -interface bus_if (); - logic value; - logic mirror; - logic hidden; - - modport consumer(input value, output mirror); -endinterface - module test; bus_if bus(); @@ -39,3 +31,11 @@ module child( ); assign bus.mirror = bus.value; endmodule + +interface bus_if (); + logic value; + logic mirror; + logic hidden; + + modport consumer(input value, output mirror); +endinterface diff --git a/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v b/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v index a0fb3ab79..79a1ced9f 100644 --- a/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v +++ b/ivtest/ivltests/sv_interface_port_forwarding_restrict_fail.v @@ -4,13 +4,6 @@ // This file is placed into the Public Domain, for any use, without // warranty. -interface bus_if (); - logic value; - logic hidden; - - modport consumer(input value); -endinterface - module test; bus_if bus(); parent dut(.bus(bus)); @@ -29,3 +22,10 @@ module child( assign sample = bus.hidden; endmodule + +interface bus_if (); + logic value; + logic hidden; + + modport consumer(input value); +endinterface diff --git a/ivtest/ivltests/sv_interface_port_missing_type_fail.v b/ivtest/ivltests/sv_interface_port_missing_type_fail.v new file mode 100644 index 000000000..df1a5a513 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_missing_type_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_positional.v b/ivtest/ivltests/sv_interface_port_positional.v index 8b6d47551..d0cb7d057 100644 --- a/ivtest/ivltests/sv_interface_port_positional.v +++ b/ivtest/ivltests/sv_interface_port_positional.v @@ -3,13 +3,6 @@ // This file is placed into the Public Domain, for any use, without // warranty. -interface bus_if (); - logic value; - logic sample; - - modport consumer(input value, output sample); -endinterface - module test; bus_if bus(); @@ -31,3 +24,10 @@ module bus_user( ); assign bus.sample = bus.value; endmodule + +interface bus_if (); + logic value; + logic sample; + + modport consumer(input value, output sample); +endinterface diff --git a/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v b/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v index 90e17871a..5b9627b52 100644 --- a/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v +++ b/ivtest/ivltests/sv_interface_port_positional_unconnected_fail.v @@ -3,12 +3,6 @@ // This file is placed into the Public Domain, for any use, without // warranty. -interface bus_if (); - logic value; - - modport consumer(input value); -endinterface - module test; bus_user dut(); endmodule @@ -17,3 +11,9 @@ module bus_user( bus_if.consumer bus ); endmodule + +interface bus_if (); + logic value; + + modport consumer(input value); +endinterface diff --git a/ivtest/ivltests/sv_interface_port_unmodported_basic.v b/ivtest/ivltests/sv_interface_port_unmodported_basic.v index aa981cf2f..c8bad7778 100644 --- a/ivtest/ivltests/sv_interface_port_unmodported_basic.v +++ b/ivtest/ivltests/sv_interface_port_unmodported_basic.v @@ -4,12 +4,6 @@ // This file is placed into the Public Domain, for any use, without // warranty. -interface bus_if (); - logic [7:0] lhs; - logic [7:0] rhs; - logic [8:0] sum; -endinterface - module test; logic [7:0] lhs; logic [7:0] rhs; @@ -38,3 +32,9 @@ module add_if( ); assign bus.sum = bus.lhs + bus.rhs; endmodule + +interface bus_if (); + logic [7:0] lhs; + logic [7:0] rhs; + logic [8:0] sum; +endinterface diff --git a/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v b/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v new file mode 100644 index 000000000..b02bb3985 --- /dev/null +++ b/ivtest/ivltests/sv_interface_port_unmodported_missing_type_fail.v @@ -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 diff --git a/ivtest/ivltests/sv_interface_port_wildcard.v b/ivtest/ivltests/sv_interface_port_wildcard.v index 693e702ed..e13630fb6 100644 --- a/ivtest/ivltests/sv_interface_port_wildcard.v +++ b/ivtest/ivltests/sv_interface_port_wildcard.v @@ -3,13 +3,6 @@ // This file is placed into the Public Domain, for any use, without // warranty. -interface bus_if (); - logic value; - logic sample; - - modport consumer(input value, output sample); -endinterface - module test; bus_if bus(); @@ -31,3 +24,10 @@ module bus_user( ); assign bus.sample = bus.value; endmodule + +interface bus_if (); + logic value; + logic sample; + + modport consumer(input value, output sample); +endinterface diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index 9fd43c39b..d205e357e 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -270,12 +270,14 @@ 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_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 diff --git a/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json b/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json new file mode 100644 index 000000000..bc986c918 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_missing_type_fail.json @@ -0,0 +1,6 @@ +{ + "type" : "CE", + "source" : "sv_interface_port_missing_type_fail.v", + "gold" : "sv_interface_port_missing_type_fail", + "iverilog-args" : [ "-g2005-sv" ] +} diff --git a/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json b/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json new file mode 100644 index 000000000..e35cde0c3 --- /dev/null +++ b/ivtest/vvp_tests/sv_interface_port_unmodported_missing_type_fail.json @@ -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" : [ "-g2005-sv" ] +} diff --git a/lexor.lex b/lexor.lex index 97e07560c..0e0c2646c 100644 --- a/lexor.lex +++ b/lexor.lex @@ -120,6 +120,8 @@ static list keyword_mask_stack; static int comment_enter; static bool in_module = false; static bool in_UDP = false; +static bool in_module_port_list = false; +static bool module_port_list_start = false; bool in_celldefine = false; UCDriveType uc_drive = UCD_NONE; static int ts_state = 0; @@ -139,6 +141,12 @@ void lex_in_package_scope(PPackage*pkg) in_package_scope = pkg; } +void lex_in_module_port_list(bool flag) +{ + in_module_port_list = flag; + module_port_list_start = flag; +} + %} %x CCOMMENT @@ -160,6 +168,7 @@ void lex_in_package_scope(PPackage*pkg) %x REAL_SCALE W [ \t\b\f\r]+ +ID [a-zA-Z_][a-zA-Z0-9$_]* S [afpnumkKMGT] @@ -335,7 +344,7 @@ TU [munpf] "z0" { return K_edge_descriptor; } "z1" { return K_edge_descriptor; } -[a-zA-Z_][a-zA-Z0-9$_]* { +{ID} { int rc = lexor_keyword_code(yytext, yyleng); switch (rc) { case IDENTIFIER: @@ -434,6 +443,22 @@ TU [munpf] } } + if (rc == IDENTIFIER && gn_system_verilog() && + in_module_port_list && module_port_list_start) { + char save_ch = *yy_c_buf_p; + *yy_c_buf_p = yy_hold_char; + const char*cp = yy_c_buf_p; + while (*cp == ' ' || *cp == '\t' || *cp == '\b' || + *cp == '\f' || *cp == '\r' || *cp == '\n') + cp += 1; + if (*cp == '.' || isalpha(static_cast(*cp)) || + *cp == '_' || *cp == '\\') + rc = INTERFACE_IDENTIFIER; + *yy_c_buf_p = save_ch; + } + + if (in_module_port_list) + module_port_list_start = false; return rc; } @@ -906,7 +931,16 @@ TU [munpf] `{W} { VLerror(yylloc, "error: Stray tic (`) here. Perhaps you put white " "space between the tic and preprocessor directive?"); } -. { return yytext[0]; } +. { + if (in_module_port_list) { + if (yytext[0] == '(' || yytext[0] == ',') + module_port_list_start = true; + else if (yytext[0] != ')' && yytext[0] != '[' && + yytext[0] != ']' && yytext[0] != ':') + module_port_list_start = false; + } + return yytext[0]; +} /* Final catchall. something got lost or mishandled. */ /* XXX Should we tell the user something about the lexical state? */ diff --git a/parse.y b/parse.y index 792454140..aa6fc0a97 100644 --- a/parse.y +++ b/parse.y @@ -4781,9 +4781,11 @@ module port_declaration_context_init(); } module_package_import_list_opt module_parameter_port_list_opt + { lex_in_module_port_list(true); } module_port_list_opt + { lex_in_module_port_list(false); } module_attribute_foreign ';' - { pform_module_set_ports($8); } + { pform_module_set_ports($9); } timeunits_declaration_opt { pform_set_scope_timescale(@2); } module_item_list_opt @@ -4806,16 +4808,16 @@ module } // Check that program/endprogram and module/endmodule // keywords match. - if ($2 != $15) { + if ($2 != $17) { switch ($2) { case K_module: - yyerror(@15, "error: module not closed by endmodule."); + yyerror(@17, "error: module not closed by endmodule."); break; case K_program: - yyerror(@15, "error: program not closed by endprogram."); + yyerror(@17, "error: program not closed by endprogram."); break; case K_interface: - yyerror(@15, "error: interface not closed by endinterface."); + yyerror(@17, "error: interface not closed by endinterface."); break; default: break; @@ -4831,13 +4833,13 @@ module // module. switch ($2) { case K_module: - check_end_label(@17, "module", $4, $17); + check_end_label(@19, "module", $4, $19); break; case K_program: - check_end_label(@17, "program", $4, $17); + check_end_label(@19, "program", $4, $19); break; case K_interface: - check_end_label(@17, "interface", $4, $17); + check_end_label(@19, "interface", $4, $19); break; default: break; diff --git a/parse_misc.h b/parse_misc.h index b669a9af0..a9eb7d19b 100644 --- a/parse_misc.h +++ b/parse_misc.h @@ -83,6 +83,13 @@ extern UCDriveType uc_drive; */ extern void lex_in_package_scope(PPackage*pkg); +/* + * The parser signals when the lexor is scanning a module/interface/program + * port list so that ambiguous SystemVerilog interface formals can be + * tokenized without depending on declaration order. + */ +extern void lex_in_module_port_list(bool flag); + /* * Test if this identifier is a type identifier in the current * context. The pform code needs to help the lexor here because the From 84dc4ec99fd6883ea4aeaf96d235ec46ca10b704 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Sat, 16 May 2026 15:16:47 -0700 Subject: [PATCH 07/11] Update copyright year in elab_net.cc --- elab_net.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elab_net.cc b/elab_net.cc index dc9dc9782..753ab6842 100644 --- a/elab_net.cc +++ b/elab_net.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999-2025 Stephen Williams (steve@icarus.com) + * Copyright (c) 1999-2026 Stephen Williams (steve@icarus.com) * Copyright CERN 2012 / Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it From 9ff4a42171c246129957c9e31f252b78692135e5 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Sat, 16 May 2026 15:17:52 -0700 Subject: [PATCH 08/11] Update copyright year in Module.cc --- Module.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Module.cc b/Module.cc index 957822743..abbaf6bc8 100644 --- a/Module.cc +++ b/Module.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2022 Stephen Williams (steve@icarus.com) + * Copyright (c) 1998-2026 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU From 73cee3b3e05606d050d3e7d736e1f5c55bbe98fc Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Sat, 16 May 2026 15:18:15 -0700 Subject: [PATCH 09/11] Update copyright year in Module.h --- Module.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Module.h b/Module.h index 62082ab68..cd4fde766 100644 --- a/Module.h +++ b/Module.h @@ -1,7 +1,7 @@ #ifndef IVL_Module_H #define IVL_Module_H /* - * Copyright (c) 1998-2025 Stephen Williams (steve@icarus.com) + * Copyright (c) 1998-2026 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU From 9f3f35e45111403d963a81d976fa2531cf3c96b0 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Sat, 16 May 2026 15:19:02 -0700 Subject: [PATCH 10/11] Update copyright year in netmisc.h --- netmisc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmisc.h b/netmisc.h index ff4aa0e01..d87b5d027 100644 --- a/netmisc.h +++ b/netmisc.h @@ -1,7 +1,7 @@ #ifndef IVL_netmisc_H #define IVL_netmisc_H /* - * Copyright (c) 1999-2025 Stephen Williams (steve@icarus.com) + * Copyright (c) 1999-2026 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU From b605f42a1e9c814e57a152952e4d12ad696a8bb5 Mon Sep 17 00:00:00 2001 From: "Cary R." Date: Sat, 16 May 2026 15:19:40 -0700 Subject: [PATCH 11/11] Update copyright year in parse_misc.h --- parse_misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse_misc.h b/parse_misc.h index a9eb7d19b..62adbe9e6 100644 --- a/parse_misc.h +++ b/parse_misc.h @@ -1,7 +1,7 @@ #ifndef IVL_parse_misc_H #define IVL_parse_misc_H /* - * Copyright (c) 1998-2024 Stephen Williams (steve@icarus.com) + * Copyright (c) 1998-2026 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU