Files
verilator/src/V3LinkLValue.cpp
T
Geza Lore 02546791da Internals: Remove V3LinkDot::linkDotArrayed (#8495)
Follow up to #8476 and #8493, now that elaboration fully expands
instance arrays, linkDotArrayed's only remaining purpose was to name
AstBegin blocks created after V3Width.

Fix up earlier passes to name all Begin blocks they create, or avoid
creating AstBegin in the first place if possible.

One warning about assigning to a modport input of a generic interface
port had to move into LinkLValue (previously this only used to be
reported by linkDotArrayed, as the type/modportness only resolved in
V3Param, and the lvalueness is subsequently only known in LinkLValue).

Then remove linkDotArrayed and simplify V3LinkDot.cpp
2026-09-24 22:41:24 +01:00

400 lines
16 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: LValue module/signal name references
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// LinkLValue TRANSFORMATIONS:
// Top-down traversal
// Set lvalue() attributes on appropriate VARREFs.
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3LinkLValue.h"
#include "V3Task.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Link state, as a visitor of each AstNode
class LinkLValueVisitor final : public VNVisitor {
// NODE STATE
// STATE - for current visit position (use VL_RESTORER)
bool m_setContinuously = false; // Set that var has some continuous assignment
bool m_setForcedByCode = false; // Set that var is the target of an AstAssignForce/AstRelease
bool m_setIfRand = false; // Update VarRefs if var declared as rand
bool m_setStrengthSpecified = false; // Set that var has assignment with strength specified.
bool m_inFunc = false; // Set if inside AstNodeFTask
bool m_inInitialStatic = false; // Set if inside AstInitialStatic
bool m_inInitialStaticStmt = false; // Set if inside AstInitialStaticStmt
VAccess m_setRefLvalue; // Set VarRefs to lvalues for pin assignments
// VISITORS
// Result handing
void visit(AstNodeVarRef* nodep) override {
// VarRef: LValue its reference
if (m_setIfRand && !(nodep->varp() && nodep->varp()->isRand())) return;
if (m_setRefLvalue != VAccess::NOCHANGE) nodep->access(m_setRefLvalue);
// Linked via an input-only modport by V3LinkDot, before it was known if written
if (AstVarXRef* const xrefp = VN_CAST(nodep, VarXRef)) {
if (xrefp->readOnlyModport()) {
if (nodep->access().isWriteOrRW()) {
nodep->v3error(
"Attempt to drive input-only modport: " << nodep->prettyNameQ());
}
xrefp->readOnlyModport(false);
}
}
if (nodep->varp() && nodep->access().isWriteOrRW()) {
if (nodep->varp()->isParam()) {
// All parameters that did get constified happened before now
// as V3LinkLValue runs after V3Param
nodep->v3error("Storing to parameter variable "
<< nodep->prettyNameQ()
<< " in a context that is determined only at runtime");
}
if (m_setContinuously) {
nodep->varp()->isContinuously(true);
// Strength may only be specified in continuous assignment,
// so it is needed to check only if m_setContinuously is true
if (m_setStrengthSpecified) nodep->varp()->hasStrengthAssignment(true);
}
if (const AstClockingItem* const itemp
= VN_CAST(nodep->varp()->backp(), ClockingItem)) {
UINFO(5, "ClkOut " << nodep);
if (itemp->outputp()) nodep->varp(itemp->outputp()->varp());
}
if (m_setForcedByCode) {
nodep->varp()->setForcedByCode();
// If a public signal is being forced in SystemVerilog and VPI
// is enabled, mark it as forceable to ensure that the VPI
// functions read the forced value correctly
if (v3Global.opt.vpi()
&& (nodep->varp()->isSigPublic() || nodep->varp()->isSigModPublic())) {
nodep->varp()->setForceable();
}
} else if (!nodep->varp()->isFuncLocal() && nodep->varp()->isReadOnly()) {
// This is allowed with IEEE 1800-2009 module input with default value.
// the checking now happens in V3Width::visit(AstNodeVarRef*)
// If you were to check here, it would fail on module inputs with default value,
// because Inputs are isReadOnly()=true, and we don't yet have visibility into
// it being an Initial style procedure.
}
}
iterateChildren(nodep);
}
// Nodes that start propagating down lvalues
void visit(AstPin* nodep) override {
VL_RESTORER(m_setRefLvalue);
if (nodep->modVarp() && nodep->modVarp()->isWritable()) {
// When the varref's were created, we didn't know the I/O state
// Now that we do, and it's from a output, we know it's a lvalue
m_setRefLvalue = VAccess::WRITE;
iterateChildren(nodep);
m_setRefLvalue = VAccess::NOCHANGE;
} else {
iterateChildren(nodep);
}
}
void visit(AstNodeAssign* nodep) override {
VL_RESTORER(m_setRefLvalue);
VL_RESTORER(m_setContinuously);
VL_RESTORER(m_setStrengthSpecified);
{
m_setRefLvalue = VAccess::WRITE;
m_setContinuously = VN_IS(nodep, AssignW);
if (const AstAssignW* const assignwp = VN_CAST(nodep, AssignW)) {
if (assignwp->strengthSpecp()) m_setStrengthSpecified = true;
}
{
VL_RESTORER(m_setForcedByCode);
m_setForcedByCode = VN_IS(nodep, AssignForce);
iterateAndNextNull(nodep->lhsp());
}
m_setRefLvalue = VAccess::NOCHANGE;
m_setContinuously = false;
m_setStrengthSpecified = false;
iterateAndNextNull(nodep->rhsp());
}
if ((m_inInitialStatic || m_inInitialStaticStmt) && m_inFunc) {
const bool rhsHasIO = nodep->rhsp()->exists([](const AstNodeVarRef* const refp) {
// Exclude module I/O referenced from a function/task.
return refp->varp() && refp->varp()->isIO()
&& refp->varp()->lifetime() != VLifetime::NONE;
});
if (rhsHasIO) {
nodep->rhsp()->v3warn(E_UNSUPPORTED,
"Static variable initializer\n"
<< nodep->rhsp()->warnMore()
<< "is dependent on function/task I/O variable");
} else {
const bool rhsHasAutomatic
= nodep->rhsp()->exists([](const AstNodeVarRef* const refp) {
return refp->varp() && refp->varp()->lifetime().isAutomatic();
});
if (rhsHasAutomatic) {
nodep->rhsp()->v3error("Static variable initializer\n"
<< nodep->rhsp()->warnMore()
<< "is dependent on automatic variable");
}
}
}
}
void visit(AstAlias* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::READWRITE;
iterateChildren(nodep);
}
void visit(AstInitialStatic* nodep) override {
VL_RESTORER(m_inInitialStatic);
m_inInitialStatic = true;
iterateChildren(nodep);
}
void visit(AstInitialStaticStmt* nodep) override {
VL_RESTORER(m_inInitialStaticStmt);
m_inInitialStaticStmt = true;
iterateChildren(nodep);
}
void visit(AstRelease* nodep) override {
VL_RESTORER(m_setRefLvalue);
VL_RESTORER(m_setContinuously);
VL_RESTORER(m_setForcedByCode);
m_setRefLvalue = VAccess::WRITE;
m_setContinuously = false;
m_setForcedByCode = true;
iterateAndNextNull(nodep->lhsp());
}
void visit(AstDeassign* nodep) override {
VL_RESTORER(m_setRefLvalue);
VL_RESTORER(m_setContinuously);
m_setRefLvalue = VAccess::WRITE;
m_setContinuously = false;
iterateAndNextNull(nodep->lhsp());
}
void visit(AstFireEvent* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->operandp());
}
void visit(AstCastDynamic* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->fromp());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->top());
}
void visit(AstFError* nodep) override {
VL_RESTORER(m_setRefLvalue);
iterateAndNextNull(nodep->filep());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->strp());
}
void visit(AstFGetS* nodep) override {
VL_RESTORER(m_setRefLvalue);
iterateAndNextNull(nodep->filep());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->strgp());
}
void visit(AstFRead* nodep) override {
VL_RESTORER(m_setRefLvalue);
iterateAndNextNull(nodep->filep());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->memp());
}
void visit(AstFScanF* nodep) override {
VL_RESTORER(m_setRefLvalue);
iterateAndNextNull(nodep->filep());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->exprsp());
}
void visit(AstFUngetC* nodep) override {
VL_RESTORER(m_setRefLvalue);
iterateAndNextNull(nodep->filep());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->rhsp());
}
void visit(AstSScanF* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->exprsp());
}
void visit(AstSysIgnore* nodep) override {
// Can't know if lvalue or not; presume not
iterateChildren(nodep);
}
void visit(AstRand* nodep) override {
VL_RESTORER(m_setRefLvalue);
if (!nodep->urandom()) m_setRefLvalue = VAccess::READWRITE;
iterateAndNextNull(nodep->seedp());
}
void visit(AstReadMem* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->memp());
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->filenamep());
iterateAndNextNull(nodep->lsbp());
iterateAndNextNull(nodep->msbp());
}
void visit(AstTestPlusArgs* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->searchp());
}
void visit(AstValuePlusArgs* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->searchp());
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->outp());
}
void visit(AstSFormat* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->lhsp());
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->fmtp());
}
void visit(AstNodeDistBiop* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::READWRITE;
iterateAndNextNull(nodep->lhsp());
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->rhsp());
}
void visit(AstNodeDistTriop* nodep) override {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::READWRITE;
iterateAndNextNull(nodep->lhsp());
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->rhsp());
iterateAndNextNull(nodep->thsp());
}
// cppcheck-suppress constParameterPointer
void prepost_visit(AstNodeUniop* nodep) {
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = VAccess::WRITE;
iterateAndNextNull(nodep->lhsp());
}
void visit(AstPreInc* nodep) override { prepost_visit(nodep); }
void visit(AstPostInc* nodep) override { prepost_visit(nodep); }
void visit(AstPreDec* nodep) override { prepost_visit(nodep); }
void visit(AstPostDec* nodep) override { prepost_visit(nodep); }
// Nodes that change LValue state
void visit(AstSel* nodep) override {
VL_RESTORER(m_setRefLvalue);
iterateAndNextNull(nodep->fromp());
// Only set lvalues on the from
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->lsbp());
}
void visit(AstNodeSel* nodep) override {
VL_RESTORER(m_setRefLvalue);
// Only set lvalues on the from
iterateAndNextNull(nodep->fromp());
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->bitp());
}
void visit(AstCellArrayRef* nodep) override {
VL_RESTORER(m_setRefLvalue);
// selp is not an lvalue
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->selp());
}
void visit(AstNodePreSel* nodep) override {
if (AstSelBit* const selbitp = VN_CAST(nodep, SelBit)) selbitp->access(m_setRefLvalue);
VL_RESTORER(m_setRefLvalue);
// Only set lvalues on the from
iterateAndNextNull(nodep->fromp());
m_setRefLvalue = VAccess::NOCHANGE;
iterateAndNextNull(nodep->rhsp());
iterateAndNextNull(nodep->thsp());
}
void visit(AstMemberSel* nodep) override {
if (m_setRefLvalue != VAccess::NOCHANGE) {
nodep->access(m_setRefLvalue);
if (nodep->varp() && nodep->access().isWriteOrRW()) {
if (const AstClockingItem* const itemp
= VN_CAST(nodep->varp()->backp(), ClockingItem)) {
UINFO(5, "ClkOut " << nodep);
if (itemp->outputp()) nodep->varp(itemp->outputp()->varp());
}
}
} else {
// It is the only place where the access is set to member select nodes.
// If it doesn't have to be set to WRITE, it means that it is READ.
nodep->access(VAccess::READ);
}
iterateChildren(nodep);
}
void visit(AstNodeFTaskRef* nodep) override {
const AstNodeFTask* const taskp = nodep->taskp();
// We'll deal with mismatching pins later
if (!taskp) return;
const V3TaskConnects tconnects
= V3Task::taskConnects(nodep, taskp->stmtsp(), nullptr, false);
for (const auto& tconnect : tconnects) {
const AstVar* const portp = tconnect.first;
const AstArg* const argp = tconnect.second;
if (!argp) continue;
AstNodeExpr* const pinp = argp->exprp();
if (!pinp) continue;
VL_RESTORER(m_setRefLvalue);
m_setRefLvalue = portp->direction().pinAccess();
iterate(pinp);
}
if (nodep->withp()) iterate(nodep->withp());
}
void visit(AstConstraint* nodep) override {
VL_RESTORER(m_setIfRand);
m_setIfRand = true;
iterateChildren(nodep);
}
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_inFunc);
m_inFunc = true;
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
LinkLValueVisitor(AstNode* nodep, VAccess start)
: m_setRefLvalue{start} {
iterate(nodep);
}
~LinkLValueVisitor() override = default;
};
//######################################################################
// Link class functions
void V3LinkLValue::linkLValue(AstNetlist* nodep) {
UINFO(4, __FUNCTION__ << ": ");
{ LinkLValueVisitor{nodep, VAccess::NOCHANGE}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("linklvalue", 0, dumpTreeEitherLevel() >= 6);
}
void V3LinkLValue::linkLValueSet(AstNode* const nodep, const VAccess access) {
// Called by later link functions when it is known a node needs
// to be converted to a lvalue.
UINFO(9, __FUNCTION__ << ": ");
{ LinkLValueVisitor{nodep, access}; }
}