Report UDP declaration errors instead of asserting

Malformed UDP declarations can reach `pform_make_udp()` with conflicting
duplicate port declarations or with an initial value on an output that was not
declared as a register. These cases currently trigger internal assertions
instead of reporting normal compile errors.

The ANSI-style UDP output initializer path also treats the initializer
expression as if it was the old-style `initial out = value` assignment
statement. This makes a valid `output reg out = 1'b0` initializer assert as
well.

Report errors for the invalid declarations and read the ANSI-style initializer
value directly from the initializer expression.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This commit is contained in:
Lars-Peter Clausen 2026-07-04 12:44:52 -07:00
parent 025cbcc81f
commit 8d4faf2744
1 changed files with 91 additions and 24 deletions

115
pform.cc
View File

@ -1947,11 +1947,25 @@ void pform_make_udp(const struct vlltype&loc, perm_string name,
ivl_assert(loc, (*decl)[idx]); ivl_assert(loc, (*decl)[idx]);
if ((*decl)[idx]->get_port_type() != NetNet::PIMPLICIT) { if ((*decl)[idx]->get_port_type() != NetNet::PIMPLICIT) {
bool rc = cur->set_port_type((*decl)[idx]->get_port_type()); bool rc = cur->set_port_type((*decl)[idx]->get_port_type());
ivl_assert(loc, rc); if (!rc) {
cerr << loc << ": error: "
<< "Port " << port_name << " of primitive "
<< name << " has conflicting port declarations."
<< endl;
error_count += 1;
local_errors += 1;
}
} }
if ((*decl)[idx]->get_wire_type() != NetNet::IMPLICIT) { if ((*decl)[idx]->get_wire_type() != NetNet::IMPLICIT) {
bool rc = cur->set_wire_type((*decl)[idx]->get_wire_type()); bool rc = cur->set_wire_type((*decl)[idx]->get_wire_type());
ivl_assert(loc, rc); if (!rc) {
cerr << loc << ": error: "
<< "Port " << port_name << " of primitive "
<< name << " has conflicting data declarations."
<< endl;
error_count += 1;
local_errors += 1;
}
} }
} else { } else {
@ -1959,6 +1973,14 @@ void pform_make_udp(const struct vlltype&loc, perm_string name,
} }
} }
if (local_errors > 0) {
delete parms;
delete decl;
delete table;
delete init_expr;
return;
}
/* Put the parameters into a vector of wire descriptions. Look /* Put the parameters into a vector of wire descriptions. Look
in the map for the definitions of the name. In this loop, in the map for the definitions of the name. In this loop,
@ -2070,22 +2092,56 @@ void pform_make_udp(const struct vlltype&loc, perm_string name,
registered. Then save the initial value that I get. */ registered. Then save the initial value that I get. */
verinum::V init = verinum::Vx; verinum::V init = verinum::Vx;
if (init_expr) { if (init_expr) {
// XXXX if (pins[0]->get_wire_type() != NetNet::REG) {
ivl_assert(loc, pins[0]->get_wire_type() == NetNet::REG); cerr << init_expr->get_fileline() << ": error: "
<< "Initial value for primitive " << name
<< " requires a registered output port." << endl;
error_count += 1;
local_errors += 1;
}
const PAssign*pa = dynamic_cast<PAssign*>(init_expr); auto*pa = dynamic_cast<PAssign*>(init_expr);
ivl_assert(*init_expr, pa); if (!pa) {
cerr << init_expr->get_fileline() << ": error: "
<< "Invalid initial statement for primitive "
<< name << "." << endl;
error_count += 1;
local_errors += 1;
}
const PEIdent*id = dynamic_cast<const PEIdent*>(pa->lval()); if (pa) {
ivl_assert(*init_expr, id); auto*id = dynamic_cast<const PEIdent*>(pa->lval());
if (!id) {
cerr << init_expr->get_fileline() << ": error: "
<< "Invalid initial statement for primitive "
<< name << "." << endl;
error_count += 1;
local_errors += 1;
}
// XXXX // XXXX
//ivl_assert(*init_expr, id->name() == pins[0]->name()); //ivl_assert(*init_expr, id->name() == pins[0]->name());
const PENumber*np = dynamic_cast<const PENumber*>(pa->rval()); auto*np = dynamic_cast<const PENumber*>(pa->rval());
ivl_assert(*init_expr, np); if (!np) {
cerr << init_expr->get_fileline() << ": error: "
<< "Invalid initial value for primitive "
<< name << "." << endl;
error_count += 1;
local_errors += 1;
} else {
init = np->value()[0];
}
}
if (local_errors > 0) {
delete parms;
delete decl;
delete table;
delete init_expr;
return;
}
init = np->value()[0];
} }
// Put the primitive into the primitives table // Put the primitive into the primitives table
@ -2159,20 +2215,31 @@ void pform_make_udp(const struct vlltype&loc, perm_string name,
registered. Then save the initial value that I get. */ registered. Then save the initial value that I get. */
verinum::V init = verinum::Vx; verinum::V init = verinum::Vx;
if (init_expr) { if (init_expr) {
// XXXX bool local_errors = false;
ivl_assert(*init_expr, pins[0]->get_wire_type() == NetNet::REG);
const PAssign*pa = dynamic_cast<PAssign*>(init_expr); if (pins[0]->get_wire_type() != NetNet::REG) {
ivl_assert(*init_expr, pa); cerr << init_expr->get_fileline() << ": error: "
<< "Initial value for primitive " << name
<< " requires a registered output port." << endl;
error_count += 1;
local_errors = true;
}
const PEIdent*id = dynamic_cast<const PEIdent*>(pa->lval()); auto*np = dynamic_cast<const PENumber*>(init_expr);
ivl_assert(*init_expr, id); if (!np) {
cerr << init_expr->get_fileline() << ": error: "
<< "Invalid initial value for primitive "
<< name << "." << endl;
error_count += 1;
local_errors = true;
}
// XXXX if (local_errors) {
//ivl_assert(*init_expr, id->name() == pins[0]->name()); delete parms;
delete table;
const PENumber*np = dynamic_cast<const PENumber*>(pa->rval()); delete init_expr;
ivl_assert(*init_expr, np); return;
}
init = np->value()[0]; init = np->value()[0];
} }