mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
Narrow NBA shadow copies for partial writes (#8266)
Signed-off-by: Artur Bieniek <[email protected]>
This commit is contained in:
+44
-1
@@ -56,6 +56,10 @@
|
||||
//
|
||||
// Constraint 3 should always hold with V3Delayed, will check assert it.
|
||||
//
|
||||
// If old-value reads prevent elimination, constant partial writes can still narrow both
|
||||
// copies to the words containing those writes. Other shadow bits are never read. The
|
||||
// assignments keep their original positions, so evaluation order and scheduling are unchanged.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
||||
@@ -124,6 +128,7 @@ class LifePostDlyVisitor final : public VNVisitorConst {
|
||||
const AstExecGraph* m_execGraphp = nullptr; // Current AstExecGraph being processed (or null)
|
||||
const ExecMTask* m_execMTaskp = nullptr; // Current ExecMTask being processed (or null)
|
||||
VDouble0 m_statAssnDel; // Statistic tracking
|
||||
VDouble0 m_statWordsSaved; // Words removed from NBA shadow copies
|
||||
// Maps from Varscope to all their reads and writes
|
||||
using LocMap = std::unordered_map<const AstVarScope*, std::vector<Location<AstVarRef>>>;
|
||||
LocMap m_reads; // VarScope read locations
|
||||
@@ -134,6 +139,40 @@ class LifePostDlyVisitor final : public VNVisitorConst {
|
||||
bool m_inEvalNba = false; // Traversing under the 'nba' region entry point
|
||||
|
||||
// METHODS
|
||||
void narrowCopies(AstNodeAssign* postp, AstVarScope* dVscp,
|
||||
const std::vector<Location<AstVarRef>>& writes) {
|
||||
if (!dVscp->isWide()) return;
|
||||
AstNodeAssign* const prep = VN_AS(writes[0].nodep()->backp(), NodeAssign);
|
||||
UASSERT_OBJ(VN_AS(prep->lhsp(), VarRef)->varScopep()
|
||||
== VN_AS(postp->rhsp(), VarRef)->varScopep()
|
||||
&& VN_AS(prep->rhsp(), VarRef)->varScopep()
|
||||
== VN_AS(postp->lhsp(), VarRef)->varScopep(),
|
||||
prep, "NBA shadow pre/post assignments are not reverse copies");
|
||||
|
||||
int lsb = dVscp->width();
|
||||
int end = 0;
|
||||
for (size_t i = 1; i < writes.size(); ++i) {
|
||||
const AstSel* const selp = VN_CAST(writes[i].nodep()->backp(), Sel);
|
||||
if (!selp || !VN_IS(selp->lsbp(), Const)) return;
|
||||
const int start = selp->lsbConst();
|
||||
if (start > dVscp->width() - selp->width()) return;
|
||||
lsb = std::min(lsb, start);
|
||||
end = std::max(end, start + selp->width());
|
||||
}
|
||||
// Copy one word-aligned range enclosing all writes, including any gaps.
|
||||
lsb = VL_BITWORD_E(lsb) * VL_EDATASIZE;
|
||||
end = std::min(VL_WORDS_I(end) * VL_EDATASIZE, dVscp->width());
|
||||
const int width = end - lsb;
|
||||
if (width == dVscp->width()) return;
|
||||
for (AstNodeAssign* const assignp : {prep, postp}) {
|
||||
FileLine* const flp = assignp->fileline();
|
||||
assignp->lhsp(new AstSel{flp, assignp->lhsp()->unlinkFrBack(), lsb, width});
|
||||
assignp->rhsp(new AstSel{flp, assignp->rhsp()->unlinkFrBack(), lsb, width});
|
||||
assignp->dtypeFrom(assignp->lhsp());
|
||||
}
|
||||
m_statWordsSaved += 2 * (dVscp->widthWords() - VL_WORDS_I(width));
|
||||
}
|
||||
|
||||
void squashAssignposts() {
|
||||
for (const Location<AstNodeAssign>& assign : m_assigns) {
|
||||
AstVarScope* const dVscp = VN_AS(assign.nodep()->rhsp(), VarRef)->varScopep();
|
||||
@@ -172,7 +211,10 @@ class LifePostDlyVisitor final : public VNVisitorConst {
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
if (!qRdOK) continue;
|
||||
if (!qRdOK) {
|
||||
narrowCopies(assign.nodep(), dVscp, dWrites);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark variable for replacement
|
||||
@@ -308,6 +350,7 @@ public:
|
||||
}
|
||||
~LifePostDlyVisitor() override {
|
||||
V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
|
||||
V3Stats::addStat("Optimizations, Lifetime NBA copy words removed", m_statWordsSaved);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/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')
|
||||
|
||||
# Exercise ascending packed ranges and a slice extending past the upper bound.
|
||||
test.compile(verilator_flags2=['--stats', '-Wno-ASCRANGE', '-Wno-SELRANGE'])
|
||||
if test.vlt_all:
|
||||
test.file_grep(test.stats, r'Optimizations, Lifetime NBA copy words removed\s+(\d+)', 512)
|
||||
test.execute()
|
||||
test.passes()
|
||||
@@ -0,0 +1,153 @@
|
||||
// DESCRIPTION: Verilator: Constant partial nonblocking assignments
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%p exp=%p (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
int cyc = 0;
|
||||
always @(posedge clk) cyc <= cyc + 1;
|
||||
always @(negedge clk) begin
|
||||
if (cyc == 18) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
nba_partial #(
|
||||
.W(95),
|
||||
.BASE(6)
|
||||
) narrow_low (
|
||||
.clk,
|
||||
.cyc
|
||||
);
|
||||
nba_partial #(
|
||||
.W(95)
|
||||
) narrow_high (
|
||||
.clk,
|
||||
.cyc
|
||||
);
|
||||
nba_partial #(
|
||||
.W(4097)
|
||||
) wide (
|
||||
.clk,
|
||||
.cyc
|
||||
);
|
||||
nba_partial #(
|
||||
.W(4099),
|
||||
.ASC(1)
|
||||
) wide_other (
|
||||
.clk,
|
||||
.cyc
|
||||
);
|
||||
|
||||
nba_fallback fallback (
|
||||
.clk,
|
||||
.cyc
|
||||
);
|
||||
endmodule
|
||||
|
||||
module nba_fallback (
|
||||
input clk,
|
||||
input int cyc
|
||||
);
|
||||
bit [4096:0] direct;
|
||||
bit [4096:0] direct_expected;
|
||||
bit [4096:0] mixed;
|
||||
bit [4096:0] mixed_expected;
|
||||
bit [4096:0] looped;
|
||||
bit [4096:0] looped_expected;
|
||||
bit [16:0][32:0] dense;
|
||||
bit [16:0][32:0] dense_expected;
|
||||
bit [94:0] ends = '1;
|
||||
bit [94:0] ends_expected = '1;
|
||||
bit [94:0] overrun = '1;
|
||||
bit [94:0] overrun_expected = '1;
|
||||
|
||||
always @(posedge clk) begin
|
||||
`checkh(direct, direct_expected);
|
||||
direct_expected[cyc%4097] = cyc[0];
|
||||
direct[cyc%4097] <= cyc[0];
|
||||
|
||||
`checkh(mixed, mixed_expected);
|
||||
mixed_expected = mixed;
|
||||
if (cyc[0]) begin
|
||||
mixed <= '0;
|
||||
mixed_expected = '0;
|
||||
end
|
||||
mixed[cyc%4097] <= mixed[(cyc+1)%4097] ^ cyc[1];
|
||||
mixed_expected[cyc%4097] = mixed[(cyc+1)%4097] ^ cyc[1];
|
||||
|
||||
`checkh(looped, looped_expected);
|
||||
looped_expected = looped;
|
||||
// Each assignment can execute more than once per clock cycle.
|
||||
for (int i = 0; i < (cyc & 3) + 1; ++i) begin
|
||||
looped[cyc+i] <= looped[cyc+i+1] ^ cyc[0];
|
||||
looped_expected[cyc+i] = looped[cyc+i+1] ^ cyc[0];
|
||||
end
|
||||
|
||||
`checkh(dense, dense_expected);
|
||||
dense_expected = {dense[15:0], 1'b0, 32'(cyc + 1)};
|
||||
// Individually partial writes collectively update the entire pipeline.
|
||||
for (int i = 16; i > 0; --i) dense[i] <= dense[i-1];
|
||||
dense[0] <= {1'b0, 32'(cyc + 1)};
|
||||
|
||||
`checkh(ends, ends_expected);
|
||||
ends_expected[0] = cyc[0];
|
||||
ends_expected[94] = ends[0];
|
||||
ends[0] <= cyc[0];
|
||||
ends[94] <= ends[0];
|
||||
|
||||
// Only the in-range bits of this partly out-of-range slice are written.
|
||||
`checkh(overrun, overrun_expected);
|
||||
overrun_expected[94:91] = cyc[3:0];
|
||||
overrun_expected[0] = overrun[91];
|
||||
overrun[91+:7] <= cyc[6:0];
|
||||
overrun[0] <= overrun[91];
|
||||
end
|
||||
endmodule
|
||||
|
||||
module nba_partial #(
|
||||
parameter W = 4097,
|
||||
parameter ASC = 0,
|
||||
parameter BASE = 65
|
||||
) (
|
||||
input clk,
|
||||
input int cyc
|
||||
);
|
||||
typedef bit [(ASC ? 3 : W+2):(ASC ? W+2 : 3)] state_t;
|
||||
state_t q = '1;
|
||||
state_t expected = '1;
|
||||
bit [32:0] value;
|
||||
|
||||
always @(posedge clk) begin
|
||||
`checkh(q, expected);
|
||||
expected = q;
|
||||
value = {cyc[0], 32'(cyc * 76543)};
|
||||
if (cyc[0]) begin
|
||||
q[BASE+:33] <= value;
|
||||
expected[BASE+:33] = value;
|
||||
end
|
||||
if (cyc[1]) begin
|
||||
// Overlapping writes use old values, keep their order, and preserve untouched bits.
|
||||
q[BASE+7+:9] <= q[BASE+:9] ^ value[8:0];
|
||||
expected[BASE+7+:9] = q[BASE+:9] ^ value[8:0];
|
||||
end
|
||||
if (cyc[2]) begin
|
||||
q[BASE-3] <= 1'b1;
|
||||
expected[BASE-3] = 1'b1;
|
||||
end
|
||||
// Repeated updates to the same bit.
|
||||
for (int i = 0; i < (cyc & 3); ++i) begin
|
||||
q[BASE+2] <= q[BASE+3] ^ 1'(cyc >> i);
|
||||
expected[BASE+2] = q[BASE+3] ^ 1'(cyc >> i);
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
Reference in New Issue
Block a user