diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 226c5138e..5bfed9ffd 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -422,9 +422,11 @@ class TristateVisitor final : public TristateBaseVisitor { struct RefStrength final { AstNodeVarRef* m_varrefp; VStrength m_strength; - RefStrength(AstNodeVarRef* varrefp, VStrength strength) + AstDelay* m_delayp; // Explicit delay on the continuous assignment + RefStrength(AstNodeVarRef* varrefp, VStrength strength, AstDelay* delayp) : m_varrefp{varrefp} - , m_strength{strength} {} + , m_strength{strength} + , m_delayp{delayp} {} }; using RefStrengthVec = std::vector; using VarMap = std::map; @@ -449,6 +451,7 @@ class TristateVisitor final : public TristateBaseVisitor { bool m_processedIfaces = false; // Interface modules already processed (interfaces-first pass) VStrength m_currentStrength = VStrength::STRONG; // Current strength of assignment, // Used only on LHS of assignment + AstDelay* m_currentDelayp = nullptr; // Delay of assignment currently collecting LHS drivers const AstNode* m_logicp = nullptr; // Current logic being built TristateGraph m_tgraph; // Logic graph // Map: interface AstVar* -> list of per-module (enVarp, outVarp) contribution pairs @@ -635,7 +638,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstVar* const key = nodep->varp(); const auto pair = m_lhsmap.emplace(key, nullptr); if (pair.second) pair.first->second = new RefStrengthVec; - pair.first->second->push_back(RefStrength{nodep, m_currentStrength}); + pair.first->second->push_back(RefStrength{nodep, m_currentStrength, m_currentDelayp}); } AstNodeExpr* newEnableDeposit(AstSel* selp, AstNodeExpr* enp) { @@ -854,6 +857,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstAssignW* const enLhspAssignp = new AstAssignW{ refp->fileline(), new AstVarRef{refp->fileline(), newEnLhsp, VAccess::WRITE}, getEnp(refp)}; + if (it->m_delayp) { enLhspAssignp->timingControlp(it->m_delayp->cloneTree(false)); } UINFO(9, " newenlhspAssignp " << enLhspAssignp); nodep->addStmtsp(new AstAlways{enLhspAssignp}); @@ -1567,11 +1571,18 @@ class TristateVisitor final : public TristateBaseVisitor { void visitAssign(AstNodeAssign* nodep) { VL_RESTORER(m_alhs); VL_RESTORER(m_currentStrength); + VL_RESTORER(m_currentDelayp); if (VN_IS(nodep->rhsp(), CReset)) return; + if (AstAssignW* const assignWp = VN_CAST(nodep, AssignW)) { + m_currentDelayp = VN_CAST(assignWp->timingControlp(), Delay); + } if (m_graphing) { if (AstAssignW* assignWp = VN_CAST(nodep, AssignW)) { - if (assignWp->timingControlp() || assignWp->getLhsNetDelay()) return; - addToAssignmentList(assignWp); + if (assignWp->getLhsNetDelay()) return; + if (assignWp->timingControlp() && !m_currentDelayp) return; + // Separate data and enable assignments cannot preserve distinct rise/fall delays. + if (m_currentDelayp && m_currentDelayp->fallDelay()) return; + if (!assignWp->timingControlp()) addToAssignmentList(assignWp); } if (nodep->user2() & U2_GRAPHING) return; diff --git a/test_regress/t/t_risefall_delay.v b/test_regress/t/t_risefall_delay.v index 5e9e76f46..7607c47e3 100644 --- a/test_regress/t/t_risefall_delay.v +++ b/test_regress/t/t_risefall_delay.v @@ -4,26 +4,73 @@ // 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=%0x exp=%0x (%s !== %s)\n", `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0) `define check_scalar(exp) do begin `checkh(out_assign, exp); `checkh(out_buf, exp); `checkh(out_net, exp); end while(0) +// verilog_format: on module t; + parameter delay = 0; + localparam int TRI_DELAY = 3; + logic in = 0; logic [3:0] in_vec = 4'h0; + logic tri_data = 0; + logic tri_enable = 0; wire out_assign; wire out_buf; - wire #(5,3.3) out_net; + wire out_bufif0; + wire out_bufif1; + wire out_issue; + wire out_nmos; + wire out_notif0; + wire out_notif1; + wire out_pmos; + wire #(5, 3.3) out_net; wire [3:0] out_vec_assign; - assign #(5,3) out_assign = in; - buf #(5,3) u_buf (out_buf, in); + assign #(5, 3) out_assign = in; + buf #(5, 3) u_buf (out_buf, in); + bufif0 #(delay) u_bufif0 (out_bufif0, in, 1'b0); + bufif1 #(TRI_DELAY) u_bufif1 (out_bufif1, tri_data, tri_enable); + bufif1 #(delay) u_issue (out_issue, in, 1'b0); + nmos #(delay) u_nmos (out_nmos, in, 1'b1); + notif0 #(delay) u_notif0 (out_notif0, in, 1'b0); + notif1 #(delay) u_notif1 (out_notif1, in, 1'b1); + pmos #(delay) u_pmos (out_pmos, in, 1'b0); assign out_net = in; - assign #(5,3) out_vec_assign = in_vec; + assign #(5, 3) out_vec_assign = in_vec; + + initial begin + #4; + `checkh(out_bufif1, 1'bz); + tri_enable = 1'b1; + #2; + `checkh(out_bufif1, 1'bz); + #1; + `checkh(out_bufif1, 1'b0); + tri_data = 1'b1; + #2; + `checkh(out_bufif1, 1'b0); + #1; + `checkh(out_bufif1, 1'b1); + tri_enable = 1'b0; + #2; + `checkh(out_bufif1, 1'b1); + #1; + `checkh(out_bufif1, 1'bz); + end initial begin #4; `check_scalar(1'b0); + `checkh(out_bufif0, 1'b0); + `checkh(out_issue, 1'bz); + `checkh(out_nmos, 1'b0); + `checkh(out_notif0, 1'b1); + `checkh(out_notif1, 1'b1); + `checkh(out_pmos, 1'b0); `checkh(out_vec_assign, 4'h0); // Rise canceled by a fall before the rise delay expires.