squash
This commit is contained in:
parent
1d29f65eae
commit
674a1192bb
|
|
@ -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
|
||||
|
|
@ -283,6 +286,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
|
||||
|
|
@ -415,8 +429,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)
|
||||
|
|
@ -431,8 +448,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
|
||||
|
|
@ -441,10 +467,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
|
||||
|
|
@ -819,9 +852,8 @@ class DelayedVisitor final : public VNVisitor {
|
|||
AstVarScope* const flagVscp = createTemp(flp, scopep, "__VdlySet" + baseName, 1);
|
||||
flagVscp->varp()->setIgnorePostWrite();
|
||||
// Set the flag at the original NBA
|
||||
nodep->addHereThisAsNext( //
|
||||
new AstAssign{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE},
|
||||
new AstConst{flp, AstConst::BitTrue{}}});
|
||||
nodep->addHereThisAsNext(new AstAssign{flp, new AstVarRef{flp, flagVscp, VAccess::WRITE},
|
||||
new AstConst{flp, AstConst::BitTrue{}}});
|
||||
// Add the 'Post' scheduled commit
|
||||
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, flagVscp, VAccess::READ}};
|
||||
vscpInfo.flagUniqueKit().postp->addStmtsp(ifp);
|
||||
|
|
@ -853,9 +885,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
|
||||
|
|
@ -914,39 +949,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};
|
||||
|
|
@ -955,17 +960,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);
|
||||
|
|
@ -984,6 +988,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
|
||||
|
|
@ -1005,6 +1054,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) {
|
||||
|
|
@ -1043,6 +1093,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1089,6 +1142,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);
|
||||
|
|
@ -1154,6 +1234,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");
|
||||
|
|
@ -1262,6 +1343,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) {
|
||||
|
|
@ -1299,6 +1385,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -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
|
||||
|
|
@ -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"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue