Support #0 delays with IEEE-1800 compliant semantics (#7079)

This patch adds IEEE-1800 compliant scheduling support for the Inactive
scheduling region used for #0 delays.

Implementing this requires that **all** IEEE-1800 active region events
are placed in the internal 'act' section. This has simulation
performance implications. It prevents some optimizations (e.g.
V3LifePost), which reduces single threaded performance. It also reduces
the available work and parallelism in the internal 'nba' section, which
reduced the effectiveness of multi-threading severely.

Performance impact on RTLMeter when using scheduling adjusted to support
proper #0 delays is ~10-20% slowdown in single-threaded mode, and ~100%
(2x slower) with --threads 4.

To avoid paying this performance penalty unconditionally, the scheduling
is only adjusted if either:
1. The input contains a statically known #0 delay
2. The input contains a variable #x delay unknown at compile time

If no #0 is present, but #x variable delays are, a ZERODLY warning is
issued advising the use of '--no-sched-zero-delay' which is a promise
by the user that none of the variable delays will evaluate to a zero
delay at run-time. This warning is turned off if '--sched-zero-delay'
is explicitly given. This is similar to the '--timing' option.

If '--no-sched-zero-delay' was used at compile time, then executing
a zero delay will fail at runtime.

A ZERODLY warning is also issued if a static #0 if found, but the user
specified '--no-sched-zero-delay'. In this case the scheduling is not
adjusted to support #0, so executing it will fail at runtime. Presumably
the user knows it won't be executed.

The intended behaviour with all this is the following:

No #0, no #var in the design (#constant is OK)
-> Same as current behaviour, scheduling not adjusted,
   same code generated as before

Has static #0 and '--no-sched-zero-delay' is NOT given:
-> No warnings, scheduling adjusted so it just works, runs slow

Has static #0 and '--no-sched-zero-delay' is given:
-> ZERODLY on the #0, scheduling not adjusted, fails at runtime if hit

No static #0, but has #var and no option is given:
-> ZERODLY on the #var advising use of '--no-sched-zero-delay' or
   '--sched-zero-delay' (similar to '--timing'), scheduling adjusted
   assuming it can be a zero delay and it just works

No static #0, but has #var and '--no-sched-zero-delay' is given:
-> No warning, scheduling not adjusted, fails at runtime if zero delay

