This commit is contained in:
Ethan Sifferman 2026-07-13 23:25:55 +00:00 committed by GitHub
commit 0eb01eec04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 455 additions and 45 deletions

View File

@ -152,6 +152,9 @@ class DelayedVisitor final : public VNVisitor {
const AstActive* m_fistActivep = nullptr;
bool m_whole = false; // Used on LHS of NBA directly via VarRef
bool m_partial = false; // Used on LHS of NBA under a Sel
bool m_multiNbasSameProc = false; // Same process has >1 NBA to this variable
AstNodeProcedure* m_lastSuspNbaProcp = nullptr; // Last process with susp NBA to this var
bool m_hasBlockingWrite = false; // Also has blocking write (not just NBAs)
bool m_inLoop = false; // Used on LHS of NBA in a loop
bool m_inSuspOrFork = false; // Used on LHS of NBA in suspendable process or fork
Scheme m_scheme = Scheme::Undecided; // Conversion scheme to use for this variable
@ -284,6 +287,17 @@ class DelayedVisitor final : public VNVisitor {
AstAssignDly* m_nextDlyp = nullptr; // The nextp of the previous AstAssignDly
AstVarScope* m_prevVscp = nullptr; // The target of the previous AstAssignDly
// STATE - for inline commit insertion in suspendable processes
std::set<AstVarScope*> m_suspBlockingWriteVscps; // Arrays with blocking writes in susp
std::map<AstNodeProcedure*, std::vector<AstCAwait*>>
m_suspCawaits; // CAwait nodes per suspendable process
struct CommitQueuePair final {
AstVarScope* queueVscp; // The commit queue variable
AstVarScope* targetVscp; // The target variable
};
std::vector<CommitQueuePair> m_suspCommitQueues; // Commit queues for inline commits
std::set<AstNodeProcedure*> m_suspValueQueueProcs; // Processes with ValueQueuePartial NBAs
// STATE - Statistic tracking
VDouble0 m_nSchemeShadowVar; // Number of variables using Scheme::ShadowVar
VDouble0 m_nSchemeShadowVarMasked; // Number of variabels using Scheme::ShadowVarMasked
@ -417,8 +431,11 @@ class DelayedVisitor final : public VNVisitor {
const AstNodeDType* const dtypep = vscp->dtypep()->skipRefp();
// Unpacked arrays
if (const AstUnpackArrayDType* const uaDTypep = VN_CAST(dtypep, UnpackArrayDType)) {
// If whole array is target of NBA, use ShadowVar
if (vscpInfo.m_whole) return Scheme::ShadowVar;
// If whole array is target of NBA, use ShadowVar. In a suspendable or fork,
// fall through to the value-queue handling below, which preserves NBA execution
// order across suspend points (whole-array assigns are split into per-element
// enqueues in convertSchemeValueQueue).
if (vscpInfo.m_whole && !vscpInfo.m_inSuspOrFork) return Scheme::ShadowVar;
// Basic underlying type of elements, if any.
const AstBasicDType* const basicp = uaDTypep->basicp();
// If used in a loop, we must have a dynamic commit queue. (Also works in suspendables)
@ -433,8 +450,17 @@ class DelayedVisitor final : public VNVisitor {
if (vscpInfo.m_partial) return Scheme::ValueQueuePartial;
return Scheme::ValueQueueWhole;
}
// In a suspendable of fork, we must use the unique flag scheme, TODO: why?
if (vscpInfo.m_inSuspOrFork) return Scheme::FlagUnique;
// In a suspendable or fork, use value queue for same-process multi-NBA
// variables to preserve execution order across suspend points.
// Variables with blocking writes fall back to FlagUnique to avoid
// circular dependencies in the ordering graph.
if (vscpInfo.m_inSuspOrFork) {
if (vscpInfo.m_multiNbasSameProc && basicp && basicp->isIntegralOrPacked()
&& !vscpInfo.m_hasBlockingWrite) {
return Scheme::ValueQueuePartial;
}
return Scheme::FlagUnique;
}
// Otherwise if an array of packed/basic elements, use the shared flag scheme
if (basicp) return Scheme::FlagShared;
// Finally fall back on the shadow variable scheme, e.g. for
@ -443,10 +469,17 @@ class DelayedVisitor final : public VNVisitor {
return Scheme::ShadowVar;
}
// In a suspendable of fork, we must use the unique flag scheme, TODO: why?
if (vscpInfo.m_inSuspOrFork) return Scheme::FlagUnique;
const bool isIntegralOrPacked = dtypep->isIntegralOrPacked();
// In a suspendable or fork, use value queue for same-process multi-NBA
// packed variables ("last NBA wins" with correct execution order).
// Otherwise use the unique flag scheme.
if (vscpInfo.m_inSuspOrFork) {
if (vscpInfo.m_multiNbasSameProc && isIntegralOrPacked) {
return Scheme::ValueQueuePartial;
}
return Scheme::FlagUnique;
}
// Check for mixed usage (this also warns if not OK)
if (checkMixedUsage(vscp, isIntegralOrPacked)) {
// If it's a variable updated by both blocking and non-blocking
@ -855,9 +888,12 @@ class DelayedVisitor final : public VNVisitor {
queueVscp->varp()->noReset(true);
queueVscp->varp()->setIgnorePostWrite();
vscpInfo.valueQueueKit().vscp = queueVscp;
// Record for inline commits if in a suspendable process
if (vscpInfo.m_inSuspOrFork) m_suspCommitQueues.push_back({queueVscp, vscp});
// Create the AstActive for the Post logic
AstActive* const activep
= new AstActive{flp, "nba-value-queue-whole", vscpInfo.senTreep()};
= new AstActive{flp, N_Partial ? "nba-value-queue-partial" : "nba-value-queue-whole",
vscpInfo.senTreep()};
activep->senTreeStorep(vscpInfo.senTreep());
scopep->addBlocksp(activep);
// Add 'Post' scheduled process for the commit
@ -871,6 +907,44 @@ class DelayedVisitor final : public VNVisitor {
postp->addStmtsp(callp->makeStmt());
}
// Create a properly-sized 32-bit unsigned constant (post-WidthCommit, width==widthMin).
AstConst* makeU32Const(FileLine* flp, const AstNode* contextp, uint32_t value) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{},
contextp->findBasicDType(VBasicDTypeKwd::UINT32)};
cp->num().setLong(value);
return cp;
}
// Split a whole-(sub-)array NBA ('arr <= rhs_arr') into per-element NBAs and convert each.
// The partial commit queue's enqueue() takes a scalar element, so we recurse through
// convertSchemeValueQueue - each per-element NBA then hits the normal ArraySel path.
void splitAndConvertWholeArrayNba(AstAssignDly* nodep, AstVarScope* vscp,
VarScopeInfo& vscpInfo,
const AstUnpackArrayDType* uaDTypep) {
FileLine* const flp = nodep->fileline();
AstScope* const scopep = VN_AS(nodep->user2p(), Scope);
AstNodeExpr* const origLhsp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* const origRhsp = nodep->rhsp()->unlinkFrBack();
const int loIdx = uaDTypep->rangep()->loConst();
const int hiIdx = loIdx + uaDTypep->elementsConst();
std::vector<AstAssignDly*> perElementNbas;
perElementNbas.reserve(hiIdx - loIdx);
for (int i = loIdx; i < hiIdx; ++i) {
AstAssignDly* const elemNbap = new AstAssignDly{
flp, new AstArraySel{flp, origLhsp->cloneTree(false), makeU32Const(flp, vscp, i)},
new AstArraySel{flp, origRhsp->cloneTree(false), makeU32Const(flp, vscp, i)}};
elemNbap->user2p(scopep);
nodep->addHereThisAsNext(elemNbap);
perElementNbas.push_back(elemNbap);
}
VL_DO_DANGLING(pushDeletep(origLhsp), origLhsp);
VL_DO_DANGLING(pushDeletep(origRhsp), origRhsp);
pushDeletep(nodep->unlinkFrBack());
for (AstAssignDly* const elemNbap : perElementNbas) {
convertSchemeValueQueue(elemNbap, vscp, vscpInfo, /* partial: */ true);
}
}
void convertSchemeValueQueue(AstAssignDly* nodep, AstVarScope* vscp, VarScopeInfo& vscpInfo,
bool partial) {
UASSERT_OBJ(partial ? vscpInfo.m_scheme == Scheme::ValueQueuePartial
@ -879,6 +953,16 @@ class DelayedVisitor final : public VNVisitor {
FileLine* const flp = vscp->fileline();
AstScope* const scopep = VN_AS(nodep->user2p(), Scope);
// A whole-(sub-)array NBA under the partial scheme needs splitting into per-element NBAs,
// as enqueue() takes a scalar element, not a whole VlUnpacked array.
if (partial) {
if (const AstUnpackArrayDType* const uaDTypep
= VN_CAST(nodep->lhsp()->dtypep()->skipRefp(), UnpackArrayDType)) {
splitAndConvertWholeArrayNba(nodep, vscp, vscpInfo, uaDTypep);
return;
}
}
// Base name suffix for signals constructed below
const std::string baseName = uniqueTmpName(scopep, vscp, vscpInfo);
@ -916,39 +1000,9 @@ class DelayedVisitor final : public VNVisitor {
AstNodeExpr* const sLsbp = lSelp->lsbp();
const int sWidth = lSelp->widthConst();
// Create mask value
maskp = [&]() -> AstNodeExpr* {
// Constant mask we can compute here
if (const AstConst* const cLsbp = VN_CAST(sLsbp, Const)) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{}, eDTypep};
cp->num().setMask(sWidth, cLsbp->toSInt());
return cp;
}
// A non-constant mask we must compute at run-time.
AstConst* const onesp = new AstConst{flp, AstConst::WidthedValue{}, sWidth, 0};
onesp->num().setAllBits1();
return createWidened(flp, scopep, eDTypep, sLsbp, sWidth,
"__VdlyMask" + baseName, onesp, nodep);
}();
// Adjust value to element size
valuep = [&]() -> AstNodeExpr* {
// Constant value with constant select we can compute here
if (AstConst* const cValuep = VN_CAST(valuep, Const)) {
if (const AstConst* const cLsbp = VN_CAST(sLsbp, Const)) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{}, eDTypep};
cp->num().setAllBits0();
cp->num().opSelInto(cValuep->num(), cLsbp->toSInt(), sWidth);
VL_DO_DANGLING(valuep->deleteTree(), valuep);
return cp;
}
}
// A non-constant value we must adjust.
return createWidened(flp, scopep, eDTypep, sLsbp, sWidth, //
"__VdlyElem" + baseName, valuep, nodep);
}();
// Use helper to create mask and widened value
std::tie(maskp, valuep) = createPartialUpdateMaskAndValue(
flp, scopep, eDTypep, sLsbp, sWidth, baseName, valuep, nodep);
} else {
// If this assignment is not partial, set mask to ones and we are done
AstConst* const ones = new AstConst{flp, AstConst::DTyped{}, eDTypep};
@ -957,17 +1011,16 @@ class DelayedVisitor final : public VNVisitor {
}
}
// Extract array indices
// Extract array indices (if any - scalars have none)
std::vector<AstNodeExpr*> idxps;
{
UASSERT_OBJ(VN_IS(lhsNodep, ArraySel), lhsNodep, "Unexpected LHS form");
if (VN_IS(lhsNodep, ArraySel)) {
while (AstArraySel* const aSelp = VN_CAST(lhsNodep, ArraySel)) {
idxps.emplace_back(aSelp->bitp()->unlinkFrBack());
lhsNodep = aSelp->fromp();
}
UASSERT_OBJ(VN_IS(lhsNodep, VarRef), lhsNodep, "Unexpected LHS form");
std::reverse(idxps.begin(), idxps.end());
}
UASSERT_OBJ(VN_IS(lhsNodep, VarRef), lhsNodep, "Unexpected LHS form");
// Done with the LHS at this point
VL_DO_DANGLING(pushDeletep(capturedLhsp), capturedLhsp);
@ -986,6 +1039,51 @@ class DelayedVisitor final : public VNVisitor {
pushDeletep(nodep->unlinkFrBack());
}
// Create mask and widened value for a partial bit-select update
std::pair<AstNodeExpr*, AstNodeExpr*>
createPartialUpdateMaskAndValue(FileLine* flp, AstScope* scopep, AstNodeDType* eDTypep,
AstNodeExpr* lsbExpr, int width, const std::string& baseName,
AstNodeExpr* value, AstNode* insertBefore) {
AstNodeExpr* maskp = nullptr;
AstNodeExpr* valuep = value;
// Create mask value
maskp = [&]() -> AstNodeExpr* {
// Constant mask we can compute here
if (const AstConst* const cLsbp = VN_CAST(lsbExpr, Const)) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{}, eDTypep};
cp->num().setMask(width, cLsbp->toSInt());
return cp;
}
// A non-constant mask we must compute at run-time.
AstConst* const onesp = new AstConst{flp, AstConst::WidthedValue{}, width, 0};
onesp->num().setAllBits1();
return createWidened(flp, scopep, eDTypep, lsbExpr, width, "__VdlyMask" + baseName,
onesp, insertBefore);
}();
// Adjust value to element size
valuep = [&]() -> AstNodeExpr* {
// Constant value with constant select we can compute here
if (AstConst* const cValuep = VN_CAST(valuep, Const)) {
if (const AstConst* const cLsbp = VN_CAST(lsbExpr, Const)) {
AstConst* const cp = new AstConst{flp, AstConst::DTyped{}, eDTypep};
cp->num().setAllBits0();
cp->num().opSelInto(cValuep->num(), cLsbp->toSInt(), width);
VL_DO_DANGLING(valuep->deleteTree(), valuep);
return cp;
}
}
// A non-constant value we must adjust.
return createWidened(flp, scopep, eDTypep, lsbExpr, width, "__VdlyElem" + baseName,
valuep, insertBefore);
}();
return {maskp, valuep};
}
// Record where a variable is assigned
void recordWriteRef(AstVarRef* nodep, bool nonBlocking) {
// Ignore references in certain contexts
@ -1013,6 +1111,7 @@ class DelayedVisitor final : public VNVisitor {
// Decide which scheme to use for each variable and do the 'prepare' step
for (AstVarScope* const vscp : m_vscps) {
VarScopeInfo& vscpInfo = m_vscpInfo(vscp);
vscpInfo.m_hasBlockingWrite = m_suspBlockingWriteVscps.count(vscp) > 0;
vscpInfo.m_scheme = chooseScheme(vscp, vscpInfo);
// Run 'prepare' step
switch (vscpInfo.m_scheme) {
@ -1051,6 +1150,9 @@ class DelayedVisitor final : public VNVisitor {
case Scheme::ValueQueuePartial: {
++m_nSchemeValueQueuesPartial;
prepareSchemeValueQueue</* Partial: */ true>(vscp, vscpInfo);
if (vscpInfo.m_lastSuspNbaProcp) {
m_suspValueQueueProcs.insert(vscpInfo.m_lastSuspNbaProcp);
}
break;
}
}
@ -1097,6 +1199,33 @@ class DelayedVisitor final : public VNVisitor {
break;
}
}
// Insert inline commit calls before each CAwait in processes that have
// ValueQueuePartial NBAs. Only target those processes, not other suspendable
// processes (e.g., timed continuous assigns) which would create ordering cycles.
if (!m_suspCommitQueues.empty()) {
for (AstNodeProcedure* const procp : m_suspValueQueueProcs) {
const auto it = m_suspCawaits.find(procp);
if (it == m_suspCawaits.end()) continue;
for (AstCAwait* const awaitp : it->second) {
// Find the enclosing statement
AstNodeStmt* stmtp = nullptr;
for (AstNode* np = awaitp; np; np = np->backp()) {
if ((stmtp = VN_CAST(np, NodeStmt))) break;
}
if (!stmtp) continue;
for (const auto& cq : m_suspCommitQueues) {
FileLine* const flp = stmtp->fileline();
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, cq.queueVscp, VAccess::READWRITE},
VCMethod::SCHED_COMMIT};
callp->dtypeSetVoid();
callp->addPinsp(new AstVarRef{flp, cq.targetVscp, VAccess::WRITE});
stmtp->addHereThisAsNext(callp->makeStmt());
}
}
}
}
}
void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
@ -1182,6 +1311,7 @@ class DelayedVisitor final : public VNVisitor {
}
void visit(AstCAwait* nodep) override {
if (nodep->sentreep()) m_timingDomains.insert(nodep->sentreep());
if (m_inSuspendableOrFork) m_suspCawaits[m_procp].push_back(nodep);
}
void visit(AstFireEvent* nodep) override {
UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
@ -1292,6 +1422,11 @@ class DelayedVisitor final : public VNVisitor {
vscpInfo.m_partial |= VN_IS(nodep->lhsp(), Sel);
vscpInfo.m_inLoop |= m_inLoop;
vscpInfo.m_inSuspOrFork |= m_inSuspendableOrFork;
// Track same-process multi-NBA for ValueQueuePartial scheme selection
if (m_inSuspendableOrFork) {
if (vscpInfo.m_lastSuspNbaProcp == m_procp) vscpInfo.m_multiNbasSameProc = true;
vscpInfo.m_lastSuspNbaProcp = m_procp;
}
// Sensitivity might be non-clocked, in a suspendable process, which are handled elsewhere
if (m_activep->sentreep()->hasClocked()) {
if (vscpInfo.m_fistActivep != m_activep) {
@ -1329,6 +1464,11 @@ class DelayedVisitor final : public VNVisitor {
if (nodep == m_currNbaLhsRefp) return;
// Only care about write refs
if (!nodep->access().isWriteOrRW()) return;
// Track blocking writes to arrays in suspendable processes
if (m_inSuspendableOrFork
&& VN_IS(nodep->varScopep()->dtypep()->skipRefp(), UnpackArrayDType)) {
m_suspBlockingWriteVscps.insert(nodep->varScopep());
}
// Record write reference
recordWriteRef(nodep, false);
}

View File

@ -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: 2024 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--assert", "-Wno-ZERODLY"])
test.execute()
test.passes()

View File

@ -0,0 +1,44 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Ethan Sifferman
// SPDX-License-Identifier: CC0-1.0
module t;
int delay = 0; always #1 delay = int'($time) / 4;
task automatic do_delay;
if (delay > 0) #(delay);
endtask
// `a` should match `b`
logic a = 0;
always begin
a <= 1;
do_delay();
a <= 0;
#1;
end
logic b = 0;
always begin
b <= 1;
do_delay();
b <= 0;
#1;
b <= 1; // this line should do nothing
end
always #1 assert (a == b);
initial begin
$dumpfile("dump.vcd");
$dumpvars;
#20;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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: 2024 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=["--binary", "--assert", "-Wno-ZERODLY"])
test.execute()
test.passes()

View File

@ -0,0 +1,190 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Ethan Sifferman
// SPDX-License-Identifier: CC0-1.0
// Test that mixing whole-variable and partial NBAs with suspend points
// correctly implements "last NBA wins" semantics for both scalars and arrays.
module t;
int delay = 0; always #1 delay = int'($time) / 4;
task automatic do_delay;
if (delay > 0) #(delay);
endtask
// ---- Scalar tests ----
// Test S1: whole then partial with suspend between
logic [7:0] s1 = 0;
always begin
s1 <= 8'hff;
do_delay();
s1[3:0] <= 4'h0;
#1;
assert (s1 == 8'hf0) else $fatal(0, "S1 whole->partial: expected f0, got %0h", s1);
end
// Test S2: partial then whole with suspend between
logic [7:0] s2 = 0;
always begin
s2[3:0] <= 4'h0;
do_delay();
s2 <= 8'hff;
#1;
assert (s2 == 8'hff) else $fatal(0, "S2 partial->whole: expected ff, got %0h", s2);
end
// Test S3: two partials to non-overlapping ranges
logic [7:0] s3 = 0;
always begin
s3[7:4] <= 4'ha;
do_delay();
s3[3:0] <= 4'h5;
#1;
assert (s3 == 8'ha5) else $fatal(0, "S3 partial+partial: expected a5, got %0h", s3);
end
// Test S4: two partials to same range (last wins)
logic [7:0] s4 = 0;
always begin
s4[3:0] <= 4'ha;
do_delay();
s4[3:0] <= 4'h5;
#1;
assert (s4 == 8'h05) else $fatal(0, "S4 partial overwrite: expected 05, got %0h", s4);
end
// Test S5: whole, partial, whole (last wins)
logic [7:0] s5 = 0;
always begin
s5 <= 8'haa;
do_delay();
s5[3:0] <= 4'hb;
do_delay();
s5 <= 8'hcc;
#1;
assert (s5 == 8'hcc) else $fatal(0, "S5 whole-partial-whole: expected cc, got %0h", s5);
end
// ---- Unpacked array element tests ----
// Test A1: whole element then partial element with suspend between
logic [7:0] a1 [0:3];
initial for (int i = 0; i < 4; i++) a1[i] = 0;
always begin
a1[1] <= 8'hff;
do_delay();
a1[1][3:0] <= 4'h0;
#1;
assert (a1[1] == 8'hf0) else $fatal(0, "A1 elem->partial: expected f0, got %0h", a1[1]);
end
// Test A2: partial element then whole element with suspend between
logic [7:0] a2 [0:3];
initial for (int i = 0; i < 4; i++) a2[i] = 0;
always begin
a2[2][7:4] <= 4'hf;
do_delay();
a2[2] <= 8'h00;
#1;
assert (a2[2] == 8'h00) else $fatal(0, "A2 partial->elem: expected 00, got %0h", a2[2]);
end
// Test A3: writes to different indices (both should apply)
logic [7:0] a3 [0:3];
initial for (int i = 0; i < 4; i++) a3[i] = 0;
always begin
a3[0] <= 8'haa;
do_delay();
a3[1] <= 8'hbb;
#1;
assert (a3[0] == 8'haa) else $fatal(0, "A3 idx0: expected aa, got %0h", a3[0]);
assert (a3[1] == 8'hbb) else $fatal(0, "A3 idx1: expected bb, got %0h", a3[1]);
end
// Test A4: same index overwrite (last wins)
logic [7:0] a4 [0:3];
initial for (int i = 0; i < 4; i++) a4[i] = 0;
always begin
a4[0] <= 8'haa;
do_delay();
a4[0] <= 8'hbb;
#1;
assert (a4[0] == 8'hbb) else $fatal(0, "A4 idx overwrite: expected bb, got %0h", a4[0]);
end
// ---- Full array assignment tests ----
// Test F1: full array then element (element write wins for that index)
logic [7:0] f1 [0:3];
initial for (int i = 0; i < 4; i++) f1[i] = 0;
logic [7:0] f1_src [0:3];
initial begin f1_src[0]=8'h11; f1_src[1]=8'h22; f1_src[2]=8'h33; f1_src[3]=8'h44; end
always begin
f1 <= f1_src;
do_delay();
f1[1] <= 8'hff;
#1;
assert (f1[0] == 8'h11) else $fatal(0, "F1 [0]: expected 11, got %0h", f1[0]);
assert (f1[1] == 8'hff) else $fatal(0, "F1 [1]: expected ff, got %0h", f1[1]);
assert (f1[2] == 8'h33) else $fatal(0, "F1 [2]: expected 33, got %0h", f1[2]);
assert (f1[3] == 8'h44) else $fatal(0, "F1 [3]: expected 44, got %0h", f1[3]);
end
// Test F2: element then full array (full array wins)
logic [7:0] f2 [0:3];
initial for (int i = 0; i < 4; i++) f2[i] = 0;
logic [7:0] f2_src [0:3];
initial begin f2_src[0]=8'h11; f2_src[1]=8'h22; f2_src[2]=8'h33; f2_src[3]=8'h44; end
always begin
f2[1] <= 8'hff;
do_delay();
f2 <= f2_src;
#1;
assert (f2[0] == 8'h11) else $fatal(0, "F2 [0]: expected 11, got %0h", f2[0]);
assert (f2[1] == 8'h22) else $fatal(0, "F2 [1]: expected 22, got %0h", f2[1]);
assert (f2[2] == 8'h33) else $fatal(0, "F2 [2]: expected 33, got %0h", f2[2]);
assert (f2[3] == 8'h44) else $fatal(0, "F2 [3]: expected 44, got %0h", f2[3]);
end
// Test F3: full array then partial element (partial wins for that element's bits)
logic [7:0] f3 [0:3];
initial for (int i = 0; i < 4; i++) f3[i] = 0;
logic [7:0] f3_src [0:3];
initial begin f3_src[0]=8'haa; f3_src[1]=8'hbb; f3_src[2]=8'hcc; f3_src[3]=8'hdd; end
always begin
f3 <= f3_src;
do_delay();
f3[2][3:0] <= 4'h0;
#1;
assert (f3[0] == 8'haa) else $fatal(0, "F3 [0]: expected aa, got %0h", f3[0]);
assert (f3[1] == 8'hbb) else $fatal(0, "F3 [1]: expected bb, got %0h", f3[1]);
assert (f3[2] == 8'hc0) else $fatal(0, "F3 [2]: expected c0, got %0h", f3[2]);
assert (f3[3] == 8'hdd) else $fatal(0, "F3 [3]: expected dd, got %0h", f3[3]);
end
// Test F4: partial element then full array (full array wins)
logic [7:0] f4 [0:3];
initial for (int i = 0; i < 4; i++) f4[i] = 0;
logic [7:0] f4_src [0:3];
initial begin f4_src[0]=8'haa; f4_src[1]=8'hbb; f4_src[2]=8'hcc; f4_src[3]=8'hdd; end
always begin
f4[2][3:0] <= 4'hf;
do_delay();
f4 <= f4_src;
#1;
assert (f4[0] == 8'haa) else $fatal(0, "F4 [0]: expected aa, got %0h", f4[0]);
assert (f4[1] == 8'hbb) else $fatal(0, "F4 [1]: expected bb, got %0h", f4[1]);
assert (f4[2] == 8'hcc) else $fatal(0, "F4 [2]: expected cc, got %0h", f4[2]);
assert (f4[3] == 8'hdd) else $fatal(0, "F4 [3]: expected dd, got %0h", f4[3]);
end
initial begin
#20;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule