diff --git a/src/V3LifePost.cpp b/src/V3LifePost.cpp index b68e3880c..40aae8f7b 100644 --- a/src/V3LifePost.cpp +++ b/src/V3LifePost.cpp @@ -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>>; 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>& 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& 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); } }; diff --git a/test_regress/t/t_nba_partial_late.py b/test_regress/t/t_nba_partial_late.py new file mode 100755 index 000000000..3597637c7 --- /dev/null +++ b/test_regress/t/t_nba_partial_late.py @@ -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() diff --git a/test_regress/t/t_nba_partial_late.v b/test_regress/t/t_nba_partial_late.v new file mode 100644 index 000000000..429312755 --- /dev/null +++ b/test_regress/t/t_nba_partial_late.v @@ -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