No static #0, but has #var and '--sched-zero-delay' is given:
-> No warning, scheduling adjusted so it just works
This commit is contained in:
Geza Lore
2026-02-16 03:55:55 +00:00
committed by GitHub
parent a0b89dde8e
commit 505d33b35a
43 changed files with 672 additions and 78 deletions
+89 -18
View File
@@ -66,10 +66,12 @@
#include "V3Const.h"
#include "V3EmitV.h"
#include "V3Global.h"
#include "V3Graph.h"
#include "V3MemberMap.h"
#include "V3SenExprBuilder.h"
#include "V3SenTree.h"
#include "V3Stats.h"
#include "V3UniqueNames.h"
#include <queue>
@@ -475,6 +477,8 @@ class TimingControlVisitor final : public VNVisitor {
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
bool m_hasStaticZeroDelay = false; // True if we have a static #0 delay
std::vector<FileLine*> m_unknownDelayFlps; // Locations of AstDelay with non-constant value
// Unique names
V3UniqueNames m_dlyforkNames{"__Vdlyfork"}; // Names for temp AssignW vars
@@ -501,6 +505,11 @@ class TimingControlVisitor final : public VNVisitor {
SenTreeFinder m_finder{m_netlistp}; // Sentree finder and uniquifier
SenExprBuilder* m_senExprBuilderp = nullptr; // Sens expression builder for current m_scope
// Stats
size_t m_statZeroDelays = 0; // Number of statically known #0 delays
size_t m_statConstDelays = 0; // Number of statically known #const (non-zero) delays
size_t m_statVariableDelays = 0; // Number of delays with value unknown at compile time
// METHODS
// Transform an assignment with an intra timing control into a timing control with the
// assignment under it
@@ -916,27 +925,69 @@ class TimingControlVisitor final : public VNVisitor {
UASSERT_OBJ(!nodep->isCycleDelay(), nodep,
"Cycle delays should have been handled in V3AssertPre");
FileLine* const flp = nodep->fileline();
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
AstConst* const constp = VN_CAST(valuep, Const);
if (!constp || !constp->isZero()) {
// Scale the delay
const double timescaleFactor = calculateTimescaleFactor(nodep, nodep->timeunit());
if (valuep->dtypep()->skipRefp()->isDouble()) {
valuep = new AstRToIRoundS{
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<uint64_t>(timescaleFactor)}};
}
} else if (constp->num().is1Step()) {
AstNodeExpr* valuep = nodep->lhsp()->unlinkFrBack();
if (VN_IS(valuep, Const) && VN_AS(valuep, Const)->num().is1Step()) {
// #1step special case
VL_DO_DANGLING(valuep->deleteTree(), valuep);
valuep = new AstConst{flp, AstConst::Unsized64{}, 1};
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
} else {
// Scale the delay
const double timescaleFactorD = calculateTimescaleFactor(nodep, nodep->timeunit());
if (valuep->dtypep()->skipRefp()->isDouble()) {
AstConst* const tsfp = new AstConst{flp, AstConst::RealDouble{}, timescaleFactorD};
valuep = new AstMulD{flp, valuep, tsfp};
valuep = new AstRToIRoundS{flp, valuep};
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
} else {
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
const uint64_t timescaleFactorU = static_cast<uint64_t>(timescaleFactorD);
AstConst* const tsfp = new AstConst{flp, AstConst::Unsized64{}, timescaleFactorU};
valuep = new AstMul{flp, valuep, tsfp};
}
// Simplify
valuep = V3Const::constifyEdit(valuep);
}
// Statistics
if (valuep->isZero()) {
++m_statZeroDelays;
} else if (VN_IS(valuep, Const)) {
++m_statConstDelays;
} else {
++m_statVariableDelays;
}
// Decide scheduling support for #0
if (v3Global.opt.schedZeroDelay().isSetTrue()) {
// User said to schedule for #0 support, nothing else to do
v3Global.setUsesZeroDelay();
} else if (v3Global.opt.schedZeroDelay().isSetFalse()) {
// User said to schedule without #0 support. Still warn if a static #0 delay exists
if (valuep->isZero()) {
nodep->v3warn(
ZERODLY,
"Static #0 delay exists, but '--no-sched-zero-delay' was given.\n"
<< nodep->warnMore() //
<< "... Can proceed, but this will fail at runtime if executed.");
}
} else {
// User did not express preference, decide based on presence of delays
if (valuep->isZero()) {
// Statically known #0 delay exists, schedule for #0 support
v3Global.setUsesZeroDelay();
m_hasStaticZeroDelay = true;
// Don't warn on variable delays, as no point
m_unknownDelayFlps.clear();
} else if (!VN_IS(valuep, Const)) {
// Delay is not known at compiile time. Conservatively schedule for #0 support,
// but warn if no static #0 delays used as performance might be improved
v3Global.setUsesZeroDelay();
if (!m_hasStaticZeroDelay) m_unknownDelayFlps.push_back(nodep->fileline());
}
}
// Replace self with a 'co_await dlySched.delay(<valuep>)'
AstCMethodHard* const delayMethodp = new AstCMethodHard{
flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE},
@@ -1320,8 +1371,28 @@ public:
explicit TimingControlVisitor(AstNetlist* nodep)
: m_netlistp{nodep} {
iterate(nodep);
// If there is no static #0 in the design, but an unknown delay was found,
// and the user did not specify preference, then warn on all unknown delays
// as we will be assuming they can be #0, which can cause performance degradation.
for (FileLine* const flp : m_unknownDelayFlps) {
flp->v3warn(ZERODLY,
"Value of # delay control statically unknown. Assuming it can be #0.\n"
<< flp->warnMore() //
<< "... If all # delays are non-zero at runtime,\n"
<< flp->warnMore() //
<< "... use '--no-sched-zero-delay' for improved performance.\n"
<< flp->warnMore() //
<< "... If a real #0 is expected at runtime,\n"
<< flp->warnMore() //
<< "... use '--sched-zero-delay' to suppress this warning.");
}
}
~TimingControlVisitor() override {
V3Stats::addStat("Timing, known #0 delays", m_statZeroDelays);
V3Stats::addStat("Timing, known #const delays", m_statConstDelays);
V3Stats::addStat("Timing, unknown #variable delays", m_statVariableDelays);
}
~TimingControlVisitor() override = default;
};
//######################################################################