Randomize disable iff tests with reference models, dedup Link OR propagation, extract shift-chain helpers

This commit is contained in:
Yilou Wang 2026-07-07 16:15:01 +02:00
parent c618361fa2
commit c3f0fcc1a3
8 changed files with 277 additions and 165 deletions

View File

@ -33,6 +33,7 @@
#include "V3UniqueNames.h"
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -1652,98 +1653,93 @@ class SvaNfaLowering final {
AstNodeExpr* throughoutRejectp = nullptr; // Reject when a throughout guard drops
};
// Pack ##N delay and uniform b[*N] repetition sub-chains -- maximal simple
// paths of registered vertices, each holding the previous vertex's state
// delayed one cycle -- into single vectors shifted once per clock
// (verilator/verilator#7792). Sets shiftVecp/shiftBit/shiftStepCondp.
static const SvaTransEdge* singleClockedInEdge(SvaStateVertex* vtxp) {
const SvaTransEdge* inp = nullptr;
for (const V3GraphEdge& er : vtxp->inEdges()) {
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
if (!te.m_consumesCycle) return nullptr; // an incoming Link disqualifies
if (inp) return nullptr; // more than one clocked source -> OR-merge
inp = &te;
}
return inp;
}
static bool shiftable(const std::vector<SvaStateVertex*>& vtx, int i, int startIdx) {
SvaStateVertex* const v = vtx[i];
if (!v->datap()->needsReg) return false;
if (i == startIdx || v->m_isMatch) return false;
if (v->m_isCounter || v->m_isAndCombiner || v->m_isRejectSink) return false;
if (v->m_isUnbounded) return false; // self-loop accumulator, not a pure shift
if (v->m_strongPending) return false; // final-block liveness reads its own reg
if (!v->m_throughoutConds.empty()) return false;
return singleClockedInEdge(v) != nullptr;
}
// Chain predecessor of a registered vertex and the per-step condition into it
// (null = unconditional ##N delay; else the b[*N] Link boolean).
static int chainPred(const std::vector<SvaStateVertex*>& vtx, int ci, int startIdx,
AstNodeExpr*& condpr) {
condpr = nullptr;
const SvaTransEdge* const e = singleClockedInEdge(vtx[ci]);
if (!e || e->m_rejectOnFail || e->m_condVtxp) return -1;
const int mi = e->fromVtxp()->color();
if (shiftable(vtx, mi, startIdx)) { // direct clocked step
condpr = e->m_condp;
return mi;
}
if (e->m_condp) return -1; // pass-through requires an unconditional ##1 edge
SvaStateVertex* const m = vtx[mi];
const SvaTransEdge* linkp = nullptr;
for (const V3GraphEdge& er : m->inEdges()) {
if (linkp) return -1; // more than one input -> not a clean pass-through
linkp = static_cast<const SvaTransEdge*>(&er);
}
if (!linkp || linkp->m_consumesCycle || linkp->m_rejectOnFail || linkp->m_condVtxp)
return -1;
const int pi = linkp->fromVtxp()->color();
if (!shiftable(vtx, pi, startIdx)) return -1;
condpr = linkp->m_condp;
return pi;
}
static bool sameCond(const AstNodeExpr* a, const AstNodeExpr* b) {
if (!a && !b) return true;
if (!a || !b) return false;
return a->sameTree(b);
}
// Pack ##N delay and uniform b[*N] repetition chains of registered vertices
// into single vectors shifted once per clock. Sets shiftVecp/shiftBit/etc.
void detectShiftChains(const std::vector<SvaStateVertex*>& vtx, int N, int startIdx,
const std::string& baseName, FileLine* flp) {
const auto singleClockedInEdge = [](SvaStateVertex* v) -> const SvaTransEdge* {
const SvaTransEdge* inp = nullptr;
for (const V3GraphEdge& er : v->inEdges()) {
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
if (!te.m_consumesCycle) return nullptr; // an incoming Link disqualifies
if (inp) return nullptr; // more than one clocked source -> OR-merge
inp = &te;
}
return inp;
};
const auto shiftable = [&](int i) -> bool {
SvaStateVertex* const v = vtx[i];
if (!v->datap()->needsReg) return false;
if (i == startIdx || v->m_isMatch) return false;
if (v->m_isCounter || v->m_isAndCombiner || v->m_isRejectSink) return false;
if (v->m_isUnbounded) return false; // self-loop accumulator, not a pure shift
if (v->m_strongPending) return false; // final-block liveness reads its own reg
if (!v->m_throughoutConds.empty()) return false;
return singleClockedInEdge(v) != nullptr;
};
// Chain predecessor of a registered vertex, plus the per-step condition
// into it (null = unconditional). A shift step is a direct clocked edge
// (##N delay) or a clocked edge fed through one pass-through condition
// Link vertex (`b[*N]` repetition; the Link boolean is the condition).
const auto chainPred = [&](int ci, AstNodeExpr*& condpr) -> int {
condpr = nullptr;
const SvaTransEdge* const e = singleClockedInEdge(vtx[ci]);
if (!e || e->m_rejectOnFail || e->m_condVtxp) return -1;
const int mi = e->fromVtxp()->color();
if (shiftable(mi)) { // direct clocked step
condpr = e->m_condp;
return mi;
}
if (e->m_condp) return -1; // pass-through requires an unconditional ##1 edge
SvaStateVertex* const m = vtx[mi];
const SvaTransEdge* linkp = nullptr;
for (const V3GraphEdge& er : m->inEdges()) {
if (linkp) return -1; // more than one input -> not a clean pass-through
linkp = static_cast<const SvaTransEdge*>(&er);
}
if (!linkp || linkp->m_consumesCycle || linkp->m_rejectOnFail || linkp->m_condVtxp)
return -1;
const int pi = linkp->fromVtxp()->color();
if (!shiftable(pi)) return -1;
condpr = linkp->m_condp;
return pi;
};
const auto sameCond = [](const AstNodeExpr* a, const AstNodeExpr* b) -> bool {
if (!a && !b) return true;
if (!a || !b) return false;
return a->sameTree(b);
};
// Bond each vertex to its chain predecessor; a predecessor feeding more
// than one chain vertex branches and cannot shift unambiguously.
// Link each shiftable vertex to its unique chain predecessor and successor.
struct Bond final {
int pred = -1;
int next = -1;
int childCount = 0;
bool hasPrev = false;
AstNodeExpr* stepCondp = nullptr; // borrowed step condition into this vertex
};
std::vector<Bond> bond(N);
std::vector<int> childCount(N, 0);
for (int i = 0; i < N; ++i) {
if (!shiftable(i)) continue;
if (!shiftable(vtx, i, startIdx)) continue;
AstNodeExpr* cp = nullptr;
const int p = chainPred(i, cp);
const int p = chainPred(vtx, i, startIdx, cp);
if (p < 0) continue;
bond[i] = {p, cp};
++childCount[p];
bond[i].pred = p;
bond[i].stepCondp = cp;
++bond[p].childCount;
}
std::vector<int> nextInChain(N, -1);
std::vector<bool> hasPrevInChain(N, false);
for (int i = 0; i < N; ++i) {
const int p = bond[i].pred;
if (p < 0 || childCount[p] != 1) continue;
nextInChain[p] = i;
hasPrevInChain[i] = true;
if (p < 0 || bond[p].childCount != 1) continue;
bond[p].next = i;
bond[i].hasPrev = true;
}
// Walk each chain head, splitting into maximal segments whose interior
// steps share one condition; each segment of >= 2 vertices packs into
// one vector. Segments cap at 64 bits: a wider (VlWide) shift emitted
// after V3Width is not word-split and breaks V3Subst. A capped chain
// carries into the next vector through the shared clocked predecessor.
// Split each chain into maximal segments sharing one step condition,
// each packed into one vector. Cap at 64 bits (wider VlWide breaks V3Subst).
constexpr int kMaxShiftVec = 64;
for (int h = 0; h < N; ++h) {
if (hasPrevInChain[h] || nextInChain[h] == -1) continue;
if (bond[h].hasPrev || bond[h].next == -1) continue;
std::vector<int> chain;
for (int j = h; j != -1; j = nextInChain[j]) chain.push_back(j);
for (int j = h; j != -1; j = bond[j].next) chain.push_back(j);
int a = 0;
while (a + 1 < static_cast<int>(chain.size())) {
AstNodeExpr* const segCondp = bond[chain[a + 1]].stepCondp;
@ -1787,8 +1783,7 @@ class SvaNfaLowering final {
AstNodeExpr* srcSigp = c.vtx[fromIdx]->datap()->stateSigp->cloneTreePure(false);
srcSigp = andCond(c.flp, srcSigp, te.m_condp);
// Zero in-flight state while the disable is active; the edge
// counter misses a held or mid-window disable (IEEE 1800-2023 16.12)
// Zero in-flight state while the disable is active.
if (c.disableExprp) {
AstNodeExpr* const notDisp
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};
@ -1811,10 +1806,8 @@ class SvaNfaLowering final {
}
}
// Delay / uniform-repetition chains: one masked shift per vector.
// One masked shift per vector; bit 0 injects the head's feeder:
// vec <= (((vec << 1) & {W{step}}) | inject) & {W{!disable & !kill}}
// bit 0 injects the head's feeder; `step` is the shared per-step
// condition (absent for a pure ##N delay), on the shifted bits only.
for (int i = 0; i < c.N; ++i) {
if (!c.vtx[i]->datap()->shiftVecp || c.vtx[i]->datap()->shiftBit != 0) continue;
AstVar* const vecp = c.vtx[i]->datap()->shiftVecp;
@ -1919,8 +1912,7 @@ class SvaNfaLowering final {
AstNodeExpr* donep = new AstLogOr{c.flp, killActive(c),
new AstLogOr{c.flp, matchedNowp, counterAtEndp}};
// A mid-window disable aborts the in-flight count (IEEE 1800-2023
// 16.12); the expiry reject is separately disable-gated
// A mid-window disable aborts the in-flight count.
if (c.disableExprp)
donep = new AstLogOr{c.flp, donep, c.disableExprp->cloneTreePure(false)};
@ -2004,8 +1996,7 @@ class SvaNfaLowering final {
AstNodeExpr* clearCondp = new AstLogOr{
c.flp, killActive(c), c.vtx[ai]->datap()->stateSigp->cloneTreePure(false)};
// A mid-window disable clears a half-latched side so a disabled
// attempt cannot pair with a later attempt (IEEE 1800-2023 16.12)
// A mid-window disable clears a half-latched side.
if (c.disableExprp)
clearCondp = new AstLogOr{c.flp, clearCondp, c.disableExprp->cloneTreePure(false)};
AstIf* const topp = new AstIf{c.flp, clearCondp, clearLp, setLIfp};
@ -2215,6 +2206,7 @@ class SvaNfaLowering final {
// Fixed-point propagation along zero-delay (Link) edges.
// Worst case: longest chain is N hops; SAnd seeding adds one extra round;
// factor-of-2 covers reverse-order dependencies.
std::unordered_map<const SvaTransEdge*, const AstNodeExpr*> consumedSrcp;
for (int pass = 0; pass < 2 * c.N + 2; ++pass) {
bool changed = false;
// Seed SAnd combiners (sub-NFA termVertices may only be available
@ -2246,14 +2238,18 @@ class SvaNfaLowering final {
}
// Propagate Link edges
for (int fi = 0; fi < c.N; ++fi) {
if (!c.vtx[fi]->datap()->stateSigp) continue;
AstNodeExpr* const srcp = c.vtx[fi]->datap()->stateSigp;
if (!srcp) continue;
for (const V3GraphEdge& er : c.vtx[fi]->outEdges()) {
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
if (te.m_consumesCycle) continue;
const int ti = te.toVtxp()->color();
if (te.toVtxp()->m_isMatch || te.toVtxp()->m_isRejectSink) continue;
AstNodeExpr* const contributionp = andCond(
c.flp, c.vtx[fi]->datap()->stateSigp->cloneTreePure(false), te.m_condp);
// Consume each Link edge once per distinct source value
if (consumedSrcp[&te] == srcp) continue;
consumedSrcp[&te] = srcp;
AstNodeExpr* const contributionp
= andCond(c.flp, srcp->cloneTreePure(false), te.m_condp);
if (!c.vtx[ti]->datap()->stateSigp) {
c.vtx[ti]->datap()->stateSigp = contributionp;
changed = true;

View File

@ -88,9 +88,6 @@ module t (
`checkd(rand_bounded_pass_q.size(), 0);
`checkd(rand_bounded_fail_q.size(), 20); // Other sims: 19, 11
`checkd(disable_bounded_pass_q.size(), 0);
// A disable true anywhere in the [0:3] window suppresses the attempt;
// the residual vs other sims is a disable arriving only after an
// in-window failure already fired, which a streaming NFA cannot un-fire.
`checkd(disable_bounded_fail_q.size(), 8); // Other sims: 5, 6
$write("*-* All Finished *-*\n");
$finish;

View File

@ -11,6 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator')
test.sim_time = 16000
test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing'])
test.execute()

View File

@ -9,38 +9,59 @@
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// IEEE 1800-2023 16.12: a disable iff true at ANY point of a multi-cycle
// attempt window disables it. A range delay ##[1:N] with N over the 256 unroll
// limit lowers to a counter-FSM backend (not a state-register chain), which had
// the same mid-window-disable hole as the register path
// (verilator/verilator#7792 follow-up). value never matches, so a live attempt
// fails at window end; skip pulses once mid-window and must abort the in-flight
// counter so the disabled assert does not fire.
// disable iff mid-window on the counter-FSM path (##[1:N], N > 256 unroll limit).
module t (
input clk
);
localparam int N = 257; // > 256 unroll limit -> counter FSM path
localparam int PERIOD = 260; // > N -> attempts never overlap
localparam int NATT = 30;
int cyc = 0;
wire trig = (cyc == 5);
wire value = 1'b0; // never matches -> attempt would fail at window end
wire skip = (cyc == 50); // single mid-window pulse
reg [63:0] crc = 64'h5aef0c8d_d70a4497;
int phase = 0;
int idx = 0;
wire in_run = (idx < NATT);
wire trig = in_run && (phase == 0);
wire value = 1'b0;
reg do_dis = 0;
int dis_at = 0;
wire dis = in_run && do_dis && (phase == dis_at);
int n_dis_fire = 0;
int n_ctrl_fire = 0;
int exp_dis_fire = 0;
// Range > 256 -> counter FSM. The cyc-5 attempt is hit by the skip pulse.
assert property (@(posedge clk) disable iff (skip) trig |-> ##[1:300] value)
assert property (@(posedge clk) disable iff (dis) trig |-> ##[1:N] value)
else n_dis_fire <= n_dis_fire + 1;
// Control: same property never disabled -> the cyc-5 attempt fails once.
assert property (@(posedge clk) disable iff (1'b0) trig |-> ##[1:300] value)
assert property (@(posedge clk) disable iff (1'b0) trig |-> ##[1:N] value)
else n_ctrl_fire <= n_ctrl_fire + 1;
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 320) begin
`checkd(n_dis_fire, 0);
`checkd(n_ctrl_fire, 1);
crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]};
if (phase == PERIOD - 1) begin
phase <= 0;
idx <= idx + 1;
end else begin
phase <= phase + 1;
end
if (phase == 0) begin
do_dis <= crc[3];
dis_at <= 1 + (int'(crc[20:12]) % (N - 1));
end
if (in_run && phase == N && !do_dis) exp_dis_fire <= exp_dis_fire + 1;
if (idx == NATT && phase == 4) begin
`ifdef TEST_VERBOSE
$write("n_dis_fire=%0d exp=%0d n_ctrl_fire=%0d\n", n_dis_fire, exp_dis_fire, n_ctrl_fire);
`endif
`checkd(n_dis_fire, exp_dis_fire);
`checkd(n_ctrl_fire, NATT);
if (n_dis_fire == 0 || n_dis_fire == NATT) $stop; // guard a degenerate seed
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -9,47 +9,65 @@
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// IEEE 1800-2023 16.12: a disable iff condition held continuously true must
// disable every attempt of a multi-cycle property (verilator/verilator#7792).
// en_held is a plain non-$sampled, non-constant signal held 1, so it exercises
// the NFA disable-counter path. The held assert/cover must never fire; the
// `disable iff (1'b0)` controls prove the same assert/cover do fire when enabled.
// A disable iff held true for the whole attempt window disables it.
module t (
input clk
);
localparam int N = 5;
localparam int PERIOD = 9;
localparam int NATT = 40;
int cyc = 0;
reg [63:0] crc = 64'h5aef0c8d_d70a4497;
wire a = crc[0];
wire b = crc[4];
bit en_held = 1'b1;
int phase = 0;
int idx = 0;
wire in_run = (idx < NATT);
wire trig = in_run && (phase == 0);
int n_held_assert = 0;
int n_held_cover = 0;
int n_ctrl_assert = 0;
int n_ctrl_cover = 0;
reg hold = 0;
reg fail_now = 0;
wire value = !(in_run && (phase == N) && fail_now);
wire dis = in_run && hold && (phase >= 1);
// Held-true disable: assert + cover must be fully suppressed.
assert property (@(posedge clk) disable iff (en_held) (a ##1 b))
else n_held_assert <= n_held_assert + 1;
cover property (@(posedge clk) disable iff (en_held) (a ##1 b))
n_held_cover <= n_held_cover + 1;
int n_held_fire = 0;
int n_ctrl_fire = 0;
int exp_held_fire = 0;
int exp_ctrl_fire = 0;
// Enabled control (disable iff 1'b0): same assert + cover must fire.
assert property (@(posedge clk) disable iff (1'b0) (a ##1 b))
else n_ctrl_assert <= n_ctrl_assert + 1;
cover property (@(posedge clk) disable iff (1'b0) (a ##1 b))
n_ctrl_cover <= n_ctrl_cover + 1;
assert property (@(posedge clk) disable iff (dis) trig |-> ##N value)
else n_held_fire <= n_held_fire + 1;
assert property (@(posedge clk) disable iff (1'b0) trig |-> ##N value)
else n_ctrl_fire <= n_ctrl_fire + 1;
always @(posedge clk) begin
cyc <= cyc + 1;
crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]};
if (cyc == 99) begin
`checkd(n_held_assert, 0);
`checkd(n_held_cover, 0);
`checkd(n_ctrl_assert, 58);
`checkd(n_ctrl_cover, 26); // Others: 26, One other: 0
if (phase == PERIOD - 1) begin
phase <= 0;
idx <= idx + 1;
end
else begin
phase <= phase + 1;
end
if (phase == 0) begin
hold <= crc[3];
fail_now <= crc[7];
end
if (in_run && phase == N) begin
exp_ctrl_fire <= exp_ctrl_fire + (fail_now ? 1 : 0);
if (!hold) exp_held_fire <= exp_held_fire + (fail_now ? 1 : 0);
end
if (idx == NATT && phase == 4) begin
`ifdef TEST_VERBOSE
$write("n_held_fire=%0d exp=%0d n_ctrl_fire=%0d exp_ctrl=%0d\n", n_held_fire, exp_held_fire,
n_ctrl_fire, exp_ctrl_fire);
`endif
`checkd(n_held_fire, exp_held_fire);
`checkd(n_ctrl_fire, exp_ctrl_fire);
if (n_held_fire == 0 || n_held_fire == NATT) $stop;
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -9,39 +9,67 @@
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// IEEE 1800-2023 16.12: a disable iff condition true at ANY point of a
// multi-cycle attempt window disables that attempt, not only when held
// continuously (verilator/verilator#7792 follow-up to #7841). skip is a plain
// non-$sampled, non-constant signal pulsed true for a single cycle in the
// MIDDLE of the ##10 window. value is low only at the attempt's match cycle, so
// exactly one attempt would fail; the mid-window skip pulse must disable it.
// Pre-fix the in-flight NFA states were not zeroed on a mid-window pulse, so the
// disabled assert fired; the control proves the same attempt fails when enabled.
// Mid-window disable pulse on the packed ##N delay path (N < unroll limit).
module t (
input clk
);
localparam int N = 8;
localparam int PERIOD = 12;
localparam int NATT = 40;
int cyc = 0;
wire value = (cyc != 10); // low only at the cyc-0 attempt's match cycle
wire skip = (cyc == 5); // single-cycle pulse, mid-window of [0..10]
reg [63:0] crc = 64'h5aef0c8d_d70a4497;
int phase = 0;
int idx = 0;
wire in_run = (idx < NATT);
wire trig = in_run && (phase == 0);
reg do_dis = 0;
int dis_at = 0;
reg fail_now = 0;
wire value = !(in_run && (phase == N) && fail_now);
wire dis = in_run && do_dis && (phase == dis_at);
int n_dis_fire = 0;
int n_ctrl_fire = 0;
int exp_dis_fire = 0;
int exp_ctrl_fire = 0;
// Mid-window-pulse disable: the cyc-0 attempt (matches at cyc 10, where value
// is low -> would fail) must be disabled by the skip pulse at cyc 5.
assert property (@(posedge clk) disable iff (skip) (##10 value))
assert property (@(posedge clk) disable iff (dis) trig |-> ##N value)
else n_dis_fire <= n_dis_fire + 1;
// Control: same property always enabled -> the cyc-0 attempt fails once.
assert property (@(posedge clk) disable iff (1'b0) (##10 value))
assert property (@(posedge clk) disable iff (1'b0) trig |-> ##N value)
else n_ctrl_fire <= n_ctrl_fire + 1;
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 20) begin
`checkd(n_dis_fire, 0);
`checkd(n_ctrl_fire, 1);
crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]};
if (phase == PERIOD - 1) begin
phase <= 0;
idx <= idx + 1;
end
else begin
phase <= phase + 1;
end
if (phase == 0) begin
do_dis <= crc[3];
dis_at <= 1 + (int'(crc[20:12]) % (N - 1));
fail_now <= crc[7];
end
if (in_run && phase == N) begin
exp_ctrl_fire <= exp_ctrl_fire + (fail_now ? 1 : 0);
if (!do_dis) exp_dis_fire <= exp_dis_fire + (fail_now ? 1 : 0);
end
if (idx == NATT && phase == 4) begin
`ifdef TEST_VERBOSE
$write("n_dis_fire=%0d exp=%0d n_ctrl_fire=%0d exp_ctrl=%0d\n", n_dis_fire, exp_dis_fire,
n_ctrl_fire, exp_ctrl_fire);
`endif
`checkd(n_dis_fire, exp_dis_fire);
`checkd(n_ctrl_fire, exp_ctrl_fire);
if (n_dis_fire == 0 || n_dis_fire == NATT) $stop;
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -11,6 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator')
test.sim_time = 4000
test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing'])
test.execute()

View File

@ -9,29 +9,77 @@
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// A ##130 delay is a 129-vertex shift chain packed across three <=64-bit
// vectors (verilator/verilator#7792 optimization). This exercises the
// cross-chunk carry: each next vector's bit 0 injects the previous vector's top
// bit through the shared clocked predecessor. res is high only exactly 130
// cycles after the single antecedent match, so an off-by-one or dropped carry
// makes the consequent miss and the assert fire.
// ##N delay and held[*N] repetition pack shift chains across 64-bit chunks;
// the completion must land on exactly cycle N (off-by-one brackets fail).
module t (
input clk
);
localparam int LEN = 130;
localparam int PERIOD = 140;
localparam int NATT = 12;
int cyc = 0;
wire trig = (cyc == 5);
wire res = (cyc == 5 + 130);
reg [63:0] crc = 64'h5aef0c8d_d70a4497;
int n_fire = 0;
int phase = 0;
int idx = 0;
wire in_run = (idx < NATT);
wire trig = in_run && (phase == 0);
assert property (@(posedge clk) trig |-> ##130 res)
else n_fire <= n_fire + 1;
reg res_fail = 0;
reg rep_fail = 0;
wire res = in_run && (phase == LEN) && !res_fail;
wire rep_res = in_run && (phase == LEN) && !rep_fail;
wire held = in_run && (phase <= LEN - 1);
int n_d129 = 0;
int n_d130 = 0;
int n_d131 = 0;
int n_rok = 0;
int n_rbad = 0;
int exp_d130 = 0;
int exp_rok = 0;
assert property (@(posedge clk) trig |-> ##(LEN-1) res)
else n_d129 <= n_d129 + 1;
assert property (@(posedge clk) trig |-> ##LEN res)
else n_d130 <= n_d130 + 1;
assert property (@(posedge clk) trig |-> ##(LEN+1) res)
else n_d131 <= n_d131 + 1;
assert property (@(posedge clk) trig |-> held[*LEN] ##1 rep_res)
else n_rok <= n_rok + 1;
assert property (@(posedge clk) trig |-> held[*(LEN + 1)] ##1 rep_res)
else n_rbad <= n_rbad + 1;
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 160) begin
`checkd(n_fire, 0);
crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]};
if (phase == PERIOD - 1) begin
phase <= 0;
idx <= idx + 1;
end
else begin
phase <= phase + 1;
end
if (phase == 0) begin
res_fail <= crc[2];
rep_fail <= crc[9];
end
if (in_run && phase == LEN) exp_d130 <= exp_d130 + (res_fail ? 1 : 0);
if (in_run && phase == LEN) exp_rok <= exp_rok + (rep_fail ? 1 : 0);
if (idx == NATT && phase == 4) begin
`ifdef TEST_VERBOSE
$write("d129=%0d d130=%0d exp=%0d d131=%0d rok=%0d exp=%0d rbad=%0d\n", n_d129, n_d130,
exp_d130, n_d131, n_rok, exp_rok, n_rbad);
`endif
`checkd(n_d129, NATT);
`checkd(n_d130, exp_d130);
`checkd(n_d131, NATT);
`checkd(n_rok, exp_rok);
`checkd(n_rbad, NATT);
if (exp_d130 == 0 || exp_d130 == NATT) $stop;
$write("*-* All Finished *-*\n");
$finish;
end