Remove special interface identifier token

The lexer currently returns `INTERFACE_IDENTIFIER` for names of interfaces
that have already been parsed. There are two issues with this approach:

1. Once an interface has been parsed, its name can no longer be used in
   grammar positions that accept an ordinary identifier.
2. The LRM allows an interface to be used before its declaration. Before the
   declaration has been parsed, however, the name remains an ordinary
   identifier and can not be accepted by productions requiring
   `INTERFACE_IDENTIFIER`. Lexer lookahead tries to recognize forward
   interface port types, but this depends on parser-managed port-list state
   and does not cover all cases.

Parse interface port declarations from ordinary `IDENTIFIER` tokens instead.
An interface port can not simply be added to `port_declaration` using an
`IDENTIFIER` token. This creates a shift/reduce conflict on the identifier at
the start of the port list. Shifting starts an old-style `port_reference`,
while reducing the empty `attribute_list_opt` starts an interface
`port_declaration`. The parser has not yet seen the following identifier or
`.` that distinguishes the two forms.

Add leading interface port declarations as base cases of
`list_of_port_declarations`. Both the old-style and ANSI port-list rules can
then shift the common identifier and use the following identifier or `.` to
distinguish the interface port. Keep a separate base case for a non-empty
attribute list so no empty reduction is needed before the identifier. Handle
later interface ports in the recursive list rule and use
`interface_port_modport_opt` to share the forms with and without a modport.
Interface instances can use the existing module instantiation rules.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This commit is contained in:
Lars-Peter Clausen 2026-07-12 12:07:25 -07:00
parent 35a809d55b
commit 60f366790b
4 changed files with 39 additions and 97 deletions

View File

@ -120,8 +120,6 @@ static list<int> 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;
@ -141,12 +139,6 @@ 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
@ -426,13 +418,6 @@ 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()) {
@ -443,22 +428,6 @@ 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<unsigned char>(*cp)) ||
*cp == '_' || *cp == '\\')
rc = INTERFACE_IDENTIFIER;
*yy_c_buf_p = save_ch;
}
if (in_module_port_list)
module_port_list_start = false;
return rc;
}
@ -474,10 +443,6 @@ 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;
@ -933,16 +898,7 @@ TU [munpf]
`{W} { VLerror(yylloc, "error: Stray tic (`) here. Perhaps you put white "
"space between the tic and preprocessor directive?"); }
. {
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];
}
. { return yytext[0]; }
/* Final catchall. something got lost or mishandled. */
/* XXX Should we tell the user something about the lexical state? */

70
parse.y
View File

@ -761,7 +761,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
enum type_restrict_t::type_t type_restrict;
};
%token <text> IDENTIFIER INTERFACE_IDENTIFIER SYSTEM_IDENTIFIER STRING TIME_LITERAL
%token <text> IDENTIFIER SYSTEM_IDENTIFIER STRING TIME_LITERAL
%token <type_identifier> TYPE_IDENTIFIER
%token <package> PACKAGE_IDENTIFIER
%token <discipline> DISCIPLINE_IDENTIFIER
@ -879,7 +879,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%type <wires> udp_port_decl udp_port_decls
%type <statement> udp_initial udp_init_opt
%type <text> event_variable label_opt
%type <text> event_variable label_opt interface_port_modport_opt
%type <text> block_identifier_opt
%type <text> identifier_name
%type <identifiers> event_variable_list
@ -5000,6 +5000,18 @@ list_of_port_declarations
(*tmp)[0] = $1;
$$ = tmp;
}
// Keep interface ports as list base cases so the leading identifier is
// shifted before choosing between an interface port and an old-style port
// reference. The attributed form is separate because attribute_list_opt
// can be empty.
| IDENTIFIER interface_port_modport_opt identifier_name dimensions_opt
{ Module::port_t*port = module_declare_interface_port(@3, $1, $2, $3, $4, 0);
$$ = new std::vector<Module::port_t*>(1, port);
}
| attribute_instance_list IDENTIFIER interface_port_modport_opt identifier_name dimensions_opt
{ Module::port_t*port = module_declare_interface_port(@4, $2, $3, $4, $5, $1);
$$ = new std::vector<Module::port_t*>(1, port);
}
| list_of_port_declarations ',' port_declaration
{ std::vector<Module::port_t*>*tmp = $1;
tmp->push_back($3);
@ -5044,12 +5056,28 @@ list_of_port_declarations
ports->push_back(port);
$$ = ports;
}
// Once an ANSI port declaration list has been established, an identifier
// can unambiguously start an interface port. Keeping this case out of
// port_declaration avoids ambiguity with an old-style port reference at
// the start of the list.
| list_of_port_declarations ',' attribute_list_opt IDENTIFIER interface_port_modport_opt identifier_name dimensions_opt
{ std::vector<Module::port_t*> *ports = $1;
ports->push_back(module_declare_interface_port(@6, $4, $5, $6, $7, $3));
$$ = ports;
}
| list_of_port_declarations ','
{ yyerror(@2, "error: Superfluous comma in port declaration list."); }
| list_of_port_declarations ';'
{ yyerror(@2, "error: ';' is an invalid port declaration separator."); }
;
interface_port_modport_opt
: '.' identifier_name
{ $$ = $2; }
|
{ $$ = 0; }
;
// All of port direction, port kind and data type are optional, but at least
// one has to be specified, so we need multiple rules.
port_declaration
@ -5057,12 +5085,6 @@ port_declaration
{ $$ = module_declare_port(@4, $4.id, $4.lexical_pos,
$2, $3, $4.type, $4.ranges, $5, $1);
}
| attribute_list_opt INTERFACE_IDENTIFIER '.' identifier_name identifier_name dimensions_opt
{ $$ = module_declare_interface_port(@5, $2, $4, $5, $6, $1);
}
| attribute_list_opt INTERFACE_IDENTIFIER identifier_name dimensions_opt
{ $$ = module_declare_interface_port(@3, $2, 0, $3, $4, $1);
}
| attribute_list_opt net_type_or_var data_type_or_implicit_plus_id_dim initializer_opt
{ pform_requires_sv(@3, "Partial ANSI port declaration");
$$ = module_declare_port(@3, $3.id, $3.lexical_pos,
@ -5182,11 +5204,9 @@ 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($9); }
{ pform_module_set_ports($8); }
timeunits_declaration_opt
{ pform_set_scope_timescale(@2); }
module_item_list_opt
@ -5209,16 +5229,16 @@ module
}
// Check that program/endprogram and module/endmodule
// keywords match.
if ($2 != $17) {
if ($2 != $15) {
switch ($2) {
case K_module:
yyerror(@17, "error: module not closed by endmodule.");
yyerror(@15, "error: module not closed by endmodule.");
break;
case K_program:
yyerror(@17, "error: program not closed by endprogram.");
yyerror(@15, "error: program not closed by endprogram.");
break;
case K_interface:
yyerror(@17, "error: interface not closed by endinterface.");
yyerror(@15, "error: interface not closed by endinterface.");
break;
default:
break;
@ -5234,13 +5254,13 @@ module
// module.
switch ($2) {
case K_module:
check_end_label(@19, "module", $4, $19);
check_end_label(@17, "module", $4, $17);
break;
case K_program:
check_end_label(@19, "program", $4, $19);
check_end_label(@17, "program", $4, $17);
break;
case K_interface:
check_end_label(@19, "interface", $4, $19);
check_end_label(@17, "interface", $4, $17);
break;
default:
break;
@ -5583,13 +5603,6 @@ 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");
@ -5597,13 +5610,6 @@ 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. */

View File

@ -83,13 +83,6 @@ 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
@ -99,11 +92,6 @@ extern void lex_in_module_port_list(bool flag);
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.

View File

@ -955,14 +955,6 @@ 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<perm_string,Module*>::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<named_pexpr_t> &parms)