diff --git a/Module.cc b/Module.cc index 6aefe59ab..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 @@ -21,13 +21,65 @@ # include "Module.h" # include "PGate.h" +# include "PModport.h" # include "PWire.h" +# include "parse_api.h" # include "ivl_assert.h" +# include using namespace std; list Module::user_defparms; +Module::port_t::port_t() +: port_kind(P_SIGNAL), default_value(0), interface_unpacked_dimensions(0), lexical_pos(0) +{ +} + +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) @@ -63,12 +115,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..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 @@ -43,6 +43,7 @@ class PFunction; class PWire; class PProcess; class Design; +class LineInfo; class NetScope; /* @@ -65,9 +66,25 @@ 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; + std::list*interface_unpacked_dimensions; + unsigned lexical_pos; + + bool is_interface_port() const { return port_kind == P_INTERFACE; } }; public: @@ -148,6 +165,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) @@ -181,4 +199,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/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 986ffcf74..604e8d99d 100644 --- a/elab_expr.cc +++ b/elab_expr.cc @@ -4733,6 +4733,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 3cde04797..6e69f398e 100644 --- a/elab_net.cc +++ b/elab_net.cc @@ -563,6 +563,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..1526e1426 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,12 @@ bool Module::elaborate_sig(Design*des, NetScope*scope) const if (pp == 0) continue; + if (pp->is_interface_port()) { + interface_formal_port_t formal; + resolve_interface_formal_port(this, des, pp, formal, true); + 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 +463,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 +484,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 09c9ca2f5..3eee2f890 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" @@ -1248,6 +1249,432 @@ 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 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 || + (rmod->ports[j]->is_interface_port() && + sr.scope != 0 && sr.scope->is_interface())) { + 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 false; + } + + std::copy(get_pins().begin(), get_pins().end(), pins.begin()); + } + + return true; +} + +struct interface_actual_scope_t { + interface_actual_scope_t() : scope(nullptr), modport(nullptr) { } + + NetScope*scope; + const PModport*modport; + 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, + 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.size() > 1) { + return false; + } + + 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(), + 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; +} + +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()) { + 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]; + 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, nullptr); + } + 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, + const vector&pins, + const vector&) 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[idx]) { + cerr << get_fileline() << ": error: Interface port `" + << port->name << "' of module " << rmod->mod_name() + << " is not connected." << endl; + des->errors += 1; + flag = false; + 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 != nullptr; + + 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 (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()) { + 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 `" + << port->name << "' must be connected to a simple " + "interface instance name." << endl; + des->errors += 1; + flag = false; + continue; + } + + 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() != formal.module->mod_name()) { + cerr << pins[idx]->get_fileline() << ": error: Interface port `" + << port->name << "' expects interface type `" + << port->interface_type << "' but actual `" << actual.display_name + << "' has type `" << actual.scope->module_name() << "'." << endl; + des->errors += 1; + flag = false; + continue; + } + + 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.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, + 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 +1701,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 +1736,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_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/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_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_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_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_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/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_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_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_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_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/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_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_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_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/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 227fec970..3f78202bb 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -273,6 +273,27 @@ 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_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_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 sv_lval_concat_class_fail1 vvp_tests/sv_lval_concat_class_fail1.json sv_lval_concat_class_fail2 vvp_tests/sv_lval_concat_class_fail2.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..e6cc98c0c --- /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" : [ "-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 new file mode 100644 index 000000000..262a5e80a --- /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" : [ "-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 new file mode 100644 index 000000000..2e9c815f8 --- /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" : [ "-g2005-sv" ] +} 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..2c45baa13 --- /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" : [ "-g2005-sv" ] +} 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..5642f3d7e --- /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" : [ "-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 new file mode 100644 index 000000000..96d4040ef --- /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" : [ "-g2005-sv" ] +} 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..8ea7b2d4f --- /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" : [ "-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 new file mode 100644 index 000000000..7fb5875fa --- /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" : [ "-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 new file mode 100644 index 000000000..86f475edc --- /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" : [ "-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 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_modport_input_write_fail.json b/ivtest/vvp_tests/sv_interface_port_modport_input_write_fail.json new file mode 100644 index 000000000..401281de9 --- /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" : [ "-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 new file mode 100644 index 000000000..0fbfdea28 --- /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" : [ "-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 new file mode 100644 index 000000000..89a8d8039 --- /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" : [ "-g2005-sv" ] +} 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..bd2e7ad3a --- /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" : [ "-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 new file mode 100644 index 000000000..6403f0a87 --- /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" : [ "-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 new file mode 100644 index 000000000..ea864c616 --- /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" : [ "-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 new file mode 100644 index 000000000..880858381 --- /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" : [ "-g2005-sv" ] +} 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..97611b589 --- /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" : [ "-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/ivtest/vvp_tests/sv_interface_port_wildcard.json b/ivtest/vvp_tests/sv_interface_port_wildcard.json new file mode 100644 index 000000000..11daa24f7 --- /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" : [ "-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 new file mode 100644 index 000000000..6c3f2096c --- /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" : [ "-g2005-sv" ] +} diff --git a/lexor.lex b/lexor.lex index f48149b94..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: @@ -417,6 +426,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()) { @@ -427,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; } @@ -442,6 +474,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; @@ -895,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/net_scope.cc b/net_scope.cc index d90b49b82..ec9d6f912 100644 --- a/net_scope.cc +++ b/net_scope.cc @@ -812,6 +812,60 @@ 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; +} + +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 +{ + auto arr = interface_port_alias_arrays_.find(formal_name); + if (arr == interface_port_alias_arrays_.end()) + return 0; + + auto 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 +{ + auto 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 @@ -867,6 +921,10 @@ bool NetScope::symbol_exists(perm_string sym) return true; if (find_event(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 d1eb80764..cfaeba861 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,31 @@ 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(nullptr), modport(nullptr) { } + 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; + 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; @@ -1347,6 +1373,8 @@ class NetScope : public Definitions, public Attrib { NetScope*unit_; 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/netmisc.h b/netmisc.h index be460ad04..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 @@ -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 44d1101d9..45dcadb37 100644 --- a/parse.y +++ b/parse.y @@ -461,6 +461,32 @@ 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 *udims, + std::list *attributes) +{ + pform_requires_sv(loc, "Interface port declaration"); + + 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), udims); + + delete[] type; + if (modport) + 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 { @@ -575,7 +601,7 @@ Module::port_t *module_declare_port(const YYLTYPE&loc, char *id, 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 @@ -4598,10 +4624,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; } @@ -4617,6 +4653,12 @@ 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 INTERFACE_IDENTIFIER '.' IDENTIFIER IDENTIFIER dimensions_opt + { $$ = module_declare_interface_port(@5, $2, $4, $5, $6, $1); + } + | 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 { pform_requires_sv(@4, "Partial ANSI port declaration"); $$ = module_declare_port(@4, $4, port_declaration_context.port_type, @@ -4734,9 +4776,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 @@ -4759,16 +4803,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; @@ -4784,13 +4828,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; @@ -5160,6 +5204,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"); @@ -5167,6 +5218,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. */ diff --git a/parse_misc.h b/parse_misc.h index 23547e85c..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 @@ -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 @@ -92,6 +99,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 c7e337b63..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) @@ -1386,6 +1394,35 @@ 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, + list*udims) +{ + 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->interface_unpacked_dimensions = udims; + 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..95d60d458 100644 --- a/pform.h +++ b/pform.h @@ -167,6 +167,15 @@ 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, + std::list*udims = 0); +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 255d608c4..863c9717e 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 @@ -301,6 +321,52 @@ 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; + } + } 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) { + 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. if (prefix_scope) break; @@ -396,3 +462,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; +}