Skip to content

Commit

Permalink
Schedule #0 coroutines after any other act triggers
Browse files Browse the repository at this point in the history
Signed-off-by: Krzysztof Boronski <[email protected]>
  • Loading branch information
kboronski-ant committed Nov 15, 2023
1 parent cc982ec commit dcbde87
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 21 deletions.
6 changes: 3 additions & 3 deletions include/verilated_timing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void VlDelayScheduler::resume() {
VL_DEBUG_IF(dump(); VL_DBG_MSGF(" Resuming delayed processes\n"););
#endif
while (awaitingCurrentTime()) {
if (m_queue.begin()->first != m_context.time()) {
if (m_queue.begin()->first.time != m_context.time()) {
VL_FATAL_MT(__FILE__, __LINE__, "",
"%Error: Encountered process that should've been resumed at an "
"earlier simulation time. Missed a time slot?");
Expand All @@ -74,7 +74,7 @@ uint64_t VlDelayScheduler::nextTimeSlot() const {
if (empty()) {
VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: There is no next time slot scheduled");
}
return m_queue.begin()->first;
return m_queue.begin()->first.time;
}

#ifdef VL_DEBUG
Expand All @@ -84,7 +84,7 @@ void VlDelayScheduler::dump() const {
} else {
VL_DBG_MSGF(" Delayed processes:\n");
for (const auto& susp : m_queue) {
VL_DBG_MSGF(" Awaiting time %" PRIu64 ": ", susp.first);
VL_DBG_MSGF(" Awaiting time %" PRIu64 ": ", susp.first.time);
susp.second.dump();
}
}
Expand Down
29 changes: 25 additions & 4 deletions include/verilated_timing.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,25 @@ class VlCoroutineHandle final {
#endif
};

enum class VlDelayPhase : bool { ACTIVE, INACTIVE };

struct VlDelayTime {
uint64_t time;
VlDelayPhase phase;

bool operator<(const VlDelayTime& other) const {
return (time < other.time) || ((time == other.time) && phase < other.phase);
}
};

//=============================================================================
// VlDelayScheduler stores coroutines to be resumed at a certain simulation time. If the current
// time is equal to a coroutine's resume time, the coroutine gets resumed.

class VlDelayScheduler final {
// TYPES
// Time-sorted queue of timestamps and handles
using VlDelayedCoroutineQueue = std::multimap<const uint64_t, VlCoroutineHandle>;
using VlDelayedCoroutineQueue = std::multimap<const VlDelayTime, VlCoroutineHandle>;

// MEMBERS
VerilatedContext& m_context;
Expand All @@ -175,7 +186,7 @@ class VlDelayScheduler final {
bool empty() const { return m_queue.empty(); }
// Are there coroutines to resume at the current simulation time?
bool awaitingCurrentTime() const {
return !empty() && m_queue.begin()->first <= m_context.time();
return !empty() && m_queue.begin()->first.time <= m_context.time();
}
#ifdef VL_DEBUG
void dump() const;
Expand All @@ -187,15 +198,25 @@ class VlDelayScheduler final {
VlProcessRef process; // Data of the suspended process, null if not needed
VlDelayedCoroutineQueue& queue;
uint64_t delay;
VlDelayPhase phase;
VlFileLineDebug fileline;

bool await_ready() const { return false; } // Always suspend
void await_suspend(std::coroutine_handle<> coro) {
queue.emplace(delay, VlCoroutineHandle{coro, process, fileline});
queue.emplace(VlDelayTime{delay, phase},
VlCoroutineHandle{coro, process, fileline});
}
void await_resume() const {}
};
return Awaitable{process, m_queue, m_context.time() + delay,

VlDelayPhase phase = (delay == 0) ? VlDelayPhase::INACTIVE : VlDelayPhase::ACTIVE;
if (phase == VlDelayPhase::INACTIVE) {
VL_WARN_MT(filename, lineno, VL_UNKNOWN,
"Ran into #0 delay. However, process "
"will be resumed before combinational logic evaluation.");
}

return Awaitable{process, m_queue, m_context.time() + delay, phase,
VlFileLineDebug{filename, lineno}};
}
};
Expand Down
14 changes: 13 additions & 1 deletion src/V3SchedTiming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,23 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
m_resumeFuncp->isConst(false);
m_resumeFuncp->declPrivate(true);
scopeTopp->addBlocksp(m_resumeFuncp);

AstActive* dlyShedActivep = nullptr;

// Put all the timing actives in the resume function
for (auto& p : m_lbs) {
// Put all the timing actives in the resume function
AstActive* const activep = p.second;
// Hack to ensure that #0 delays will be executed after any other `act` events.
// Just handle delayed coroutines last.
AstVarRef* schedrefp = VN_AS(
VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard)->fromp(), VarRef);
if (schedrefp->varScopep()->dtypep()->basicp()->isDelayScheduler()) {
dlyShedActivep = activep;
continue;
}
m_resumeFuncp->addStmtsp(activep);
}
if (dlyShedActivep) { m_resumeFuncp->addStmtsp(dlyShedActivep); }
}
AstCCall* const callp = new AstCCall{m_resumeFuncp->fileline(), m_resumeFuncp};
callp->dtypeSetVoid();
Expand Down
5 changes: 1 addition & 4 deletions src/V3Timing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -896,10 +896,7 @@ class TimingControlVisitor final : public VNVisitor {
FileLine* const flp = nodep->fileline();
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
auto* const constp = VN_CAST(valuep, Const);
if (constp && constp->isZero()) {
nodep->v3warn(ZERODLY, "Unsupported: #0 delays do not schedule process resumption in "
"the Inactive region");
} else {
if (!constp || !constp->isZero()) {
// Scale the delay
if (valuep->dtypep()->isDouble()) {
valuep = new AstRToIRoundS{
Expand Down
6 changes: 0 additions & 6 deletions test_regress/t/t_delay_var.out

This file was deleted.

2 changes: 0 additions & 2 deletions test_regress/t/t_delay_var.pl
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
compile(
verilator_flags2 => ["--exe --main --timing"],
make_main => 0,
fails => $Self->{vlt},
expect_filename => $Self->{golden_filename},
);

execute(
Expand Down
1 change: 0 additions & 1 deletion test_regress/t/t_delay_var.v
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ module t;
wire #PDLY d_param = in;

initial begin
#0 in = 1'b1;
#2 in = 1'b0;
#100;
$write("*-* All Finished *-*\n");
Expand Down
23 changes: 23 additions & 0 deletions test_regress/t/t_timing_zerodly.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2022 by Antmicro Ltd. 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,
);

ok(1);
1;
42 changes: 42 additions & 0 deletions test_regress/t/t_timing_zerodly.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0

class Foo;
static task fork_w_zerodly(longint unsigned current_time);
bit my_bit;
bit zero_dly_first = 0;
// Th code below relies on Verilator's deterministic scheduler and is not
// compatible across different simulators.
//
// The `zero_dly` block is going to be executed first and then suspended at the #0 delay.
// Then the `finish_before` block is going to be executed. Once that happens, the
// execution of `zero_dly` block should be resumed, all within a single time-slot.
//
// IF THIS TEST FAILS AFTER CHANGES TO VERILATOR'S SCHEDULER, IT DOESN'T NECESSARILY
// MEAN THE CHANGES ARE WRONG.
fork
begin : zero_dly
zero_dly_first = 1;
#0;
if (current_time != $time) $stop;
if (my_bit == 0) $stop;
end : zero_dly
begin : finish_before
if (!zero_dly_first) $stop;
my_bit = 1;
end : finish_before
join_none
#1 $display("After fork."); // Check if there's no skipped coroutine
endtask
endclass

module test();
initial begin
Foo::fork_w_zerodly($time);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

0 comments on commit dcbde87

Please sign in to comment.