Merge d006744f59 into f232449252
This commit is contained in:
commit
df278b7008
|
|
@ -211,4 +211,13 @@ AstVarXRef::AstVarXRef(FileLine* fl, AstVar* varp, const string& dotted, const V
|
|||
|
||||
AstStmtExpr* AstNodeExpr::makeStmt() { return new AstStmtExpr{fileline(), this}; }
|
||||
|
||||
// Walk up the AST via backp() to find the containing AstNodeModule.
|
||||
// Returns nullptr if not found.
|
||||
inline AstNodeModule* findParentModule(AstNode* nodep) {
|
||||
for (AstNode* curp = nodep; curp; curp = curp->backp()) {
|
||||
if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif // Guard
|
||||
|
|
|
|||
|
|
@ -1946,6 +1946,8 @@ class AstVar final : public AstNode {
|
|||
bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering
|
||||
bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations)
|
||||
bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing
|
||||
bool m_dfgTriLowered : 1; // Signal/temporary introduced by tristate lowering
|
||||
bool m_dfgAllowMultidriveTri : 1; // Allow DFG MULTIDRIVEN warning for intentional tri nets
|
||||
bool m_globalConstrained : 1; // Global constraint per IEEE 1800-2023 18.5.8
|
||||
bool m_isStdRandomizeArg : 1; // Argument variable created for std::randomize (__Varg*)
|
||||
void init() {
|
||||
|
|
@ -2001,6 +2003,8 @@ class AstVar final : public AstNode {
|
|||
m_ignorePostWrite = false;
|
||||
m_ignoreSchedWrite = false;
|
||||
m_dfgMultidriven = false;
|
||||
m_dfgTriLowered = false;
|
||||
m_dfgAllowMultidriveTri = false;
|
||||
m_globalConstrained = false;
|
||||
m_isStdRandomizeArg = false;
|
||||
}
|
||||
|
|
@ -2174,6 +2178,10 @@ public:
|
|||
void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; }
|
||||
bool dfgMultidriven() const { return m_dfgMultidriven; }
|
||||
void setDfgMultidriven() { m_dfgMultidriven = true; }
|
||||
bool dfgTriLowered() const { return m_dfgTriLowered; }
|
||||
void setDfgTriLowered() { m_dfgTriLowered = true; }
|
||||
bool dfgAllowMultidriveTri() const { return m_dfgAllowMultidriveTri; }
|
||||
void setDfgAllowMultidriveTri() { m_dfgAllowMultidriveTri = true; }
|
||||
void globalConstrained(bool flag) { m_globalConstrained = flag; }
|
||||
bool globalConstrained() const { return m_globalConstrained; }
|
||||
bool isStdRandomizeArg() const { return m_isStdRandomizeArg; }
|
||||
|
|
|
|||
|
|
@ -662,6 +662,39 @@ class AstToDfgSynthesize final {
|
|||
return drivers;
|
||||
}
|
||||
|
||||
// Returns true if the driver cone contains any variable introduced by
|
||||
// tristate lowering. Used to distinguish intentional tristate contributor
|
||||
// overlap from accidental multidrive.
|
||||
static bool containsTriLoweredVar(DfgVertex* rootp) {
|
||||
std::vector<const DfgVertex*> stack;
|
||||
std::vector<const DfgVertex*> visited;
|
||||
stack.emplace_back(rootp);
|
||||
while (!stack.empty()) {
|
||||
const DfgVertex* const vtxp = stack.back();
|
||||
stack.pop_back();
|
||||
if (std::find(visited.begin(), visited.end(), vtxp) != visited.end()) continue;
|
||||
visited.emplace_back(vtxp);
|
||||
if (const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>()) {
|
||||
AstVar* const astVarp = [&]() -> AstVar* {
|
||||
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
||||
return reinterpret_cast<AstVarScope*>(varp->nodep())->varp();
|
||||
} else {
|
||||
return reinterpret_cast<AstVar*>(varp->nodep());
|
||||
}
|
||||
}();
|
||||
if (astVarp->dfgTriLowered()) return true;
|
||||
}
|
||||
vtxp->foreachSource([&](const DfgVertex& src) {
|
||||
stack.emplace_back(&src);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if the driver is a direct variable forward (no logic).
|
||||
static bool isDirectVarDriver(const DfgVertex* vtxp) { return vtxp->is<DfgVertexVar>(); }
|
||||
|
||||
// Gather all synthesized drivers of an unresolved variable
|
||||
static std::vector<Driver> gatherDriversUnresolved(DfgUnresolved* vtxp) {
|
||||
std::vector<Driver> drivers;
|
||||
|
|
@ -822,6 +855,17 @@ class AstToDfgSynthesize final {
|
|||
|
||||
// Loop index often abused, so suppress
|
||||
if (getAstVar(varp)->isUsedLoopIdx()) continue;
|
||||
// Tristate lowering can intentionally create overlapping contributors.
|
||||
// Keep the signal marked multidriven for DFG fallback, but suppress
|
||||
// warning only when both overlapping driver cones look tri-lowered.
|
||||
if (getAstVar(varp)->dfgAllowMultidriveTri()) {
|
||||
const bool iTri = containsTriLoweredVar(iD.m_vtxp);
|
||||
const bool jTri = containsTriLoweredVar(jD.m_vtxp);
|
||||
const bool triPair = iTri && jTri;
|
||||
const bool triAndBridge = (iTri && isDirectVarDriver(jD.m_vtxp))
|
||||
|| (jTri && isDirectVarDriver(iD.m_vtxp));
|
||||
if (triPair || triAndBridge) continue;
|
||||
}
|
||||
|
||||
// Warn the user now
|
||||
const std::string lo = std::to_string(jD.m_lo);
|
||||
|
|
|
|||
|
|
@ -411,9 +411,9 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
|
||||
// TYPES
|
||||
struct RefStrength final {
|
||||
AstVarRef* m_varrefp;
|
||||
AstNodeVarRef* m_varrefp;
|
||||
VStrength m_strength;
|
||||
RefStrength(AstVarRef* varrefp, VStrength strength)
|
||||
RefStrength(AstNodeVarRef* varrefp, VStrength strength)
|
||||
: m_varrefp{varrefp}
|
||||
, m_strength{strength} {}
|
||||
};
|
||||
|
|
@ -441,6 +441,8 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
// Used only on LHS of assignment
|
||||
const AstNode* m_logicp = nullptr; // Current logic being built
|
||||
TristateGraph m_tgraph; // Logic graph
|
||||
// Map: interface AstVar* -> list of per-module (enVarp, outVarp) contribution pairs
|
||||
std::map<AstVar*, std::vector<std::pair<AstVar*, AstVar*>>> m_ifaceContribs;
|
||||
|
||||
// STATS
|
||||
VDouble0 m_statTriSigs; // stat tracking
|
||||
|
|
@ -469,9 +471,32 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
AstConst* const newp = new AstConst{nodep->fileline(), num};
|
||||
return newp;
|
||||
}
|
||||
// Create AstVarXRef with inlinedDots copied from a source xref
|
||||
AstVarXRef* newVarXRef(FileLine* fl, AstVar* varp, const string& dotted, VAccess access,
|
||||
const string& inlinedDots) {
|
||||
AstVarXRef* const xrefp = new AstVarXRef{fl, varp, dotted, access};
|
||||
xrefp->inlinedDots(inlinedDots);
|
||||
return xrefp;
|
||||
}
|
||||
// Check if a module accesses an interface through a modport, by looking up
|
||||
// the first component of the dotted path among the module's interface port vars.
|
||||
// No symbol table is available at V3Tristate time, so linear scan is required.
|
||||
AstModport* findModportForDotted(AstNodeModule* modp, const string& dottedPath) {
|
||||
if (dottedPath.empty()) return nullptr;
|
||||
const string firstComp = dottedPath.substr(0, dottedPath.find('.'));
|
||||
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
AstVar* const ivarp = VN_CAST(stmtp, Var);
|
||||
if (!ivarp || ivarp->name() != firstComp) continue;
|
||||
const AstIfaceRefDType* const ifaceDtp
|
||||
= VN_CAST(ivarp->dtypep()->skipRefp(), IfaceRefDType);
|
||||
if (ifaceDtp && ifaceDtp->modportp()) return ifaceDtp->modportp();
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
AstNodeExpr* getEnp(AstNode* nodep) {
|
||||
if (nodep->user1p()) {
|
||||
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
|
||||
if (AstNodeVarRef* const refp = VN_CAST(nodep, NodeVarRef)) {
|
||||
if (refp->varp()->isIO()) {
|
||||
// When reading a tri-state port, we can always use the value
|
||||
// because such port will have resolution logic in upper module.
|
||||
|
|
@ -486,6 +511,16 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
// Otherwise return the previous output enable
|
||||
return VN_AS(nodep->user1p(), NodeExpr);
|
||||
}
|
||||
// Add a ModportVarRef for varp to modportp if not already present
|
||||
void addModportVarRefIfMissing(AstModport* modportp, AstVar* varp, VDirection dir) {
|
||||
for (AstNode* mrp = modportp->varsp(); mrp; mrp = mrp->nextp()) {
|
||||
AstModportVarRef* const mvrp = VN_CAST(mrp, ModportVarRef);
|
||||
if (mvrp && mvrp->varp() == varp) return; // Already present
|
||||
}
|
||||
AstModportVarRef* const mvrp = new AstModportVarRef{varp->fileline(), varp->name(), dir};
|
||||
mvrp->varp(varp);
|
||||
modportp->addVarsp(mvrp);
|
||||
}
|
||||
AstVar* getCreateEnVarp(AstVar* invarp, bool isTop) {
|
||||
// Return the master __en for the specified input variable
|
||||
if (!invarp->user1p()) {
|
||||
|
|
@ -494,7 +529,27 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
invarp->name() + "__en", invarp};
|
||||
// Inherited VDirection::INPUT
|
||||
UINFO(9, " newenv " << newp);
|
||||
modAddStmtp(invarp, newp);
|
||||
// If the variable belongs to a different module (e.g. interface),
|
||||
// create __en in that module so VarXRefs with the same dotted path can reach it.
|
||||
AstNodeModule* const ownerModp = findParentModule(invarp);
|
||||
if (ownerModp && ownerModp != m_modp) {
|
||||
ownerModp->addStmtsp(newp);
|
||||
// Add __en to any modports that reference the original var,
|
||||
// so VarXRefs through modport paths can resolve it.
|
||||
for (AstNode* stmtp = ownerModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
AstModport* const modportp = VN_CAST(stmtp, Modport);
|
||||
if (!modportp) continue;
|
||||
for (AstNode* mrp = modportp->varsp(); mrp; mrp = mrp->nextp()) {
|
||||
AstModportVarRef* const mvrp = VN_CAST(mrp, ModportVarRef);
|
||||
if (mvrp && mvrp->varp() == invarp) {
|
||||
addModportVarRefIfMissing(modportp, newp, VDirection::INPUT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modAddStmtp(invarp, newp);
|
||||
}
|
||||
invarp->user1p(newp); // find envar given invarp
|
||||
}
|
||||
return VN_AS(invarp->user1p(), Var);
|
||||
|
|
@ -511,6 +566,10 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
if (AstVarRef* const varrefp = VN_CAST(nodep, VarRef)) {
|
||||
return new AstVarRef{varrefp->fileline(), getCreateEnVarp(varrefp->varp(), false),
|
||||
VAccess::READ};
|
||||
} else if (AstVarXRef* const xrefp = VN_CAST(nodep, VarXRef)) {
|
||||
AstVar* const enVarp = getCreateEnVarp(xrefp->varp(), false);
|
||||
return newVarXRef(xrefp->fileline(), enVarp, xrefp->dotted(), VAccess::READ,
|
||||
xrefp->inlinedDots());
|
||||
} else if (AstConst* const constp = VN_CAST(nodep, Const)) {
|
||||
return getNonZConstp(constp);
|
||||
} else if (AstExtend* const extendp = VN_CAST(nodep, Extend)) {
|
||||
|
|
@ -561,7 +620,7 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
return newp;
|
||||
}
|
||||
|
||||
void mapInsertLhsVarRef(AstVarRef* nodep) {
|
||||
void mapInsertLhsVarRef(AstNodeVarRef* nodep) {
|
||||
UINFO(9, " mapInsertLhsVarRef " << nodep);
|
||||
AstVar* const key = nodep->varp();
|
||||
const auto pair = m_lhsmap.emplace(key, nullptr);
|
||||
|
|
@ -636,14 +695,62 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
// Now go through the lhs driver map and generate the output
|
||||
// enable logic for any tristates.
|
||||
// Note there might not be any drivers.
|
||||
for (auto varp : vars) { // Use vector instead of m_lhsmap iteration for node stability
|
||||
const auto it = m_lhsmap.find(varp);
|
||||
for (AstVar* varp : vars) { // Use vector instead of m_lhsmap iteration for stability
|
||||
const std::map<AstVar*, RefStrengthVec*>::iterator it = m_lhsmap.find(varp);
|
||||
if (it == m_lhsmap.end()) continue;
|
||||
AstVar* const invarp = it->first;
|
||||
RefStrengthVec* refsp = it->second;
|
||||
// Figure out if this var needs tristate expanded.
|
||||
if (m_tgraph.isTristate(invarp)) {
|
||||
insertTristatesSignal(nodep, invarp, refsp);
|
||||
// Check if the var is owned by a different module (cross-module reference).
|
||||
// For interface vars this is expected; for regular modules it's unsupported.
|
||||
AstNodeModule* const ownerModp = findParentModule(invarp);
|
||||
const bool isCrossModule = ownerModp && ownerModp != nodep && !invarp->isIO();
|
||||
const bool isIfaceTri = isCrossModule && VN_IS(ownerModp, Iface);
|
||||
|
||||
if (isCrossModule && !isIfaceTri) {
|
||||
// Hierarchical writes to non-interface module tri wires are unsupported
|
||||
for (RefStrength& rs : *refsp) {
|
||||
if (VN_IS(rs.m_varrefp, VarXRef)) {
|
||||
rs.m_varrefp->v3warn(
|
||||
E_UNSUPPORTED,
|
||||
"Unsupported tristate construct: hierarchical driver"
|
||||
" of non-interface tri signal: "
|
||||
<< invarp->prettyNameQ());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (isIfaceTri) {
|
||||
// Interface tristate vars: drivers from different interface instances
|
||||
// (different VarXRef dotted paths) must be processed separately.
|
||||
// E.g. io_ifc.d and io_ifc_local.d both target the same AstVar d in
|
||||
// the ifc interface, but each instance needs its own contribution slot.
|
||||
struct PartitionInfo final {
|
||||
RefStrengthVec refs;
|
||||
string inlinedDots;
|
||||
};
|
||||
std::map<string, PartitionInfo> partitions;
|
||||
for (RefStrength& rs : *refsp) {
|
||||
if (AstVarXRef* const xrefp = VN_CAST(rs.m_varrefp, VarXRef)) {
|
||||
PartitionInfo& pi = partitions[xrefp->dotted()];
|
||||
pi.refs.push_back(rs);
|
||||
if (pi.inlinedDots.empty()) { pi.inlinedDots = xrefp->inlinedDots(); }
|
||||
} else {
|
||||
partitions[""].refs.push_back(rs);
|
||||
}
|
||||
}
|
||||
for (auto& kv : partitions) {
|
||||
insertTristatesSignal(nodep, invarp, &kv.second.refs, true, kv.first,
|
||||
kv.second.inlinedDots,
|
||||
findModportForDotted(nodep, kv.first));
|
||||
}
|
||||
} else if (VN_IS(nodep, Iface)) {
|
||||
// Local driver in an interface module - use contribution mechanism
|
||||
// so it can be combined with any external drivers later
|
||||
insertTristatesSignal(nodep, invarp, refsp, true, "", "", nullptr);
|
||||
} else {
|
||||
insertTristatesSignal(nodep, invarp, refsp, false, "", "", nullptr);
|
||||
}
|
||||
} else {
|
||||
UINFO(8, " NO TRISTATE ON:" << invarp);
|
||||
}
|
||||
|
|
@ -663,20 +770,35 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
AstNodeExpr* enp = nullptr;
|
||||
|
||||
for (auto it = beginStrength; it != endStrength; it++) {
|
||||
AstVarRef* refp = it->m_varrefp;
|
||||
AstNodeVarRef* refp = it->m_varrefp;
|
||||
|
||||
// create the new lhs driver for this var
|
||||
AstVar* const newLhsp = new AstVar{varp->fileline(), VVarType::MODULETEMP,
|
||||
varp->name() + "__out" + cvtToStr(m_unique),
|
||||
varp}; // 2-state ok; sep enable
|
||||
newLhsp->setDfgTriLowered();
|
||||
UINFO(9, " newout " << newLhsp);
|
||||
nodep->addStmtsp(newLhsp);
|
||||
refp->varp(newLhsp);
|
||||
// When retargeting a VarXRef to a local __out var, the dotted path
|
||||
// becomes inconsistent. Replace the VarXRef with a local VarRef.
|
||||
if (VN_IS(refp, VarXRef)) {
|
||||
AstVarRef* const localRefp
|
||||
= new AstVarRef{refp->fileline(), newLhsp, VAccess::WRITE};
|
||||
localRefp->user1p(refp->user1p());
|
||||
refp->user1p(nullptr);
|
||||
refp->replaceWith(localRefp);
|
||||
VL_DO_DANGLING(pushDeletep(refp), refp);
|
||||
refp = localRefp;
|
||||
it->m_varrefp = localRefp;
|
||||
} else {
|
||||
refp->varp(newLhsp);
|
||||
}
|
||||
|
||||
// create a new var for this drivers enable signal
|
||||
AstVar* const newEnLhsp
|
||||
= new AstVar{varp->fileline(), VVarType::MODULETEMP,
|
||||
varp->name() + "__en" + cvtToStr(m_unique++), envarp}; // 2-state ok
|
||||
newEnLhsp->setDfgTriLowered();
|
||||
UINFO(9, " newenlhsp " << newEnLhsp);
|
||||
nodep->addStmtsp(newEnLhsp);
|
||||
|
||||
|
|
@ -708,7 +830,12 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
nodep->addStmtsp(new AstAlways{enAssp});
|
||||
}
|
||||
|
||||
void insertTristatesSignal(AstNodeModule* nodep, AstVar* const invarp, RefStrengthVec* refsp) {
|
||||
// isIfaceTri: true when the var is a tristate in an interface module (local or external).
|
||||
// ifaceDottedPath/ifaceInlinedDots/ifaceModportp are non-empty only for external
|
||||
// (cross-module) drivers; empty for local drivers within the interface itself.
|
||||
void insertTristatesSignal(AstNodeModule* nodep, AstVar* const invarp, RefStrengthVec* refsp,
|
||||
bool isIfaceTri, const string& ifaceDottedPath,
|
||||
const string& ifaceInlinedDots, AstModport* ifaceModportp) {
|
||||
UINFO(8, " TRISTATE EXPANDING:" << invarp);
|
||||
++m_statTriSigs;
|
||||
m_tgraph.didProcess(invarp);
|
||||
|
|
@ -721,29 +848,31 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
// Or if this is a top-level inout, do tristate expand if requested
|
||||
// by pinsInoutEnables(). The resolution will be done outside of
|
||||
// verilator.
|
||||
// Interface tristate vars skip port conversion (they use contribution vars instead).
|
||||
AstVar* envarp = nullptr;
|
||||
AstVar* outvarp = nullptr; // __out
|
||||
AstVar* lhsp = invarp; // Variable to assign drive-value to (<in> or __out)
|
||||
bool isTopInout
|
||||
= (invarp->direction() == VDirection::INOUT) && invarp->isIO() && nodep->isTop();
|
||||
if ((v3Global.opt.pinsInoutEnables() && isTopInout)
|
||||
|| (!nodep->isTop() && invarp->isIO())) {
|
||||
// This var becomes an input
|
||||
invarp->varType2In(); // convert existing port to type input
|
||||
// Create an output port (__out)
|
||||
outvarp = getCreateOutVarp(invarp, isTopInout);
|
||||
outvarp->varType2Out();
|
||||
lhsp = outvarp; // Must assign to __out, not to normal input signal
|
||||
UINFO(9, " TRISTATE propagates up with " << lhsp);
|
||||
// Create an output enable port (__en)
|
||||
// May already be created if have foo === 1'bz somewhere
|
||||
envarp = getCreateEnVarp(invarp, isTopInout); // direction to be set in visit(AstPin*)
|
||||
//
|
||||
outvarp->user1p(envarp);
|
||||
m_varAux(outvarp).pullp = m_varAux(invarp).pullp; // AstPull* propagation
|
||||
if (m_varAux(invarp).pullp) UINFO(9, "propagate pull to " << outvarp);
|
||||
} else if (invarp->user1p()) {
|
||||
envarp = VN_AS(invarp->user1p(), Var); // From CASEEQ, foo === 1'bz
|
||||
const bool isTopInout = !isIfaceTri && (invarp->direction() == VDirection::INOUT)
|
||||
&& invarp->isIO() && nodep->isTop();
|
||||
if (!isIfaceTri) {
|
||||
if ((v3Global.opt.pinsInoutEnables() && isTopInout)
|
||||
|| (!nodep->isTop() && invarp->isIO())) {
|
||||
// This var becomes an input
|
||||
invarp->varType2In(); // convert existing port to type input
|
||||
// Create an output port (__out)
|
||||
outvarp = getCreateOutVarp(invarp, isTopInout);
|
||||
outvarp->varType2Out();
|
||||
lhsp = outvarp; // Must assign to __out, not to normal input signal
|
||||
UINFO(9, " TRISTATE propagates up with " << lhsp);
|
||||
// Create an output enable port (__en)
|
||||
// May already be created if have foo === 1'bz somewhere
|
||||
envarp = getCreateEnVarp(invarp, isTopInout); // dir set in visit(AstPin*)
|
||||
outvarp->user1p(envarp);
|
||||
m_varAux(outvarp).pullp = m_varAux(invarp).pullp; // AstPull* propagation
|
||||
if (m_varAux(invarp).pullp) UINFO(9, "propagate pull to " << outvarp);
|
||||
} else if (invarp->user1p()) {
|
||||
envarp = VN_AS(invarp->user1p(), Var); // From CASEEQ, foo === 1'bz
|
||||
}
|
||||
}
|
||||
|
||||
AstNodeExpr* orp = nullptr;
|
||||
|
|
@ -752,25 +881,38 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
std::sort(refsp->begin(), refsp->end(),
|
||||
[](RefStrength a, RefStrength b) { return a.m_strength > b.m_strength; });
|
||||
|
||||
auto beginStrength = refsp->begin();
|
||||
RefStrengthVec::iterator beginStrength = refsp->begin();
|
||||
while (beginStrength != refsp->end()) {
|
||||
auto endStrength = beginStrength + 1;
|
||||
RefStrengthVec::iterator endStrength = beginStrength + 1;
|
||||
while (endStrength != refsp->end()
|
||||
&& endStrength->m_strength == beginStrength->m_strength)
|
||||
endStrength++;
|
||||
|
||||
FileLine* const fl = beginStrength->m_varrefp->fileline();
|
||||
const string strengthVarName = lhsp->name() + "__" + beginStrength->m_strength.ascii();
|
||||
// For interface tristate vars, uniquify strength var names by including the
|
||||
// interface instance path, since multiple interface instances may have
|
||||
// identically-named tristate signals (e.g. io_ifc.d and io_ifc_mc.d both
|
||||
// create d__strong in the driving module)
|
||||
string strengthPrefix;
|
||||
if (isIfaceTri && !ifaceDottedPath.empty()) {
|
||||
strengthPrefix = ifaceDottedPath;
|
||||
std::replace(strengthPrefix.begin(), strengthPrefix.end(), '.', '_');
|
||||
strengthPrefix += "__";
|
||||
}
|
||||
const string strengthVarName
|
||||
= strengthPrefix + lhsp->name() + "__" + beginStrength->m_strength.ascii();
|
||||
|
||||
// var__strength variable
|
||||
AstVar* const varStrengthp = new AstVar{fl, VVarType::MODULETEMP, strengthVarName,
|
||||
invarp}; // 2-state ok; sep enable;
|
||||
varStrengthp->setDfgTriLowered();
|
||||
UINFO(9, " newstrength " << varStrengthp);
|
||||
nodep->addStmtsp(varStrengthp);
|
||||
|
||||
// var__strength__en variable
|
||||
AstVar* const enVarStrengthp = new AstVar{
|
||||
fl, VVarType::MODULETEMP, strengthVarName + "__en", invarp}; // 2-state ok;
|
||||
enVarStrengthp->setDfgTriLowered();
|
||||
UINFO(9, " newenstrength " << enVarStrengthp);
|
||||
nodep->addStmtsp(enVarStrengthp);
|
||||
|
||||
|
|
@ -796,11 +938,74 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
beginStrength = endStrength;
|
||||
}
|
||||
|
||||
// For interface tristate vars, store per-module contributions for later combining.
|
||||
// The interface module owns the contribution vars; resolution happens in
|
||||
// combineIfaceContribs() after all modules are processed.
|
||||
if (isIfaceTri) {
|
||||
// This net intentionally has multiple contributors; DFG should not emit
|
||||
// MULTIDRIVEN warnings for this lowered tristate pattern.
|
||||
invarp->setDfgAllowMultidriveTri();
|
||||
AstNodeModule* const ifaceModp = findParentModule(invarp);
|
||||
UASSERT_OBJ(ifaceModp, invarp, "Interface tristate var has no parent module");
|
||||
const int contribIdx = static_cast<int>(m_ifaceContribs[invarp].size());
|
||||
FileLine* const fl = invarp->fileline();
|
||||
|
||||
// Create contribution vars in the interface module
|
||||
AstVar* const contribOutp = new AstVar{
|
||||
fl, VVarType::MODULETEMP, invarp->name() + "__out" + cvtToStr(contribIdx), invarp};
|
||||
contribOutp->setDfgTriLowered();
|
||||
UINFO(9, " iface contribOut " << contribOutp);
|
||||
ifaceModp->addStmtsp(contribOutp);
|
||||
|
||||
AstVar* const contribEnp = new AstVar{
|
||||
fl, VVarType::MODULETEMP, invarp->name() + "__en" + cvtToStr(contribIdx), invarp};
|
||||
contribEnp->setDfgTriLowered();
|
||||
UINFO(9, " iface contribEn " << contribEnp);
|
||||
ifaceModp->addStmtsp(contribEnp);
|
||||
|
||||
// If the driving module accesses the interface through a modport,
|
||||
// add the new contribution vars to the modport so VarXRefs
|
||||
// can be resolved by V3LinkDot through the modport path.
|
||||
if (ifaceModportp) {
|
||||
UINFO(9, " adding to modport " << ifaceModportp->name());
|
||||
addModportVarRefIfMissing(ifaceModportp, contribOutp, VDirection::OUTPUT);
|
||||
addModportVarRefIfMissing(ifaceModportp, contribEnp, VDirection::OUTPUT);
|
||||
}
|
||||
|
||||
// Assign drive value and enable to contribution vars.
|
||||
// External drivers use VarXRef; local drivers use VarRef.
|
||||
{
|
||||
AstNodeVarRef* const lhsp
|
||||
= ifaceDottedPath.empty() ? static_cast<AstNodeVarRef*>(
|
||||
new AstVarRef{fl, contribOutp, VAccess::WRITE})
|
||||
: static_cast<AstNodeVarRef*>(
|
||||
newVarXRef(fl, contribOutp, ifaceDottedPath,
|
||||
VAccess::WRITE, ifaceInlinedDots));
|
||||
AstAssignW* const assp = new AstAssignW{fl, lhsp, orp};
|
||||
assp->user2Or(U2_BOTH);
|
||||
nodep->addStmtsp(new AstAlways{assp});
|
||||
}
|
||||
{
|
||||
AstNodeVarRef* const lhsp
|
||||
= ifaceDottedPath.empty() ? static_cast<AstNodeVarRef*>(
|
||||
new AstVarRef{fl, contribEnp, VAccess::WRITE})
|
||||
: static_cast<AstNodeVarRef*>(
|
||||
newVarXRef(fl, contribEnp, ifaceDottedPath,
|
||||
VAccess::WRITE, ifaceInlinedDots));
|
||||
AstAssignW* const assp = new AstAssignW{fl, lhsp, enp};
|
||||
assp->user2Or(U2_BOTH);
|
||||
nodep->addStmtsp(new AstAlways{assp});
|
||||
}
|
||||
|
||||
m_ifaceContribs[invarp].push_back({contribEnp, contribOutp});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!outvarp) {
|
||||
// This is the final pre-forced resolution of the tristate, so we apply
|
||||
// the pull direction to any undriven pins.
|
||||
const AstPull* const pullp = m_varAux(lhsp).pullp;
|
||||
bool pull1 = pullp && pullp->direction() == 1; // Else default is down
|
||||
const bool pull1 = pullp && pullp->direction() == 1; // Else default is down
|
||||
|
||||
AstNodeExpr* undrivenp;
|
||||
if (envarp) {
|
||||
|
|
@ -1753,7 +1958,7 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
void visit(AstVarRef* nodep) override {
|
||||
void handleNodeVarRef(AstNodeVarRef* nodep) {
|
||||
UINFO(9, dbgState() << nodep);
|
||||
if (m_graphing) {
|
||||
if (nodep->access().isWriteOrRW()) associateLogic(nodep, nodep->varp());
|
||||
|
|
@ -1789,12 +1994,21 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
&& m_tgraph.feedsTri(nodep)) {
|
||||
// Then propagate the enable from the original variable
|
||||
UINFO(9, " Ref-to-tri " << nodep);
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstVar* const enVarp = getCreateEnVarp(nodep->varp(), false);
|
||||
nodep->user1p(new AstVarRef{nodep->fileline(), enVarp, VAccess::READ});
|
||||
if (AstVarXRef* const xrefp = VN_CAST(nodep, VarXRef)) {
|
||||
nodep->user1p(newVarXRef(fl, enVarp, xrefp->dotted(), VAccess::READ,
|
||||
xrefp->inlinedDots()));
|
||||
} else {
|
||||
nodep->user1p(new AstVarRef{fl, enVarp, VAccess::READ});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstVarRef* nodep) override { handleNodeVarRef(nodep); }
|
||||
void visit(AstVarXRef* nodep) override { handleNodeVarRef(nodep); }
|
||||
|
||||
void visit(AstVar* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
UINFO(9, dbgState() << nodep);
|
||||
|
|
@ -1897,12 +2111,79 @@ class TristateVisitor final : public TristateBaseVisitor {
|
|||
checkUnhandled(nodep);
|
||||
}
|
||||
|
||||
void combineIfaceContribs() {
|
||||
// After all modules have been processed, combine per-module contributions
|
||||
// for each interface tristate signal into final resolution logic.
|
||||
// Key is the canonical AstVar in the interface module (shared across instances).
|
||||
for (std::pair<AstVar* const, std::vector<std::pair<AstVar*, AstVar*>>>& kv :
|
||||
m_ifaceContribs) {
|
||||
AstVar* const invarp = kv.first;
|
||||
std::vector<std::pair<AstVar*, AstVar*>>& contribs = kv.second;
|
||||
AstNodeModule* const ifaceModp = findParentModule(invarp);
|
||||
UASSERT_OBJ(ifaceModp, invarp, "Interface tristate var has no parent module");
|
||||
FileLine* const fl = invarp->fileline();
|
||||
|
||||
UINFO(8, " IFACE COMBINE " << contribs.size() << " contribs for " << invarp);
|
||||
|
||||
// Build resolution: d = (out0 & en0) | (out1 & en1) | ... | (~combined_en & pull)
|
||||
AstNodeExpr* orp = nullptr;
|
||||
AstNodeExpr* enp = nullptr;
|
||||
|
||||
for (std::pair<AstVar*, AstVar*>& contrib : contribs) {
|
||||
AstVar* const contribEnp = contrib.first;
|
||||
AstVar* const contribOutp = contrib.second;
|
||||
|
||||
AstNodeExpr* const outRefp = new AstVarRef{fl, contribOutp, VAccess::READ};
|
||||
AstNodeExpr* const enRefp = new AstVarRef{fl, contribEnp, VAccess::READ};
|
||||
AstNodeExpr* const andp = new AstAnd{fl, outRefp, enRefp};
|
||||
|
||||
orp = (!orp) ? andp : new AstOr{fl, orp, andp};
|
||||
|
||||
AstNodeExpr* const enRef2p = new AstVarRef{fl, contribEnp, VAccess::READ};
|
||||
enp = (!enp) ? enRef2p : new AstOr{fl, enp, enRef2p};
|
||||
}
|
||||
|
||||
// Create or get __en var for this interface signal
|
||||
AstVar* envarp = VN_CAST(invarp->user1p(), Var);
|
||||
if (!envarp) {
|
||||
envarp = new AstVar{fl, VVarType::MODULETEMP, invarp->name() + "__en", invarp};
|
||||
envarp->setDfgTriLowered();
|
||||
ifaceModp->addStmtsp(envarp);
|
||||
invarp->user1p(envarp);
|
||||
}
|
||||
|
||||
// Assign combined enable
|
||||
AstAssignW* const enAssp
|
||||
= new AstAssignW{fl, new AstVarRef{fl, envarp, VAccess::WRITE}, enp};
|
||||
UINFOTREE(9, enAssp, "", "iface-enAssp");
|
||||
ifaceModp->addStmtsp(new AstAlways{enAssp});
|
||||
|
||||
// Pull resolution
|
||||
const AstPull* const pullp = m_varAux(invarp).pullp;
|
||||
const bool pull1 = pullp && pullp->direction() == 1; // Else default is down
|
||||
|
||||
AstNodeExpr* undrivenp = new AstNot{fl, new AstVarRef{fl, envarp, VAccess::READ}};
|
||||
undrivenp = new AstAnd{fl, undrivenp, newAllZerosOrOnes(invarp, pull1)};
|
||||
orp = new AstOr{fl, orp, undrivenp};
|
||||
|
||||
// Assign final value to invarp
|
||||
AstAssignW* const assp
|
||||
= new AstAssignW{fl, new AstVarRef{fl, invarp, VAccess::WRITE}, orp};
|
||||
assp->user2Or(U2_BOTH);
|
||||
UINFOTREE(9, assp, "", "iface-lhsp-eqn");
|
||||
ifaceModp->addStmtsp(new AstAlways{assp});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit TristateVisitor(AstNetlist* netlistp) {
|
||||
m_tgraph.clearAndCheck();
|
||||
iterateChildrenBackwardsConst(netlistp);
|
||||
|
||||
// Combine interface tristate contributions after all modules processed
|
||||
combineIfaceContribs();
|
||||
|
||||
#ifdef VL_LEAK_CHECKS
|
||||
// It's a bit chaotic up there
|
||||
std::vector<AstNode*> unusedRootps;
|
||||
|
|
|
|||
|
|
@ -5,88 +5,111 @@
|
|||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
interface counter_if;
|
||||
logic [3:0] value;
|
||||
logic reset;
|
||||
modport counter_mp (input reset, output value);
|
||||
modport core_mp (output reset, input value);
|
||||
logic [3:0] value;
|
||||
logic reset;
|
||||
modport counter_mp (input reset, output value);
|
||||
modport core_mp (output reset, input value);
|
||||
endinterface
|
||||
|
||||
// Check can have inst module before top module
|
||||
module counter_ansi
|
||||
(
|
||||
input clkm,
|
||||
counter_if c_data,
|
||||
input logic [3:0] i_value
|
||||
);
|
||||
module counter_ansi (
|
||||
input clkm,
|
||||
counter_if c_data,
|
||||
input logic [3:0] i_value
|
||||
);
|
||||
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
endmodule : counter_ansi
|
||||
|
||||
// Issue 3466: inout inside interface modport
|
||||
interface inout_if;
|
||||
wire we;
|
||||
wire d;
|
||||
modport ctrl (output we, inout d);
|
||||
modport prph (input we, inout d);
|
||||
endinterface
|
||||
|
||||
module inout_mod(inout_if.prph io_if);
|
||||
assign io_if.d = io_if.we ? 1'b1 : 1'bz;
|
||||
endmodule
|
||||
|
||||
module inout_mod_wrap(input we, inout d);
|
||||
inout_if io_if();
|
||||
assign io_if.we = we;
|
||||
assign io_if.d = d;
|
||||
inout_mod prph (.*);
|
||||
endmodule
|
||||
|
||||
module t (/*AUTOARG*/
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
|
||||
input clk;
|
||||
integer cyc=1;
|
||||
input clk;
|
||||
integer cyc=1;
|
||||
|
||||
counter_if c1_data();
|
||||
counter_if c2_data();
|
||||
counter_if c3_data();
|
||||
counter_if c4_data();
|
||||
counter_if c1_data();
|
||||
counter_if c2_data();
|
||||
counter_if c3_data();
|
||||
counter_if c4_data();
|
||||
|
||||
counter_ansi c1 (.clkm(clk),
|
||||
.c_data(c1_data.counter_mp),
|
||||
.i_value(4'h1));
|
||||
counter_ansi c1 (.clkm(clk),
|
||||
.c_data(c1_data.counter_mp),
|
||||
.i_value(4'h1));
|
||||
`ifdef VERILATOR counter_ansi `else counter_nansi `endif
|
||||
/**/ c2 (.clkm(clk),
|
||||
.c_data(c2_data.counter_mp),
|
||||
.i_value(4'h2));
|
||||
counter_ansi_m c3 (.clkm(clk),
|
||||
.c_data(c3_data),
|
||||
.i_value(4'h3));
|
||||
/**/ c2 (.clkm(clk),
|
||||
.c_data(c2_data.counter_mp),
|
||||
.i_value(4'h2));
|
||||
counter_ansi_m c3 (.clkm(clk),
|
||||
.c_data(c3_data),
|
||||
.i_value(4'h3));
|
||||
`ifdef VERILATOR counter_ansi_m `else counter_nansi_m `endif
|
||||
/**/ c4 (.clkm(clk),
|
||||
.c_data(c4_data),
|
||||
.i_value(4'h4));
|
||||
/**/ c4 (.clkm(clk),
|
||||
.c_data(c4_data),
|
||||
.i_value(4'h4));
|
||||
|
||||
initial begin
|
||||
c1_data.value = 4'h4;
|
||||
c2_data.value = 4'h5;
|
||||
c3_data.value = 4'h6;
|
||||
c4_data.value = 4'h7;
|
||||
end
|
||||
logic inout_we;
|
||||
tri inout_d;
|
||||
inout_mod_wrap inout_u (.we(inout_we), .d(inout_d));
|
||||
|
||||
always @ (posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc<2) begin
|
||||
c1_data.reset <= 1;
|
||||
c2_data.reset <= 1;
|
||||
c3_data.reset <= 1;
|
||||
c4_data.reset <= 1;
|
||||
end
|
||||
if (cyc==2) begin
|
||||
c1_data.reset <= 0;
|
||||
c2_data.reset <= 0;
|
||||
c3_data.reset <= 0;
|
||||
c4_data.reset <= 0;
|
||||
end
|
||||
if (cyc==20) begin
|
||||
$write("[%0t] cyc%0d: c1 %0x %0x c2 %0x %0x c3 %0x %0x c4 %0x %0x\n", $time, cyc,
|
||||
c1_data.value, c1_data.reset,
|
||||
c2_data.value, c2_data.reset,
|
||||
c3_data.value, c3_data.reset,
|
||||
c4_data.value, c4_data.reset);
|
||||
if (c1_data.value != 2) $stop;
|
||||
if (c2_data.value != 3) $stop;
|
||||
if (c3_data.value != 4) $stop;
|
||||
if (c4_data.value != 5) $stop;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
initial begin
|
||||
c1_data.value = 4'h4;
|
||||
c2_data.value = 4'h5;
|
||||
c3_data.value = 4'h6;
|
||||
c4_data.value = 4'h7;
|
||||
inout_we = 1'b0;
|
||||
end
|
||||
|
||||
always @ (posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc<2) begin
|
||||
c1_data.reset <= 1;
|
||||
c2_data.reset <= 1;
|
||||
c3_data.reset <= 1;
|
||||
c4_data.reset <= 1;
|
||||
end
|
||||
if (cyc==2) begin
|
||||
c1_data.reset <= 0;
|
||||
c2_data.reset <= 0;
|
||||
c3_data.reset <= 0;
|
||||
c4_data.reset <= 0;
|
||||
end
|
||||
if (cyc==20) begin
|
||||
$write("[%0t] cyc%0d: c1 %0x %0x c2 %0x %0x c3 %0x %0x c4 %0x %0x\n", $time, cyc,
|
||||
c1_data.value, c1_data.reset,
|
||||
c2_data.value, c2_data.reset,
|
||||
c3_data.value, c3_data.reset,
|
||||
c4_data.value, c4_data.reset);
|
||||
if (c1_data.value != 2) $stop;
|
||||
if (c2_data.value != 3) $stop;
|
||||
if (c3_data.value != 4) $stop;
|
||||
if (c4_data.value != 5) $stop;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
`ifndef VERILATOR
|
||||
|
|
@ -94,26 +117,26 @@ endmodule
|
|||
module counter_nansi
|
||||
(clkm, c_data, i_value);
|
||||
|
||||
input clkm;
|
||||
counter_if c_data;
|
||||
input logic [3:0] i_value;
|
||||
input clkm;
|
||||
counter_if c_data;
|
||||
input logic [3:0] i_value;
|
||||
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
endmodule : counter_nansi
|
||||
`endif
|
||||
|
||||
module counter_ansi_m
|
||||
(
|
||||
input clkm,
|
||||
counter_if.counter_mp c_data,
|
||||
input logic [3:0] i_value
|
||||
);
|
||||
(
|
||||
input clkm,
|
||||
counter_if.counter_mp c_data,
|
||||
input logic [3:0] i_value
|
||||
);
|
||||
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
endmodule : counter_ansi_m
|
||||
|
||||
`ifndef VERILATOR
|
||||
|
|
@ -121,12 +144,12 @@ endmodule : counter_ansi_m
|
|||
module counter_nansi_m
|
||||
(clkm, c_data, i_value);
|
||||
|
||||
input clkm;
|
||||
counter_if.counter_mp c_data;
|
||||
input logic [3:0] i_value;
|
||||
input clkm;
|
||||
counter_if.counter_mp c_data;
|
||||
input logic [3:0] i_value;
|
||||
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
always @ (posedge clkm) begin
|
||||
c_data.value <= c_data.reset ? i_value : c_data.value + 1;
|
||||
end
|
||||
endmodule : counter_nansi_m
|
||||
`endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(timing_loop=True, verilator_flags2=['--timing'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// 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: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
// verilator lint_off MULTIDRIVEN
|
||||
|
||||
interface ifc;
|
||||
wire we0, we2;
|
||||
tri [15:0] d;
|
||||
endinterface
|
||||
|
||||
interface ifc_multi;
|
||||
wire we0, wea, web;
|
||||
tri [15:0] d;
|
||||
endinterface
|
||||
|
||||
module bot (
|
||||
ifc io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.we2 ? 16'hd2 : 16'hzzzz;
|
||||
endmodule
|
||||
|
||||
module passthru (
|
||||
ifc io_ifc
|
||||
);
|
||||
bot u_bot (.*);
|
||||
endmodule
|
||||
|
||||
module bot_a (
|
||||
ifc_multi io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.wea ? 16'hd2 : 16'hzzzz;
|
||||
endmodule
|
||||
|
||||
module bot_b (
|
||||
ifc_multi io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.web ? 16'hd2 : 16'hzzzz;
|
||||
endmodule
|
||||
|
||||
module passthru_multi_c (
|
||||
ifc_multi io_ifc
|
||||
);
|
||||
bot_a u_bot_a (.*);
|
||||
bot_b u_bot_b (.*);
|
||||
endmodule
|
||||
|
||||
module passthru_deep (
|
||||
ifc io_ifc
|
||||
);
|
||||
passthru u_inner (.*);
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
ifc io_ifc ();
|
||||
ifc io_ifc_local ();
|
||||
ifc io_ifc_b0 ();
|
||||
ifc io_ifc_b1 ();
|
||||
ifc_multi io_ifc_mc ();
|
||||
ifc io_arr [1:0]();
|
||||
ifc io_ifc_deep ();
|
||||
|
||||
// Test top assignment
|
||||
assign io_ifc.d = io_ifc.we0 ? 16'hd0 : 16'hzzzz;
|
||||
assign io_ifc_local.d = io_ifc_local.we0 ? 16'hd0 : 16'hzzzz;
|
||||
assign io_ifc_mc.d = io_ifc_mc.we0 ? 16'hd0 : 16'hzzzz;
|
||||
|
||||
logic we0, we2;
|
||||
logic we2_b0, we2_b1;
|
||||
logic we0_mc, wea_mc, web_mc;
|
||||
assign io_ifc.we0 = we0;
|
||||
assign io_ifc.we2 = we2;
|
||||
assign io_ifc_local.we0 = we0;
|
||||
assign io_ifc_local.we2 = 1'b0;
|
||||
assign io_ifc_b0.we0 = 1'b0;
|
||||
assign io_ifc_b0.we2 = we2_b0;
|
||||
assign io_ifc_b1.we0 = 1'b0;
|
||||
assign io_ifc_b1.we2 = we2_b1;
|
||||
assign io_ifc_mc.we0 = we0_mc;
|
||||
assign io_ifc_mc.wea = wea_mc;
|
||||
assign io_ifc_mc.web = web_mc;
|
||||
|
||||
// Interface array signals
|
||||
logic we2_arr0, we2_arr1;
|
||||
assign io_arr[0].we0 = 1'b0;
|
||||
assign io_arr[0].we2 = we2_arr0;
|
||||
assign io_arr[1].we0 = 1'b0;
|
||||
assign io_arr[1].we2 = we2_arr1;
|
||||
|
||||
// Deep nesting signals
|
||||
logic we0_deep, we2_deep;
|
||||
assign io_ifc_deep.we0 = we0_deep;
|
||||
assign io_ifc_deep.we2 = we2_deep;
|
||||
assign io_ifc_deep.d = io_ifc_deep.we0 ? 16'hd0 : 16'hzzzz;
|
||||
|
||||
passthru u_passthru (.*);
|
||||
passthru u_passthru_b0 (.io_ifc(io_ifc_b0));
|
||||
passthru u_passthru_b1 (.io_ifc(io_ifc_b1));
|
||||
passthru_multi_c u_passthru_mc (.io_ifc(io_ifc_mc));
|
||||
passthru u_passthru_arr0 (.io_ifc(io_arr[0]));
|
||||
passthru u_passthru_arr1 (.io_ifc(io_arr[1]));
|
||||
passthru_deep u_passthru_deep (.io_ifc(io_ifc_deep));
|
||||
|
||||
initial begin
|
||||
#1;
|
||||
we0 = 1'b0;
|
||||
we2 = 1'b0;
|
||||
we2_b0 = 1'b0;
|
||||
we2_b1 = 1'b0;
|
||||
we0_mc = 1'b0;
|
||||
wea_mc = 1'b0;
|
||||
web_mc = 1'b0;
|
||||
we2_arr0 = 1'b0;
|
||||
we2_arr1 = 1'b0;
|
||||
we0_deep = 1'b0;
|
||||
we2_deep = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc.d, 16'hzzzz);
|
||||
`checkh(io_ifc_local.d, 16'hzzzz);
|
||||
`checkh(io_ifc_b0.d, 16'hzzzz);
|
||||
`checkh(io_ifc_b1.d, 16'hzzzz);
|
||||
`checkh(io_ifc_mc.d, 16'hzzzz);
|
||||
`checkh(io_arr[0].d, 16'hzzzz);
|
||||
`checkh(io_arr[1].d, 16'hzzzz);
|
||||
`checkh(io_ifc_deep.d, 16'hzzzz);
|
||||
|
||||
#1;
|
||||
we0 = 1'b1;
|
||||
we2 = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc.d, 16'hd0);
|
||||
`checkh(io_ifc_local.d, 16'hd0);
|
||||
|
||||
#1;
|
||||
we0 = 1'b0;
|
||||
we2 = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc.d, 16'hzzzz);
|
||||
`checkh(io_ifc_local.d, 16'hzzzz);
|
||||
|
||||
#1;
|
||||
we0 = 1'b0;
|
||||
we2 = 1'b1;
|
||||
#1;
|
||||
`checkh(io_ifc.d, 16'hd2);
|
||||
`checkh(io_ifc_local.d, 16'hzzzz);
|
||||
|
||||
// Interface passed a->b, where b is instantiated multiple times (separate interfaces)
|
||||
#1;
|
||||
we2_b0 = 1'b1;
|
||||
we2_b1 = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_b0.d, 16'hd2);
|
||||
`checkh(io_ifc_b1.d, 16'hzzzz);
|
||||
|
||||
#1;
|
||||
we2_b0 = 1'b0;
|
||||
we2_b1 = 1'b1;
|
||||
#1;
|
||||
`checkh(io_ifc_b0.d, 16'hzzzz);
|
||||
`checkh(io_ifc_b1.d, 16'hd2);
|
||||
|
||||
#1;
|
||||
we2_b0 = 1'b0;
|
||||
we2_b1 = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_b0.d, 16'hzzzz);
|
||||
`checkh(io_ifc_b1.d, 16'hzzzz);
|
||||
|
||||
// Interface passed a->b, where c is instantiated multiple times (same interface)
|
||||
#1;
|
||||
we0_mc = 1'b1;
|
||||
wea_mc = 1'b0;
|
||||
web_mc = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_mc.d, 16'hd0);
|
||||
|
||||
#1;
|
||||
we0_mc = 1'b0;
|
||||
wea_mc = 1'b1;
|
||||
web_mc = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_mc.d, 16'hd2);
|
||||
|
||||
#1;
|
||||
we0_mc = 1'b0;
|
||||
wea_mc = 1'b0;
|
||||
web_mc = 1'b1;
|
||||
#1;
|
||||
`checkh(io_ifc_mc.d, 16'hd2);
|
||||
|
||||
#1;
|
||||
wea_mc = 1'b1;
|
||||
web_mc = 1'b1;
|
||||
#1;
|
||||
`checkh(io_ifc_mc.d, 16'hd2);
|
||||
|
||||
#1;
|
||||
wea_mc = 1'b0;
|
||||
web_mc = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_mc.d, 16'hzzzz);
|
||||
|
||||
// Interface array: each element is independent
|
||||
#1;
|
||||
we2_arr0 = 1'b1;
|
||||
we2_arr1 = 1'b0;
|
||||
#1;
|
||||
`checkh(io_arr[0].d, 16'hd2);
|
||||
`checkh(io_arr[1].d, 16'hzzzz);
|
||||
|
||||
#1;
|
||||
we2_arr0 = 1'b0;
|
||||
we2_arr1 = 1'b1;
|
||||
#1;
|
||||
`checkh(io_arr[0].d, 16'hzzzz);
|
||||
`checkh(io_arr[1].d, 16'hd2);
|
||||
|
||||
#1;
|
||||
we2_arr0 = 1'b0;
|
||||
we2_arr1 = 1'b0;
|
||||
#1;
|
||||
`checkh(io_arr[0].d, 16'hzzzz);
|
||||
`checkh(io_arr[1].d, 16'hzzzz);
|
||||
|
||||
// Deep nesting: passthru_deep -> passthru -> bot (3 levels)
|
||||
#1;
|
||||
we0_deep = 1'b1;
|
||||
we2_deep = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_deep.d, 16'hd0);
|
||||
|
||||
#1;
|
||||
we0_deep = 1'b0;
|
||||
we2_deep = 1'b1;
|
||||
#1;
|
||||
`checkh(io_ifc_deep.d, 16'hd2);
|
||||
|
||||
#1;
|
||||
we0_deep = 1'b0;
|
||||
we2_deep = 1'b0;
|
||||
#1;
|
||||
`checkh(io_ifc_deep.d, 16'hzzzz);
|
||||
|
||||
#1;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
%Warning-MULTIDRIVEN: t/t_lint_multidriven_coverage_bad.v:13:15: Bit [0] of signal 'w' have multiple combinational drivers. This can cause performance degradation.
|
||||
: ... note: In instance 't'
|
||||
t/t_lint_multidriven_coverage_bad.v:15:15: ... Location of offending driver
|
||||
15 | assign w[0] = a;
|
||||
| ^
|
||||
t/t_lint_multidriven_coverage_bad.v:16:15: ... Location of offending driver
|
||||
16 | assign w[0] = b;
|
||||
| ^
|
||||
... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest
|
||||
... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(verilator_flags2=["--coverage"], fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic a,
|
||||
input logic b,
|
||||
output logic [1:0] y
|
||||
);
|
||||
|
||||
logic [1:0] w;
|
||||
|
||||
assign w[0] = a; // <--- Warning
|
||||
assign w[0] = b; // <--- Warning
|
||||
assign w[1] = 1'b0;
|
||||
|
||||
assign y = w;
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:34:16: Unsupported tristate construct: hierarchical driver of non-interface tri signal: 'bus'
|
||||
: ... note: In instance 't'
|
||||
34 | assign u_sub.bus = hier_we ? 8'hBB : 8'hzz;
|
||||
| ^~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:23:13: Unsupported tristate construct (in graph; not converted): VAR 'bus'
|
||||
: ... note: In instance 't.u_sub'
|
||||
23 | tri [7:0] bus;
|
||||
| ^~~
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Test case #1: Hierarchical write to a non-interface submodule's tri wire.
|
||||
// A parent module drives a child module's internal tri wire via hierarchical
|
||||
// reference, in addition to the child's own internal driver.
|
||||
//
|
||||
// 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: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
// verilator lint_off MULTIDRIVEN
|
||||
|
||||
module sub_with_tri (
|
||||
input we_internal
|
||||
);
|
||||
tri [7:0] bus;
|
||||
// Internal driver: drives 8'hAA when we_internal is high
|
||||
assign bus = we_internal ? 8'hAA : 8'hzz;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
logic sub_we_internal;
|
||||
sub_with_tri u_sub(.we_internal(sub_we_internal));
|
||||
|
||||
logic hier_we;
|
||||
// Drive u_sub.bus hierarchically from this module
|
||||
assign u_sub.bus = hier_we ? 8'hBB : 8'hzz;
|
||||
|
||||
initial begin
|
||||
// All drivers off -> high-Z
|
||||
#1;
|
||||
hier_we = 1'b0;
|
||||
sub_we_internal = 1'b0;
|
||||
#1;
|
||||
`checkh(u_sub.bus, 8'hzz);
|
||||
|
||||
// External hierarchical driver on
|
||||
#1;
|
||||
hier_we = 1'b1;
|
||||
sub_we_internal = 1'b0;
|
||||
#1;
|
||||
`checkh(u_sub.bus, 8'hBB);
|
||||
|
||||
// Internal driver on, external off
|
||||
#1;
|
||||
hier_we = 1'b0;
|
||||
sub_we_internal = 1'b1;
|
||||
#1;
|
||||
`checkh(u_sub.bus, 8'hAA);
|
||||
|
||||
// Both off again
|
||||
#1;
|
||||
hier_we = 1'b0;
|
||||
sub_we_internal = 1'b0;
|
||||
#1;
|
||||
`checkh(u_sub.bus, 8'hzz);
|
||||
|
||||
#1;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
%Error: t/t_tri_iface_eqcase_modport_bad.v:23:25: Can't find definition of 'd' in dotted variable/method: 'io_ifc.d'
|
||||
23 | assign is_z = (io_ifc.d === 8'hzz);
|
||||
| ^
|
||||
... Known scopes under 'io_ifc': <no instances found>
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Verify that modport access control is not relaxed by tristate __en
|
||||
// auto-insertion: accessing a tri signal not exposed through the modport
|
||||
// should still produce a linker error.
|
||||
//
|
||||
// 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: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
interface ifc;
|
||||
logic we;
|
||||
tri [7:0] d;
|
||||
modport no_d_mp (input we); // d is NOT exposed
|
||||
endinterface
|
||||
|
||||
module chk_bad (
|
||||
ifc.no_d_mp io_ifc,
|
||||
output logic is_z
|
||||
);
|
||||
assign is_z = (io_ifc.d === 8'hzz);
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
ifc i ();
|
||||
logic is_z;
|
||||
chk_bad u (.io_ifc(i), .is_z(is_z));
|
||||
initial $finish;
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(timing_loop=True, verilator_flags2=['--timing'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// 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: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
// verilator lint_off MULTIDRIVEN
|
||||
|
||||
// ---------------- Compare semantics (=== / !==) ----------------
|
||||
interface ifc_cmp;
|
||||
logic we;
|
||||
tri [7:0] d;
|
||||
endinterface
|
||||
|
||||
module drv_cmp (
|
||||
ifc_cmp io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz;
|
||||
endmodule
|
||||
|
||||
module chk_cmp (
|
||||
ifc_cmp io_ifc,
|
||||
output logic is_z,
|
||||
output logic is_5a,
|
||||
output logic not_z,
|
||||
output logic not_5a
|
||||
);
|
||||
assign is_z = (io_ifc.d === 8'hzz);
|
||||
assign is_5a = (io_ifc.d === 8'h5A);
|
||||
assign not_z = (io_ifc.d !== 8'hzz);
|
||||
assign not_5a = (io_ifc.d !== 8'h5A);
|
||||
endmodule
|
||||
|
||||
interface ifc_cmp_mp;
|
||||
logic we;
|
||||
tri [7:0] d;
|
||||
modport drv_mp (input we, inout d);
|
||||
modport chk_mp (input we, inout d);
|
||||
endinterface
|
||||
|
||||
module drv_cmp_mp (
|
||||
ifc_cmp_mp.drv_mp io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz;
|
||||
endmodule
|
||||
|
||||
module chk_cmp_mp (
|
||||
ifc_cmp_mp.chk_mp io_ifc,
|
||||
output logic is_z,
|
||||
output logic is_5a,
|
||||
output logic not_z,
|
||||
output logic not_5a
|
||||
);
|
||||
assign is_z = (io_ifc.d === 8'hzz);
|
||||
assign is_5a = (io_ifc.d === 8'h5A);
|
||||
assign not_z = (io_ifc.d !== 8'hzz);
|
||||
assign not_5a = (io_ifc.d !== 8'h5A);
|
||||
endmodule
|
||||
|
||||
module passthru_cmp (
|
||||
ifc_cmp io_ifc
|
||||
);
|
||||
drv_cmp u_drv (.*);
|
||||
endmodule
|
||||
|
||||
// ---------------- Mixed local + external contributors ----------------
|
||||
interface ifc_mix;
|
||||
logic we_local;
|
||||
logic we_ext;
|
||||
tri [7:0] d;
|
||||
assign d = we_local ? 8'hA5 : 8'hzz;
|
||||
endinterface
|
||||
|
||||
module drv_mix (
|
||||
ifc_mix io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz;
|
||||
endmodule
|
||||
|
||||
interface ifc_mix_mp;
|
||||
logic we_local;
|
||||
logic we_ext;
|
||||
tri [7:0] d;
|
||||
modport drv_mp (input we_ext, inout d);
|
||||
assign d = we_local ? 8'hA5 : 8'hzz;
|
||||
endinterface
|
||||
|
||||
module drv_mix_mp (
|
||||
ifc_mix_mp.drv_mp io_ifc
|
||||
);
|
||||
assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
// ---- Compare semantics: basic ----
|
||||
ifc_cmp i_cmp ();
|
||||
logic is_z, is_5a, not_z, not_5a;
|
||||
drv_cmp u_drv_cmp (.io_ifc(i_cmp));
|
||||
chk_cmp u_chk_cmp (.io_ifc(i_cmp), .is_z(is_z), .is_5a(is_5a),
|
||||
.not_z(not_z), .not_5a(not_5a));
|
||||
|
||||
// ---- Compare semantics: modport ----
|
||||
ifc_cmp_mp i_cmp_mp ();
|
||||
logic mp_is_z, mp_is_5a, mp_not_z, mp_not_5a;
|
||||
drv_cmp_mp u_drv_cmp_mp (.io_ifc(i_cmp_mp));
|
||||
chk_cmp_mp u_chk_cmp_mp (.io_ifc(i_cmp_mp), .is_z(mp_is_z), .is_5a(mp_is_5a),
|
||||
.not_z(mp_not_z), .not_5a(mp_not_5a));
|
||||
|
||||
// ---- Compare semantics: deep hierarchy ----
|
||||
ifc_cmp i_cmp_deep ();
|
||||
logic deep_is_z, deep_is_5a, deep_not_z, deep_not_5a;
|
||||
passthru_cmp u_cmp_deep (.io_ifc(i_cmp_deep));
|
||||
chk_cmp u_chk_cmp_deep (.io_ifc(i_cmp_deep), .is_z(deep_is_z), .is_5a(deep_is_5a),
|
||||
.not_z(deep_not_z), .not_5a(deep_not_5a));
|
||||
|
||||
// ---- Mixed contributors ----
|
||||
ifc_mix i_mix ();
|
||||
drv_mix u_mix (.io_ifc(i_mix));
|
||||
|
||||
ifc_mix_mp i_mix_mp ();
|
||||
drv_mix_mp u_mix_mp (.io_ifc(i_mix_mp));
|
||||
|
||||
initial begin
|
||||
// ---- Compare semantics ----
|
||||
i_cmp.we = 1'b0;
|
||||
i_cmp_mp.we = 1'b0;
|
||||
i_cmp_deep.we = 1'b0;
|
||||
#1;
|
||||
|
||||
`checkh(is_z, 1'b1);
|
||||
`checkh(is_5a, 1'b0);
|
||||
`checkh(not_z, 1'b0);
|
||||
`checkh(not_5a, 1'b1);
|
||||
`checkh(mp_is_z, 1'b1);
|
||||
`checkh(mp_is_5a, 1'b0);
|
||||
`checkh(mp_not_z, 1'b0);
|
||||
`checkh(mp_not_5a, 1'b1);
|
||||
`checkh(deep_is_z, 1'b1);
|
||||
`checkh(deep_is_5a, 1'b0);
|
||||
|
||||
#1;
|
||||
i_cmp.we = 1'b1;
|
||||
i_cmp_mp.we = 1'b1;
|
||||
i_cmp_deep.we = 1'b1;
|
||||
#1;
|
||||
|
||||
`checkh(is_z, 1'b0);
|
||||
`checkh(is_5a, 1'b1);
|
||||
`checkh(not_z, 1'b1);
|
||||
`checkh(not_5a, 1'b0);
|
||||
`checkh(mp_is_z, 1'b0);
|
||||
`checkh(mp_is_5a, 1'b1);
|
||||
`checkh(mp_not_z, 1'b1);
|
||||
`checkh(mp_not_5a, 1'b0);
|
||||
`checkh(deep_is_z, 1'b0);
|
||||
`checkh(deep_is_5a, 1'b1);
|
||||
|
||||
#1;
|
||||
i_cmp.we = 1'b0;
|
||||
i_cmp_mp.we = 1'b0;
|
||||
i_cmp_deep.we = 1'b0;
|
||||
#1;
|
||||
|
||||
`checkh(is_z, 1'b1);
|
||||
`checkh(is_5a, 1'b0);
|
||||
`checkh(not_z, 1'b0);
|
||||
`checkh(not_5a, 1'b1);
|
||||
`checkh(mp_is_z, 1'b1);
|
||||
`checkh(mp_is_5a, 1'b0);
|
||||
`checkh(mp_not_z, 1'b0);
|
||||
`checkh(mp_not_5a, 1'b1);
|
||||
`checkh(deep_is_z, 1'b1);
|
||||
`checkh(deep_is_5a, 1'b0);
|
||||
|
||||
// ---- Mixed contributors ----
|
||||
i_mix.we_local = 1'b0;
|
||||
i_mix.we_ext = 1'b0;
|
||||
i_mix_mp.we_local = 1'b0;
|
||||
i_mix_mp.we_ext = 1'b0;
|
||||
#1;
|
||||
|
||||
`checkh(i_mix.d, 8'hzz);
|
||||
`checkh(i_mix_mp.d, 8'hzz);
|
||||
|
||||
#1;
|
||||
i_mix.we_local = 1'b1;
|
||||
i_mix.we_ext = 1'b0;
|
||||
i_mix_mp.we_local = 1'b1;
|
||||
i_mix_mp.we_ext = 1'b0;
|
||||
#1;
|
||||
`checkh(i_mix.d, 8'hA5);
|
||||
`checkh(i_mix_mp.d, 8'hA5);
|
||||
|
||||
#1;
|
||||
i_mix.we_local = 1'b0;
|
||||
i_mix.we_ext = 1'b1;
|
||||
i_mix_mp.we_local = 1'b0;
|
||||
i_mix_mp.we_ext = 1'b1;
|
||||
#1;
|
||||
`checkh(i_mix.d, 8'h3C);
|
||||
`checkh(i_mix_mp.d, 8'h3C);
|
||||
|
||||
#1;
|
||||
i_mix.we_local = 1'b1;
|
||||
i_mix.we_ext = 1'b1;
|
||||
i_mix_mp.we_local = 1'b1;
|
||||
i_mix_mp.we_ext = 1'b1;
|
||||
#1;
|
||||
`checkh(i_mix.d, 8'hBD);
|
||||
`checkh(i_mix_mp.d, 8'hBD);
|
||||
|
||||
#1;
|
||||
i_mix.we_local = 1'b0;
|
||||
i_mix.we_ext = 1'b0;
|
||||
i_mix_mp.we_local = 1'b0;
|
||||
i_mix_mp.we_ext = 1'b0;
|
||||
#1;
|
||||
`checkh(i_mix.d, 8'hzz);
|
||||
`checkh(i_mix_mp.d, 8'hzz);
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(timing_loop=True, verilator_flags2=['--timing'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Test case #4: $root absolute hierarchical reference to tristate signal.
|
||||
// A submodule reads a tristate signal from the top module via $root.
|
||||
//
|
||||
// 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: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
module sub_root_reader (
|
||||
output logic [7:0] val
|
||||
);
|
||||
assign val = $root.t.root_bus;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
tri [7:0] root_bus;
|
||||
logic root_we;
|
||||
assign root_bus = root_we ? 8'hCC : 8'hzz;
|
||||
|
||||
logic [7:0] root_readback;
|
||||
sub_root_reader u_reader(.val(root_readback));
|
||||
|
||||
initial begin
|
||||
#1;
|
||||
root_we = 1'b0;
|
||||
#1;
|
||||
`checkh(root_readback, 8'hzz);
|
||||
|
||||
#1;
|
||||
root_we = 1'b1;
|
||||
#1;
|
||||
`checkh(root_readback, 8'hCC);
|
||||
|
||||
#1;
|
||||
root_we = 1'b0;
|
||||
#1;
|
||||
`checkh(root_readback, 8'hzz);
|
||||
|
||||
#1;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue