diff --git a/Changes b/Changes index 055678d16..414a560c3 100644 --- a/Changes +++ b/Changes @@ -14,7 +14,7 @@ Verilator 5.021 devel **Minor:** * Remove deprecated 32-bit pointer mode (`gcc -m32`). - +* Fix delays using wrong timeunits when modules inlined (#4806). [Paul Wright] Verilator 5.020 2024-01-01 diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 40803b5c4..34104eab2 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -189,7 +189,9 @@ private: m_clockingp->addNextHere(new AstAlwaysObserved{ flp, new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, forkp}); if (v3Global.opt.timing().isSetTrue()) { - assignp->timingControlp(new AstDelay{flp, skewp->unlinkFrBack(), false}); + AstDelay* const delayp = new AstDelay{flp, skewp->unlinkFrBack(), false}; + delayp->timeunit(m_modp->timeunit()); + assignp->timingControlp(delayp); } else if (v3Global.opt.timing().isSetFalse()) { nodep->v3warn(E_NOTIMING, "Clocking output skew greater than #0 requires --timing"); diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 48839a26e..ee2b52808 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2728,6 +2728,7 @@ class AstDelay final : public AstNodeStmt { // Delay statement // @astgen op1 := lhsp : AstNodeExpr // Delay value // @astgen op2 := stmtsp : List[AstNode] // Statements under delay + VTimescale m_timeunit; // Delay's time unit const bool m_isCycle; // True if it is a cycle delay public: @@ -2739,8 +2740,10 @@ public: ASTGEN_MEMBERS_AstDelay; void dump(std::ostream& str) const override; bool isTimingControl() const override { return true; } - bool isCycleDelay() const { return m_isCycle; } bool same(const AstNode* /*samep*/) const override { return true; } + void timeunit(const VTimescale& flag) { m_timeunit = flag; } + VTimescale timeunit() const { return m_timeunit; } + bool isCycleDelay() const { return m_isCycle; } }; class AstDisable final : public AstNodeStmt { string m_name; // Name of block diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 728539c2a..5d3e36da8 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -536,6 +536,13 @@ class LinkParseVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); } + void visit(AstDelay* nodep) override { + cleanFileline(nodep); + UASSERT_OBJ(m_modp, nodep, "Delay not under module"); + nodep->timeunit(m_modp->timeunit()); + iterateChildren(nodep); + } + void visit(AstNodeForeach* nodep) override { // FOREACH(array, loopvars, body) UINFO(9, "FOREACH " << nodep << endl); diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 3ed545153..b9993687a 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -472,7 +472,6 @@ class TimingControlVisitor final : public VNVisitor { AstScope* m_scopep = nullptr; // Current scope AstActive* m_activep = nullptr; // Current active AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under - double m_timescaleFactor = 1.0; // Factor to scale delays by int m_forkCnt = 0; // Number of forks inside a module bool m_underJumpBlock = false; // True if we are inside of a jump-block bool m_underProcedure = false; // True if we are under an always or initial @@ -548,8 +547,10 @@ class TimingControlVisitor final : public VNVisitor { return stmtp == nodep ? nullptr : stmtp; } // Calculate the factor to scale delays by - double calculateTimescaleFactor(VTimescale timeunit) const { - int scalePowerOfTen = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen(); + double calculateTimescaleFactor(AstNode* nodep, VTimescale timeunit) const { + UASSERT_OBJ(!timeunit.isNone(), nodep, "timenunit must be set"); + const int scalePowerOfTen + = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen(); return std::pow(10.0, scalePowerOfTen); } // Creates the global delay scheduler variable @@ -782,8 +783,6 @@ class TimingControlVisitor final : public VNVisitor { UASSERT(!m_classp, "Module or class under class"); VL_RESTORER(m_classp); m_classp = VN_CAST(nodep, Class); - VL_RESTORER(m_timescaleFactor); - m_timescaleFactor = calculateTimescaleFactor(nodep->timeunit()); VL_RESTORER(m_forkCnt); m_forkCnt = 0; iterateChildren(nodep); @@ -895,20 +894,20 @@ class TimingControlVisitor final : public VNVisitor { "Cycle delays should have been handled in V3AssertPre"); FileLine* const flp = nodep->fileline(); AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack()); - auto* const constp = VN_CAST(valuep, Const); + AstConst* const constp = VN_CAST(valuep, Const); if (!constp || !constp->isZero()) { // Scale the delay + const double timescaleFactor = calculateTimescaleFactor(nodep, nodep->timeunit()); if (valuep->dtypep()->isDouble()) { valuep = new AstRToIRoundS{ - flp, - new AstMulD{flp, valuep, - new AstConst{flp, AstConst::RealDouble{}, m_timescaleFactor}}}; + flp, new AstMulD{flp, valuep, + new AstConst{flp, AstConst::RealDouble{}, timescaleFactor}}}; valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); } else { valuep->dtypeSetBitSized(64, VSigning::UNSIGNED); valuep = new AstMul{flp, valuep, new AstConst{flp, AstConst::Unsized64{}, - static_cast(m_timescaleFactor)}}; + static_cast(timescaleFactor)}}; } } // Replace self with a 'co_await dlySched.delay()' diff --git a/test_regress/t/t_timing_timescale.out b/test_regress/t/t_timing_timescale.out new file mode 100644 index 000000000..041fa0bc7 --- /dev/null +++ b/test_regress/t/t_timing_timescale.out @@ -0,0 +1,26 @@ +[0] clkb is 1 +[5e-07] clkb is 0 +[1e-06] clkb is 1 +[1.5e-06] clkb is 0 +[1.75e-06] Initial finishing, clkb = 0 +[2e-06] clkb is 1 +[2.5e-06] clkb is 0 +[3e-06] clkb is 1 +[3.5e-06] clkb is 0 +[4e-06] clkb is 1 +[4.5e-06] clkb is 0 +[5e-06] clkb is 1 +[5.5e-06] clkb is 0 +[6e-06] clkb is 1 +[6.5e-06] clkb is 0 +[7e-06] clkb is 1 +[7.5e-06] clkb is 0 +[8e-06] clkb is 1 +[8.5e-06] clkb is 0 +[9e-06] clkb is 1 +[9.5e-06] clkb is 0 +[1e-05] clkb is 1 +[1e-05] Finishing (TOP.t.bot) +*-* All Finished *-* +[10500] final (TOP.t) +[1.05e-05] final (TOP.t.bot) count was 21 diff --git a/test_regress/t/t_timing_timescale.pl b/test_regress/t/t_timing_timescale.pl new file mode 100755 index 000000000..3feb337e3 --- /dev/null +++ b/test_regress/t/t_timing_timescale.pl @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_timescale.v b/test_regress/t/t_timing_timescale.v new file mode 100644 index 000000000..1ec17d076 --- /dev/null +++ b/test_regress/t/t_timing_timescale.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Paul Wright. +// SPDX-License-Identifier: CC0-1.0 + +module t; + timeunit 1ns; + timeprecision 1ps; + logic clkb, clk; + + initial begin + clkb = 0; + end + + always @(clk) begin + clkb <= ~clk; + end + + bot bot (.clkb(clkb), .clk(clk)); + + final begin + $display("[%g] final (%m)", $realtime()); + end +endmodule + +module bot (input logic clkb, output logic clk); + timeunit 1s; + timeprecision 1fs; + integer count; + real delay; + + initial begin + count = 0; + delay = 500e-9; + clk = clkb; + #(3.5 * delay) $display("[%g] Initial finishing, clkb = %b", $realtime(), clkb); + end + + always @(clkb) begin + $display("[%g] clkb is %b", $realtime(), clkb); + count++; + #(delay) clk = clkb; + end + + always @(count) begin + if (count > 20) begin + $display("[%g] Finishing (%m)", $realtime()); + if ($realtime() < (delay * 20)) begin + $display("[%g] %%Error: That was too quick!", $realtime()); + end + $write("*-* All Finished *-*\n"); + $finish; + end + end + + final begin + $display("[%g] final (%m) count was %0d", $realtime(), count); + end + +endmodule